Django 备忘清单

Django 是 Python 的一款 Web 框架,本备忘单旨在快速理解 Django 所涉及的主要概念,提供了最常用的 API 示例参考

入门

准备环境

$ python --version
# Python 3.9.2
$ pip --version
# pip 20.2.3 from c:\python39\lib\site-packages\pip (python 3.9)

如果你没有安装 PIP,你可以从这个页面下载并安装它:https://pypi.org/project/pip/

入门

  • 创建虚拟环境

    $ py -m venv myproject # Windows
    $ python -m venv myproject # Unix/MacOS
    
  • 其中包含子文件夹和文件,如下所示

    myproject
     ├┈Include
     ├┈Lib
     ├┈Scripts
     ╰┈pyvenv.cfg
    
  • 以下命令来激活环境

    # Windows:
    myproject\Scripts\activate.bat
    # Unix/MacOS:
    source myproject/bin/activate
    
  • 提示符中看到以下结果:

    # Windows:
    (myproject) C:\Users\Your Name>
    # Unix/MacOS:
    (myproject) ... $
    
  • 安装 Django

    # Windows:
    (myproject) C:\Users\Name>py -m pip install Django
    # Unix/MacOS:
    (myproject) ... $ python -m pip install Django
    

创建项目

$ django-admin startproject myworld

创建了一个 myworld 文件夹,内容如下:

myworld
  ├┈ manage.py
  ╰┈ myworld/
     ├┈ __init__.py
     ├┈ asgi.py
     ├┈ settings.py
     ├┈ urls.py
     ╰┈ wsgi.py

运行 Django 项目

$ py manage.py runserver     # Windows
$ python manage.py runserver # Unix/MacOS

打开一个新的浏览器窗口并在地址栏中输入 127.0.0.1:8000

检查 Django 版本

(myproject) C:\Users\Your Name>django-admin --version
# 6.0.x

创建应用

$ py manage.py startapp members

项目中创建了一个名为 members 的文件夹,内容如下:

myworld
  ├┈ manage.py
  ├┈ myworld/
  ╰┈ members/
     ├┈ migrations/
     ┆  ╰┈ __init__.py
     ├┈ __init__.py
     ├┈ admin.py
     ├┈ apps.py
     ├┈ models.py
     ├┈ tests.py
     ╰┈ views.py

首先,看一下名为 views.py 的文件。这是我们收集发送回正确响应所需的信息的地方。

应用目录介绍

  • Django 接收 URL,检查 urls.py 文件,并调用与 URL 匹配的视图。
  • 位于 views.py 中的视图检查相关模型。
  • 模型是从 models.py 文件中导入的。
  • 然后视图将数据发送到模板文件夹中的指定模板。
  • 模板包含 HTMLDjango 标记,并使用数据将完成的 HTML 内容返回给浏览器

视图

Django 视图是接受 http 请求并返回 http 响应的 Python 函数,就像 HTML 文档一样。

使用 Django 的网页充满了不同任务和任务的视图。

视图通常放在一个名为 views.py 的文件中,该文件位于应用程序的文件夹中。

您的 members 文件夹中有一个 views.py,如下所示:

from django.shortcuts import render

# Create your views here.

找到它并打开它,并将内容替换为:

from django.shortcuts import render
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello world!")

这是一个关于如何将响应发送回浏览器的简单示例。

但是我们如何执行视图呢? 好吧,我们必须通过 URL 调用视图。

URLs

在与 views.py 文件相同的文件夹中创建一个名为 urls.py 的文件,并在其中输入以下代码:

from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='index'),
]

刚刚创建的 urls.py 文件是特定于成员应用程序的。我们还必须在根目录 myworld 中进行一些路由。

myworld 文件夹中有一个名为 urls.py 的文件,打开该文件并在 import 语句中添加 include 模块,并在列表中添加一个 path() 函数。文件将如下所示:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
  path('members/', include('members.urls')),
  path('admin/', admin.site.urls),
]

如果服务器未运行,请导航到 /myworld 文件夹并在命令提示符下执行此命令:

$ py manage.py runserver

在浏览器窗口的地址栏中输入 127.0.0.1:8000/members/

模板

members 文件夹中创建一个 templates 文件夹,并创建一个名为 myfirst.htmlHTML 文件。文件结构应该是这样的:

myworld
 ├┈ manage.py
 ├┈ myworld/
 ╰┈ members/
    ╰┈ templates/
       ╰┈ myfirst.html

打开 HTML 文件并插入以下内容:

<!DOCTYPE html>
<html>
<body>
  <h1>Hello World!</h1>
  <p>欢迎来到我的第一个 Django 项目!</p>
</body>
</html>

修改视图 members/views.py

from django.http import HttpResponse
from django.template import loader

def index(request):
  template = loader.get_template('myfirst.html')
  return HttpResponse(template.render())

更改设置

为了能够处理比“Hello World!”更复杂的东西,我们必须告诉 Django 一个新的应用程序已创建

这是在 myworld 文件夹的 myworld/settings.py 文件中完成的。查找 INSTALLED_APPS[] 列表并添加成员应用程序,如下所示:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'members.apps.MembersConfig'
]

然后运行这个命令:

$ py manage.py migrate

通过导航到 /myworld 文件夹启动服务器并执行以下命令:

$ py manage.py runserver

在浏览器窗口的地址栏中输入 127.0.0.1:8000/members/

创建表(模型)

/members/ 文件夹中,打开 models.py 文件。要在我们的数据库中添加成员表,首先创建一个成员类,并描述其中的表字段:

from django.db import models

class Members(models.Model):
  firstname = models.CharField(max_length=255)
  lastname = models.CharField(max_length=255)

然后导航到 /myworld/ 文件夹并运行以下命令:

$ py manage.py makemigrations members
# Migrations for 'members':
#   members\migrations\0001_initial.py
#     - Create model Members

创建一个包含任何新更改的文件并将该文件存储在 /migrations/ 文件夹中。下次运行 py manage.py migrate 时,Django 将根据迁移文件夹中新文件的内容创建并执行一条 SQL 语句。运行迁移命令:

$ py manage.py migrate

从模型创建的 SQL 语句是:

CREATE TABLE "members_members" (
  "id" INT NOT NULL PRIMARY KEY AUTOINCREMENT,
  "firstname" varchar(255) NOT NULL,
  "lastname" varchar(255) NOT NULL
);

常用命令

django-admin 与 manage.py

命令说明
django-admin startproject mysite创建项目骨架
python manage.py startapp blog创建应用
python manage.py runserver启动开发服务器
python manage.py makemigrations根据模型变更生成迁移
python manage.py migrate执行数据库迁移
python manage.py showmigrations查看迁移状态
python manage.py createsuperuser创建后台管理员
python manage.py collectstatic收集静态文件到 STATIC_ROOT
python manage.py shell打开 Django 上下文中的 Python Shell
python manage.py test运行测试
python manage.py check运行系统检查
python manage.py check --deploy运行部署安全检查

manage.py 会自动设置 DJANGO_SETTINGS_MODULE,适合项目内命令;django-admin 更适合创建项目或在显式指定设置模块时使用。

设置清单

设置用途
INSTALLED_APPS启用 Django 内置应用和项目应用
MIDDLEWARE按顺序处理请求和响应
ROOT_URLCONF根 URL 配置模块
TEMPLATES模板后端、目录和上下文处理器
DATABASES数据库连接配置
STATIC_URL静态文件 URL 前缀
STATIC_ROOTcollectstatic 输出目录
MEDIA_URL / MEDIA_ROOT用户上传文件 URL 与存储目录
SECRET_KEY加密签名密钥,生产环境必须保密
DEBUG生产环境必须关闭
ALLOWED_HOSTS允许访问的 Host 列表

ORM 与模型

字段与关系

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)

    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="articles")
    tags = models.ManyToManyField("Tag", blank=True)
    published_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

常用关系字段:

  • ForeignKey:多对一关系,必须设置 on_delete
  • OneToOneField:一对一关系,常用于扩展用户资料
  • ManyToManyField:多对多关系,可用 through 指定中间表

查询速查

from django.utils import timezone
from blog.models import Article, Author

# 创建
author = Author.objects.create(name="Ada", email="ada@example.com")
Article.objects.create(title="Django", body="...", author=author)

# 查询
Article.objects.all()
Article.objects.filter(title__icontains="django")
Article.objects.exclude(published_at__isnull=True)
Article.objects.get(pk=1)

# 排序与切片
Article.objects.order_by("-created_at")[:10]

# 更新与删除
Article.objects.filter(pk=1).update(published_at=timezone.now())
Article.objects.filter(published_at__isnull=True).delete()

# 关系预加载,减少 N+1 查询
Article.objects.select_related("author")
Article.objects.prefetch_related("tags")

QuerySet 方法

方法用途
filter()添加查询条件
exclude()排除查询条件
get()获取单个对象,不存在或多个会抛异常
order_by()排序
values()返回字典序列
values_list()返回元组或字段列表
select_related()通过 SQL JOIN 预加载外键/一对一
prefetch_related()额外查询并预加载多对多/反向关系
annotate()添加聚合或表达式字段
aggregate()返回聚合结果
exists()判断是否存在匹配记录

事务

from django.db import transaction

with transaction.atomic():
    author = Author.objects.create(name="Grace", email="grace@example.com")
    Article.objects.create(title="Atomic", body="...", author=author)

在同一业务动作中需要同时写入多张表时,使用 transaction.atomic() 保证要么全部成功,要么全部回滚。

视图与表单

快捷函数

from django.shortcuts import get_object_or_404, redirect, render

from .models import Article

def detail(request, pk):
    article = get_object_or_404(Article, pk=pk)
    return render(request, "blog/detail.html", {"article": article})

def go_home(request):
    return redirect("home")

ModelForm

from django.forms import ModelForm

from .models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ["title", "body", "tags"]

ModelForm 会根据模型字段生成表单,并可通过 form.save() 创建或更新模型实例。

表单处理

# forms.py
from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
# views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import ContactForm

def contact(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            subject = form.cleaned_data["subject"]
            return HttpResponseRedirect("/thanks/")
    else:
        form = ContactForm()

    return render(request, "contact.html", {"form": form})
<form method="post">
  {% csrf_token %}
  {{ form }}
  <button type="submit">提交</button>
</form>

处理会修改数据的表单时使用 POST,模板中加入 {% csrf_token %}

类视图

from django.urls import reverse_lazy
from django.views.generic import CreateView, DetailView, ListView

from .models import Article

class ArticleListView(ListView):
    model = Article
    paginate_by = 20
    template_name = "blog/article_list.html"

class ArticleDetailView(DetailView):
    model = Article
    template_name = "blog/article_detail.html"

class ArticleCreateView(CreateView):
    model = Article
    fields = ["title", "body", "tags"]
    success_url = reverse_lazy("article-list")
from django.urls import path

from .views import ArticleCreateView, ArticleDetailView, ArticleListView

urlpatterns = [
    path("articles/", ArticleListView.as_view(), name="article-list"),
    path("articles/new/", ArticleCreateView.as_view(), name="article-create"),
    path("articles/<int:pk>/", ArticleDetailView.as_view(), name="article-detail"),
]

后台、认证与测试

Admin

from django.contrib import admin
from .models import Article

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "author", "published_at"]
    list_filter = ["published_at"]
    search_fields = ["title", "body"]

$ python manage.py createsuperuser
$ python manage.py runserver

访问 /admin/ 管理数据。生产环境应限制后台入口、启用 HTTPS 并保护管理员账号。

认证装饰器

from django.contrib.auth.decorators import login_required, permission_required

@login_required
def profile(request):
    ...

@permission_required("blog.add_article", raise_exception=True)
def create_article(request):
    ...

测试

from django.test import TestCase
from django.urls import reverse

from .models import Author

class ArticleTests(TestCase):
    def test_article_list_page(self):
        Author.objects.create(name="Ada", email="ada@example.com")
        response = self.client.get(reverse("article-list"))
        self.assertEqual(response.status_code, 200)
$ python manage.py test
$ python manage.py test blog.tests.ArticleTests

部署检查

上线前必查

检查项建议
DEBUG生产环境设置为 False
SECRET_KEY从环境变量或密钥管理系统读取
ALLOWED_HOSTS只允许真实域名和必要的主机名
HTTPS设置 SECURE_SSL_REDIRECT、Cookie Secure 选项
CSRF配置可信来源并保留中间件
静态文件配置 STATIC_ROOT 并运行 collectstatic
数据库使用生产数据库账号、备份和迁移流程
日志配置 LOGGING,保存错误与审计信息
系统检查执行 python manage.py check --deploy

Django 模板

模板变量

<!-- template.html -->
<h1>你好 {{ firstname }},你好吗?</h1>

在视图 (views.py) 中创建变量,上面示例中的变量 firstname 通过视图发送到模板:

from django.http import HttpResponse
from django.template import loader

def testing(request):
  template = loader.get_template('template.html')
  context = {
    'firstname': '狂徒张三',
  }
  return HttpResponse(template.render(context, request))

模板中创建变量

{% with firstname="Tobias" %}
<h1>你好 {{ firstname }},你好吗?</h1>

数组循环

<ul>
  {% for x in mymembers %}
    <li>{{ x.firstname }}</li>
  {% endfor %}
</ul>

模板标签参考

标签描述
autoescape指定自动转义模式是打开还是关闭
block指定块部分
comment指定注释部分
csrf_token保护表单免受跨站点请求伪造
cycle指定要在循环的每个循环中使用的内容
debug指定调试信息
extends指定父模板
filter在返回之前过滤内容
firstof返回第一个非空变量
for指定一个 for 循环
if指定一个 if 语句
ifchanged仅当自上次迭代以来值已更改时才输出块
(用于 for 循环)
include指定包含的内容/模板
load从另一个库加载模板标签
lorem输出随机文本
now输出当前日期/时间
regroup按组对对象进行排序
resetcycle循环使用,重置循环
spaceless删除 HTML 标签之间的空格
templatetag输出指定的模板标签
url返回 URL 的绝对 URL 部分
verbatim指定不应由模板引擎呈现的内容
widthratio给定值和最大值之间的比率计算宽度值
with指定要在块中使用的变量

If 语句

{% if greeting == 1 %}
  <h1>Hello</h1>
{% elif greeting == 2 %}
  <h1>Welcome</h1>
{% else %}
  <h1>Goodbye</h1>
{% endif %} 

For 循环

{% for x in cars %}
  <h1>{{ x.brand }}</h1>
  <p>{{ x.model }}</p>
  <p>{{ x.year }}</p>
{% endfor %} 

数据 cars 空的展示内容:

<ul>
  {% for x in cars %}
    <h1>{{ x.brand }}</h1>
    <p>{{ x.model }}</p>
    <p>{{ x.year }}</p>
  {% empty %}
    <li>No members</li>
  {% endfor %}
</ul> 

循环变量

  • forloop.counter 当前循环,从 1 开始
  • forloop.counter0 当前循环,从 0 开始
  • forloop.first 循环是否在其第一次循环中
  • forloop.last 循环是否在其最后一次循环中
  • forloop.parentloop
  • forloop.revcounter 如果从末尾开始并向后计数,则以 1 结束
  • forloop.revcounter0 如果从末尾开始并向后计数,则以 0 结束

过滤值

<h1>你好 {{ firstname|upper }},你好吗?</h1>

返回带有大写字母的变量名

注释

<h1>欢迎大家{# 较小的注释 #}</h1>
{% comment %}
  <h1>欢迎女士们先生们</h1>
{% endcomment %}

注释描述

<h1>欢迎大家{# 较小的注释 #}</h1>
{% comment "这是最初的欢迎信息" %}
    <h1>欢迎女士们先生们</h1>
{% endcomment %}

注释允许您拥有应该被忽略的代码部分

双过滤值

<h1>
你好 {{ firstname|first|upper }},你好吗?
</h1>

返回变量 firstname 的第一个字符,小写

过滤器标签

{% filter upper %}
  <h1>Hello everyone, how are you?</h1>
{% endfilter %}

返回内容大写

cycle

如果你想为每次循环使用新的背景颜色,你可以使用 cycle 标签来做到这一点

<ul>
  {% for x in members %}
    <li style='background-color:{% cycle 'lightblue' 'pink' 'yellow' 'coral' 'grey' %}'>
      {{ x.firstname }}
    </li>
  {% endfor %}
</ul> 

将参数值保存在变量中,以便以后使用:

<ul>
  {% for x in members %}
    {% cycle 'lightblue' 'pink' 'yellow' 'coral' 'grey' as bgcolor silent %}
    <li style='background-color:{{ bgcolor }}'>
      {{ x.firstname }}
    </li>
  {% endfor %}
</ul> 

你注意到 silent 关键字了吗? 确保添加这个,否则参数值将在输出中显示两次

<ul>
  {% for x in members %}
    {% cycle 'lightblue' 'pink' 'yellow' 'coral' 'grey' as bgcolor silent %}
    {% if forloop.counter == 3 %}
      {% resetcycle %}
    {% endif %}
    <li style='background-color:{{ bgcolor }}'>
      {{ x.firstname }}
    </li>
  {% endfor %}
</ul> 

您可以使用 {% resetcycle %} 标签强制循环重新开始

每一行添加行号

{% filter upper|linenumbers %}Hello!
my name is
Emil.
What is your name?{% endfilter %}

返回内容大写并在每一行添加行号

导入模板

footer.html:

<p>您已到达本页底部,感谢您抽出宝贵时间</p>

template.html:

<h1>Hello</h1>
<p>此页面包含模板中的页脚</p>
{% include 'footer.html' %} 

导入模板传入变量

mymenu.html:

<div>HOME | {{ me }} | ABOUT | FORUM | {{ sponsor }}</div>

template.html:

{% include mymenu.html with me="张三" sponsor="Reference" %}

<h1>Welcome</h1>

<p>This is my webpage</p>

过滤器参考

Keyword描述
add添加指定的值
addslashes在任何引号字符之前添加一个斜杠,以转义字符串
capfirst返回大写的第一个字母
center使值在指定宽度的中间居中
cut删除任何指定的字符或短语
date以指定格式返回日期
default如果值为 False,则返回指定值
default_if_none如果值为 None,则返回指定的值
dictsort按给定值对字典进行排序
dictsortreversed按给定值对字典进行反向排序
divisibleby如果该值可以除以指定的数字,则返回 True,否则返回 False
escape从字符串中转义 HTML 代码
escapejs从字符串中转义 JavaScript 代码
filesizeformat将数字返回为文件大小格式
first返回对象的第一项(对于字符串,返回第一个字符)
floatformat将浮点数四舍五入到指定的小数位数,默认为一位小数
force_escape从字符串中转义 HTML 代码
get_digit返回数字的特定数字
iriencodeIRI 转换为 URL 友好字符串
join将列表中的项目返回为字符串
json_script将一个对象返回为由 <script></script> 标签包围的 JSON 对象
last返回对象的最后一项(对于字符串,返回最后一个字符)
length返回对象中的项目数,或字符串中的字符数
length_is如果长度与指定的数字相同,则返回 True
linebreaks返回带有 <br> 而不是换行符和 <p> 而不是多个换行符的文本
linebreaksbr返回带有 <br> 的文本,而不是换行符
linenumbers返回每行带有行号的文本
ljust根据指定的宽度左对齐值
lower以小写字母返回文本
make_list将值转换为列表对象
phone2numeric将带字母的电话号码转换为数字电话号码
pluralize如果指定的数值不是 1,则在值的末尾添加一个 s
pprint
random返回对象的随机项
rjust根据指定的宽度右对齐值
safe标记此文本是安全的,不应进行 HTML 转义
safeseq将对象的每个项目标记为安全且项目不应进行 HTML 转义
slice返回文本或对象的指定切片
slugify将文本转换为一个长字母数字小写单词
stringformat将值转换为指定格式
striptags从文本中删除 HTML 标记
time以指定格式返回时间
timesince返回两个日期时间之间的差
timeuntil返回两个日期时间之间的差
title文本中每个单词的第一个字符大写,所有其他字符都转换为小写
truncatechars将字符串缩短为指定数量的字符
truncatechars_html将字符串缩短为指定数量的字符,而不考虑任何 HTML 标记的长度
truncatewords将字符串缩短为指定数量的单词
truncatewords_html将字符串缩短为指定数量的单词,而不考虑任何 HTML 标记
unordered_list将对象的项目返回为无序列的 HTML 列表
upper以大写字母返回文本
urlencodeURL 对字符串进行编码
urlize将字符串中的任何 URL 作为 HTML 链接返回
urlizetrunc将字符串中的任何 URL 作为 HTML 链接返回,但会将链接缩短为指定的字符数
wordcount返回文本中的单词数
wordwrap以指定的字符数换行
yesno将布尔值转换为指定值
i18n
l10n
tz

字段查询参考

Keyword描述
contains包含短语
icontains与包含相同,但不区分大小写
date匹配日期
day匹配日期(日期,1-31)(日期)
endswith以。。结束
iendswith与 endwidth 相同,但不区分大小写
exact完全匹配
iexact与精确相同,但不区分大小写
in匹配其中一个值
isnull匹配 NULL 值
gt比...更棒
gte大于或等于
hour匹配一个小时(对于日期时间)
lt少于
lte小于或等于
minute匹配一分钟(对于日期时间)
month匹配一个月(日期)
quarter匹配一年中的一个季度 (1-4)(用于日期)
range之间的匹配
regex匹配正则表达式
iregex与正则表达式相同,但不区分大小写
second匹配一秒(对于日期时间)
startswith以 ... 开始
istartswithstartswith 相同,但不区分大小写
time匹配时间(用于日期时间)
week匹配周数 (1-53)(用于日期)
week_day匹配一周中的某一天 (1-7) 1 是星期日
iso_week_day匹配 ISO 8601 星期几 (1-7) 1 是星期一
year匹配一年(日期)
iso_year匹配 ISO 8601 年份(日期)

添加静态文件

添加 CSS 文件

myworld
  ├┈ manage.py
  ├┈ myworld/
  ╰┈ members/
     ├┈ templates/
     ├┈ static/
        ╰┈ myfirst.css

打开 CSS 文件 (members/static/myfirst.css) 并插入以下内容:

body {
  background-color: lightblue;
  font-family: verdana;
}

修改模板 (members/templates/template.html) 引入 css 文件

{% load static %}
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="{% static 'myfirst.css' %}">
<body>

添加 JS 文件

myworld
  ├┈ manage.py
  ├┈ myworld/
  ╰┈ members/
     ├┈ templates/
     ├┈ static/
        ╰┈ myfirst.js

打开 JS 文件 (members/static/myfirst.js) 并插入以下内容:

function myFunction() {
  alert("Hello from a static file!");
}

修改模板 (members/templates/template.html) 引入 JS 文件

{% load static %}
<!DOCTYPE html>
<html>
<script src="{% static 'myfirst.js' %}"></script>
<body>
<button onclick="myFunction()">Click me!</button>

添加图片文件

myworld
  ├┈ manage.py
  ├┈ myworld/
  ╰┈ members/
     ├┈ templates/
     ├┈ static/
        ╰┈ pineapple.jpg

打开 JS 文件 (members/static/pineapple.jpg) 并插入以下内容:

function myFunction() {
  alert("Hello from a static file!");
}

修改模板 (members/templates/template.html) 引入 jpg 文件

{% load static %}
<!DOCTYPE html>
<html>
<body>
<img src="{% static 'pineapple.jpg' %}">
</body>
</html>

另见