Django的历史
- 2003年 − Adrian Holovaty和Simon Willison在劳伦斯新闻报纸内部项目中启动了Django。
- 2005年 − 在2005年7月发布,并将其命名为Django,以爵士吉他手Django Reinhardt命名。
- 2005年 − 成熟到足以处理几个高流量的站点。
- 目前 − Django现在是一个拥有来自世界各地贡献者的开源项目。
Django – 设计原则
Django遵循以下设计原则 −
- 松耦合 − Django旨在使其堆栈中的每个元素独立于其他元素。
- 更少编码 − 编写更少的代码以实现快速开发。
- 避免重复(DRY) − 每个东西应该在完全一个地方开发,而不是反复重复。
- 快速开发 − Django的理念是尽一切可能促进高速开发。
- 清晰的设计 − Django严格维护其自身代码的清晰设计,并使其易于遵循最佳的Web开发实践
Django的优势
以下是使用Django的几个优势 −
- 对象关系映射(ORM)支持 − Django为数据模型和数据库引擎提供了一个桥梁,并支持包括MySQL、Oracle、Postgres等在内的大量数据库系统。Django还通过Django-nonrel分支支持NoSQL数据库。目前,仅支持MongoDB和Google App Engine这两种NoSQL数据库。
- 多语言支持 − Django通过其内置的国际化系统支持多语言网站。因此,您可以开发支持多种语言的网站。
- 框架支持 − Django内置支持Ajax、RSS、缓存和各种其他框架。
- 管理界面 − Django提供了一个漂亮的现成用户界面,用于管理活动。
- 开发环境 − Django配备了一个轻量级Web服务器,以便于端到端的应用程序开发和测试。
Django MVT模式
Django框架采用的是MVT模式,即模型(Model)、视图(View)和模板(Template)的设计模式。MVT模式与MVC(模型-视图-控制器)模式类似,但在Django中,控制器的职责被视图和模型共同承担。
开发人员提供了模型、视图和模板,然后将其映射到一个URL,Django会自动将其提供给用户。
Django 环境搭建
安装Django5
Django5 仅支持python3.10/3.11/3.12。确保已经安装python3.10以上版本
1 | pip install django==5.0.3 |
创建Django项目
- 使用命令行方式
1 | django-admin startproject [project] |
运行项目
1
2
3
4
5Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。这里是端口占用
1 | python manage.py runserver 127.0.0.1:8888 |
项目结构介绍
manage.py
:项目交互基本上都是基于这个文件。一般在终端输入 python manage.py [命令参数]。settings.py
:本项目的配置项,以后所有和项目相关的配置都放在这里urls.py
:这个文件是用来配置URL路由的。比如访问http://127.0.0.1/news/ 访问新闻列表页wsgi.py
:项目与WSGI协议兼容的web服务器入口,部署的时候会用到
project和app的关系
django由多个app模块组成。app是django项目的组成部分。一个app代表项目中的一个模块。所有URL请求的响应都是有app来处理。比如豆瓣网包含了图书,电影,音乐等等。豆瓣网就像这个整个django项目。图书,电影,音乐就像是多个app模块。这些app模块组成一个豆瓣网项目。
创建app模块
1 | $ python manage.py startapp book |
URL 与视图函数的映射
设置返回一串字符串
1 | # urls.py |
url 两种传参方式
url传递参数 -
http://127.0.0.1:8888/book?id=3
(http://127.0.0.1:8888/book?key=value
)*
1
2
3
4
5
6# views.py
from django.shortcuts import render,HttpResponse
def bookId(request):
book_id = request.GET.get('id')
return HttpResponse(f"你访问的图书id为{book_id}")1
2
3
4
5
6
7
8
9
10# urls.py
from django.contrib import admin
from django.urls import path
from django.shortcuts import HttpResponse
from book import views
urlpatterns = [
path('admin/',admin.site.urls),
path('book', views.bookId)
]路由携带id -
http://127.0.0.1:8888/3
(http://127.0.0.1:8888/:id
)1
2
3
4
5# views.py
from django.shortcuts import render,HttpResponse
def bookId(request, bookId):
return HttpResponse(f"你访问的图书id为{book_id}")1
2
3
4
5
6
7
8
9
10# urls.py
from django.contrib import admin
from django.urls import path
from django.shortcuts import HttpResponse
from book import views
urlpatterns = [
path('admin/',admin.site.urls),
path('book/<int:bookId>', views.bookId)
]
path 函数定义
router参数
url的匹配规则。可以传递一个id。传递参数通过
<>
进行指定。并且在传递参数时,可以指定这个参数的数据类型。str:非空的字符串类型。默认的转换器。但是不能包含斜杠
1
2
3urlpatterns = [
path('book/<str:bookId>', views.bookId) # '11'
]int:匹配任意的零或者正数的整形。到视图函数中就是一个int类型
1
2
3urlpatterns = [
path('book/<int:bookId>', views.bookId) # 11
]slug:由英文中的横杠 - ,或下划线_ 连接英文字符或数字而形成的字符串
1
2
3urlpatterns = [
path('book/<slug:bookId>', views.bookId) # 11-22_33
]uuid:匹配uuid字符串
1
2
3urlpatterns = [
path('book/<int:bookId>', views.bookId) #
]path:匹配非空的英文字符串,可以包含斜杠 /
1
2
3urlpatterns = [
path('book/<int:bookId>', views.bookId) # '11/22'
]
view
参数可以为一个视图函数或者是
类视图.as_view()
或者是django.urls.include()
函数的返回值name
参数这个参数是给url取个名字的。这在项目比较大,url比较多时用处大
1
2
3urlpatterns = [
path('book/<int:bookId>', views.bookId, name='book_id')
]如果有多个模块,为了防止命名冲突,可以使用应用名称(应用命名空间),来防止冲突
1
2
3
4app_name='book'
urlpatterns = [
path('book/<int:bookId>', views.bookId, name='book_id')
]
路由模块化
使用include函数将新的URLconf模块引入主URLconf。
以book
为主模块开发,后期添加picture
模块。
1 | # picture/urls.py |
1 | # book/urls.py |
当访问picture模块page时,就可以通过 http://
url
反转
通过路由定义的
name
,反向获取url
1 | # book/urls.py |
- 传参
1 | # book/urls.py |
模版
Django模板语言(DTL)是一种轻量级但功能强大的模板语言,具有类似Python语法的特点。在模板中,可以使用变量、标签和过滤器来获取和处理数据,实现逻辑控制、循环和条件语句。这样,开发人员可以在模板中动态地显示数据、生成链接、渲染表单等。
Django模板的主要特点包括:
- 可重用性:模板可以被多个页面重复使用,增加了代码的重用性。
- 分离性:模板将界面设计和业务逻辑分离,使代码更易维护和扩展。
- 安全性:Django提供了对模板中敏感数据的安全渲染,避免了常见的安全漏洞。
- 可扩展性:可以通过自定义模板标签和过滤器来扩展模板功能,满足各种需求
DTL与普通的HTML文件的区别
DTL模版是一种带有特殊语法的HTML文件,这个HTML文件可以被Django编译,可以传递参数进去,实现数据动态化。在变异完成后,生成一个普通的HTML文件,然后发送给客户端
渲染模版
render_to_string
: 通过HttpResponse类包装成一个HttpResponse对象返回回去1
2
3
4
5
6from django.template.loader import render_to_string
from django.http import HttpResponse
def book_detail(request, bookId):
html = render_to_string("book_detail.html")
return HttpResponse(html)render
: 直接使用render函数将模版渲染成字符串和包装成HttpResponse对象一步完成1
2
3
4from django.shortcuts import render
def book_detail(request, bookId):
return render(request, "book_detail.html")
模版查找路径配置
在Django中,模板查找路径是指Django用来查找HTML模板文件的路径列表。通过配置模板查找路径,可以告诉Django在哪些目录下搜索模板文件,以便正确加载和渲染模板
在项目的setttings.py 文件中。存在TEMPLATES配置,这个配置包含了模版引擎的配置,模版查找路径的配置,模版上下文的配置等。模版路径可以在两个地方配置
DIRS
:这是一个列表,在这个列表中宏可以存放所有模版路径,以后在视图中使用render
或者render_to_string
渲染模版的时候,会在这个列表的路径中查找模版APP_DIRS
:默认为True
,这个设置为True
,会在INSTALLED_APPS
的安装了APP下的templates
文件夹中查找模版- 查找顺序:比如代码
render('list.html')
。 会在DIRS这个列表中一次查找路径下有没有这个模版,如果有,就返回。如果DIRS列表中所有的路径都没有找到,那么会先检查当前这个视图所处的aoo是否已经安装,如果已经安装了,那么就会在当前这个app下的template文件夹中查找模版,如果没有找到,那么会在其他已经安装了的app中查找。如果所有路径下都没有找到,那么会抛出一个TemplateDoesNotExist的异常
1 | # settings.py |
模版变量渲染
Django在渲染模版的时候,可以传递变量对应的值进行替换。变量命名规范和python非常类似,只能是阿拉伯数字和英文字符以及下划线的组合,不能出现标点符号等特殊字符。
视图函数在使用render或render_to_string的时候传递一个context,这个参数时一个字典类型。
1 | <!-- profile.html --> |
1 | # view.py |
常用模版标签
if
标签
if标签相当于python中的if语句,有elif和else相对应,但是所有的标签都需要用标签符号({%%}
)进行包裹,if标签可以使用==、!=、<、<=、>、>=、in、not in、is、is not 等判断运算符
1 | {% if "张三" in persons %} |
for in
标签
for in
,可以遍历列表、元组、字符串、字典等一切可以遍历的对象
1 | {% for person in persons %} |
如果想要反向遍历,那么在遍历的时候就加上一个 reversed。
1 | {% for person in persons reversed %} |
遍历字典的时候,需要使用items、keys、values等方法。在DTL中,执行一个方法不能使用圆括号的形式
1 | {% for key,value in person.items %} |
在for循环中,DTL提供了一些变量可供使用:
forloop.counter
:当前循环的下标,以1作为起始值forloop.counter0
:当前循环的下标,以0作为起始值forloop.revcounter
:当前循环的反向下标值。比如列表有5个元素,那么第一次遍历这个属性是等于5,第二次是4,以此推类。最后以1作为最后一个元素下标forloop.revcounter0
:以forloop.revcounter不过下标是从0开始forloop.first
:是否第一次遍历forloop.last
:是否最后一次遍历forloop.parentloop
:如果有多个循环嵌套,那么这个属性代表的是上一级for循环
for in empty
标签
这个标签使用跟for in类似,只不过在遍历的对象如果没有元素的情况下,会执行empty中的内容
1 | {% for value in person %} |
with
标签
用于创建一个新的上下文变量,局部变量可以在嵌套中使用
在模版中定义变量,有时候一个变量访问的时候比较复杂,那么可以先把这个复杂的变量缓存到一个变量上,以后就可以直接使用这个变量就可以了
1 | <!-- |
url
标签
在模版中,经常要写一些url,比如某个a标签的href属性
1 | <a href="{% url 'book:list' %}">图书列表页</a> |
如果url反转需要传递参数,可以在后面添加参数。位置参数和关键字参数不能同时使用
1 | <!-- |
spaceless
标签
移除html标签中的空白字符。包括空格,tab,换行等
1 | {% spaceless %} |
include
标签
用于包含其他模板。
1 | <p>This is the main content.</p> |
csrf_token
标签
用于在表单中实现 CSRF保护。
1 | {% csrf_token %} |
常用模版过滤器
在模版中,有时候需要对数据进行处理以后才能使用。一般在python中我们通过函数的形式来完成。而在模版中,则是通过过滤器实现。过滤器使用|
实现
add
将传进来的参数添加到原来的值上面。这个过滤器会尝试将值和参数转换成整形然后进行相加。如果转换成整形过程中失败了,那么将值和参数进行拼接。
1 | {{ value|add:"2" }} |
如果value
为4
,那么结果将是6
;如果是普通字符串abc
,则为abc2
1 | # add源代码 |
default
如果变量为 None
或者空,则返回默认值
1 | {{ variable|default:"默认值" }} |
default_if_none
如果变量为 None
,则返回默认值
1 | {{ variable|default_if_none:"默认值" }} |
length
返回列表或字符串的长度。
1 | {{ my_list|length }} |
date
格式化日期。
1 | {{ my_date|date:"Y-m-d" }} |
slice
切片操作,提取列表或字符串的一部分
1 | {{ my_list|slice:":3" }} {# 返回前 3 个元素 #} |
lower/upper
转换字符串的大小写。
1 | {{ my_string|lower }} |
join
将列表中的元素连接成一个字符串,使用指定的分隔符。
1 | {{ my_list|join:", " }} |
truncatewords
截取指定数量的单词,并在末尾添加省略号。
1 | {{ my_text|truncatewords:30 }} |
自定义过滤器
如果内置的过滤器不能满足需求,你还可以自定义过滤器。以下是创建自定义过滤器的步骤:
- 在你的 Django 应用中创建
templatetags
目录(如果还没有的话)。 - 在该目录下创建一个 Python 文件,例如
my_filters.py
。 - 在文件中注册自定义过滤器。
1 | # myapp/templatetags/my_filters.py |
- 在模板中加载自定义过滤器并使用它。
1 | {% load my_filters %} |
加载静态文件
在一个网页中,不仅仅有一个html,还需要css样式文件,js执行文件以及一些图片等。因此在DTL中加载静态文件是一个必须要解决的问题。在DTL中,使用static标签来加载静态文件。要使用static标签,需要{% load static %}
加载静态文件
首先确保模块已经添加在
settings.INSTALLED_APPS
中
1. 设置 settings.py
确保在你的 settings.py
文件中配置了静态文件的相关设置
1 | import os |
2.创建静态文件目录
在你的 Django 项目的根目录下创建一个名为 static
的文件夹,并在其中放置你的静态文件(如 CSS、JavaScript 和图片等)。
1 | your_project/ |
3.在模板中加载静态文件
在你的 HTML 模板文件中使用 load static
标签来加载静态文件。首先,你需要在模板的顶部加载 static
模块
1 | {% load static %} |
4. 然后你可以用 static
标签来引用静态文件,例如
1 | {% load static %} |
如果不想每个html 顶部都写{% load static %}
,可以在settings.py
中设置
1 | # settings.py |
5. 上传下载
上传下载用来加载媒体文件,需要手动将请求静态文件的url与静态文件的路径进行映射。
在settings.py中设置自定义变量,MEDIA_URL和MEDIA_ROOT配置
1 | # settings.py |
通过urls.py设置路由
1 | # urls.py |
6.开发环境和生产环境
- 开发环境: Django 在开发模式下会自动处理静态文件。
- 生产环境: 使用
collectstatic
命令收集所有静态文件到一个目录,并通过 Web 服务器(如 Nginx 或 Apache)来服务这些文件
1 | $ python manage.py collectstatic |
7.启动开发服务器
确保启动 Django 开发服务器以查看效果
1 | $ python manage.py runserver |
Django 操作数据库
刚创建的项目settings.py 中DATABASES是默认为sqlite3
Django连接数据库,不需要单独创建一个连接对象,只需要在settings.py文件中做好数据库相关的配置就可以
配置连接MySQL数据库
1 | # settings.py |
Django操作数据库
在Django中操作数据库有两种方式。
使用原生SQL语句操作
使用原生sql语句操作其实就是使用python db api的接口操作。如果你的mysql驱动使用的是pymysql。那么就是用pymysql操作。只不过Django将数据库连接的这部分封装好了。我们只需要在settings.py 中配置数据库就好了
使用ORM模型操作
ORM全程(Object Relational Mapping) 中文叫做对象关系映射,通过ORM我们可以通过类的方式去操作数据库,而不用再写原生sql语句。通过把表映射成类,把行做实例,把字段做属性,ORM在执行对象操作的时候最终还是会把对应操作转换成数据库原生语句
采用原生sql方式代码会产生大量sql语句
且sql代码容易出现一些问题,例如:- sql语句重复利用率不高
- 容易忽略安全问题,例如sql注入
使用ORM优点:
- 易用性
- 性能损耗低
- 设计灵活
- 可移植性
使用原生SQL语句操作
1 | # 使用django 封装好的connection对象,会自动读取settings.py中数据库的配置信息 |
- 插入更新删除
1 | # 插入数据 |
- 使用事务
1 | with transaction.atomic(): |
使用ORM模型操作
- 创建模型
1 | # 创建一个Book模型 |
- 迁移数据库
1 | # 生成迁移脚本 |
- 添加数据
1 | from myapp.models import Book |
- 查询数据
1 | # 获取所有书籍 |
条件查询
在Django中,ORM(对象关系映射)提供了一种通过 Python 对象与数据库进行交互的方式,其中使用
__
(双下划线)是进行查询时一种常见的语法。这种语法可用于执行更复杂的查询和过滤操作,包括跨表关系、字段查找等。字段查找
通过使用
__
,可以在查询条件中精确指定查找类型,例如精确匹配 (exact
)、不等于 (iexact
)、包含 (contains
)、不包含 (icontains
)、大于 (gt
)、小于 (lt
) 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from myapp.models import Author
# 查找名字精确是 'John Doe' 的作者
authors = Author.objects.filter(name__exact='John Doe')
# 查找名字不等于 'John Doe' 的作者
authors = Author.objects.exclude(name__iexact='John Doe')
# 查找名字包含 'John' 的作者
authors = Author.objects.filter(name__contains='John')
# 查找创建日期大于某个日期的作者
from django.utils import timezone
from datetime import timedelta
authors = Author.objects.filter(created_at__gt=timezone.now() - timedelta(days=30))关系查询
当使用外键或一对多关系时,可以使用
__
来访问相关模型的字段。1
2
3
4
5
6
7from myapp.models import Book
# 查找属于 'John Doe' 的书籍
books = Book.objects.filter(author__name='John Doe')
# 查找书名包含 'Python' 的书籍,并且作者名为 'John Doe'
books = Book.objects.filter(title__contains='Python', author__name='John Doe')聚合和注释
当进行聚合函数调用时,也可以使用
__
来指定字段的层级。1
2
3
4
5
6
7
8from django.db.models import Count
from myapp.models import Author
# 查找每位作者写的书籍数量
authors_with_book_counts = Author.objects.annotate(book_count=Count('book'))
# 查询书籍数量大于等于 5 的作者
authors = authors_with_book_counts.filter(book_count__gte=5)组合查询
可以使用
Q
对象结合&
(与)和|
(或)运算符进行更复杂的查询。1
2
3
4
5
6
7from django.db.models import Q
from myapp.models import Author
# 查找名字是 'John Doe' 或者创建日期在过去30天内的作者
authors = Author.objects.filter(
Q(name='John Doe') | Q(created_at__gt=timezone.now() - timedelta(days=30))
)值范围查询
可以使用
__range
来查询某个范围内的值。1
2# 查找创建日期在某个范围内的作者
authors = Author.objects.filter(created_at__range=[start_date, end_date])使用连接查询
在复杂的查询中,例如多个外键层级,你也可以使用
__
来进行深层查询。1
2
3
4from myapp.models import Review
# 查找书籍名称为 'Python Basics' 的书籍的所有评论内容
reviews = Review.objects.filter(book__title='Python Basics')
更新数据
1 | # 更新某个记录 |
- 删除数据
1 | # 删除某个记录 |
- 高级查询 - 聚合、注释、Q对象等等
1 | from django.db.models import Q |
F表达式和Q表达式
- F表达式:用于在数据库中引用字段值,可以进行字段间的操作,适合在更新和计算时使用。
- Q表达式:用于构建复杂的查询条件,适合于组合和逻辑查询,使您能够使用
AND
和OR
等操作符来创建灵活的查询。
F表达式
F
表达式主要用于引用数据库模型中字段的值,并允许在查询中直接对字段进行操作(如加、减、乘、除等)。这在需要根据一个字段的值来更新或过滤文档时特别有用。用法示例:
字段间的比较:
假设你有一个模型
Product
,其中有字段price
和discount_price
,你想查找原价大于折后价的所有产品:1
2
3
4from django.db.models import F
from myapp.models import Product
products = Product.objects.filter(price__gt=F('discount_price'))更新操作:
你可以使用
F
表达式来进行字段的更新,比如给所有产品增加10%的价格:1
Product.objects.update(price=F('price') * 1.1)
在聚合操作中使用:
也可以在聚合操作中使用
F
表达式,例如计算两个字段的和:1
2
3from django.db.models import Sum
total_value = Product.objects.aggregate(total=Sum(F('price') + F('discount_price')))
Q表达式
Q
表达式用于构建复杂的查询条件,尤其是当您需要使用逻辑运算符(如AND
和OR
)来组合查询时。Q
表达式允许您使用组合条件来实现更复杂的查询逻辑。用法示例:
逻辑运算:
假设你有一个
Author
模型,你想查找名字是’John’或姓氏是’Doe’的所有作者:1
2
3
4from django.db.models import Q
from myapp.models import Author
authors = Author.objects.filter(Q(name='John') | Q(last_name='Doe'))组合条件:
可以使用
&
(与)和|
(或)运算符组合多个Q
表达式:1
authors = Author.objects.filter(Q(name='John') & Q(last_name='Doe'))
使用
~
表示取反:~
操作符用于获取条件的补集。例如,查找名字不是’John’的所有作者:1
authors = Author.objects.filter(~Q(name='John'))
复杂查询:
你可以构建更复杂的查询,比如查找所有名为’John’并且创建日期在过去30天内的作者:
1
2
3
4
5
6from django.utils import timezone
from datetime import timedelta
authors = Author.objects.filter(
Q(name='John') & Q(created_at__gt=timezone.now() - timedelta(days=30))
)
ORM模型常用Field和参数
常用字段
AutoField
映射到数据库中的int类型,可以有自动增长特性。一般不需要使用,如果不指定主键,模型会自动生成一个id自动增长的主键。
BigAutoField
64位整型,类似于AutoField,只不过是产生的数据范围是从1~9223372036854775807
BooleanField
模型层接受的是True或False。在数据库时tinyint类型。如果没有指定默认值,默认为None
CharField
在数据库是varchar类型。在python就是普通字符串。这个类型在使用的时候必须要指定最大的长度,也就是必须传递max_length关键字
DateField
日期类型,在python中是datetime.date类型,可以记录年月日。在映射到数据库中也是date类型。使用这个Field可以传递一下几个参数:
- auto_now:每次数据保存,都是用当前时间。作为记录修改日期字段,设置属性为True
- auto_now_add:每次数据第一次被添加的时候,都使用当前时间。作为记录第一次入库时间。设置属性为True
DateTimeField
日期事件类型,类似于DateField。不仅仅可以存储日期,还可以存储时间。映射到数据库是datetime类型。这个Field也可以使用auto_now和auto_now_add两个属性
TimeField
时间类型。在数据库中是time类型。在python中是datetime.time类型
EmailField
类似CharField。在数据库底层也是一个varchar类型。最大长度为254个字符
FileField
用来存储文件
ImageField
用来存储图片
FloatField
浮点类型,映射到数据库中是float 类型
IntegerField
整型,区间:-2147483648~-2147483647
BigIntegerField
大整形。区间:-9223372036854775808~9223372036854775807
PositiveIntegerField
正整形,区间值:0~2147483647
SmallIntegerField
小整形,区间:-32768~-32767
PositiveSmallIntegerField
正小整型,区间:0~-32767
TextField
文本类型,映射数据库中longtext类型
UUIDField
只能存储uuid格式字符串,uuid是一个32位的全球唯一字符串,一般用来做主键
URLField
类似CharField,不过只能用来存储url格式字符串。默认max_length 为200
Meta配置
在Django中,Meta
类用于在模型(Model)中定义一些额外的选项和配置,能够影响模型的行为。Meta
类是定义在模型内部的一个内部类
db_table
指定数据库表的名称。如果不指定,Django会默认生成一个名称,通常是appname_modelname
。
1 | class MyModel(models.Model): |
ordering
定义查询集返回结果的默认排序方式。可以使用字段名称和'-'
来指定降序。
1 | class MyModel(models.Model): |
verbose_name
为模型定义一个可读的名字,在Django的管理后台和其他地方使用。
1 | class MyModel(models.Model): |
verbose_name_plural
定义模型的复数形式。
1 | class MyModel(models.Model): |
unique_together
指定一个字段组合在数据库中必须是唯一的。
1 | class MyModel(models.Model): |
index_together
指定一个字段组合在数据库中创建索引,以提高特定查询的性能。
1 | class MyModel(models.Model): |
permissions
定义自定义的权限,该权限将与模型相关联。
1 | class MyModel(models.Model): |
abstract
如果将模型设置为抽象模型,Django将不会为该模型创建一个数据库表,但它的字段将被所有子类继承
1 | class BaseModel(models.Model): |
swappable
指定该模型是否可以被替换(Swap),通常用于用户模型。
1 | class User(models.Model): |
外键和表
外键是Django ORM中建立一对多关系的关键构件。通过外键定义,Django能够在对象之间创建清晰的关系,并在数据库中以高效的方式进行存储和查询。
外键
在Django中,外键(Foreign Key)是用于建立不同模型之间的一对多关系的字段类型。通过外键,一个模型的实例可以与另一个模型的多个实例相关联。
定义
在Django模型中,使用
ForeignKey
字段来定义外键。在外键关系中,一个模型(称为“从表”)会引用另一个模型(称为“主表”)的主键。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
# ForeignKey(与那个表关联, 级联操作)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
"""
dels.CASCADE:
当主记录被删除时,所有关联的从记录也会自动被删除。这是最常用的级联操作之一。
SET_NULL:
当主记录被删除时,相关的从记录的外键字段将被设置为NULL。使用此选项时,确保外键字段是可为空的。
PROTECT:
防止删除主记录,如果存在与之关联的从记录。试图删除主记录时将引发ProtectedError。
SET_DEFAULT:
当主记录被删除时,相关的从记录的外键字段将被设置为默认值。需要在定义外键字段时设置一个默认值。
DO_NOTHING:
当主记录被删除时,不执行任何操作。你需要自己处理这种情况,通常需要手动编写逻辑来确保数据的完整性。这样做可能导致数据库中的孤儿记录。
"""在这个例子中:
Author
模型是主表。Book
模型是从表。Book
模型有一个外键字段author
,它引用Author
模型。
外键在数据库中的关系
- 一对多关系: 一个
Author
可以拥有多个Book
,但每本书只属于一个作者。这种关系在数据库中通常通过在Book
表中添加一个author_id
列来实现,存储关联的作者的主键。 - 删除行为(on_delete):
on_delete
参数定义了当主表中的一条记录被删除时,从表中与之相关联的记录应如何处理。常见的选项包括:CASCADE
:当删除主记录时,从记录也会被删除。SET_NULL
:将从记录中的外键字段设置为NULL
。PROTECT
:防止删除主记录,如果它有相关的从记录。SET_DEFAULT
:将从记录中的外键字段设置为默认值。
- 创建和查询:保存外键关系时,Django会自动使用外键主表的ID。在查询时,可以通过
.
操作符来访问关联的对象。
1 | # 创建作者 |
反向关系
Django还为外键提供了反向关系。在上面的例子中,author.book_set
是反向访问的方式,用于获取与某个作者相关的所有书籍。如果希望自定义反向访问的名称,可以使用related_name
参数:
1 | class Book(models.Model): |
可以通过author.books.all()
来访问与作者相关的书籍
外键关系在同一张表内
如果模型的外键引用的是本身自己这个模型,那么to参数可以为 ‘self’,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身
1 | class Comment(models.Model): |
外键删除操作
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过on_delete来指定可以指定的类型如下:
dels.CASCADE:
当主记录被删除时,所有关联的从记录也会自动被删除。这是最常用的级联操作之一。SET_NULL:
当主记录被删除时,相关的从记录的外键字段将被设置为NULL。使用此选项时,确保外键字段是可为空的。PROTECT:
防止删除主记录,如果存在与之关联的从记录。试图删除主记录时将引发ProtectedError。SET_DEFAULT:
当主记录被删除时,相关的从记录的外键字段将被设置为默认值。需要在定义外键字段时设置一个默认值。SET():
如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接受一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去
DO_NOTHING:
当主记录被删除时,不执行任何操作。你需要自己处理这种情况,通常需要手动编写逻辑来确保数据的完整性。这样做可能导致数据库中的孤儿记录。
数据库 表关系
一对多
应用场景:比如文章和作者。一个文章对应一个作者。一个作者可以写多篇文章
实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解
1
2
3
4
5
6
7
8
9
10from django.db import models
class User(models.Model)
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey()那么以后在给Article 对象指定author,就可以使用以下代码来完成:
1
2
3
4
5
6article = Article(title='abc', content='123')
author = User(username='zhiliao',password='111111')
# 保存数据
author.save()
article.author = author
article.save()以后如果想要获取某个用户下所有文章,可以通过article_set来实现
1
2
3
4
5user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
print(article)
一对一
应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另一张表中我们叫做UserExtension。
实现方式:Django为了一对一提供了专门的Field叫做OneToOneField来实现一对一操作
1
2
3
4
5
6
7
8class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class UserExtension(models.Model):
birthday = models.DateTimeField(null=True)
school = models.CharField(blank=True, max_length=50)
user = models.OneToOneField('User', on_delete=models.CASCADE)在UserExtension模型上增加了一个一对一关系映射,其实底层是在UserExtension这个表上增加了一个user_id,来和user表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一
多对多
应用场景:比如文章和标签。一篇文章可以有多个标签,一个标签可以引用在多个文章上。因此标签和文章的关系是多对多关系
实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。
1
2
3
4
5
6
7class Article(models.Model):
title = models.CharField(max_length = 100)
content = models.TextField()
tags = models.ManyToManyField("Tag",related_name="articles")
class Tag(models.Model):
name = models.CharField(max_length=50)在数据库层面,实际上Django是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article和tag两张表的主键
Django的cookie和session和token
Cookie
Cookie是存储在客户端(用户浏览器)的小段数据。它们通常用于存储用户的偏好设置或其他小数据
特点:
- 客户端存储:数据存储在浏览器中。
- 容量限制:通常每个Cookie大小限制为4KB。
- 持久性:可以设置过期时间。若未设置,Cookie将在浏览器关闭时失效。
- 自动发送:每个HTTP请求会自动附带Cookie。
使用示例:
1 | # 设置Cookie |
Session
Session是存储在服务器端的用户状态信息,通常用于保存用户在会话中的信息。Session通过一个唯一的Session ID与客户端关联。
特点:
- 服务器端存储:数据存储在服务器上,而不会透露给客户端。
- 更大的存储:可以存储比Cookie更多的数据。
- 安全性:更适合存储敏感信息(例如用户身份),因为数据不直接在客户端存储。
- 会话管理:支持会话过期机制,可以自动失效。
使用示例:
1 | # 设置Session |
Token
Token是一种用于身份验证和授权的机制。它常用于无状态的API和Web应用。Token通常是服务端生成的字符串,由客户端在后续请求中携带,以验证用户身份。
特点:
- 无状态:Token不需要在服务器上保留状态信息,适合RESTful API。
- 可扩展性:Token通常支持更复杂的授权机制(如OAuth2)。
- 安全性:通常使用加密的JWT(JSON Web Token)格式,可在多个服务中使用。
- 易于使用:可以在HTTP头中发送,方便于使用AJAX等请求。
使用示例(JWT):
1 | # 生成Token |
总结对比
特性 | Cookie | Session | Token |
---|---|---|---|
存储位置 | 客户端 | 服务器 | 客户端 |
数据量限制 | 4KB | 较大(没有固定限制) | 通常较小(包含必要信息) |
安全性 | 不适合存储敏感数据 | 适合存储敏感数据 | 通常经过加密,适合无状态身份验证 |
过期机制 | 可以设置过期时间 | 支持会话时间管理 | 通常使用短期和长期Token |
使用场景 | 简单偏好设置或跟踪信息 | 用户状态管理 | API身份验证和授权 |
适用场景
- 使用Cookie:用于存储简单的非敏感数据(如主题设置、偏好选项等)。
- 使用Session:存储用户会话信息,如登录状态、购物车内容等,特别是对敏感信息的处理。
- 使用Token:适合RESTful API、微服务架构或需要跨域访问的情况,尤其是需要无状态验证时。
Django CSRF攻击
在Django中,CSRF(Cross-Site Request Forgery)是指跨站请求伪造攻击,一种通过伪装成用户并请求受信任网站的方式来欺骗用户的攻击。为了保护应用程序免受这种攻击,Django提供了一个内置的CSRF防护机制。
CSRF保护机制
Django通过生成并验证CSRF令牌来防止CSRF攻击。该令牌是随机生成的,并且与用户的会话关联。只有在表单提交或AJAX请求中包含该令牌时,Django才会接受请求。
如何在Django中使用CSRF保护
中间件配置:
确保在Django的settings.py
中启用了CsrfViewMiddleware
,它默认是启用的1
2
3
4
5MIDDLEWARE = [
...
'django.middleware.csrf.CsrfViewMiddleware',
...
]在模板中使用CSRF令牌:
在任何需要CSRF保护的表单中,使用模板标签{% csrf_token %}
将CSRF令牌嵌入到表单中。这样,当用户提交表单时,令牌会被发送到服务器,供验证。1
2
3
4
5<form method="post">
{% csrf_token %}
<!-- 其他表单字段 -->
<input type="submit" value="提交">
</form>AJAX请求中包括CSRF令牌:
对于AJAX请求,需要在请求头中包含CSRF令牌。可以通过JavaScript获取该令牌并在每个请求中设置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Check if this cookie string begins with the name we want
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
fetch('/your-url/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body: JSON.stringify(data),
});视图中处理CSRF保护:
在基于类的视图中,Django会自动处理CSRF防护。在基于函数的视图中,可以使用装饰器来添加CSRF保护。1
2
3
4
5from django.views.decorators.csrf import csrf_protect
def my_view(request):
...禁用CSRF保护:
在某些情况下,如果你需要禁用CSRF保护(例如API视图),可以使用@csrf_exempt
装饰器1
2
3
4
5from django.views.decorators.csrf import csrf_exempt
def my_exempt_view(request):
...
重要注意事项
- 始终使用CSRF保护:尤其是在处理敏感操作(例如表单提交、账户设置等)时,请确保启用CSRF保护。
- 使用HTTPS:使用HTTPS能够保护数据在传输过程中的安全性,进一步防止CSRF和其他攻击。