官方文档地址:Django 文档 | Django 文档 | Django (djangoproject.com)

创建项目

  1. 使用如下命令创建一个Django项目:

    django-admin startproject mysite

    此命令会在当前目录下创建一个 mysite 目录,其内容如下:

    mysite/
    manage.py
    mysite/
    __init__.py
    settings.py
    urls.py
    asgi.py
    wsgi.py

    其中,需要重点关注的是urls.pysettings.py文件,他们分别用于管理站点的路由和配置。

  2. 使用如下名命令创建一个应用:

    python manage.py startapp app_name

    此命令会创建一个app_name目录,内容大致如下:

    app_name/
    __init__.py
    admin.py
    apps.py
    migrations/
    __init__.py
    models.py
    tests.py
    views.py

    models.pyviews.py分别存放应用的模型层和视图层代码。此外,还可以新建一个urls.py文件,将应用的所有路由配置存放在这,方便管理。

  3. 使用如下命令可以启动内置的简易web服务器,用于调试:

    python manage.py runserver

路由管理

一般情况下,Django会根据配置文件中的ROOT_URLCONF项(默认为项目根目录的urls.py文件)寻找名为 urlpatterns 变量并且按序匹配正则表达式。路由列表项一般由pathre_path函数生成。

关于路由是如何匹配的,在此不再赘述,官方文档讲解非常详细:URL调度器 | Django 文档 | Django (djangoproject.com)

反向解析

当项目的url地址发生变化,我们需要同步修改代码中使用到的url,这显然非常容易出错和遗漏。使用反向解析技术,在使用url时,不直接硬编码,而是使用url映射的地址,这样,即使url发生了变化,在代码中也可自动的映射到正确的地址。

在使用path(或re_path)指定urlpatterns时,在函数中指定name变量的值,如:path('articles/<int:year>/', views.year_archive, name='news-year-archive'),,可以直接利用news-year-archive这个名字映射到正确的url地址。

在模板和视图中,可以采用如下方式进行反向解析:

  • 模板,使用 url 模板标签

    {# 参数是可选的 #}
    {% url '别名' ['参数1' '参数2...'] %}
    {# 如 #}
    <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
    <a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a>
  • 视图,使用 reverse() 函数

    from django.http import HttpResponseRedirect
    from django.urls import reverse

    def redirect_to_year(request):
    # ...
    year = 2006
    # ...
    return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))

模板

默认情况下,在配置文件的TEMPLATES项中,APP_DIRS为True,即Django会搜索每个应用子目录下的templates文件夹搜索模板源文件。为了方便,可以配置'DIRS': [BASE_DIR / 'templates'],并在项目根目录下新建一个templates目录,且为每个应用再创建对应的子目录,将应用的模板源文件放在对应的子目录内。Django按照一定的规则搜索指定的模板文件,使用这种方式可以避免同名模板文件匹配错误的问题。

可能的目录结构如下图所示:

Django 内置了自己的模板系统后端,即 Django 模板语言(DTL),也支持Jinja2及其他后端。下面以DTL为例,介绍模板的使用方式。

模板 | Django 文档 | Django (djangoproject.com)

语法

变量,{{ }}

变量从上下文中输出一个值,上下文是一个类似于字典的对象,将键映射到值。如:

My first name is {{ first_name }}. My last name is {{ last_name }}.

对于字典查找,属性查找和列表索引查找均以点符号实现:

{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}

标签,{% %}

内置模板标签和过滤器 | Django 文档 | Django (djangoproject.com)

用于将一些服务器端的功能嵌入到模板中,如流程控制等。大部分标签需要与结束标签成对使用,内置标签见上文的链接。

  • if表达式

    {% if 条件表达式1 %}
    ...
    {% elif 条件表达式2 %}
    ...
    {% else %}
    ...
    {% endif %}
    • 可以使用 andornot 来测试一些变量或取反某个变量
    • 可以使用运算符 ==!=<><=>=innot inisis not
  • for表达式

    {% for 变量 in 可迭代对象 [reversed] %}
    ... |--反向循环一个列表
    {% empty %}
    ... 可迭代对象无数据时填充的语句
    {% endfor %}
    • 内置变量forloop

      变量名 描述
      forloop.counter 循环计数器,表示当前循环的索引(从 1 开始)。
      forloop.counter0 循环计数器,表示当前循环的索引(从 0 开始)。
      forloop.revcounter 反向循环计数器(以最后一次循环为 1,反向计数)。
      forloop.revcounter0 反向循环计数器(以最后一次循环为 0,反向计数)。
      forloop.first 当前循环为首个循环时,该变量为 True
      forloop.last 当前循环为最后一个循环时,该变量为 True
      forloop.parentloop 在嵌套循环中,指向当前循环的上级循环

过滤器,|

过滤器转换变量和标签参数的值。

{{ 变量|过滤器1:'参数1'|过滤器2:'参数2' }}

常用过滤器

过滤器 说明
lower 将一个字符串转换为全小写
upper 将一个字符串转换为全大写
safe 标记一个字符串在输出前不需要进一步的 HTML 转义
add:“n” 将参数添加到值中
truncatechars:“n” 如果一个字符串的长度超过指定的字符数,则截断它。截断后的字符串将以一个可翻译的省略号(“…”)结束。

模板继承

模板继承允许你建立一个基本的“骨架”模板,它包含了你网站的所有常用元素,并定义了子模板可以覆盖的。对于多个网页有相同内容的网站十分有用。

  1. 建立base.html模板文件(名字随意),在其中用{% block block_name %}{% endblock %}定义可以被子模板覆盖的块。

  2. 建立子模板test.html,其内容如下:

    {% extends "base.html" %}

    {% block block_name %}My content{% endblock %}

    第一行使用extends标签继承基模板,后面根据需要,使用block标签覆盖对应的块。对于没有指明需要覆盖的块,则会继承基模板中的内容。

请求和响应对象

请求和响应对象 | Django 文档 | Django (djangoproject.com)

请求对象 HttpRequest

请求字符串

HttpRequest.GET(以下简称request.GET)是一个QueryDict类型,继承自MultiValueDict,是一个多值的字典,即一个键对应的值可以是一个列表。在http请求中,GET方法在请求时可以对一个参数携带多个数值,如?abc=321&abc=123,此时在Django中输出request.GET,其结果为<QueryDict: {'abc': ['321', '123']}>

只要url中含有请求字符串(?xx=xxx的形式),无论是POST还是GET方法,都可以通过request.GET来获取请求字符串中的数据。

# 请求链接为:GET 127.0.0.1:8000?test=123&abc=123&abc=321

print(request.GET)
# 返回:<QueryDict: {'test': ['123'], 'abc': ['123', '321']}>

print(request.GET.get('abc'))
# 返回:123
# 对于多个值的情况,仅返回最后一个值

print(request.GET.getlist('abc'))
# 返回:['123', '321']

print(request.GET.dict())
# 返回:{'test': '123', 'abc': '321'}

请求体

HttpRequest.body获得原始的请求体的字节字符串,可用于处理非常规的HTML表单数据。对于处理传统的表单数据,应该使用HttpRequest.POST

HttpRequest.POST获取表单数据,返回值类型为QueryDict。

响应 HttpResponse

每个视图都要负责实例化、填充和返回一个 HttpResponse 对象。

  • content,响应的正文内容
  • status,状态码
  • content-type,响应内容的MIME类型

如果想返回图片,可以参考使用如下的方式:

content = open("a.png","rb").read()
return HttpResponse(content=content, content_type="image/png")

HttpResponseRedirect 重定向

接收一个url参数表明要重定向的地址。该参数除了可以使用完整的 url路径(https://www.baidu.com),还可以使用不含域名的绝对(/admin/)/相对(admin/)路径。

另外,Django提供了针对重定向的快捷函数redirect

Django 便捷函数 | Django 文档 | Django (djangoproject.com)

# 1. 跳转到完全指定的URL
HttpResponseRedirect('https://www.baidu.com')

# 2. 绝对路径,跳转到 127.0.0.1:8000/admin/
HttpResponseRedirect('/admin/')

# 3. 相对路径,假设访问地址为127.0.0.1:8000/index,
# 则转到127.0.0.1:8000/index/admin/
HttpResponseRedirect('admin/')

JsonResponse返回json格式的数据

class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs)

帮助创建一个 JSON 编码的响应,并将Content-Type 头设置为 application/json

  • 如果safe为True,则data必须是一个字典,否则会引发TypeError。如果为False,则data可以为任何JSON 可序列化的对象。
  • encoder为序列化器,可以指定其他的序列化器。
  • json_dumps_params 参数是一个关键字参数的字典,用来传递给 json.dumps() 调用,用于生成响应。
from django.http import JsonResponse
response = JsonResponse({'foo': 'bar'})
# response.content => b'{"foo": "bar"}'

模型层

模型 | Django 文档 | Django (djangoproject.com)

定义模型

模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。

自定义的模型需要继承自 django.db.models.Model,其每个属性都与数据库中的字段对应。如果没有使用primary_key=True显式地指明主键,Django会自动给模型创建一个自增的主键。一个模型类可以按照如下方式定义:

from django.db import models

class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)

模型中的字段是某个 Field 类的实例,它用于指定数据库数据类型,并且在渲染表单时会使用对应的HTML视图,另外还有基本的有效性验证功能。

字段类型

完整文档:模型字段参考 | Django 文档 | Django (djangoproject.com)

常用的主要有以下几个字段:

  • BooleanField
  • CharField
    • 字符串字段
    • 默认表单部件是一个 TextInput
    • 需要指定max_length
    • 大的文本需要使用TextField
  • DateField,DateTimeField
    • 日期、日期时间字段
    • 默认表单部件分别是 DateInputDateTimeInput
    • 有三个参数可选,但不可同时使用
      • auto_now,每次保存对象时,自动将该字段设置为现在。适用于需要记录修改时间的情况
      • auto_now_add,当第一次创建对象时,自动将该字段设置为现在。适用于记录创建时间的情况。指定该参数后,即使在创建对象时为该字段赋值也会被忽略。
      • default,默认值,当创建对象时会对该字段赋值,则会使用默认值。如果想要将当前时间作为默认值使用,对于这两种字段可以分别使用**date.today(来自datetime)和timezone.now**(来自django.utils)
  • DecimalFiel
    • 一个固定精度的十进制数,在 Python 中用一个 Decimal 实例来表示。
    • 有两个必要参数
      • max_digits,指定允许的最大位数
      • decimal_places,指定小数位数
  • EmailField
  • IntegerField
    • 一个整数。
    • -21474836482147483647 的值在 Django 支持的所有数据库中都是安全的。

此外,FileFieldImageFieldTimeField等也很常用。

字段选项

模型字段参考 | Django 文档 | Django (djangoproject.com)

可以为字段指定一些额外的参数,部分字段有其自己独有的字段选项,下面介绍的是通用的字段选项:

  • null
    • 默认为 False
    • 如果设置为 True,当该字段为空时,Django 会将数据库中该字段设置为 NULL
  • blank
    • 默认为 False
    • 如果设置为 True,该字段允许为空。即在进行表单验证时,允许该字段为空
  • choices
    • 一系列二元组,用作此字段的选项。如果提供了二元组,默认表单小部件是一个选择框,而不是标准文本字段,并将限制给出的选项。
    • 比较复杂,参考文档:choices
  • db_column
    • 这个字段要使用的数据库列名。如果没有给出列名,Django 将使用字段名
  • db_index
    • 如果是 True,将为该字段创建数据库索引
  • default
    • 设置默认值
    • 新增字段时,必须设置该参数
  • primary_key
    • 如果设置为 True ,将该字段设置为该模型的主键
  • unique
    • 如果设置为 True,这个字段必须在整个表中保持值唯一

Meta选项

可以在模型内部创建Meta类,来给模型赋予一些属性。

模型 Meta 选项 | Django 文档 | Django (djangoproject.com)

  • db_table
    • 指定模型数据库的表的名称

使用模型

  1. 修改设置文件中的 INSTALLED_APPS ,在这个设置中添加包含 models.py 文件的模块名称。

  2. 使用以下命令进行数据库迁移

    python manage.py makemigrations
    python manage.py migrate

操作数据库

执行查询 | Django 文档 | Django (djangoproject.com)

模型类含有一个objects管理器对象,数据库的增删改查都可以通过管理器实现。

创建对象

有两种方式可以创建对象:

# 1. 使用create()方法
MyModel.objects.create(arg1=val,arg2=val2,...)

#2. 创建实例并使用save()保存
obj = MyModel(arg1=val,arg2=val2,...)
obj.save()

查询

查询操作需要依赖模型的管理器对象(默认为objects),大部分情况返回的是一个QuerySet,内部存放模型实例。

方法 说明
all() 查询全部记录
get() 查询单个对象,直接返回对象而不是QuerySet。如果结果多于一条或没有则分别引发Model.MultipleObjectsReturnedModel.DoesNotExist 异常
filter(属性1=值1,属性2=值2) 查询满足给定查询参数的对象,逗号表示与
exclude() 查询不满足给定查询参数的对象
values() 可指定要查询的列名,返回值是QuerySet但内部存放的是字典,如{‘列1’:值1, ‘列2’:值2}
values_list() 与values()类似,但返回的QuerySet内是各列的值组成的元组
order_by(‘-列1’,‘列2’) 默认升序排列,降序在列名前加’-’

自定义查找

当内置的查找语句无法满足要求时(如大于、包含关系等),可以自定义查找方法。Field 查找

使用方法为字段__查询谓词

  • __exact,等值匹配

    # SELECT ... WHERE id = 14;
    Entry.objects.get(id__exact=14)

    # SELECT ... WHERE id IS NULL;
    Entry.objects.get(id__exact=None)
  • __contains,__icontains, 包含指定值,i前缀表示不区分大小写

    # SELECT ... WHERE headline LIKE '%Lennon%';
    Entry.objects.get(headline__contains='Lennon')
  • __(i)startswith__(i)endswith,以…开头/结尾,i前缀同上

    # SELECT ... WHERE headline LIKE 'Lennon%';
    Entry.objects.filter(headline__startswith='Lennon')

    # SELECT ... WHERE headline LIKE '%Lennon';
    Entry.objects.filter(headline__endswith='Lennon')
  • __gt(e)__lt(e),大于(等于)/小于(等于)

    # SELECT ... WHERE id > 4;
    Entry.objects.filter(id__gt=4)

此外,还有inrange,日期查询等,详见文档。

修改

单个数据的修改

  1. 通过get()等方法查询得到需要修改的实例对象
  2. 修改得到的对象的属性
  3. 调用save()方法保存修改,写入数据库

批量修改

直接调用QuerySet的update(属性=值)方法

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

删除

与修改相似,也有单个和批量两种方式,分别调用对象和QuerySet的delete()方法即可。

可以使用伪删除的方式,即在模型中加一个字段标记该对象是否删除。

在执行查询操作时需要对该字段进行过滤。

F对象

一个F对象代表数据库中某条记录的字段的信息,可以在不获取字段值的情况下对字段进行操作。

# 将所有记录的stories_filed加1
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)

F()是直接委托数据库进行字段的操作,python本身不会获取字段值,因此如果要使用新值,需要调用对象的refresh_from_db()方法。

另外,F() 分配给模型字段的对象在保存模型实例后会持续存在,并将应用于每个 save()。如:

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()

reporter.name = 'Tintin Jr.'
reporter.save()

在这种情况下,stories_filed 将被更新两次。如果最初是 1,最终值将是 3。这种持久性可以通过在保存模型对象后重新加载来避免,例如,使用 refresh_from_db()

Q对象

使用Q对象可以执行更复杂的查询(如OR,NOT等)。

Q 对象能通过 &| 操作符连接起来。当操作符被用于两个 Q 对象之间时会生成一个新的 Q 对象。

# WHERE question LIKE 'Who%' OR question LIKE 'What%'
Q(question__startswith='Who') | Q(question__startswith='What')

此外, Q 对象也可通过 ~ 操作符反转,允许在组合查询中组合普通查询或反向 (NOT) 查询。

查询函数能混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q 对象)均通过 “AND” 连接。然而,若提供了 Q 对象,那么它必须位于所有关键字参数之前

# SELECT * from polls WHERE question LIKE 'Who%'
# AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)

聚合查询

聚合 | Django 文档 | Django (djangoproject.com)

对部分或全部数据进行查询和操作。

整表聚合

支持SumAvgCountMaxMin

语法:MyModel.objects.aggregate(结果变量名=聚合函数('列')),返回字典{'结果变量名':值}

分组聚合

指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(或平均值等),即为查询集的每一项生成聚合。

语法:QuerySet.annotate(结果变量名=聚合函数('列')),返回值为QuerySet

原生SQL操作

执行原生 SQL 查询 | Django 文档 | Django (djangoproject.com)

有两种方式:

  1. 使用管理器的raw(raw_query, params=(), translations=None)方法,仅支持查询操作,返回RawQuerySet集合对象。
  2. 使用django.db.connection对象,该对象代表默认的数据库连接
    1. 调用 connection.cursor() 来获取一个指针对象,使用完成后需要释放,因此可使用with语句
    2. 调用 cursor.execute(sql, [params]) 来执行该 SQL
    3. 调用cursor.fetchone(),或 cursor.fetchall() 获取结果数据

不推荐使用原生SQL操作,使用不当会遭到SQL注入攻击。

模型关系

在定义模型关系的时候,也需要指定删除该模型时,对关联模型的操作,这一行为通过字段的on_delete参数进行设定,主要有以下几种方式:

  • CASCADE,级联删除。Django 模拟了 SQL 约束 ON DELETE CASCADE 的行为,也删除了包含 ForeignKey 的对象。

  • PROTECT,通过引发 ProtectedError,防止删除被引用对象。

  • RESTRICT,通过引发 RestrictedError 来防止删除被引用的对象。与 PROTECT 不同的是,如果被引用的对象也引用了一个在同一操作中被删除的不同对象,但通过 CASCADE 关系,则允许删除被引用的对象。

  • SET_NULL,设置 ForeignKey 为空;只有当 nullTrue 时,才有可能。

  • SET_DEFAULT,将 ForeignKey 设置为默认值,必须为 ForeignKey 设置一个默认值。

  • SET(),将 ForeignKey 设置为传递给 SET() 的值,如果传递了一个可调用的值,则为调用它的结果。

    from django.conf import settings
    from django.contrib.auth import get_user_model
    from django.db import models

    def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]

    class MyModel(models.Model):
    user = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    on_delete=models.SET(get_sentinel_user),
    )
  • DO_NOTHING,不采取任何行动。

以下例子来源于B站视频:2021最新版Django全套视频(django框架快速上手)_哔哩哔哩_bilibili。例子举得不错,讲解也很详细。

一对一关系

在模型中使用OneToOneField字段实现。

创建模型

以男性作家与其妻子是一对一的关系为例:

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

class Wife(models.Model):
name = models.CharField(max_length=10)
# 在任意一个模型中加入OneToOneField即可
author = models.OneToOneField(Author, on_delete=models.CASCADE)

其模型关系示意图如下所示:

创建数据

# 无外键的模型,按正常方式创建
a1 = Author.objects.create(name="test")
a2 = Author.objects.create(name="t2")

# 有外键的模型,额外指定外键的值
# 法1. 用定义的字段名直接关联对象
w1 = Wife.objects.create(name = "wife", author=a1)
# 法2. 关联对应的主键值
w2 = Wife.objects.create(name = "w2", author_id=a2.id)

此时数据库中的内容如下图所示:

image-20211004150931770

查询数据

  • 正向查询,即直接通过外键属性查询,本例中是通过Wife类查找对应的Author

    w = Wife.objects.get(name='wife')
    # 获得对应的Author
    au = w.author
  • 反向查询,即没有外键的一方,调用反向属性查询关联的另一方,即Author查找Wife

    反向关联属性为实例对象.引用类名的小写,如作家对象.wife

    au = Author.objects.get(name='test')
    # 获得对应的Wife
    wi = au.wife

一对多关系

的表上使用ForeignKey设置外键。

创建模型

以出版社和书籍是一对多的关系为例:

class Publisher(models.Model):
""" 出版社,一 """
name = models.CharField(max_length=10)


class Book(models.Model):
""" 书籍,多 """
name = models.CharField(max_length=20)
# 定义外键
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

其模型关系示意图如下所示:

创建数据

先创建“一”,再创建“多”。

p1 = Publisher.objects.create(name="Pub1")
# 直接关联对象
Book.objects.create(name="b1", publisher=p1)
# 关联主键
Book.objects.create(name="b2", publisher_id=p1.id)

数据库中内容如下:

image-20211004153218635

查询数据

  • 正向查询,通过BookPublisher

    pub = Book.objects.get(name="b1").publisher
  • 反向查询,通过PublisherBook,利用_set集合

    pub = Publisher.objects.get(name="Pub1")
    # 通过book_set获取对应的多个Book对象,返回QuerySet
    books = pub.book_set.all()
    # 也可通过Book的filter方法获取
    books = Book.objects.filter(publisher=pub)

多对多关系

在任意一个模型中使用ManyToManyField字段。

创建模型

以作者和书籍为例,一本书可以被多个作者编写,一个作者也可以编写多本书。

如果手动创建这种关系,需要第三张表来记录两个模型之间的关系,而Django会自动帮助我们创建这第三张表,因此仍然只要定义两个模型即可。

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


class Book(models.Model):
name = models.CharField(max_length=20)
# 定义多对多关系
authors = models.ManyToManyField(Author)

其模型关系示意图如下所示:

创建数据

两者创建没有先后顺序之分。

# 法1. 先创建Author再关联Book
a1 = Author.objects.create(name='a1')
a2 = Author.objects.create(name='a2')
b1 = a1.book_set.create(name='b1')
a2.book_set.add(b1)

# 法2. 先创建Book再关联Author
b2 = Book.objects.create(name='b2')
a3 = b2.authors.create(name='a3')
b2.authors.add(a1)

Book和Author数据库表中的数据如下所示:

image-20211004161027428

两者的关系表中的数据如下所示,记录了两者的所有关系:

image-20211004161128680

查询数据

  • 正向查询,通过BookAuthor

    # 获取所有数据
    b1.authors.all()
    # 过滤数据
    b1.authors.filter('...')
  • 反向查询,通过AuthorBook

    a1.book_set.all()
    a1.book_set.filter('...')