从其他技术栈转移到 Django:部分概念、部件
从其他技术栈转移到 Django 技术栈,了解其他技术栈中的概念在 Django 中的对应概念

起因

以往在做 Web 后端的开发的时候,我大多使用 C# 的 ASP.NET Core 或者一些别的技术栈。
这不,最近来了个活,使用 Django 做后端开发,所以得先来了解了解 Django。
得益于在其他技术栈上的经验,我不需要从头开始理解这个技术栈中的每个概念,可以找到其中相近的概念,就地迁移。

一些基础概念

概念 在 Django 中
Router(路由)

在 Django 中使用一个名为 urls.py的文件(urls模块),模块中对外暴露一个名为 urlpatterns 的数组,配合 django.urls.* 来设置路由。

同时 DRF 中提供 rest_framework.routers.* 用于实现熟悉的路由注册等功能。

Controller(控制器)

Django 中的控制器称为 View(虽说是 View,但不要和 MVC 里的 View 搞混),页面返回、API响应都是靠这个。

View 可由一个接受 request,返回 django.http.HttpResponse 的 函数/Callable 组成,也有 Class-based View,使用 as_view() 方法来转换成 Callable。

同时,DRF 中还有如 ViewSet 这些概念。

Middleware(中间件)

Django 同样也有中间件,与上面讲到的控制器接近一样,不多阐述。

ORM

Django 自带 ORM (django.db.*),一般无需再找其他库。

Model(数据模型)

一个继承django.db.models.Model的类,使用如 django.db.models.xxxField 或者 django.db.models.ForeignKey 来创建字段。

Depencency Inject(依赖注入)

Django 没有自带依赖注入,Python 作为弱类型语言也不好做依赖注入,当然也不好做 Service Layer。

细节 / 小抄

下列例程取自项目模板、各种在线技术文档

Router (urls)

原文档

例子

使用 django.urls
from django.urls import path

from . import views

urlpatterns = [
    path("articles/2003/", views.special_case_2003),
    path("articles/<int:year>/", views.year_archive),
    path("articles/<int:year>/<int:month>/", views.month_archive),
    path("articles/<int:year>/<int:month>/<slug:slug>/", views.article_detail),
]

使用 DRF
from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls

说明

urls.py 导出 urlpatterns 数组变量,其成员可包括如 django.urls 的:

  • path(route, view, kwargs=None, name=None)
    设置 route 对应的 Controller / View,kwargs 将传入 view 指定的函数.

  • re_path(route, view, kwargs=None, name=None)
    path(...) 相同,但是 route 参数使用正则表达式.

  • include(module, namespace=None)
    include(pattern_list)
    include((pattern_list, app_namespace), namespace=None)
    从其他模块引入它们的 urlpatterns.

Controller (View)

原文档

例子

有关 View 的使用
from django.http import HttpResponse, HttpResponseNotFound, Http404
from django.views.decorators.http import require_http_methods
from django.shortcuts import render, redirect
import datetime

@require_http_methods(["GET", "POST"])
def current_datetime(request):
    now = datetime.datetime.now()
    html = '<html lang="en"><body>It is now %s.</body></html>' % now
    return HttpResponse(html)

def my_view(request):
    # ...
    if foo:
        return HttpResponseNotFound("<h1>Page not found</h1>")
    else:
        return HttpResponse("<h1>Page was found</h1>")

def my_view2(request):
    # ...

    # Return a "created" (201) response code.
    return HttpResponse(status=201)

def detail(request, poll_id):
    try:
        p = ...
    except Poll.DoesNotExist:
        raise Http404("Poll does not exist")
    return render(request, "polls/detail.html", { ... })

Middleware

原文档

例子

有关中间件的使用
def simple_middleware(get_response):
    # One-time configuration and initialization.
    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        response = get_response(request)
        # Code to be executed for each request/response after
        # the view is called.
        return response
    return middleware


class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        response = self.get_response(request)
        # Code to be executed for each request/response after
        # the view is called.
        return response

ORM / Model

原文档

例子

模型的定义
from django.db import models

class Person(models.Model):
    SHIRT_SIZES = {
        "S": "Small",
        "M": "Medium",
        "L": "Large",
    }
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

class Runner(models.Model):
    MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
    name = models.CharField(max_length=60)
    medal = models.CharField(blank=True, choices=MedalType, max_length=10)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through="Membership")
    def __str__(self):
        return self.name


class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)
    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["person", "group"], name="unique_person_group"
            )
        ]

模型增删查改
# 增
p = Person(name="Fred Flintstone", shirt_size="L")
p.save()

beatles = Group.objects.create(name="The Beatles")
beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})

# 删
e.delete()

# 查
somewhere = p.shirt_size
Group.objects.filter(members__name__startswith="Paul")

# 改
Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

说明

有关 INSTALLED_APPS 配置和 Migrations

需要告诉 Django 你会使用这些模型,例如模型定义在 xxx.models,那么需要在 settings.py 中的 INSTALLED_APPS 加入模块,如:

INSTALLED_APPS = [
    "xxx", # 无需 .models
    # ...
]

之后便可通过 manage.py migrate 或者 manage.py makemigrations 执行 Migrations。

多数据库使用

settings.py 中进行配置:

DATABASES = {
    "default": {
        "NAME": "app_data",
        "ENGINE": "django.db.backends.postgresql",
        "USER": "postgres_user",
        "PASSWORD": "s3krit",
    },
    "users": {
        "NAME": "user_data",
        "ENGINE": "django.db.backends.mysql",
        "USER": "mysql_user",
        "PASSWORD": "priv4te",
    },
}

在进行 Migrate 等操作时添加 --database=users 参数指定对哪个数据库进行操作

# 保存时使用 kwargs 传入 using 选择数据库
p.save(using="second", ...)

# 查询时使用 using(...) 方法
User.objects.using("legacy_users").get(username="fred")

多数据库路由

数据库路由决定哪个 Model 要不要在某个数据库上进行配置,
settings.py 中进行配置:

DATABASE_ROUTERS = ["path.to.AuthRouter", "path.to.PrimaryReplicaRouter"]
class AuthRouter:
    """
    A router to control all database operations on models in the
    auth and contenttypes applications.
    """

    route_app_labels = {"auth", "contenttypes"}

    def db_for_read(self, model, **hints):
        """
        Attempts to read auth and contenttypes models go to auth_db.
        """
        if model._meta.app_label in self.route_app_labels:
            return "auth_db"
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write auth and contenttypes models go to auth_db.
        """
        if model._meta.app_label in self.route_app_labels:
            return "auth_db"
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if a model in the auth or contenttypes apps is
        involved.
        """
        if (
            obj1._meta.app_label in self.route_app_labels
            or obj2._meta.app_label in self.route_app_labels
        ):
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the auth and contenttypes apps only appear in the
        'auth_db' database.
        """
        if app_label in self.route_app_labels:
            return db == "auth_db"
        return None

那么在 class Meta 中带有 app_label = "auth" 的模型将会走 auth_db

filter(...) 等查询可能是 lazy 的,在读取时才会求值。

其他功能(点击跳转原文档):模型元数据(Meta)模型继承数据库事务


上次修改於 2025-10-01