Skip to content

Django 技术面试

面试官: 你好,请先简单做个自我介绍,并谈谈你使用 Python 和 Django 的经验。

(应聘者自我介绍 - 此处省略)

面试官: 好的,那我们开始吧。首先,你能解释一下什么是 Django 吗?它有什么特点?

Q1: 请解释一下什么是 Django?它有什么核心特点?

Django 是什么?

Django 是一个使用 Python 编写的、免费且开源的高级 Web 框架。它的目标是帮助开发者快速地构建安全、可维护的 Web 应用。

核心特点:

  1. MTV 架构模式: Django 遵循 Model-Template-View (MTV) 架构模式。

    • Model (模型): 负责与数据库交互,定义数据结构和操作。可以理解为数据的描述。
    • Template (模板): 负责展示数据给用户,处理页面的 HTML 结构。可以理解为用户看到的界面。
    • View (视图): 接收用户的请求 (Request),进行业务逻辑处理,与 Model 和 Template 交互,并返回响应 (Response)。可以理解为连接数据和界面的桥梁。
    • (补充概念): MTV 与 MVC 的关系: MTV 经常被认为是 Django 版本的 Model-View-Controller (MVC)。主要区别在于职责划分:
      • Django 的 View 类似于 MVC 中的 Controller (处理逻辑)。
      • Django 的 Template 类似于 MVC 中的 View (负责展示)。
      • Django 还有一个 URL dispatcher (URL 分发器) 的角色,负责将请求路由到正确的 View,这部分有时会被认为是 MVC 框架中 Controller 的一部分功能。
  2. 自带 ORM (对象关系映射):

    • (概念讲解): ORM (Object-Relational Mapping) 是一种编程技术,它在关系型数据库和面向对象编程语言之间建立了一座桥梁。简单说,它让你能用 Python 代码 (对象和方法) 来操作数据库表和数据,而不需要写复杂的 SQL 语句。
    • Django 的 ORM 非常强大,支持多种数据库后端 (PostgreSQL, MySQL, SQLite, Oracle),让数据库操作变得简单直观。
  3. 强大的 URL 分发器: Django 使用正则表达式或简单的路径语法来定义 URL 模式,并将它们映射到相应的 View 函数或类。这使得 URL 设计非常灵活和清晰。

  4. 自带模板系统: Django 有一个功能强大且可扩展的模板语言 (Django Template Language - DTL),支持模板继承、变量、标签、过滤器等,方便将数据渲染到 HTML 中。

  5. 自动化的 Admin 后台: Django 最著名的特性之一就是它能根据你的 Model 自动生成一个功能完善的后台管理界面 (Admin site),极大地提高了开发效率,方便管理网站内容。

  6. 内置安全特性: Django 非常重视安全,内置了对常见 Web 攻击的防护措施,例如:

    • CSRF (跨站请求伪造) 防护: 自动添加 CSRF token。
    • SQL 注入防护: ORM 通常能防止大多数 SQL 注入。
    • XSS (跨站脚本) 防护: 模板系统默认会对输出进行 HTML 转义。
    • 点击劫持 (Clickjacking) 防护。
  7. “开箱即用” (Batteries Included): Django 提供了许多 Web 开发中常用的内置组件,如用户认证系统、表单处理、缓存框架、中间件、信号机制、国际化等,减少了开发者寻找和集成第三方库的麻烦。

  8. 良好的文档和社区: Django 拥有非常详细和高质量的官方文档,以及活跃的开发者社区,遇到问题时容易找到解决方案。

面试官: 很好,你提到了 MTV 架构。能详细说说 Django 处理一个请求的完整流程吗?

Q2: 请描述一下 Django 处理一个 HTTP 请求的完整生命周期(Request-Response Cycle)。

一个典型的 Django 请求处理流程如下:

  1. WSGI Handler (Web Server Gateway Interface): 当一个 Web 服务器 (如 Nginx, Apache) 收到一个 HTTP 请求后,如果配置了 Django 应用,请求会被传递给 WSGI 服务器 (如 Gunicorn, uWSGI)。WSGI 服务器负责加载 Django 应用,并将请求转换为 Python 能理解的 WSGI environ 字典。

    • (概念讲解): WSGI 是一个为 Python 定义的 Web 服务器和 Web 应用/框架之间的标准接口规范。它解耦了服务器和框架,让你可以自由组合不同的服务器和框架。
  2. 中间件 (Middleware) - 请求阶段: 请求首先会经过 Django 配置的中间件层。中间件是一些钩子 (hooks),可以在请求处理过程的特定阶段执行代码。请求会按照 settings.pyMIDDLEWARE 列表定义的顺序,依次通过每个中间件的 process_request() (较早版本) 或在基于类的中间件中处理请求的方法。中间件可以修改请求对象,或者提前返回响应。

    • (例子): 常用的中间件包括处理 Session、CSRF 保护、用户认证等。
  3. URL 解析 (URL Resolving): Django 使用根 urls.py 文件 (在 settings.py 中由 ROOT_URLCONF 指定) 来查找与请求 URL 匹配的 URL 模式。

    • Django 会按顺序遍历 urlpatterns 列表中的 path()re_path()
    • 一旦找到匹配的模式,Django 会停止搜索,并调用与该模式关联的 View 函数或类的方法,同时将从 URL 中捕获的参数 (如果有的话) 传递给 View。
    • 如果遍历完所有模式都没有找到匹配项,Django 会抛出一个 Http404 异常。
  4. 视图处理 (View Processing): View 是处理请求的核心。它接收 HttpRequest 对象和从 URL 捕获的参数。

    • View 会执行业务逻辑,这可能包括:
      • 与数据库交互 (通过 Models 和 ORM)。
      • 处理表单数据 (使用 Django Forms)。
      • 调用其他服务或 API。
      • 根据逻辑决定渲染哪个模板。
    • View 最终必须返回一个 HttpResponse 对象 (或其子类,如 JsonResponse, HttpResponseRedirect)。
  5. 中间件 (Middleware) - 视图/异常/模板响应阶段 (可选): 在 View 处理过程中或之后,特定的中间件方法也可能被调用:

    • process_view(): 在 URL 解析之后、调用 View 之前被调用。
    • process_exception(): 只有在 View 抛出异常时才会被调用。
    • process_template_response(): 只有当 View 返回的对象包含 render() 方法时 (通常是 TemplateResponse) 才会被调用。
  6. 模板渲染 (Template Rendering) (如果 View 需要的话): 如果 View 返回的是一个需要渲染模板的响应对象 (如 render(request, 'template.html', context) 函数返回的 HttpResponse,或者直接返回 TemplateResponse),Django 的模板引擎会介入。

    • 它会加载指定的模板文件。
    • 使用 View 传递过来的上下文数据 (Context) 填充模板中的变量和标签。
    • 最终生成完整的 HTML 字符串。
  7. 中间件 (Middleware) - 响应阶段: 在响应发送回 WSGI 服务器之前,它会反向通过 MIDDLEWARE 列表中的每个中间件。每个中间件的 process_response() 方法会被调用。这允许中间件在最终响应发送给客户端之前对其进行修改。

  8. WSGI Handler 返回响应: WSGI 服务器接收到 HttpResponse 对象后,将其转换为符合 HTTP 规范的响应,通过 Web 服务器发送回用户的浏览器。

总结: 请求 -> (中间件 请求) -> URL 解析 -> View -> (Model/Form/Template) -> (中间件 响应) -> 响应

面试官: 理解。你刚才提到了 ORM。Django ORM 有哪些常见的查询优化方法?select_relatedprefetch_related 有什么区别和应用场景?

select_relatedprefetch_related 都是 Django ORM 提供的用于优化数据库查询性能的方法,主要目的是减少数据库查询次数,解决所谓的 "N+1 查询问题"。

(概念讲解): N+1 查询问题: 当你查询一个对象列表 (1 次查询),然后在循环中访问每个对象的关联对象时,如果关联对象没有被预先加载,ORM 可能会为每个对象的关联对象单独执行一次数据库查询 (N 次查询),总共导致 1 + N 次查询,效率低下。

区别与应用场景:

特性select_related(*fields)prefetch_related(*lookups)
关联类型主要用于一对一 (OneToOneField)外键 (ForeignKey) 关系。主要用于多对多 (ManyToManyField)反向外键 (reverse ForeignKey) 关系。也可以用于一对一和外键。
SQL 实现通过 SQL JOIN 实现。在一个数据库查询中获取主对象和关联对象。分别执行两次或多次数据库查询。第一次查询主对象,后续查询使用 IN 子句一次性获取所有关联对象。
查询次数一次数据库查询。通常是两次或更多次 (取决于预取的层级和关系数量),但仍然显著少于 N+1 次。
性能影响如果 JOIN 的表很大或很多,单次查询可能变慢。额外的查询会增加开销,但避免了大量的单次小查询。
结果集返回一个包含 JOIN 数据的单一、可能更宽的 QuerySet。Django 在 Python 层面进行关联对象的 "连接",而不是在数据库层面。
使用场景当你需要访问关联的单个对象时(如文章的作者信息)。当你需要访问关联的多个对象集合时(如一篇文章的所有标签,一个用户的所有发布的文章)。
示例Entry.objects.select_related('blog') (获取文章及其所属博客)Post.objects.prefetch_related('tags') (获取文章及其所有标签)
User.objects.prefetch_related('post_set') (获取用户及其所有发布的文章)

总结:

  • 当你需要访问 ForeignKeyOneToOneField 指向的单个关联对象时,优先考虑 select_related,它通过 JOIN 在一次查询中完成。
  • 当你需要访问 ManyToManyField 或反向 ForeignKey (即 _set) 指向的多个关联对象集合时,使用 prefetch_related,它通过额外的查询来高效获取关联对象。

为什么不用 select_related 处理多对多? 因为 select_related 使用 JOIN,如果对多对多关系使用 JOIN,可能会导致结果集行数爆炸式增长(笛卡尔积效应),反而降低效率。prefetch_related 通过单独的查询避免了这个问题。

面试官: 讲得很好。我们再聊聊视图。Django 提供了函数式视图 (FBV) 和类视图 (CBV)。你更倾向于使用哪种?它们各有什么优缺点?

Q4: Django 中的函数式视图 (FBV) 和类视图 (CBV) 有什么区别?各自的优缺点是什么?

函数式视图 (Function-Based Views - FBV): 使用 Python 函数来处理 Web 请求并返回响应。每个 URL 对应一个处理函数。

python
# FBV 示例
from django.shortcuts import render
from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # 处理 GET 请求的逻辑
        return render(request, 'my_template.html', {'foo': 'bar'})
    elif request.method == 'POST':
        # 处理 POST 请求的逻辑
        return HttpResponse('POST request processed')

类视图 (Class-Based Views - CBV): 使用 Python 类来处理 Web 请求。不同的 HTTP 方法 (GET, POST, PUT, DELETE 等) 由类中的不同方法来处理。Django 提供了一系列通用的基类视图 (如 View, TemplateView, ListView, DetailView, FormView 等) 来简化开发。

python
# CBV 示例
from django.shortcuts import render
from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request, *args, **kwargs):
        # 处理 GET 请求的逻辑
        return render(request, 'my_template.html', {'foo': 'bar'})

    def post(self, request, *args, **kwargs):
        # 处理 POST 请求的逻辑
        return HttpResponse('POST request processed')

对比:

特性函数式视图 (FBV)类视图 (CBV)
简洁性对于简单的视图,代码通常更短、更直接。对于简单的视图,可能显得有点“重”。
可读性直观,易于理解请求处理流程。需要理解类的继承和方法分发机制,初学时可能稍复杂。
代码复用复用逻辑通常需要定义额外的辅助函数。主要优势: 可以通过继承Mixins 轻松复用代码和功能。
可扩展性扩展功能通常涉及修改函数本身。主要优势: 易于通过继承重写方法或添加 Mixins 来扩展功能。
代码组织不同 HTTP 方法的逻辑写在同一个函数内(通常用 if/elif 判断 request.method)。不同 HTTP 方法由不同的类方法处理 (如 get(), post()),逻辑更分离。
通用视图无内置的通用视图模式。Django 提供了大量通用类视图 (Generic CBVs),极大简化常见模式(如列表展示、详情页、表单处理)的开发。
适用场景简单、特定的视图逻辑。复杂视图、需要代码复用、遵循 RESTful 风格、使用通用视图的场景。

总结与倾向:

  • FBV 非常适合初学者入门和编写简单、一次性的视图。它们简单明了。
  • CBV 在构建大型、复杂的应用时更能体现优势,尤其是在代码复用、扩展性和利用 Django 内置通用视图方面。当需要处理多种 HTTP 方法或实现标准 CRUD (Create, Read, Update, Delete) 操作时,CBV 通常是更好的选择。
  • 在实际项目中,两者往往会混合使用。没有绝对的哪个更好,选择取决于具体的场景和需求。熟悉 CBV 及其通用视图对于提高 Django 开发效率非常重要。

面试官: 明白了。Django 的中间件 (Middleware) 是一个重要的概念,能解释一下它是什么,以及它的执行顺序吗?举个例子说明它的用途。

Q5: 什么是 Django 中间件 (Middleware)?它的执行顺序是怎样的?请举例说明其用途。

什么是中间件?

Django 中间件是一个轻量级的、底层的插件系统,用于在全局范围内干预 Django 的请求和响应处理过程。你可以把它想象成一系列位于 Web 服务器和 Django 视图之间的“处理层”或“钩子”。

每个中间件组件负责执行特定的功能。Django 项目在 settings.pyMIDDLEWARE 设置中定义了一个中间件列表。

执行顺序:

中间件的执行顺序非常重要,它遵循一个“洋葱模型”:

  1. 请求阶段 (Request Phase):

    • 当一个请求进来时,它会按照 MIDDLEWARE 列表中的顺序 (从上到下),依次通过每个中间件。
    • 每个中间件可以处理请求,修改 HttpRequest 对象,或者甚至直接返回一个 HttpResponse 对象 (从而跳过后续的中间件和视图处理)。
  2. 响应阶段 (Response Phase):

    • 当视图处理完毕,生成一个 HttpResponse 对象后,这个响应会按照 MIDDLEWARE 列表中的逆序 (从下到上),依次通过每个中间件。
    • 每个中间件可以处理响应,修改 HttpResponse 对象。

简单来说: 请求时顺序执行,响应时逆序执行。就像剥洋葱和穿洋葱。

Request -> Middleware 1 -> Middleware 2 -> ... -> Middleware N -> View
Response <- Middleware 1 <- Middleware 2 <- ... <- Middleware N <- View

(注意: 这是简化模型,实际还有 process_view, process_exception, process_template_response 等方法,但核心的请求/响应流程顺序是这样)

用途举例:

中间件非常灵活,可以用于实现很多横切关注点 (cross-cutting concerns) 的功能:

  1. Session 处理 (SessionMiddleware): 在请求到达视图前加载用户的 Session 数据,在响应返回前保存 Session 的更改。
  2. 用户认证 (AuthenticationMiddleware): 在请求到达视图前,根据 Session 或其他信息识别当前用户,并将用户对象附加到 request.user 属性上。
  3. CSRF 防护 (CsrfViewMiddleware): 检查 POST 请求中是否包含有效的 CSRF token,防止跨站请求伪造攻击。
  4. 消息框架 (MessageMiddleware): 支持在请求之间传递一次性通知消息 (flash messages)。
  5. 安全相关的 Header (SecurityMiddleware): 添加一些增强安全性的 HTTP 头部,如 HSTS, X-Content-Type-Options 等。
  6. 内容压缩 (GZipMiddleware): 对响应内容进行 Gzip 压缩,减少传输大小。
  7. 自定义日志记录: 记录每个请求的详细信息或性能指标。
  8. 全局异常处理: 捕获未处理的异常,返回统一的错误页面或记录错误。
  9. IP 限制或访问控制: 根据请求的 IP 地址或其他特征进行访问限制。

自定义中间件示例 (简单日志):

python
# myapp/middleware.py
import time

class SimpleTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # 一次性配置和初始化

    def __call__(self, request):
        # 请求到达视图之前的代码
        start_time = time.time()

        response = self.get_response(request) # 调用下一个中间件或视图

        # 视图处理完毕,响应返回之前的代码
        end_time = time.time()
        duration = end_time - start_time
        print(f"Request to {request.path} took {duration:.4f} seconds")

        return response

# settings.py
MIDDLEWARE = [
    # ... 其他中间件
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'myapp.middleware.SimpleTimingMiddleware', # 添加自定义中间件
    # ... 其他中间件
]

面试官: 不错。现在我们来谈谈缓存。在 Django 项目中,为什么要使用缓存?Django 支持哪些缓存后端?如果我想用 Redis 做缓存,大概需要怎么配置?

Q6: 为什么在 Django 项目中使用缓存?Django 支持哪些缓存后端?如何配置使用 Redis 作为缓存后端?

为什么要使用缓存?

缓存是一种重要的性能优化手段。Web 应用中,很多操作(如数据库查询、模板渲染、复杂计算)可能会比较耗时。如果这些操作的结果在一段时间内不会改变,或者可以接受一定程度的延迟更新,那么将结果临时存储在一个快速访问的位置(缓存中),并在后续请求中直接返回缓存的结果,可以:

  1. 显著减少响应时间: 从内存或高速缓存中读取数据远快于执行数据库查询或复杂计算。
  2. 降低服务器负载: 减少了对数据库、CPU 等资源的消耗,提高了应用的并发处理能力。
  3. 提升用户体验: 更快的页面加载速度带来更好的用户满意度。

Django 支持的缓存后端:

Django 的缓存框架设计得非常灵活,支持多种缓存存储机制(后端),开箱即用的包括:

  1. 内存缓存 (LocMemCache):

    • 将缓存数据存储在当前 Django 进程的内存中。
    • 优点: 速度极快。
    • 缺点:
      • 每个进程有独立的缓存,不适合多进程部署 (如 Gunicorn/uWSGI 开多个 worker)。
      • 进程重启后缓存丢失。
      • 可能消耗大量内存。
    • 主要用于开发和测试环境。
  2. 文件系统缓存 (FileBasedCache):

    • 将每个缓存值序列化后存储为一个单独的文件。
    • 优点: 配置简单,适用于单服务器环境。
    • 缺点: 比内存缓存慢,大量缓存文件可能影响文件系统性能,不适合分布式环境。
  3. 数据库缓存 (DatabaseCache):

    • 在数据库中创建一个指定的表来存储缓存数据。
    • 优点: 可以利用现有数据库,配置相对简单。
    • 缺点: 速度通常比内存缓存慢,会增加数据库负载。
  4. 虚拟缓存 (DummyCache):

    • 实际上不进行任何缓存操作,主要用于开发或测试,或者在特定情况下禁用缓存。
  5. Memcached (MemcachedCache / PyLibMCCache):

    • 使用流行的分布式内存对象缓存系统 Memcached
    • 优点: 非常快,内存存储,支持分布式(多个 Memcached 服务器可以组成集群)。
    • 缺点: 数据存储在内存中,服务器重启或崩溃后数据通常会丢失(非持久化)。需要单独运行 Memcached 服务。
  6. Redis (django_redis 等第三方库):

    • 使用 Redis (Remote Dictionary Server) 作为缓存后端。Redis 是一个高性能的键值存储系统,通常也运行在内存中,但提供了可选的持久化功能。
    • 优点:
      • 速度极快 (通常比 Memcached 稍慢一点,但功能更丰富)。
      • 支持丰富的数据结构 (字符串、列表、集合、哈希等)。
      • 支持持久化,数据不易丢失。
      • 常用于缓存、队列、Session 存储等多种场景。
    • 缺点: 需要单独运行 Redis 服务。需要安装额外的 Python 库 (如 django-redis)。

如何配置使用 Redis 作为缓存后端:

假设我们使用 django-redis 这个流行的第三方库。

  1. 安装库:

    bash
    pip install django-redis hiredis

    (hiredis 是可选的 C 库,可以提高解析性能)

  2. 配置 settings.py:

    python
    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/1", # Redis 服务器地址和数据库编号
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                # 可选配置:
                "PASSWORD": "your_redis_password", # 如果 Redis 需要密码
                "CONNECTION_POOL_KWARGS": {"max_connections": 100}, # 连接池设置
                # "SOCKET_CONNECT_TIMEOUT": 5,  # 连接超时 (秒)
                # "SOCKET_TIMEOUT": 5,          # 读写超时 (秒)
            }
        }
        # 可以配置多个缓存别名,例如:
        # "sessions_cache": {
        #     "BACKEND": "django_redis.cache.RedisCache",
        #     "LOCATION": "redis://127.0.0.1:6379/2", # 使用不同的 Redis 数据库
        #     "OPTIONS": { ... }
        # }
    }
    
    # (可选) 将 Session 也存储在 Redis 中以提高性能
    # SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    # SESSION_CACHE_ALIAS = "default" # 或指定其他 CACHES 中的别名
    
    # (可选) 将缓存用于模板加载器,加速模板查找
    # TEMPLATES = [
    #     {
    #         'BACKEND': 'django.template.backends.django.DjangoTemplates',
    #         'DIRS': [],
    #         'OPTIONS': {
    #             'context_processors': [ ... ],
    #             'loaders': [
    #                 ('django.template.loaders.cached.Loader', [ # 使用缓存加载器
    #                     'django.template.loaders.filesystem.Loader',
    #                     'django.template.loaders.app_directories.Loader',
    #                 ]),
    #             ],
    #         },
    #     },
    # ]
  3. 使用缓存: Django 提供了多种使用缓存的方式:

    • 低级缓存 API: 直接操作缓存。
      python
      from django.core.cache import cache
      
      cache.set('my_key', 'my_value', timeout=300) # 存储,timeout 单位秒
      value = cache.get('my_key')
      cache.delete('my_key')
    • 视图缓存: 缓存整个视图的输出。
      python
      from django.views.decorators.cache import cache_page
      
      @cache_page(60 * 15) # 缓存 15 分钟
      def my_view(request):
          # ... 视图逻辑 ...
          return HttpResponse(...)
    • 模板片段缓存: 在模板中缓存部分内容。
      html
      {% load cache %}
      {% cache 500 sidebar request.user.id %} {# 缓存 500 秒,根据用户 ID 变化 #}
          <!-- 这里是需要缓存的内容 -->
          ...
      {% endcache %}

总结: 缓存对于提升 Django 应用性能至关重要。Redis 因其高性能、数据持久性和丰富功能,成为了非常流行的 Django 缓存和 Session 后端选择。通过安装 django-redis 并修改 settings.py 即可轻松集成。

面试官: 好的,最后一个问题。你能解释一下 Django 中的信号 (Signals) 是什么吗?它有什么用处?给一个使用场景的例子。

Q7: 什么是 Django 信号 (Signals)?它有什么用处?请举例说明一个使用场景。

什么是 Django 信号?

Django 信号是一种解耦应用内部组件的机制。它允许某些发送者 (Sender) 在特定动作发生时,通知一组接收者 (Receiver) 函数,而发送者和接收者之间不需要有直接的依赖关系。

可以把它想象成一个广播系统:当某个事件发生时(比如一个模型实例被保存了),系统会发出一个“信号广播”,所有订阅了这个信号的“收音机”(接收者函数)都会收到通知并执行相应的操作。

用处/优点:

  1. 解耦 (Decoupling): 主要优点。让不同的应用或模块可以在不直接相互调用的情况下进行交互。例如,一个 orders 应用在订单创建后可以发送一个信号,而一个独立的 notifications 应用可以接收这个信号并发送邮件通知,这两个应用不需要知道对方的存在。
  2. 可插拔性 (Pluggability): 方便添加或移除功能。你可以为一个已有的事件(如用户登录)添加新的处理逻辑,而无需修改原始触发事件的代码。
  3. 代码组织: 将与特定事件相关的副作用(side effects)逻辑集中到接收者函数中,使核心业务逻辑更清晰。

Django 内置信号:

Django 自身提供了很多内置信号,特别是在模型层 (django.db.models.signals):

  • pre_save / post_save: 在模型 save() 方法调用之前/之后发送。
  • pre_delete / post_delete: 在模型 delete() 方法调用之前/之后发送。
  • pre_init / post_init: 在模型 __init__() 方法调用之前/之后发送。
  • m2m_changed: 当模型的 ManyToManyField 字段发生变化时发送。

还有其他模块的信号,如请求相关的 (request_started, request_finished),测试相关的等。

使用场景举例:

场景: 当一个新用户注册成功 (User 模型实例被创建) 后,自动为该用户创建一个关联的个人资料 (Profile) 对象。

不使用信号 (耦合方式): 你可能需要在创建 User 对象的视图函数或 User.save() 方法中,硬编码创建 Profile 的逻辑。这会使 User 模型或相关视图与 Profile 模型产生强耦合。

使用信号 (解耦方式):

  1. 定义接收者函数 (Receiver Function): 通常放在 models.py 或单独的 signals.py 文件中。

    python
    # myapp/signals.py
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.contrib.auth.models import User
    from .models import Profile # 假设你有一个 Profile 模型
    
    @receiver(post_save, sender=User) # 监听 User 模型的 post_save 信号
    def create_user_profile(sender, instance, created, **kwargs):
        """
        当 User 实例被创建后,自动创建对应的 Profile 实例。
        :param sender: 发送信号的模型类 (这里是 User)
        :param instance: 被保存的实际模型实例 (这里是刚创建的 User 对象)
        :param created: 布尔值,如果实例是新创建的则为 True
        :param kwargs: 其他可能的关键字参数
        """
        if created: # 确保只在用户首次创建时执行
            Profile.objects.create(user=instance)
    
    @receiver(post_save, sender=User)
    def save_user_profile(sender, instance, **kwargs):
        """
        当 User 实例被保存时,确保其关联的 Profile 也被保存
        (如果 Profile 有需要根据 User 更新的字段)。
        这通常在 Profile 模型没有直接关联到 User 的 save() 时需要。
        """
        # 注意:如果 Profile 只需要在创建时初始化,这个函数可能不需要。
        # 但如果 Profile 需要在 User 更新时也更新,则可能需要类似逻辑。
        # 简单的例子是确保 Profile 存在,虽然上面的 create 已经处理了。
        # 更复杂的场景可能是同步某些字段。
        try:
            instance.profile.save()
        except Profile.DoesNotExist:
            # 如果因为某种原因 profile 丢失了,可能需要重新创建或记录错误
            Profile.objects.create(user=instance)
  2. 注册信号处理器: 需要在 Django 启动时加载并连接信号处理器。推荐的方式是在应用的 apps.py 文件中的 ready() 方法里导入信号模块。

    python
    # myapp/apps.py
    from django.apps import AppConfig
    
    class MyappConfig(AppConfig):
        default_auto_field = 'django.db.models.BigAutoField'
        name = 'myapp'
    
        def ready(self):
            # 当应用准备就绪时,导入信号模块,确保信号处理器被注册
            import myapp.signals

    确保在 settings.pyINSTALLED_APPS 中使用的是这个 AppConfig 的完整路径:

    python
    INSTALLED_APPS = [
        # ...
        'myapp.apps.MyappConfig', # 或者 'myapp' 如果没有自定义 AppConfig
        # ...
    ]

现在,每当一个新的 User 对象通过 User.objects.create()user.save() (且是首次保存) 被创建并保存到数据库后,post_save 信号会被触发,create_user_profile 函数就会被自动调用,为新用户创建对应的 Profile 记录。User 模型本身的代码不需要任何改动。

注意事项:

  • 信号处理函数应该尽量保持快速和简单,避免执行耗时操作,否则会阻塞发送信号的原始操作。对于耗时任务,可以考虑使用 Celery 等异步任务队列。
  • 过度使用信号可能导致代码逻辑难以追踪,需要谨慎使用。

面试官: 非常好,今天的面试就到这里。你对 Django 的基础概念和一些常用特性掌握得不错,解释得也很清晰。你有什么问题想问我吗?

(应聘者提问环节 - 此处省略)

面试官总结: 这份面试文档涵盖了 Django 的核心概念(MTV、请求生命周期、ORM、视图、模板、URL)、常用组件(Admin、Forms、Middleware、缓存、信号)、性能优化(select_related/prefetch_related、缓存)、安全(CSRF提及)以及与其他技术(如 Redis)的结合。答案力求详细并解释了相关概念,适合初学者理解。使用了 Markdown 格式,并通过表格进行了对比。希望这份文档对学习 Django 的同学有所帮助!