Django 5.1环境搭建与MVT架构解析
简单来说,学一个新框架最怕的就是环境搞崩,或者版本对不上。咱们直接上最新的,就用 Django 5.1,这版本是 2024年8月7日** 刚发布的,里面对于类型提示(Type Hints)的支持又增强了不少,写代码的时候 IDE 提示更友好。
先聊聊环境。别直接在系统 Python 里装,那是新手最容易踩的坑。咱们用虚拟环境隔离一下。打开你的终端(Windows 下可能是 PowerShell 或者 CMD),咱们这么干:
# 创建项目文件夹
mkdir django_blog_tutorial
cd django_blog_tutorial
# 创建虚拟环境,python 3.10+ 都行,Django 5.1 对 Python 版本有要求
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Mac/Linux:
source venv/bin/activate
# 安装 Django 5.1
pip install django==5.1
装好了?咱们来创建项目。Django 自带一个命令行工具 django-admin,用它来生成骨架代码最方便。
django-admin startproject config .
注意后面那个点 .,意思是把项目文件放在当前目录,而不是再套一层文件夹。这时候你的目录结构应该是这样的:
django_blog_tutorial/
├── config/ # 项目配置目录
│ ├── __init__.py
│ ├── settings.py # 核心配置文件,数据库、中间件都在这
│ ├── urls.py # 路由入口
│ └── wsgi.py # 部署用的,现在先不管
├── manage.py # 项目管理脚本,各种命令都靠它
└── venv/
跑起来试试?执行 python manage.py runserver,看到那个火箭 emoji 和 "Starting development server" 就说明成了。
接下来咱们得搞懂 Django 的 MVT 架构。很多面试会问这和 MVC 有啥区别,换个角度看,就是换了个马甲。
- Model (模型):负责数据。就是定义数据库里的表长啥样,比如文章有标题、内容、发布时间。Django 的 ORM 会帮你把这个类转成 SQL 语句。
- View (视图):负责逻辑。注意,这里的 View 不是前端页面,而是业务逻辑。比如用户请求看文章列表,View 就去数据库拿数据,然后扔给模板。
- Template (模板):负责展示。就是 HTML 页面,里面可以嵌一些模板语言,把 View 传过来的数据渲染出来。
🔧 实战技巧:刚开始别把 View 写得太臃肿。很多新手喜欢把一堆逻辑全塞进 views.py 的一个函数里,后期维护想死的心都有。尽量保持 View 只做“接收请求 -> 调用逻辑 -> 返回响应”这三件事。
咱们再创建一个应用(App),Django 推荐把功能拆分成不同的 App。
python manage.py startapp blog
创建完别忘了去 config/settings.py 里的 INSTALLED_APPS 加上 'blog',不然 Django 不知道你多了这么个应用。
# config/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # 加上这一行,注册你的应用
]
这就算把地基打好了。MVT 听起来玄乎,其实就是把“数据”、“逻辑”、“页面”分家,各管各的,这样代码才清爽。
模型(Model)与ORM:定义数据结构与迁移
搞定了环境,咱们来聊聊 Django 最核心的杀手锏——ORM (Object-Relational Mapping)。其实,就是你不用写恶心的 SQL 语句(比如 CREATE TABLE...),直接写 Python 类,Django 自动帮你翻译成数据库能懂的语言。
咱们在 blog 这个应用里搞个简单的博客系统。打开 blog/models.py,这就是定义数据结构的地方。
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name="分类名称")
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200, verbose_name="标题")
content = models.TextField(verbose_name="正文内容")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="分类")
is_published = models.BooleanField(default=False, verbose_name="是否发布")
class Meta:
ordering = ['-created_at'] # 默认按创建时间倒序排列
verbose_name = "文章"
verbose_name_plural = "文章列表"
def __str__(self):
return self.title
这里有几个点得值得留意的是,
- 字段类型:
CharField 是字符串,TextField 是大文本,DateTimeField 是时间。
on_delete:这是外键的关键。比如 models.CASCADE 意思是作者删了,文章也跟着删;SET_NULL 是分类删了,文章的分类字段设为空。
__str__ 方法:这个方法很重要,它决定了在 Admin 后台里,这条数据显示成什么样子。如果不写,你看到的就是一堆 Post object (1),鬼知道那是啥。
写好了模型,数据库还不知道这事儿呢。咱们得做迁移 (Migration)。这是 Django 的版本控制机制,记录你对模型的每一次改动。
执行这两条命令:
python manage.py makemigrations
python manage.py migrate
makemigrations 是生成迁移文件(相当于生成 SQL 脚本),migrate 是真正执行到数据库里。
⚡ 效率提示:千万别手贱去手动改数据库表结构,然后又去改 models.py。这样两边对不上,迁移文件会报错报得你怀疑人生。一定要遵循“改 Model -> makemigrations -> migrate”这个流程。
有时候你可能会遇到想执行原生 SQL 的情况,虽然 Django ORM 很强大,但复杂查询确实可能力不从心。这时候你可以这么做:
from django.db import connection
def my_custom_sql():
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM blog_post WHERE title LIKE '%%Django%%'")
row = cursor.fetchall()
return row
不过换个角度看,90% 的场景 ORM 都能搞定,而且 ORM 自带防 SQL 注入的安全机制,比自己拼字符串安全多了。Django 5.1 在异步 ORM 这块也在持续发力,虽然现在咱们写同步代码居多,但这也是未来的趋势,写代码的时候心里得有个数。
Admin后台基础:自动注册与列表页优化
说真的,Django 之所以这么多年一直火,很大一部分原因就是它自带了一个开箱即用的 Admin 后台。你想想,你刚定义完模型,几行代码就能拥有一个功能完善的数据管理界面,这在其他框架里简直不敢想。
咱们先看看最基础的注册。打开 blog/admin.py:
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
# 最简单粗暴的注册方式
admin.site.register(Post)
admin.site.register(Category)
运行 python manage.py runserver,访问 http://127.0.0.1:8000/admin/。你会发现进不去,因为你还没创建超级用户。
python manage.py createsuperuser
按提示输入用户名、邮箱、密码(输入密码的时候终端是不显示的,别以为卡住了)。登录进去,你就能看到 Post 和 Category 了。点进去能增删改查,是不是很爽?
但是,默认的界面太简陋了。比如 Post 列表页,只显示一个标题,我想看作者、发布状态、创建时间怎么办?这时候就得定制 ModelAdmin 了。
咱们把 admin.py 改得高级一点:
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
search_fields = ('name',)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 1. 列表页显示哪些字段
list_display = ('id', 'title', 'author', 'category', 'is_published', 'created_at')
# 2. 右侧增加过滤器
list_filter = ('is_published', 'category', 'author')
# 3. 顶部增加搜索框,可以按标题和正文搜
search_fields = ('title', 'content')
# 4. 编辑页的字段排序和分组
fieldsets = (
('基础信息', {
'fields': ('title', 'author', 'category')
}),
('内容发布', {
'fields': ('content', 'is_published')
}),
)
# 5. 默认排序
ordering = ('-created_at',)
# 6. 自动填充作者(这个很实用,避免每次选自己)
def save_model(self, request, obj, form, change):
if not change: # 如果是新建,而不是修改
obj.author = request.user
super().save_model(request, obj, form, change)
改完保存,刷新页面。你会发现界面瞬间清晰了。这就是 Admin 定制的魅力。
💡 经验总结:如果你发现列表页数据很多,加载很慢,记得用 list_select_related。比如咱们这里 Post 关联了 author 和 category,如果不加优化,Django 可能会执行 N+1 次查询(这是常见面试考点!)。加上这个属性,Django 会用 select_related 一次性把关联数据取出来,性能提升巨大。
class PostAdmin(admin.ModelAdmin):
# ... 其他配置 ...
list_select_related = ('author', 'category') # 优化关联查询性能
另外,Django 5.1 的 Admin 也在不断微调,虽然大变样没有,但底层的稳定性和对现代前端标准的支持一直在跟进。对于做企业内部工具(ERP/CRM)或者 CMS 系统来说,把 Admin 稍微定制一下,能省下 80% 的 CRUD 开发时间。别小看这个后台,很多创业公司早期的产品就是靠它撑起来的。
进阶实战:自定义Admin Action与Inline嵌套
简单来说,到了这一步,你的Django项目已经跑起来了,Admin后台也能看到数据了。但是,默认的Admin只能一条条地改数据,这在真实业务里简直是效率杀手。比如你是做电商后台的,运营突然说:“帮我把这100个SKU的状态批量改成‘已下架’”,你总不能让他一个个点进去改吧?这时候,自定义Admin Action就派上用场了。
另外,我们在设计数据模型时,经常有主表和子表的关系,比如一个Order(订单)对应多个OrderItem(订单项)。在Admin里,如果看个订单还要去另一个页面找订单项,那体验太差了。这时候就得用到Inline嵌套。
实战:自定义批量操作(Admin Action)
自定义Action其实很简单,就是在Admin类里定义一个方法,然后把它注册到actions列表里。
假设我们有一批文章需要批量发布。在 Django 5.1 的环境下,代码是这样的:
# admin.py
from django.contrib import admin
from django.db.models import QuerySet
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'status', 'created_at')
actions = ['make_published'] # 注册这个action
# 定义具体的Action函数
def make_published(self, request, queryset: QuerySet):
# 关键点:这里用 update 而不是 save,效率更高,直接走SQL
updated_count = queryset.update(status='published')
# 给个提示,告诉用户操作成功
self.message_user(request, f"{updated_count} 篇文章已成功发布。")
# 给这个Action起个好听的中文名
make_published.short_description = "批量发布选中的文章"
这段代码的逻辑很清晰。我们在ArticleAdmin里加了一个make_published方法,它接收request和queryset。queryset就是你在Admin列表页勾选的那些数据对象。这里有个小技巧,尽量用 queryset.update(),别用循环 obj.save(),前者是一次SQL请求,后者是N次,性能差距在批量操作时非常明显。
实战:Inline嵌套编辑
再来看Inline。假设我们有Author(作者)和Book(书)两个模型,一本书只有一个作者,但一个作者有多本书。
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=6, decimal_places=2)
def __str__(self):
return self.title
现在,我们希望在编辑作者信息的时候,直接就能添加或修改他写的书。这时候就要用到StackedInline或者TabularInline。
# admin.py
from django.contrib import admin
from .models import Author, Book
# 定义Inline类
class BookInline(admin.TabularInline): # 也可以用 StackedInline,那个是纵向堆叠,Tabular是表格形式,更省空间
model = Book
extra = 1 # 默认显示几个空的添加框
fields = ['title', 'price'] # 指定在Inline里显示哪些字段
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = [BookInline] # 把Inline加进来
配置好之后,你再去Admin后台编辑作者,会发现下面多了一个区域,可以直接在这个页面上给这个作者加书,或者修改他名下的书。这就是Inline的威力,它把一对多的关系在UI上做了一层“扁平化”处理,非常符合人类的操作直觉。
🔧 实战技巧:如果你的Inline数据量可能很大(比如一个订单有几百个商品),千万别直接用默认的Inline,因为Admin会一次性加载所有数据,页面会卡死。这时候应该考虑使用django-admin-autocomplete-filter或者自己写一个自定义的Admin页面,或者限制max_num的数量。
安全与权限:细粒度控制与CSRF防护
很多新手觉得Django自带的安全机制就是“银弹”,配置好就万事大吉了。其实不是的,安全这东西,稍微一偷懒就会出大问题。Django 5.1 延续了框架一贯的安全基因,内置了防SQL注入、XSS、CSRF等机制,但这就好比你买了辆坦克,你不能因为装甲厚就不锁门。
细粒度权限控制
Django的Admin自带了一套权限系统,默认是add、change、delete、view。但其实,这套东西太粗了。比如你的后台有“财务”和“编辑”两个角色,财务只能看金额,不能改文章;编辑只能改文章,不能看财务数据。这时候默认的Group权限就不够用了。
我们需要自定义权限(Custom Permissions)。
在models.py里定义权限:
# models.py
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
is_approved = models.BooleanField(default=False)
class Meta:
permissions = [
# codename 尽量不要有空格,用下划线
("can_approve_article", "Can Approve Article"),
("can_view_financial_data", "Can View Financial Data"),
]
然后,在Admin里重写get_queryset和has_change_permission等方法,来实现逻辑上的拦截。
# admin.py
from django.contrib import admin
from django.contrib.auth.models import Permission
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'is_approved')
def get_queryset(self, request):
qs = super().get_queryset(request)
# 如果不是超级管理员,且拥有查看财务数据的权限,才能看到特定数据
if request.user.is_superuser:
return qs
if request.user.has_perm('blog.can_view_financial_data'):
return qs.filter(is_approved=True)
return qs.none() # 没权限就返回空
def has_change_permission(self, request, obj=None):
# 只有拥有审批权限的人才能修改
if request.user.has_perm('blog.can_approve_article'):
return True
return False
这样,我们就把权限控制到了“谁能看”和“谁能改”的级别。
CSRF防护与中间件
说到安全,不得不提CSRF(跨站请求伪造)。Django默认在settings.py的MIDDLEWARE里开启了django.middleware.csrf.CsrfViewMiddleware。
这个中间件的作用就是给所有的POST表单自动注入一个csrf_token。如果你在写前端表单时忘了加{% csrf_token %},提交表单时就会遇到那个著名的403 Forbidden错误。
<!-- 模板中的表单 -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">提交</button>
</form>
实际案例提醒:很多新手在写API接口或者前后端分离项目时,会发现POST请求一直403。这时候千万别图省事直接把CSRF中间件注释掉!那是在裸奔。正确的做法是,如果是纯API,应该使用django-rest-framework并在前端处理Token;如果是非表单提交,确保你在请求头里带了X-CSRFToken。
另外,Django 5.1 在安全机制上也在持续跟进现代Web标准。比如,你可以配合django-csp(内容安全策略)这个第三方库来防止XSS攻击,或者利用框架内置的点击劫持保护(X-Frame-Options)。
⚡ 效率提示:当你在Admin里定制了一些自定义的视图(比如一个自定义的按钮触发一个函数),如果这个视图是接收POST请求的,记得要么加上@csrf_protect装饰器,要么确保你的前端请求带上了Token。对于Admin后台这种内部系统,建议开启双因子认证(2FA),这比单纯依赖Django的Session安全多了。
常见问题(FAQ)与Django Admin面试考点
作为一个写了5年Django的工程师,我见过太多新手在Admin和ORM上反复经验之谈。这里我整理了一些在2024年社区里讨论热度依然很高的问题,以及面试时经常被问到的点。
FAQ:那些让人头大的报错
Q1: 为什么我在Admin里改了数据,但是数据库没变,或者页面不显示?
这通常是因为你改了Model,但没有做makemigrations和migrate。简单来说,Django的ORM是靠那张django_migrations表来追踪变更的。你改了Python代码,数据库不知道,所以不同步。
还有一种情况是你用了queryset.annotate()或者select_related(),导致列表页显示的数据是“计算值”而不是数据库里的原始值,这时候去改,可能改不到点子上。
Q2: Admin后台加载太慢了,怎么优化?
这是典型的N+1查询问题。当你在list_display里放了一个外键字段,比如author.name,Admin默认会去查N次作者表。
解决方案:重写get_queryset方法,使用select_related(针对一对一、外键)或prefetch_related(针对多对多、反向外键)。
# admin.py
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'author_name') # 这里直接显示作者名字
def author_name(self, obj):
return obj.author.name
author_name.short_description = '作者'
def get_queryset(self, request):
# 核心要点:这里用 select_related 把 author 表 join 进来,避免循环查询
return super().get_queryset(request).select_related('author')
Q3: 如何在Admin里集成富文本编辑器?
现在最流行的是django-ckeditor或者django-summernote。以django-ckeditor为例,安装后直接把Model里的TextField替换成RichTextField即可,Admin会自动识别并渲染。
面试考点精讲
面试的时候,面试官通常不会问你“怎么用”,而是问你“为什么”。
考点1:Django的MVT和MVC有什么区别?
这是必考题。简单来说,MVC是标准,MVT是Django的特化。
- M (Model):都一样,管数据。
- V (View):在Django里,View其实更像MVC里的Controller。它负责处理业务逻辑,接收请求,返回响应。
- T (Template):在Django里,Template就是MVC里的View。它负责展示页面。
所以,Django是MTV架构,本质上还是MVC,只是把V和C的角色换了一下名字。
考点2:Django Admin是如何实现自动注册和展示的?
这涉及到Django的自动发现机制(Autodiscovery)。当你运行admin.autodiscover()(在Django 5.1中,这通常在AdminConfig里自动处理)时,Django会遍历所有已安装App下的admin.py文件。
在admin.py里,我们通过@admin.register(Model)或者admin.site.register(Model, ModelAdmin)把模型和Admin类绑定。Django内部维护了一个_registry字典,存放这些映射关系。当请求到来时,Admin的URLConf会根据这个注册表动态生成对应的视图和界面。
考点3:解释中间件(Middleware)的执行流程。
这是一个高频点。Django的中间件像是一个洋葱,请求进来是一层层进去,响应出去是一层层出来。
- 请求阶段:从上到下执行中间件的
process_request,如果返回HttpResponse,后面的中间件和视图都不执行了,直接返回。
- 视图阶段:执行
process_view。
- 响应阶段:从下到上执行
process_response。
- 异常阶段:如果视图报错,会执行
process_exception。
在2024-2026年的趋势里,随着异步支持深化,面试官可能还会问你asyncMiddleware怎么写,这时候你要提到__call__方法里对asyncio.iscoroutinefunction的判断。
📖 学习建议:准备面试时,不要死记硬背。去翻翻Django 5.1的源码,特别是django.contrib.admin.sites和django.core.handlers.base这两个文件,看看AdminSite是怎么注册URL的,看看BaseHandler是怎么处理中间件的。源码看明白了,这些面试题就是送分题。