周末抽空整理了下Django经典面试问题及答案,希望对小伙伴们学习和工作有所帮助。如果小编我是负责招聘Python Web或Django开发人员的面试官,我也会考虑问如下问题。从下面可以大致了解一个面试者对Django技术的了解程度。由于篇幅所限,本文将分为上中下三部分发表,欢迎关注我们的微信公众号【Python Web与Django开发】
1. Django的优点和缺点有哪些?
Django的优点
功能完善、要素齐全:自带大量企业Web开发常用工具和框架(比如分页,auth,权限管理), 适合快速开发企业级网站。
完善的文档:经过十多年的发展和完善,Django有广泛的实践案例和完善的在线文档。开发者遇到问题时可以搜索在线文档寻求解决方案。
强大的数据库访问组件:Django的Model层自带数据库ORM组件,使得开发者无须学习SQL语言即可对数据库进行操作。
Django先进的App设计理念: App是可插拔的,是不可多得的思想。不需要了,可以直接删除,对系统整体影响不大。
自带台管理系统admin:只需要通过简单的几行配置和代码就可以实现一个完整的后台数据管理控制平台。
Django的缺点
大包大揽: 对于一些轻量级应用不需要的功能模块Django也包括了,不如Flask轻便。
过度封装: 很多类和方法都封装了,直接使用比较简单,但改动起来就比较困难。
性能劣势: 与C, C++性能上相比,Django性能偏低,当然这是python的锅,其它python框架在流量上来后会有同样问题。
模板问题: django的模板实现了代码和样式完全分离,不允许模板里出现python代码,灵活度对某些程序员来说可能不够。
2. 说说看Django的请求生命周期
注:最重要的是回答用户请求并不是一下子通过URL匹配就达到相应视图,返回数据也不是一下子就返回给用户,中间要经历层层中间件。这个面试题其实考的核心是中间件。
更多阅读:
Django基础(33): 中间件(middleware)的工作原理和应用场景举例
在生产环境,你还需要很清楚地描述下图流程。
3. 请列举几个Django ORM中常用的获取数据查询集(queryset)的方法
常用方法包括filter和exclude方法。字符串模糊匹配可以使用icontains, in等多种方法。随便举几个例子:
qs1 = Article.objects.filter(title__icontains='django')
4. 说说看Django的Queryset有哪些特性
Django的QuerySet主要有两个特性:一是惰性的(lazy),二是自带缓存。我们来看个例子。
下例中article_list试图从数据库查询一个标题含有django的全部文章列表。
article_list = Article.objects.filter(title__contains="django")
但是当我们定义article_list的时候,Django的数据接口QuerySet并没有对数据库进行任何查询。无论你加多少过滤条件,Django都不会对数据库进行查询。只有当你需要对article_list做进一步运算时(比如打印出查询结果,判断是否存在,统计查询结果长度),Django才会真正执行对数据库的查询(见下例1)。这个过程被称为queryset的执行(evaluation)。Django这样设计的本意是尽量减少对数据库的无效操作,比如查询了结果而不用是计算资源的很大浪费。
# example 1
for article in article_list:
print(article.title)
在例1中,当你遍历queryset(article_list)时,所有匹配的记录会从数据库获取。这些结果会载入内存并保存在queryset内置的cache中。这样如果你再次遍历或读取这个article_list时,Django就不需要重复查询了,这样也可以减少对数据库的查询。
更多阅读:
Django基础(12):深夜放干货。QuerySet特性及高级使用技巧,如何减少数据库的访问,节省内存,提升网站性能。
5. 什么是基于函数的视图(FBV)和基于类的视图(CBV)以及各自的优点
FBV(function base views) 就是在视图里使用函数处理请求。CBV(class base views) 就是在视图里使用类处理请求。Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以Django在后来加入了Class-Based-View,可以让我们用类写View,这样做的优点主要下面两种:
提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性
当然基于函数的视图也有自己的优点,比如对新手更友好。
更多阅读:
Django核心基础(3): View视图详解。一旦你使用通用视图,你就会爱上她。
6. 如何给基于类的视图(CBV)使用装饰器
需要借助django.utils模块的method_decorator方法实现,它还支持decorators列表, 如下所示:
from django.utils.decorators import method_decorator
decorators = [login_required, check_user_permission]
@method_decorator(decorators, name='dispatch')
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_manage_form.html'</pre>
更多阅读:
Django基础(26): 常用装饰器应用场景及正确使用方法
一文看懂Python系列之装饰器(decorator)(工作面试必读)
7. 说说看使用基于类的视图(CBV)时get_queryset, get_context_data和get_object方法的作用
get_queryset()方法
正如其名,该方法可以返回一个量身定制的对象列表。当我们使用Django自带的ListView展示所有对象列表时,ListView默认会返回Model.objects.all()。
# Create your views here. from django.views.generic import ListView
from .models import Article
class IndexView(ListView):
model = Article
然而这可能不是我们所需要的。当我们希望只展示作者自己发表的文章列表且按文章发布时间逆序排列时,我们就可以通过更具体的get_queryset方法来返回一个我们想要显示的对象列表。
from .models import Article
from django.utils import timezone
class IndexView(ListView):
template_name = 'blog/article_list.html'
context_object_name = 'latest_articles'
def get_queryset(self):
return Article.objects.filter(author = self.request.user).order_by('-pub_date')
get_context_data()
get_context_data可以用于给模板传递模型以外的内容或参数,非常有用。例如现在的时间并不属于Article模型。如果你想把现在的时间传递给模板,你还可以通过重写get_context_data方法(如下图所示)。因为调用了父类的方法,
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或其它参数调取一个对象来进行后续操作。下面代码通过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") #等同于model = Article
template_name = 'blog/article_detail.html'
context_object_name = 'article'
然而上述代码可能满足不了你的需求。比如你希望一个用户只能查看或编辑自己发表的文章对象。当用户查看别人的对象时,返回http 404错误。这时候你可以通过更具体的get_object()方法来返回一个更具体的对象。代码如下:
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. 你能列举几个减少数据库查询次数的方法吗?
利用Django queryset的惰性和自带缓存的特性
使用select_related和prefetch_related方法在数据库层面进行Join操作
使用缓存
更多阅读:
Django基础(29): select_related和prefetch_related的用法与区别
Django基础(8): 缓存Cache应用场景及工作原理,Cache设置及如何使用
9. Django的模型继承有哪几种方式? 它们有什么区别以及何时使用它们?
Django的模型继承有如下3种方式:
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,当然你也可以取其它名字。整个目录结构如下所示:
__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
import datetime
from blog.models import Article
register = template.Library()
# use simple tag to show string @register.simple_tag def total_articles():
return Article.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_articles(count=5):
latest_articles = Article.objects.filter(status='p').order_by('-pub_date')[:count]
return {'latest_articles': latest_articles, }
11. 简单说说看 Django的CSRF防御机制
Django的CSRF保护主要是通过django.middleware.csrf.CsrfViewMiddleware
中间件来实现的。主要流程如下:
Django 第一次响应来自某个客户端的get请求时,会在服务器端随机生成一个 csrftoken(一串64位的随机字符串),把这个 token 放请求头的 cookie 里返回给用户。
所有通过POST方式提交的表单在渲染时中必须包含一个 csrfmiddlewaretoken 隐藏字段 (在模板中通过{% csrf_token %}标签生成)。
当用户通过POST提交表单时,Django会从请求头cookie取
csrftoken
这一项的值,再从POST表单里取csrfmiddlewaretoken
交由中间件进行校验两者是否一致。如果一致表明这是一个合法请求,否则返回403 Forbidden.
注意csrftoken
和csrfmiddlewaretoken
并不是简单相等的两个字符串,而是通过算法判断是否一致相等的,如下图所示。
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></pre>
2.通过jquery选择器获取csrfmiddlewaretoken
<script> $("#btn").on("click",function () {
$.ajax({
url:"/some_url/",
type:"POST",
data:{
csrfmiddlewaretoken:$('[name="csrfmiddlewaretoken"]').val(),
},
success:function (data) {
}
})
}) </script></pre>
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></pre>
更多阅读
Django基础(17): 如何上传处理文件及Ajax文件上传示范(附GitHub源码)
13. 什么情况下需要使用select_related和prefetch_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()随机获取一个对象
return MyModel.objects.order_by("?").first()
更多阅读
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‘))
更多阅读
Django基础(24): aggregate和annotate方法使用详解与示例
16. Django中如何使用redis做缓存?
安装好redis后,你需要安装django-redis才能在django中使用redis。django-redis安装命令如下:
pip install django-redis
settings.py中加入以下内容配置缓存。your_host_ip换成你的服务器地址,yourpassword换成你的服务器登陆密码。
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://your_host_ip:6379',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "yourpassword",
},
},
}
你还可以在settings.py设置缓存默认过期时间(非必须)
REDIS_TIMEOUT=72460*60
更多阅读
Django中如何使用Redis进行缓存详细教程(含Windows系统下安装redis)
17. Django项目上传到代码库时是否需要忽略数据库迁移文件?
数据库迁移文件位于每个app文件夹的migrations文件夹里,这些文件记录了模型的创建与改
动。每次当你创建模型或对模型字段进行修改,然后运行python manage.py
makemigrations命令时都会有新的迁移文件产生。Django官方文档特别说明这些迁移文件
属于Django项目代码中很重要的一部分,不应删除或忽略,所以建议上传。
更多阅读
Django项目上传.gitignore文件建议忽略文件清单及是否需要忽略数据库迁移文件
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%,我们通常做法如下。当产品很少的时候,对网站性能没影响。但如果产品数量非常多,把它们信息全部先载入内存会造成很大性能浪费。
products = Product.objects.all()
for product in products:
product.price *= 1.2
product.save()
使用F方法可以解决上述问题。我们直接可以更新数据库,而不必将所有产品载入内存。
Product.objects.update(price=F('price') * 1.2)
我们也可以使用F方法更新单个对象的字段,如下所示:
product.price = F('price') * 1.2
product.save()
但值得注意的是当你使用F方法对某个对象字段进行更新后,需要使用refresh_from_db()方法后才能获取最新的字段信息(非常重要!)。如下所示:
product.save()
print(product.price) # <CombinedExpression: F(price) + Value(1)>
product.refresh_from_db()
print(product.price) # Decimal('13.00')
20. 说说 nginx 和 uWISG 服务器之间如何配合工作的?
-
首先浏览器发起 http 请求到 nginx 服务器,Nginx 根据接收到请求包,进行 url 分析,
判断访问的资源类型。如果是静态资源,直接读取静态资源返回给浏览器。
如果请求的是动态资源就转交给 uwsgi服务器。
uwsgi 服务器根据自身的uwsgi 和 WSGI 协议,找到对应的 Django 框架。
Django 框架下的应用进行逻辑处理后,将返回值发送到 uwsgi 服务器。
uwsgi 服务器再返回给 nginx,最后 nginx将返回值返回给浏览器进行渲染显示给用户。