1. Django的优缺点


Django的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don’t Repeat Yourself)。在Django中Python被普遍使用,甚至包括配置文件和数据模型。

Django优点:

  • 完美的文档,Django近乎完美的官方文档。
  • 全套的解决方案(full-stackframework + batteries included),基本要什么有什么(比如:cache、session、feed、orm、geo、auth),而且全部Django自己造,开发网站应手的工具Django基本都给你做好了,因此开发效率是不用说的。
  • 强大的URL路由配置,Django让你可以设计出非常优雅的URL。
  • 自助管理后台,让你几乎不用写一行代码就拥有一个完整的后台管理界面。

Django的缺点:(源自Django坚持自己造所有的轮子,整个系统相对封闭)

  • 系统紧耦合,如果你觉得Django内置的某项功能不是很好,想用喜欢的第三方库来代替是很难的,比如说的ORM、Template。要在Django里用SQLAlchemy或Mako几乎是不可能,即使打了一些补丁用上了也会让你觉得非常非常别扭。
  • 自带的ORM远不如SQLAlchemy强大,SQLAlchemy是Python世界里事实上的ORM标准,其它框架都支持SQLAlchemy了,唯独Django仍然坚持自己的那一套。
  • Template功能比较弱,不能插入Python代码,要写复杂一点的逻辑需要另外用Python实现Tag或Filter。
  • URL配置虽然强大,但全部要手写,高手和初识Django的人配出来的URL会有很大差异。
  • Django的auth跟其它模块结合紧密,功能也挺强,但做的有点过了,用户的数据库schema都给你定好了,比如很多网站要求email地址唯一,可schema里这个字段的值不是唯一的。

    2. Django的请求生命周期


17 Django - 图117 Django - 图2

NOTE:

回答用户请求并不是一下子就通过URL匹配就达到相应视图,返回数据也不是一下子就返回给用户,而是要经过层层中间件。

2.1 中间件(middleware)的工作原理和应用场景

中间件(Middleware)是一个镶嵌到django的request/response处理机制中的一个钩子(hooks) 框架。它是一个可以修改django全局输入或输出的一个底层插件系统。

HttpRequest -> Middleware -> View -> Middleware -> HttpResponse
我们可以编写自己的中间件实现权限校验,限制用户请求、打印日志、改变输出内容等多种应用场景,比如:

  • 禁止特定IP地址的用户或者未登陆的用户访问view视图函数
  • 对同一IP地址单位时间内发送的请求数量做出限制
  • 在View视图函数执行前记录用户的IP地址
  • 在VIew视图函数执行前传递额外的参数变量
  • 在View视图函数执行前或者执行后把特定的信息打印到log日志
  • 在View视图函数执行后对reponse数据进行修改后返回给用户

Django基础(33): 中间件(middleware)的工作原理和应用场景举例

3 请列举几个Django ORM中常用的获取数据查询集(queryset)的方法


ORM object relational mapping

对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。把面向对象的概念跟数据库中的表概念对应起来

举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。

常用方法有 filterexclude filter表示=,exclude表示!=

  1. qs1 = Article.objects.filter(title__icontains='django')
  2. qs2 = Article.objects.filter(id__range=[1,9])
  3. qs3 = Article.objects.filter(id__in=[1, 3, 6, 7, 9])
  4. qs4 = Article.objects.filter(author=request.user).exclude(id=1)

4. 说说看Django的Queryset有哪些特性


1. 惰性的

2. 自带缓存

下例中article_list试图从数据库查询一个标题含有django的全部文章列表。

article_list = Article.objects.filter(title__contains="django")

但是当我们定义article_list的时候,Django的数据接口QuerySet并没有对数据库进行任何查询。无论你加多少过滤条件,Django都不会对数据库进行查询。只有当你需要对article_list做进一步运算时(比如打印出查询结果,判断是否存在,统计查询结果长度),Django才会真正执行对的数据(见下例1)。这个过程被称为queryset的执行(evaluation)。Django这样设计的本意是尽量减少对数据库的无效操作,比如查询了结果而不用是计算资源的很大浪费章列表。

1. # example 1
2. for article in article_list:
3.    print(article.title)
4.

在例1中,当你遍历queryset(article_list)时,所有匹配的记录会从数据库获取。这些结果会载入内存并保存在queryset内置的cache中。这样如果你再次遍历或读取这个article_list时,Django就不需要重复查询了,这样也可以减少对数据库的查询。

Django基础(12):深夜放干货。QuerySet特性及高级使用技巧,如何减少数据库的访问,节省内存,提升网站性能。

5. 基于函数的视图(Function base Views)/基于类的视图(Class base Views)


FBV(function base views) 就是在视图里使用函数处理请求。CBV(class base views) 就是在视图里使用类处理请求。Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以Django在后来加入了Class-Based-View,可以让我们用类写View,这样做的优点主要下面两种:

  • 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
  • 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性

当然基于函数的视图也有自己的优点,比如对新手更友好。

基于函数的视图

  • 展示对象列表(所有用户、文章列表)
  • 查看某个对象的详细信息(比如用户资料、文章详情)
  • 通过表单创建某个对象(文章)
  • 通过表单更新对象
  • 用户填写表单后转到某个完成页面
  • 删除某个对象

基于类的视图

  • 展示对象列表—— ListView
  • 在展示某个对象详细信息—— DetailView
  • 通过表单创建某个对象—— CreateView
  • 通过表单更新对象—— UpdateView
  • 页面跳转—— FormView
  • 删除对象—— DeleteView

更多阅读:

Django核心基础(3): View视图详解。一旦你使用通用视图,你就会爱上她。

6. 如何给基于类的视图CBV使用装饰器


需要借助django.utils模块的method_decorator方法实现,它还支持decorators列表, 如下所示:

1. 
2. from django.utils.decorators import method_decorator
3. 
4. decorators = [login_required, check_user_permission]
5. 
6. 
7. @method_decorator(decorators, name='dispatch')
8. class ArticleCreateView(CreateView):
9. model = Article
10. form_class = ArticleForm
11. template_name = 'blog/article_manage_form.html'

更多阅读:

Django基础(26): 常用装饰器应用场景及正确使用方法 一文看懂Python系列之装饰器(decorator)(工作面试必读)

7. 使用基于CBV的get_queryset/get_context_data/get_object方法的作用


get_queryset()方法

返回一个量身定制的对象列表. 当我们使用Django自带的ListView展示所有对象列表时,ListView默认会返回Model.objects.all()

from django.views.generic import ListView
from .models import Article

class IndexView(ListView):
    model = Article

然后这可能不是我们所需要的。当我们只希望展示作者自己发表的文章聊表并且按照时间逆序排列时,我们就可以通过更具体的 get_queryset 方法来返回列表

from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):
    template_name = 'blog/article_list_html'
    context_obj_name = 'latest_articles'

    def get_queryset(self):
        return Article.objects.filter(author=self.request.user).order_by('-pub_data')

get_context_data()方法

用于给模板传递**模型以外的内容和参数**。例如现在的时间并不属于Article模型。如果你想把现在的时间传递给模板,你还可以通过重写get_context_data方法(如下图所示)。因为调用了父类的方法,


from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):

    queryset = Article.objects.all().order_by("-pub_date")
    template_name = 'blog/article_list.html'
    context_object_name = 'latest_articles'

    def get_context_data(self,**kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now() 
        return context

get_object()方法

DetailView和EditView都是从URL根据pk或其它参数调取一个对象来进行后续操作。

from django.views.generic import DetailView
from django.http import Http404
from .models import Article
from django.utils import timezone

class ArticleDetailView(DetailView):
    queryset = Articles.objects.all().order_by('-pub_date') # 等同于model = Article
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

然而上述代码可能满足不了你的需求。比如你希望一个用户只能查看或编辑自己发表的文章对象。当用户查看别人的对象时,返回http 404错误。这时候你可以通过更具体的get_object()方法来返回一个更具体的对象。代码如下:


from django.views.generic import DetailView
from django.http import Http404
from .models import Article
from django.utils import timezone

class ArticleDetailView(DetailView):

    queryset = Article.objects.all().order_by("-pub_date")
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

    def get_object(self,queryset=None):
        obj = super().get_object(queryset=queryset)
        if obj.author != self.request.user:
            raise Http404()
        return obj

8. 减少数据库查询次数的方法


  1. Django queryset的惰性自带缓存的特性
  2. select_relatedprefetch_related在数据库层面进行join操作
  3. 缓存

Django基础(29): select_related和prefetch_related的用法与区别 Django基础(8): 缓存Cache应用场景及工作原理,Cache设置及如何使用

9. Django模型继承方式、使用与区别


Django模型继承有三种:

  1. 抽象模型继承(Abstract model)
  2. 多表模型继承(Multi-table inheritance)
  3. 代理模型(proxy model)

模型继承的区别

  • Django不会为抽象模型在数据库中生成自己的数据表。父类Meta中的abstract_True也不会传递给子类。很多模型有共同字段时,需要使用抽象模型继承
  • 多表模型继承与抽象模型继承的最大区别在于Django也会为父类模型创建自己的数据表,同时隐式地在父类和子类之间建立一个一对一的关系
  • 如果我们只想改变某个模型的行为方法,而不是添加额外的字段或创建额外的数据表,我们就可以使用代理模型(proxy model)。设置一个代理模型,需要在子类模型Meta选项中设置proxy=True, Django不会为代理模型生成新的数据表。

    10. 如何自定义模型标签(templatetags)和过滤器(filter)


首先你要在你的app目录下新建一个叫templatetags的文件夹(不能取其它名字), 里面必需包含init.py的空文件。在该目录下你还要新建一个python文件专门存放你自定义的模板标签函数,本例中为blog_extras.py,当然你也可以取其它名字。整个目录结构如下所示:

blog/
   __init__.py
   models.py
   templatetags/
       __init__.py
       blog_extras.py
   views.py

在模板中使用自定义的模板标签时,需要先使用{% load blog_extras %}载入自定义的过滤器,然后通过{% tag_name %} 使用它。
举例
我们将定义3个简单模板标签,一个返回string, 一个给模板context传递变量,一个显示渲染过的模板。我们在blog_extra.py里添加下面代码。
#blog_extra.py

from django import template
import datetime
from bolg.models import Article

register = templatel.Library()

# use simple tag to show string
@register.simple_tag
def total_articles():
    return Artilce.objects.filter(status='p').count()

# use simple tag to set context variable
@register.simple_tag
def get_first_article():
    return Article.objects.filter(status='p').order_by('-pub_date')[0]

# show rendered template
@register.inclusion_tag('blog/latest_article_list.html')
def show_latest_article(count=5):
    latest_articles = Article.objects.filter(status='p').order_by('-pub_date')[:count]
    return {'latest_articles':latest_articles}

Django基础(16): 模板标签(tags)的分类及如何自定义模板标签 Django实战: 利用自定义模板标签实现仿CSDN博客月度归档

11. Django的CSRF防御机制


{% CSRF_TOKEN %}** **用于解决

17 Django - 图3

CSRF Cross Site Request Forgery 跨站请求伪造

其原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。

17 Django - 图4
Django的CSRF保护主要时通过 django.middleware.csrf.CsrfViewMiddleware 中间件实现,主要流程如下:

  • Djangos第一次响应来自某个客户端的 get 请求时,会在服务器端随机生成一个 csrftoken ,将token放在请求头的 **cookie** 中返回给用户
  • 所有通过 POST 方式提交的表单在渲染中必须包含一个 csrfmiddlewaretoken** 的隐藏字段 ( 在模板中通过 {% CSRF_TOKEN%} **生成

当用户通过 POST 提交表单时,Django会从请求头cookie取出csrftoken的值,再从 POST 表单中取csrfmiddlewaretoken交由中间件进行校验两者是否一致。如果一致则表明这是一个合法请求,否则返回403 Forbidden

**17 Django - 图5

12. Django中使用AJAX发送POST请求时如何通过CSRF认证



1. 第一种方式直接在发送数据中加入csrfmiddlewaretoken
<script>
  $("#btn").on("click",function () {
        $.ajax({
            url:"/some_url/",
            type:"POST",
            data:{
                csrfmiddlewaretoken:{{ csrf_token }}, //写在模板中,才会被渲染
            },
            success:function (data) {
            }
        })
    })
</script>2.通过jquery选择器获取csrfmiddlewaretoken

<script>
  $("#btn").on("click",function () {
        $.ajax({
            url:"/some_url/",
            type:"POST",
            data:{
                csrfmiddlewaretoken:$('[name="csrfmiddlewaretoken"]').val(),
            },
            success:function (data) {
            }
        })
    })
</script>3. 使用jquery.cookie.js调用请求头cookie中的csrftoken
<script src="/static/jquery.cookie.js"></script> //必须先引入它
<script>
    $("#btn").on("click",function () {
     $.ajax({
        url:"/some_url/",
        type:"POST",
        headers:{"X-CSRFToken":$.cookie('csrftoken')},
        data:$("#f1").serialize()
    }
    )
   })
</script>

13. 什么情况下需要使用 select_relatedprefetch_related 以及区别


当你查询单个主对象或主对象列表并需要在模板或其它地方中使用到每个对象的关联对象信息时,请一定记住使用select_related和prefetch_related一次性获取所有对象信息,从而提升数据库查询效率,避免重复查询。两个方法都是Django ORM优化数据查询必须要熟练掌握的方法。

两者的区别是:

  • 对单对单(OneToOne)或单对多外键(ForeignKey)字段,使用select_related方法
  • 对于多对多字段(ManyToMany)和反向外键关系,使用prefetch_related方法
  • select_related方法执行一次数据库查询,prefetch_related方法执行两次数据库查询
  • 使用Prefetch方法可以给prefetch_related方法额外添加额外条件和属性。
  • Django基础(29):select_related和prefetch_related的用法与区别

14. 如何从数据表中获取一个随机对象


order_by('?').first()

def get_random_obj():
    return MyModel.objects.order_by('?').first()

Django ORM Cookbook精选摘录(上)

15. aggregate和annotate的作用


aggregate的中文意思是聚合, 源于SQL的聚合函数。Django的aggregate()方法作用是对一组值(比如queryset的某个字段)进行统计计算,并以字典(Dict)格式返回统计计算结果。django的aggregate方法支持的聚合操作有AVG / COUNT / MAX / MIN /SUM 等。

Student.objects.aggregate(Avg('age '), Max('age '),Min('age '))
# 同时获取学生年龄均值, 最大值和最小值, 返回字典
{ 'age__avg': 12, 'age__max': 18, 'age__min': 6, }

Hobby.objects.aggregate(Max('student__age'))
# 根据Hobby反查学生最大年龄。查询字段student和age间有双下划线哦。
{ 'student__age__max': 12 }

annotate的中文意思是注释,一个更好的理解是分组(Group By)。如果你想要对数据集先进行分组然后再进行某些聚合操作或排序时,需要使用annotate方法来实现。与aggregate方法不同的是,annotate方法返回结果的不仅仅是含有统计结果的一个字典,而是包含有新增统计字段的查询集(queryset).

# 按学生分组,统计每个学生爱好数量,并自定义字段名
Student.objects.annotate(hobby_count_by_student=Count('hobbies'))

# 按爱好分组,再统计每组学生数量。
Hobby.objects.annotate(Count('student'))

# 按爱好分组,再统计每组学生最大年龄。
Hobby.objects.annotate(Max('student__age'))

# 先按爱好分组,再统计每组学生数量, 然后筛选出学生数量大于1的爱好。
Hobby.objects.annotate(student_num=Count('student')).filter(student_num__gt=1)

# 先按爱好分组,筛选出以'd'开头的爱好,再统计每组学生数量。
Hobby.objects.filter(name__startswith="d").annotate(student_num=Count('student‘))

18. 如何在模板中获取当前访问url地址



在模板中你可以使用{{ request.path }}获取当前url,
如果要获取带querystring的完整url你可以使用{{ request.get_full_path }}。
如果你要获取完整绝对路径,你可以使用 {{ request.build_absolute_uri }}。具体使用方法如下所示:
https://jackeygao.io/search/?keyword=django
Method Output
request.path /search/
request.get_full_path search/?keyword=django
request.build_absolute_uri https://jackeygao.io/search/?keyword=django

19. 使用F方法


通常情况下我们在更新数据时需要先从数据库里将原数据取出后放在内存里,然后编辑某些字段或属性,最后提交更新数据库。使用F方法则可以帮助我们避免将所有数据先载入内存,而是直接生成SQL语句更新数据库。
假如我们需要对所有产品的价格涨20%,我们通常做法如下。当产品很少的时候,对网站性能没影响。但如果产品数量非常多,把它们信息全部先载入内存会造成很大性能浪费。

1. products = Product.objects.all()
2. for product in products:
3.     product.price *= 1.2
4.     product.save()

使用F方法可以解决上述问题。我们直接可以更新数据库,而不必将所有产品载入内存。

1. from django.db.models import F
2. 
3. Product.objects.update(price=F('price') * 1.2)

我们也可以使用F方法更新单个对象的字段,如下所示:

1. product = Product.objects.get(pk=5009)
2. product.price = F('price') * 1.2
3. product.save()

但值得注意的是当你使用F方法对某个对象字段进行更新后,需要使用refresh_from_db()方法后才能获取最新的字段信息(非常重要!)。如下所示:2-

1. product.price = F('price') + 1
2. product.save()
3. print(product.price)            # <CombinedExpression: F(price) + Value(1)>
4. product.refresh_from_db()
5. print(product.price)            # Decimal('13.00')

20 Nginx和uWISG服务器之间如何配合工作的


  • 首先浏览器发起 http 请求到 nginx 服务器,Nginx 根据接收到请求包,进行 url 分析,判断访问的资源类型。如果是静态资源,直接读取静态资源返回给浏览器。
  • 如果请求的是动态资源就转交给 uwsgi服务器。
  • uwsgi 服务器根据自身的uwsgi 和 WSGI 协议,找到对应的 Django 框架。
  • Django 框架下的应用进行逻辑处理后,将返回值发送到 uwsgi 服务器。
  • uwsgi 服务器再返回给 nginx,最后 nginx将返回值返回给浏览器进行渲染显示给用户。

21. Django信号signals的工作原理与主要应用场景


Django信号的工作原理就是当某个事件发生的时候会发出一个信号(signals), 而监听这个信号的函数(receivers)就会立即执行。Django信号的应用场景很多,尤其是用于不同模型或程序间的联动。常见例子包括创建User对象实例时创建一对一关系的UserProfile对象实例,或者每当用户下订单时触发给管理员发邮件的动作。

Django内置信号包括:

  • django.db.models.signals.pre_save & post_save在模型调用 save()方法之前或之后发送。
  • django.db.models.signals.preinit& post_init在模型调用_init方法之前或之后发送。
  • django.db.models.signals.pre_delete & post_delete在模型调用delete()方法或查询集调用delete() 方法之前或之后发送。
  • django.db.models.signals.m2m_changed在模型多对多关系改变后发送。
  • django.core.signals.request_started & request_finished Django建立或关闭HTTP 请求时发送。

更多阅读:
Django基础(31): 如何理解和正确使用Django信号(Signals)

24. Django项目中什么时候使用中间件,什么时候使用装饰器?


中间件和装饰器均广泛用于权限校验,缓存和日志。中间件对Django的输入或输出的改变是全局的,而装饰器一般只改变单个视图的输入输出。如果让你希望对Django的输入或输出做出全局性的改变时,需要使用中间件,否则使用装饰器。
举个例子,我们在装饰器一文中介绍了如何使用@login_required装饰器要求用户必须先登录才能访问我们的某个视图函数。试想我们有个网站绝大部分视图函数都需要用户登录,每个视图函数前面都需要加上@login_required装饰器是比较傻的行为。借助于中间件,我们无需使用装饰器即可全局实现:只有登录用户才能访问视图函数,匿名用户跳转到登录页面。实现原理也很简单,在一个request到达视图函数前,我们先对request.user是否验证通过进行判断,然后再进行跳转。
更多阅读:
Django基础(26): 常用装饰器应用场景及正确使用方法
一文看懂Python系列之装饰器(decorator)(工作面试必读)

27. Django如何生成静态html文件


使用render_to_string方法生成content,然后写入html文件。**

1. from django.shortcuts import render
2. from django.template.loader import render_to_string
3. import os
4. 
5. 
6. def my_view(request):
7.     context = {'some_key': 'some_value'}
8. 
9.     static_html = '/path/to/static.html'
10. 
11. if not os.path.exists(static_html):
12.         content = render_to_string('template.html', context)
13.         with open(static_html, 'w') as static_file:
14.             static_file.write(content)
15. 
16. return render(request, static_html)

27. Django如何生成静态html文件


使用render_to_string方法生成content,然后写入html文件。**

1. from django.shortcuts import render
2. from django.template.loader import render_to_string
3. import os
4. 
5. 
6. def my_view(request):
7.     context = {'some_key': 'some_value'}
8. 
9.     static_html = '/path/to/static.html'
10. 
11. if not os.path.exists(static_html):
12.         content = render_to_string('template.html', context)
13.         with open(static_html, 'w') as static_file:
14.             static_file.write(content)
15. 
16. return render(request, static_html)

29. WSGI (Web Server Gateway Interface)


Web服务器网关接口,是一套协议。用于接收用户请求并将请求进行初次封装,然后将请求交给web框架。实现wsgi协议的模块有:
1.wsgiref,本质上就是编写一个socket服务端,用于接收用户请求(django)
2.werkzeug,本质上就是编写一个socket服务端,用于接收用户请求(flask)
uwsgi:
与WSGI一样是一种通信协议,它是uWSGI服务器的独占协议,用于定义传输信息的类型
uWSGI:
是一个web服务器,实现了WSGI协议,uWSGI协议,http协议,

30. 列举5个常用的Django第三方库


答案不限于:**