Django 面试 (2)

195 阅读28分钟

django请求的生命周期?

  1. 客户端发起请求:客户端(通常是浏览器)发起一个 HTTP 请求,发送到服务器。这可能是一个 GET 请求、POST 请求,或其他类型的 HTTP 请求。
  2. 请求到达 Django:请求首先到达 Django 的 WSGI(Web Server Gateway Interface)服务器。WSGI 是 Python Web 应用的标准接口,Django 是通过 WSGI 接口与 Web 服务器(如 Gunicorn、uWSGI)进行交互的。
  3. URL 路由匹配:当请求到达 Django 后,Django 会根据请求的 URL 调用 urls.py 文件中的路由配置来决定哪个视图函数或视图类(view)来处理该请求。Django 通过 urlpatterns 中的 URL 模式匹配规则,找到对应的视图。如果没有匹配的 URL,则返回 404 错误。
  4. 中间件处理:在视图被调用之前,Django 会遍历 MIDDLEWARE 设置中列出的所有中间件类(顺序是从上到下)。每个中间件可以对请求进行修改,也可以在请求继续向下传递之前进行处理或拒绝请求。中间件的作用:常见的功能包括请求/响应修改、身份验证、会话管理、CSRF 防护、日志记录等。中间件的生命周期:process_request() → 视图 → process_response()
  5. 视图函数或视图类处理:路由匹配成功后,Django 会调用对应的视图函数或视图类来处理请求。视图函数负责接收请求、处理逻辑、查询数据库、渲染模板等。视图类(基于类的视图)则通过调用对应的 dispatch() 方法来根据请求类型(如 GET、POST)执行相应的处理逻辑。
  6. 中间件处理响应:在视图返回响应后,Django 会再次遍历 MIDDLEWARE 配置中的中间件,这次是顺序地从下到上(即响应经过的顺序)。每个中间件可以修改响应,或进行其他处理(例如设置缓存、处理 CORS、添加 HTTP 头等)。
  7. 响应返回客户端:最后,Django 将最终的响应返回给客户端。这个响应通常是一个 HTML 页面、JSON 数据、文件下载等,具体取决于视图的实现。
  8. 请求结束:客户端收到响应后,整个请求-响应的生命周期结束。客户端通常会渲染 HTML 或处理返回的数据,并进行进一步的操作。

简述什么是FBV和CBV?

  • FBV 是一种基于函数的视图。
  • CBV 是一种基于类的视图,它通过定义视图类来处理请求。在 CBV 中,可以将不同的处理逻辑(如 GET、POST)拆分到不同的类方法中,通常是 get() 和 post() 方法。

Django 的URL 路由匹配是怎么实现的?

Django 的 URL 路由匹配是一个核心功能,它决定了用户的请求会被哪个视图函数或视图类处理。Django 通过 urls.py 文件来配置路由,路由将 URL 模式映射到相应的视图函数或视图类。Django 的路由系统非常高效,支持动态 URL、命名路由、正则表达式等功能。

URL 路由匹配的核心功能通过 django.urls 模块实现。具体来说,urlpatterns 列表定义了 URL 模式与视图函数的对应关系。 每当一个请求到达 Django 时,Django 会遍历这些 URL 模式,找到第一个匹配的模式,并调用对应的视图函数。

除了 path(),Django 还提供了 re_path() 函数,可以用正则表达式来定义 URL 模式。re_path() 适用于更复杂的 URL 路由,尤其是当你需要使用正则表达式进行高度自定义时。

from django.urls import re_path
from . import views

urlpatterns = [
    re_path(r'^article/(?P<id>\d+)/$', views.article_view, name='article'),
]

这里,(?P<id>\d+) 使用了正则表达式,匹配一个数字,并将其命名为 id,然后将其传递给 article_view

Django 支持路由的嵌套,可以通过 include() 将子应用的 URL 配置包含到主应用的路由中。

# 主项目的 urls.py
from django.urls import include, path

urlpatterns = [
    path('blog/', include('blog.urls')),  # 引入 blog 应用的 URL 配置
]

在 blog 应用的 urls.py 文件中,你可以定义该应用的具体 URL 路由:

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.blog_list, name='blog_list'),
    path('<int:id>/', views.blog_detail, name='blog_detail'),
]

这种方式使得项目的 URL 路由结构更清晰、可维护。

如何给CBV的程序添加装饰器?

装饰器应用于类视图

类视图本质上是通过调用类的 dispatch() 方法来处理请求的,因此要为类视图添加装饰器,通常需要装饰 dispatch() 方法。

最常见的做法是使用 method_decorator,它允许你给类视图的特定方法(比如 getpostput 等)应用装饰器。

from django.http import HttpResponse
from django.views import View
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

# 创建一个装饰器应用到类视图的方法
@method_decorator(login_required, name='dispatch')  # 应用装饰器到 `dispatch` 方法
class MyView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse("This is a protected page!")

在这个例子中,@method_decorator(login_required, name='dispatch') 装饰器应用于类视图的 dispatch 方法。dispatch() 是类视图处理请求的核心方法,它会根据请求类型(如 GET、POST 等)调用相应的处理方法。所以,login_required 装饰器在此方式下会确保该视图只能由登录用户访问。

method_decorator是一个帮助函数,它允许你将函数装饰器(如 login_required)应用到类视图的方法上。它接受一个 name 参数,指定你希望装饰的类视图的方法(比如 getpostdispatch 等)。

name='dispatch'表示装饰器应用到 dispatch 方法,而不是某个特定的 HTTP 方法(如 getpost)。这样,所有请求都会首先经过这个装饰器

为具体方法添加装饰器

如果你只想给类视图的特定 HTTP 方法添加装饰器,而不是 dispatch() 方法,可以这样做:

from django.http import HttpResponse
from django.views import View
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

class MyView(View):
    
    @method_decorator(login_required)
    def get(self, request, *args, **kwargs):
        return HttpResponse("This is a protected page!")
    
    @method_decorator(login_required)
    def post(self, request, *args, **kwargs):
        return HttpResponse("POST request received!")

说一下Django,MIDDLEWARES中间件的作用和应用场景?

Django 中的 Middleware(中间件) 是一种轻量级的、底层的插件系统,它位于请求和响应处理的过程中,用于处理和修改传入的请求以及传出的响应。Django 的中间件允许开发者在请求处理和响应返回之前、之后插入自定义的功能逻辑。

中间件的作用是在请求和响应的生命周期中对数据进行处理,可以实现多种功能。Django 会在每个请求到达视图之前和响应返回给客户端之前,依次调用中间件。

具体来说,中间件可以实现以下功能:

  • 请求预处理:在请求到达视图之前执行特定操作,如身份验证、IP 过滤、日志记录等。
  • 响应后处理:在视图返回响应后,但在发送到客户端之前执行特定操作,如缓存响应、修改响应内容等。
  • 处理异常:捕获视图中的异常并返回适当的响应,处理错误信息。
  • 处理会话:自动管理用户会话,如保存和加载用户数据。

中间件是一个请求/响应生命周期中的钩子,可以在任何阶段处理请求和响应。

在 Django 中,中间件由一个简单的 Python 类实现,这些类是由 Django 自动加载并执行的。中间件的配置位于 settings.py 文件中的 MIDDLEWARE 列表中。

MIDDLEWARE 中列出的中间件会按照列表中的顺序逐个处理请求和响应。

# settings.py 中的 MIDDLEWARE 配置示例
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',  # 处理安全相关的中间件
    'django.contrib.sessions.middleware.SessionMiddleware',  # 处理会话管理
    'django.middleware.common.CommonMiddleware',  # 处理常见的请求和响应操作
    'django.middleware.csrf.CsrfViewMiddleware',  # CSRF 保护
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # 认证中间件
    'django.contrib.messages.middleware.MessageMiddleware',  # 消息传递中间件
    'django.middleware.clickjacking.XFrameOptionsMiddleware',  # 防止点击劫持
]

列举django中间件的5个方法?

  1. process_request(request)

在请求到达视图前执行:

class AuthMiddleware:
    def process_request(self, request):
        token = request.headers.get("Authorization")
        if token:
            request.user = get_user_from_token(token)  # 自定义逻辑
  1. process_response(self, request, response)

此方法在视图返回响应之后、响应发送到客户端之前被调用。它允许开发者对响应进行处理或修改。可以在这里修改响应数据(如响应头、响应内容等)。

class CustomMiddleware:
    def process_response(self, request, response):
        response['X-Custom-Header'] = 'Some Value'
        return response

django的request对象是在什么时候创建的?

request 对象的创建发生在请求处理的初始阶段。具体来说,是在在 WSGIHandler或者ASGIHandler 的 __call__ 方法中直接创建。

请求入口

当 Django 运行在 WSGI 服务器(如 Gunicorn)时,请求处理的入口是 django/core/handlers/wsgi.py 中的 WSGIHandler 类:

# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest  # 定义请求类

    def __call__(self, environ, start_response):
        # 创建 request 对象
        request = self.request_class(environ)
        # 后续处理(中间件、视图等)
        response = self.get_response(request)
        return response

请求对象构造

WSGIRequest 继承自 HttpRequest,在初始化时会解析 WSGI 的 environ 字典(包含原始 HTTP 请求数据)

# django/core/handlers/wsgi.py
class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        self.environ = environ
        self.path = environ.get("PATH_INFO", "")  # 解析路径
        self.method = environ.get("REQUEST_METHOD", "").upper()  # 解析HTTP方法
        # 解析其他数据(Headers、GET/POST参数等)

django的request对象都封装了哪些请求数据?它们是在何时被封装的?在request对象被创建到视图类中操作request对象,它都经过了哪些处理?

Django REST Framework(DRF)对 Django 原生的 HttpRequest 进行了包装,提供了一个更高级的 Request 对象(位于 rest_framework.request.Request 中),它在原有 Django 请求对象基础上增加了许多对 API 开发非常有用的功能和属性。

request 对象封装的核心数据

Django 的 request 对象(通常是 HttpRequest 或其子类,如 WSGIRequest/ASGIRequest)封装了以下关键数据:

request.method:HTTP 方法(GET/POST/PUT 等)

request.path:请求路径(如 /api/users/)

request.headers:请求头(通过 request.headers['User-Agent'] 访问)

request.META:原始环境变量(如 REMOTE_ADDR、HTTP_HOST)

request.scheme:协议(http/https)

request.COOKIES:客户端 Cookies

request.GET:URL 查询参数(解析自 ?name=foo

request.POST:表单数据(仅限 application/x-www-form-urlencodedmultipart/form-data 格式)

request.FILES:上传的文件对象

request.session:会话数据(需启用 SessionMiddleware

request.user:用户对象(需启用 AuthenticationMiddleware

数据封装时机

创建阶段(立即封装)

request 对象在 WSGIHandler 或 ASGIHandler 的 __call__ 方法中创建时。

# django/core/handlers/wsgi.py
class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        self.environ = environ
        self.method = environ.get('REQUEST_METHOD', '').upper()  # 解析HTTP方法
        self.path = environ.get('PATH_INFO', '')  # 解析路径
        self.GET = self._load_get_and_post()[0]   # 解析GET参数
        self.POST = self._load_get_and_post()[1]  # 解析POST参数

从 WSGI environ 字典中直接解析基础数据(方法、路径、查询参数等)。

中间件处理阶段(动态扩展)

中间件的 process_requestprocess_view 方法。

# 中间件添加自定义数据
class CustomMiddleware:
    def process_request(self, request):
        request.api_version = request.headers.get('X-API-Version', 'v1')  # 动态添加属性

中间件可以修改或扩展 request 对象(如身份验证后添加 request.user)。

视图处理阶段(按需加载)

延迟加载特性request.POSTrequest.FILES 的实际解析发生在首次访问时(避免不必要的解析开销)。

class HttpRequest:
    def _load_post_and_files(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post, self._files
    @property
    def POST(self):
        return self._load_post_and_files()[0]  # 按需加载POST数据

django rest framework 又对django的request对象都封装了哪些数据,添加了什么方法?django rest framework 框架对request又有哪些处理?这么做的好处都有什么?

DRF 对 request 对象的封装

DRF 通过自定义 Request 类(位于 rest_framework.request.Request)对 Django 的 HttpRequest 进行扩展,主要添加以下属性和方法:

  • request.data: 用于访问已经解析后的请求数据,无论是 JSON、XML、表单数据等,都可以通过 .data 属性获得一个 Python 字典或其他数据结构。在 @property data 方法中,DRF 根据请求的 Content-Type 选择相应的解析器(Parser),调用解析器的 .parse() 方法对请求体进行解析,并将结果缓存起来。
  • request.query_params: 替代 Django 的 request.GET,明确表示 URL 查询参数。实际上,.query_paramsself._request.GET 的一个别名
  • request.user: 经过 DRF 认证后的用户对象(可能是 AnonymousUser
  • request.auth: 认证令牌或其他附加认证信息(如 JWT 的 Token 对象)
  • request.parser_context: 解析器上下文(包含视图、请求方法、当前解析器等元信息)

DRF 对 request 的处理流程

DRF 的请求处理流程通过 视图类(APIView)请求处理器 协同完成,核心步骤如下:

# rest_framework.views.APIView
class APIView:
    def dispatch(self, request, *args, **kwargs):
        # 1. 封装请求对象:将 Django 的 request 转换为 DRF 的 Request 对象
        request = self.initialize_request(request, *args, **kwargs)
        #  2. 执行认证(Authentication)、检查权限(Permissions)、检查限流(Throttling)
        self.initial(request, *args, **kwargs)

请求封装

# rest_framework.views.APIView
def initialize_request(self, request, *args, **kwargs):
    return Request(
        request,  # 原始 Django request
        parsers=self.get_parsers(),          # 解析器(JSON/FormData等)
        authenticators=self.get_authenticators(),  # 认证器
        negotiator=self.get_content_negotiator(),
        parser_context=kwargs
    )

通过 解析器(Parsers) 将请求体(如 JSON)转换为 Python 数据结构,填充到 request.data

按顺序执行 认证器(Authenticators)(如 TokenAuthenticationJWTAuthentication),若认证成功,设置 request.userrequest.auth

DRF 的 Request 对象

DRF 的 Request 类接受 Django 的 HttpRequest 作为基础对象,并将其保存在内部变量 _request中。

class Request:
    def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
        self._request = request  # 原始的 Django HttpRequest 对象

这样 DRF Request 对象内部持有原始的 Django 请求数据,同时为后续的扩展做好准备。

这么做的好处

  1. 统一接口: 无论请求数据格式如何(JSON、XML、表单数据),开发者都可以通过 .data 以统一的方式访问解析后的数据。
  2. 解耦解析逻辑: 解析过程由专门的 Parser 类负责,视图层代码无需关心请求体的格式和解析过程,从而降低了代码耦合度和复杂性。
  3. 增强扩展性:DRF Request 对象不仅代理了原始的 Django 请求,还添加了认证、内容协商等一系列功能,为后续扩展(如自定义解析器、自定义认证)提供了统一的入口。
  4. 提高安全性和灵活性:通过在 Request 对象中集中处理认证和数据解析,可以在统一位置添加异常处理、数据校验和日志记录等功能,提高系统的整体安全性和可维护性。
  5. 与 DRF 其它组件无缝集成:例如,权限检查、序列化、视图处理等 DRF 模块都依赖于统一的 Request 接口,这种封装让各组件之间的协作更为高效。

作为一个前后端分离的使用Django rest framework框架的后端开发工程师,有必要了解djangode csrf机制吗?

CSRF机制的场景

使用 SessionAuthentication,API允许浏览器通过Cookie/Session认证,DRF 会强制验证CSRF Token(因为浏览器会自动携带Cookie,存在CSRF风险)。

无需处理CSRF的场景

当使用纯API架构(使用Token/JWT认证),例如TokenAuthenticationJWTAuthentication、OAuth2等,这些认证方式 不依赖Cookie,因此 不受CSRF中间件影响。

明确关闭CSRF中间件

settings.py 中移除 'django.middleware.csrf.CsrfViewMiddleware',同时禁止使用SessionAuthentication

DRF的认证跟Django的认证有什么关联和区别?是不是我在DRF自定义一个验证类,就不需要Django的验证系统以及session模块了?

关联性

  1. 用户模型共用:DRF 的认证系统默认直接使用 Django 的 User 模型(或自定义用户模型),通过 request.user 访问用户对象,与 Django 的认证系统无缝集成。
  2. Session 认证的依赖:当 DRF 使用 SessionAuthentication 时,完全依赖 Django 的 Session 模块和 AuthenticationMiddleware。此时Django 的 SessionMiddleware 负责创建和管理 Session;Django 的 AuthenticationMiddlewarerequest.user 绑定到请求对象。

区别

  1. 认证方式:Django 认证基于 Session 和 Cookie,DRF 认证支持多种方式(Token、JWT、Session、OAuth2 等)
  2. 请求处理阶段:Django 认证在中间件层处理(AuthenticationMiddleware),DRF 认证在视图层处理(通过 APIView 的认证类)
  3. 默认行为:Django 认证自动处理 Cookie 和 Session,DRF 认证需显式配置认证类(如 TokenAuthentication

列举django orm 中常见优化的方法

select_related()

当你的模型通过外键(ForeignKey)或一对一关系(OneToOneField)关联其他模型时,默认情况下 Django 在访问关联对象时会再发一次 SQL 查询。

使用 select_related() 可以在单一 SQL 查询中利用 SQL JOIN 一次性取出关联对象的数据,从而大大减少查询次数,提高性能。

假设有如下两个模型:

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

使用 select_related() 查询 Book 并同时取出关联的 Author 信息:

# views.py
from .models import Book

def book_list(request):
    # 使用 select_related 一次性取出 book 和对应的 author
    books = Book.objects.select_related('author').all()
    for book in books:
        # 此处不会产生额外的 SQL 查询,因为 author 已经一并查询出来
        print(book.title, book.author.name)
    # 返回响应...

减少数据库查询次数(N+1 查询问题),提高查询效率,尤其在需要频繁访问关联对象数据时。

prefetch_related()

对于多对多关系(ManyToManyField)或反向外键查询(即 OneToMany),prefetch_related() 会发出单独的 SQL 查询去获取相关对象,并在 Python 层面进行拼接,适用于获取大量关联对象的场景。

假设有如下模型:

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author)

使用 prefetch_related() 查询 Book 和其多对多的 Author 列表:

# views.py
from .models import Book

def book_list(request):
    # 预加载多对多关系,避免每本书循环时再查询作者数据
    books = Book.objects.prefetch_related('authors').all()
    for book in books:
        author_names = [author.name for author in book.authors.all()]
        print(book.title, author_names)
    # 返回响应...

同时加载多个关联对象,避免循环中重复查询;对于复杂关联关系能够有效减少数据库压力。

only() 与 defer()

当查询中只需要部分字段时,可以使用 only()defer() 限制加载的字段,从而减少数据传输和内存占用。

  • only()只加载指定字段,其余字段会被延迟加载(需要时再查询)
  • defer()延迟加载指定字段,其余字段正常加载。

假设有一个模型:

# models.py
class Article(models.Model):
    title = models.CharField(max_length=200)
    summary = models.TextField()
    content = models.TextField()

使用 only()

# views.py
from .models import Article

def article_list(request):
    # 只加载 title 和 summary,content 在第一次访问时会单独查询
    articles = Article.objects.only('title', 'summary').all()
    for article in articles:
        print(article.title, article.summary)
        # 第一次访问 article.content 时会触发额外查询
    # 返回响应...

使用 defer()

def article_list(request):
    # 加载除 content 外的所有字段,content 延迟加载
    articles = Article.objects.defer('content').all()
    for article in articles:
        print(article.title, article.summary)
        # 访问 article.content 时触发额外查询
    # 返回响应...

减少一次性从数据库加载的数据量,特别适用于大字段(如大文本、大二进制数据)不常用的场景,降低内存占用和网络传输压力。

values() 与 values_list()

这两个方法用于直接获取字典或元组形式的查询结果,而不是完整的模型实例。在只需部分字段数据的场景下,使用它们可以减少 ORM 实例化的开销,提高查询效率。

values()返回包含指定字段的字典列表values_list()返回包含指定字段的元组列表

假设有如下模型:

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField()

使用 values()

def product_list(request):
    # 返回每个产品的 name 和 price 的字典
    products = Product.objects.values('name', 'price')
    for product in products:
        print(product['name'], product['price'])

使用 values_list()

def product_list(request):
    # 返回元组列表,flat=True 时当只返回一个字段
    products = Product.objects.values_list('name', 'price')
    for name, price in products:
        print(name, price)
        
# flat=True时,单字段返回一维列表
book_titles = Book.objects.values_list('title', flat=True)

显著降低内存占用,适用于批量处理和数据导出等场景;分块查询可以提高大数据量场景下的性能表现。

exists()

判断查询结果是否至少存在一个对象。

len(queryset)count() 相比,exists() 会生成一个更简单的 SQL 查询,速度更快。

def check_product(request):
    if Product.objects.filter(stock__gt=0).exists():
        print("有库存的产品存在")
    else:
        print("没有库存产品")

避免不必要的数据加载,快速返回布尔结果;优化存在性判断的性能,特别在数据量大时。

bulk_create() 与 bulk_update()

对于需要创建或更新大量数据的场景,逐条创建或更新会导致大量数据库交互,使用批量操作可以显著提升效率。

bulk_create() 示例:

def create_products(request):
    product_list = [
        Product(name=f"Product {i}", price=9.99, stock=100)
        for i in range(1000)
    ]
    # 批量创建产品记录,减少数据库往返次数
    Product.objects.bulk_create(product_list, batch_size=200)
    # 返回响应...

bulk_update() 示例:

def update_products(request):
    products = list(Product.objects.filter(stock__lt=50))
    # 假设需要把这些产品的库存增加 50
    for product in products:
        product.stock += 50
    # 批量更新产品记录
    Product.objects.bulk_update(products, ['stock'], batch_size=200)
    # 返回响应...

显著减少数据库交互次数,适合大批量数据处理;提高写入和更新效率,降低系统负载。

update()

直接在数据库层面批量更新 QuerySet 中的对象,而不实例化模型对象。适用于只需更新字段而不需要触发模型的 save() 方法及其信号的场景。

def discount_products(request):
    # 为所有库存大于100的产品打九折
    updated_count = Product.objects.filter(stock__gt=100).update(price=models.F('price') * 0.9)
    print(f"更新了 {updated_count} 条记录")
    # 返回响应...

高效的批量更新,避免循环中多次调用 save();直接生成 SQL UPDATE 语句,减少 Python 层面操作。

其他基础查询方法

all()

获取模型的所有记录,返回 QuerySet,实际查询在访问数据时触发(惰性加载)。

# 获取所有书籍
books = Book.objects.all()

filter()

根据条件筛选记录。

# 获取价格大于50且标题包含"Django"的书籍
books = Book.objects.filter(price__gt=50, title__icontains="Django")

链式调用:

books = Book.objects.filter(price__gt=50).exclude(status="out_of_stock")

exclude()

排除符合条件的记录。

# 排除状态为"out_of_stock"的书籍
books = Book.objects.exclude(status="out_of_stock")

get()

获取唯一匹配条件的记录,找不到或找到多个会抛出异常。

try:
    book = Book.objects.get(id=1)  # 按主键精确查找
except Book.DoesNotExist:
    print("书籍不存在")
except Book.MultipleObjectsReturned:
    print("找到多个结果")

create()

new_book = Book.objects.create(
    title="Django ORM进阶",
    price=99,
    author=author_instance  # 假设author_instance是Author模型实例
)

其他实用方法

order_by()

对结果排序。

# 按价格降序排列,价格相同按标题升序排列
books = Book.objects.order_by('-price', 'title')

count()

返回记录数量(比 len(queryset) 高效)。

total_books = Book.objects.count()

first() 和 last()

获取 QuerySet 的第一条或最后一条记录。

first_book = Book.objects.order_by('publish_date').first()
last_book = Book.objects.order_by('publish_date').last()

事务操作

transaction.atomic()

保证数据库操作的原子性。

from django.db import transaction

try:
    with transaction.atomic():
        # 一系列数据库操作
        book = Book.objects.create(title="新书", price=100)
        author.books.add(book)
except Exception as e:
    print("操作回滚:", e)

F和Q的作用?

在 Django ORM 中,FQ 是两个用于构建复杂查询的核心工具

F 表达式(django.db.models.F

直接引用数据库字段的值,允许在查询中对字段进行运算或比较,无需先将值加载到 Python 内存中。

核心优势

  • 避免竞争条件(如并发更新问题)。
  • 直接在数据库层面完成计算,提升性能。

常见场景

  • 字段自增/自减:直接在数据库中更新字段值(如库存增减、计数器更新)。
  • 字段间比较:比较同一模型中不同字段的值(如筛选价格大于成本价的商品)。
  • 基于字段值的运算:在查询中执行算术或逻辑运算(如调整价格、计算折扣)。
from django.db.models import F

# 示例1:将所有书籍的价格增加10元
Book.objects.update(price=F("price") + 10)

# 示例2:筛选价格大于成本价的书籍
books = Book.objects.filter(price__gt=F("cost"))

# 示例3:为书籍的评分字段增加1(避免并发问题)
book = Book.objects.get(id=1)
book.rating = F("rating") + 1
book.save()  # 直接生成 SQL:UPDATE ... SET rating = rating + 1

表达式(django.db.models.Q

构建复杂查询条件(如逻辑 ORNOT、多条件组合),突破 Django ORM 默认的 AND 逻辑限制。

核心优势

  • 支持灵活的逻辑组合(如 ORANDNOT)。
  • 可重用和动态构建查询条件。

常见场景:

  • 逻辑 OR 条件:查询满足任一条件的记录(如标题包含“Django”或价格低于50的书籍)。
  • 逻辑 NOT 条件:排除符合条件的记录。
  • 复杂嵌套条件:组合多个 AND/OR 条件(如筛选特定分类下高评分或促销商品)。
from django.db.models import Q

# 示例1:查询标题包含"Django" 或 价格低于50的书籍
books = Book.objects.filter(
    Q(title__icontains="Django") | Q(price__lt=50)
)

# 示例2:排除状态为"out_of_stock" 且 价格高于100的书籍
books = Book.objects.filter(
    ~Q(status="out_of_stock") & ~Q(price__gt=100)
)

# 示例3:嵌套条件(分类为"编程"且评分>4,或分类为"小说"且价格<30)
books = Book.objects.filter(
    Q(category="编程", rating__gt=4) |
    Q(category="小说", price__lt=30)
)

什么是读写分离?什么场景需要读写分离?django orm 中如何设置读写分离?

什么是读写分离

读写分离是一种数据库架构设计,其核心思想是将写操作(如 INSERT、UPDATE、DELETE)与读操作(SELECT)分离开来:

  • 写操作统一由主库(Master)处理,保证数据的一致性。
  • 读操作则由一个或多个从库(Replica/Slave)承担,从而分担主库的负载,提高系统的并发处理能力和响应速度。

读写分离的适用场景

  • 高并发读请求:当应用程序中读请求远远多于写请求时,单一数据库服务器可能成为性能瓶颈。通过将读请求分摊到多个从库,可以大幅提升查询响应速度。
  • 负载均衡:通过增加从库实例,可以平衡数据库服务器的负载,从而提高整个系统的可扩展性和稳定性。
  • 业务场景中数据一致性要求宽松:如果应用场景中允许一定程度的读写延迟(例如新闻、博客、商品展示等场景),则读写分离是一个理想的选择。

Django ORM 中如何设置读写分离

Django 自身没有内置完整的读写分离方案,但可以借助 多数据库配置数据库路由器(Database Router) 来实现这一目的。

配置多数据库

在项目的 settings.py 中配置多个数据库,通常会设置一个主库和一个或多个从库。

DATABASES = {
    'default': {  # 主库,处理写操作
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'master_db',
        'USER': 'db_user',
        'PASSWORD': 'db_password',
        'HOST': 'master_host',
        'PORT': '3306',
    },
    'replica': {  # 从库,处理读操作
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'replica_db',
        'USER': 'db_user',
        'PASSWORD': 'db_password',
        'HOST': 'replica_host',
        'PORT': '3306',
    },
}

创建数据库路由器

数据库路由器决定了某个操作使用哪个数据库。你需要创建一个 Python 模块,例如 routers.py,在其中实现一个路由器类,通常至少实现两个方法:

  • db_for_read(model, **hints):返回适合读操作的数据库别名(比如 'replica')。
  • db_for_write(model, **hints):返回适合写操作的数据库别名(通常返回 'default')
# routers.py
class MasterSlaveRouter:
    """
    读操作使用从库,写操作使用主库。
    """
    def db_for_read(self, model, **hints):
        return 'replica'  # 所有读操作使用 'replica'

    def db_for_write(self, model, **hints):
        return 'default'  # 所有写操作使用 'default'

    def allow_relation(self, obj1, obj2, **hints):
        """
        允许关联存在于同一数据库中的对象。
        """
        db_list = ('default', 'replica')
        if obj1._state.db in db_list and obj2._state.db in db_list:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        确保所有迁移操作都在主库上执行。
        """
        return db == 'default'

配置路由器

settings.py 中,添加数据库路由器配置:

DATABASE_ROUTERS = ['myproject.routers.MasterSlaveRouter']

其中 'myproject.routers.MasterSlaveRouter' 是你路由器类的导入路径。

读写分离的工作流程

  • 写操作(如 .save(), .update(), .delete())调用时,Django 会调用 db_for_write() 方法,返回 'default' 数据库,确保所有数据写入主库。
  • 读操作(如 .filter(), .get(), .all())调用时,Django 会调用 db_for_read() 方法,返回 'replica' 数据库,从从库中读取数据。

注意事项

数据同步延迟:由于主库与从库之间存在同步延迟,读操作可能读取到旧数据。 事务一致性:在同一事务中进行写操作后,紧接着的读操作可能无法立即看到更新的数据,因此在这种情况下可能需要强制使用主库。可以通过在操作中指定 using('default') 来确保在主库中进行查询。例如:

# 强制在主库中查询最新数据
obj = MyModel.objects.using('default').get(pk=1)

负载均衡与故障转移:在生产环境中,通常还需要配合负载均衡和故障转移机制来管理多个从库和主库的状态,这部分需要额外考虑网络和数据库同步策略。

总结

读写分离是通过将写操作和读操作分配到不同的数据库实例上,从而分摊负载、提升性能的一种架构设计。

它适用于读请求多、写请求相对较少的场景,但要注意数据同步延迟事务一致性问题。

在 Django ORM 中,可以通过配置多数据库和自定义数据库路由器(Database Router)来实现读写分离,从而让写操作走主库,读操作走从库。

django内置的缓存机制?

缓存后端类型

Django 内置支持多种缓存后端,开发者可以根据部署环境和需求选择合适的后端:

  • 本地内存缓存(LocMemCache)

    • 特点:将缓存数据存储在每个进程的内存中,速度极快,但不支持跨进程共享。
    • 场景:适用于开发环境或单进程部署的小型应用。
  • 文件缓存(FileBasedCache)

    • 特点:将缓存数据存储在磁盘文件中。
    • 场景:适用于不需要高并发且可以接受磁盘读写速度的环境。
  • 数据库缓存(DatabaseCache)

    • 特点:将缓存数据存储在数据库中的一个表里。
    • 场景:在不方便部署专门的缓存服务器时,可以利用已有数据库实现缓存。
  • Memcached 缓存

    • 特点:Memcached 是一个高性能的分布式内存对象缓存系统。
    • 场景:适用于高并发、大流量的生产环境(Django 同时支持多个 Memcached 后端,如 pylibmc、python-memcached)。
  • Redis 缓存(通过第三方包,如 django-redis)

    • 特点:Redis 具备丰富的数据结构和持久化能力。
    • 场景:高性能需求以及需要更复杂缓存策略时使用。

配置示例(以 LocMemCache 和 Memcached 为例):

from django.core.cache import cache

# 存入缓存,10 分钟后失效
cache.set('my_key', 'hello, world!', timeout=600)

# 获取缓存数据
value = cache.get('my_key')
print(value)  # 输出: hello, world!

# 如果不存在则设置默认值
value = cache.get_or_set('another_key', 'default_value', timeout=300)
print(value)

统一的缓存 API

Django 缓存系统通过 django.core.cache 模块提供了统一的接口,使得缓存操作在各个后端之间保持一致。常用的低级 API 方法包括:

  • cache.get(key, default=None)
    获取指定键对应的缓存值,如果不存在返回默认值。

  • cache.set(key, value, timeout=DEFAULT_TIMEOUT)
    将数据存入缓存,指定缓存键、数据和超时时间(秒)。

  • cache.delete(key)
    删除指定缓存键的数据。

  • cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT)
    如果键不存在,则设置该键为默认值;否则返回已有值。

from django.core.cache import cache

# 存入缓存,10 分钟后失效
cache.set('my_key', 'hello, world!', timeout=600)

# 获取缓存数据
value = cache.get('my_key')
print(value)  # 输出: hello, world!

# 如果不存在则设置默认值
value = cache.get_or_set('another_key', 'default_value', timeout=300)
print(value)

优化效果与好处

  • 提高响应速度:缓存避免了重复的数据库查询和重复的计算,极大提升页面响应速度。
  • 降低数据库负载:通过缓存热门数据,减少了对数据库的直接请求,从而减轻数据库压力。
  • 提升并发能力:在高并发环境下,缓存可以显著提高系统整体的处理能力。
  • 灵活性:开发者可以灵活选择缓存层级(视图、模板片段、低级 API),以满足不同业务需求。

django的缓存能使用redis吗?如果可以的话,如何配置?

是的,Django 的缓存机制可以使用 Redis 作为后端缓存。实现这一点通常需要使用第三方包 django-redis 来集成 Redis。

安装 django-redis

pip install django-redis

修改 settings.py 中的 CACHES 配置

在你的项目的 settings.py 中配置缓存后端为 Redis。例如:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",          # 使用 django-redis 提供的缓存后端
        "LOCATION": "redis://127.0.0.1:6379/1",                # 指定 Redis 的地址、端口和数据库号(这里使用数据库1)
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            # 如果需要密码,可以添加 "PASSWORD": "your_redis_password",
        }
    }
}

使用缓存

配置完成后,你就可以像使用 Django 内置缓存那样使用 Redis 缓存了。例如:

from django.core.cache import cache

# 设置缓存(10分钟后过期)
cache.set("my_key", "hello, redis!", timeout=600)

# 获取缓存
value = cache.get("my_key")
print(value)  # 输出: hello, redis!

好处

  • 高性能:Redis 作为内存数据库,读写速度非常快,适用于高并发场景。
  • 支持丰富的数据结构:除了简单的键值存储,还支持列表、集合等数据结构,方便实现更复杂的缓存逻辑。
  • 分布式支持:Redis 可以在分布式环境下部署,方便做跨多台机器的缓存共享。

Celery是什么?哪些场景会使用Celery? 一个DRF框架的项目,我要如何配置celery?怎么在DRF框架中用celery完成一个任务?