Django
Django
帮助处理了Web开发的大部分麻烦,我们只需集中精力开发应用不用重复造轮子。
其包含数十个附加功能,可用于处理如身份验证、内容管理、文件上传和缓存处理等常见开发任务。
非常安全,避免诸如SQL注入、跨站点脚本、跨站点请求伪造和点击劫持等安全错误。
前后端开发
后端提供数据,前端只负责界面,后端只提供接口即可,一接口适配多终端。
后端项目的开发例子
django创建项目及应用
py -m venv venv
创建虚拟环境venv\Scripts\activate
激活虚拟环境pip install django pillow
在虚拟环境中安装django
和pillow
django-admin startproject project .
创建项目,其中.
的作用是解构文件夹,即将project
中的manage.py
展开到文件夹之外。此处创建的文件的作用如下:asgi.py
通过asgi
异步方式部署时的入口文件,setting.py
项目的配置文件,urls.py
项目的根路由文件,wsgi.py
通过wsgi
同步方式部署时的入口文件,manage.py
为项目的入口文件。py manage.py startapp blog
创建应用。- 把应用注册到项目的
setting.py
中。这里放入setting.py
注册的是一个应用文件中的apps.py
中的XXXXConfig(AppConfig)
中的name
属性。
应用
通过在应用的命名空间中添加app_name
属性来设置应用的命名空间。
视图和路由
视图views
一个简单的Python
函数或类,接受Web
请求并返回Web响应。无论视图本身的逻辑,都必须返回一个响应,可是一张网页的HTML
、一个重定向、一个404
错误、一个XML
文档或一张图片等。
常被放置在名为view.py
的文件之中。
类视图
其继承自django.views.View
,方法直接采用HTTP
请求名字作为函数名,如get
、post
、patch
、delete
等。
第一个参数固定为self
,第二个参数必为请求实例对象request
,方法必有返回值,为HttpResponse
及其子类。
相比于函数视图,其有更高的灵活性和可复用性,尤其对于处理有多种不同HTTP
请求方式的视图时。
路由
本质是URL
与要为其调用的视图函数间的映射表。
客户端发送请求时,django根据定义的路由规则匹配请求的URL
找到对应的视图函数处理该请求。
1 |
|
改代码可以用来生成一个发送post
请求的提交框。
接收请求中携带的参数
通过request.method
获取的值来判断当前请求类型,获取的当前请求方式名称,如:GET
请求 request.GET
(http://127.0.0.1:8000/index/?a123)、`POST`请求 request.POST
(通过表单提交的值)、接收请求的文件 request.FILES
(带文件类型的表单提交过来的)
通过request.xxx
可以获取相应的请求传递的值,此处必须在对应的html
文件的<form></form>
中加上一个enctype="multipart/form-data"
的编码类型,否则后端无法正常获取到数据。
接口开发
只要让响应返回的数据为json
格式即可,即视图的响应报文的content_type
值为application/json
。
模型model
数据库表的映射,准确定义了数据的字段和行为。
每个模型都是一个Python类,继承自django.models.Model
,类的每个属性都相当于一个数据库字段,若没有显式的指定主键,则默认有一个id主键字段。
基于模型django提供了一个自动生成、访问数据库的API。
其中:字段对应的是数据库字段;元Meta对应的是数据库的一些行为的辅助信息及模型的辅助配置。
基本操作
python manage.py makemigrations
生成迁移文件(每次模型的修改都需要先执行该命令)py manage.py migrate
迁移数据到数据库(每次模型的修改都需要再执行该命令)py manage.py shell
进入命令行环境处理API
操作。在shell
环境下,先引用对应的类,例如:from apps.blog.models import Category
,之后可以通过直接生成实例的方式生成对象:cate = Category(name="分类一")
,不过该对象并没有直接被存储在数据库中,还需要通过cate.save()
的方式来存入数据库中,可以通过Category.objects.all()
的方式来查询所有的存入数据库中的该类的实例对象;可以直接通过Category.objects.create(name="分类二")
的方式将生成的实例对象直接存入数据库中。通过Category.objects.get(id=1)
来获取对应的按顺序存入数据库的相应的实例对象,因此将其赋值后可以直接对该对象的可操作属性进行操作,此处修改后还需要通过save()
方法将其重新存入数据库中,更新的是对应位置的对象。还可以通过Category.objects.filter(name="分类二").update(name="分类二修改")
来修改相应对象的属性,其中update
之前是筛选出对应的对象,返回的是一个QuerySet
列表,而update
进行更新操作,会返回进行更新的对象数量。通过delete()
方法来进行删除。
操作一二分开的原因是:开发者将会提交迁移数据到版本控制系统中并与你的应用一同发布,这样不仅可以使开发更容易,这些迁移数据对于其他开发者同样有用。
进阶
数据关系
- 一对多:一分类下多条数据,但某一条只能归属到一个指定的分类,不可能同时属于多个分类。用
django.db.models.ForeignKey
类,和其他Field
字段类型一样,只要在模型中添加一个值为该类的属性。 - 多对多:用
django.db.models.ManyToManyField
类,和其他Field
字段类型一样,只要在模型中添加一个值为该类的属性。
管理站点
从模型中读取Meta
元数据,提供一个快速的、以模型为中心的界面,受信任用户可管理网站上的内容,管理推荐使用范围仅限于一个组织的内部管理工具。
管理有很多用于定制的钩子,但注意不要试图专门使用那些钩子。若你要提供一个以流程为中心的接口,抽象掉数据库和字段的实现细节,那么可能是时候编写自己的视图了。
使用
- admin.site.register(Model) 简单注册。
- 继承自
ModelAdmin
使用的自定义注册。
模型序列化
内置序列化方法
- 手动逐个字段序列化
dict
- 使用
queryset
的查询方法接口values
- 使用
django
内置的序列化方法serializers
。
DRF来构建Web API
其序列化器可将django模型对象序列化为JSON/XML等格式的数据,也可将前端发送的数据反序列化为Django模型对象。
便捷的请求和相应处理机制,方便的对请求进行验证、过滤、转换等操作,及对响应进行格式化、渲染等操作。
与django
的orm
深度融合,提供很多的高级用发来快速生成增删改查API
。
安装使用
pip install djangorestframework
- 将
rest-framework
添加到settings.py
的INSTALLED_APPS
中 - 在项目根目录的
urls.py
中的urlpatterns
中添加path('api-auth/', include('rest_framework.urls'))
使用方式
- 函数式视图,使用方式大致与此前的一致,不过核心是
@api_view()
装饰器 - 类视图,继承自
APIView
,APIView
是DRF
提供的所有视图的基类,核心是继承django
的基类视图View
拓展而来。但DRF
为RESTful API
开发提供了额外的特性。APIView
拥有身份认证、权限检查、流量控制等功能。传入视图方法中的是REST framework
的Request
对象,而非Django
的HttpRequest
对象;视图方法可返回REST framework
的Request
对象,视图会为响应数据设置符合前端要求的格式;任何APIException
异常都会被捕获到,并处理成合适的响应信息。
类视图
通用类视图CenericAPIView
其扩展了APIView
,并添加更多通用功能。
引入序列化器和查询集概念,使开发者可更方便处理数据的序列化和反序列化,及数据库查询。
常与一些Mixin
(混合类)一起使用,用以实现各种CRUD
(创建、读取、更新、删除)操作,如:ListModelMixin
、Create~
、Update~
等。
DRF视图集
可将多个视图组合在一起便于管理和维护,并提供了通用的CRUD操作。
与路由(Router)结合使用,可自动生成与视图集方法对应的URL路径。DRF提供了SimpleRouter
和DefaultRouter
两种路由器,它们可根据视图集的方法自动生成URL路径,并处理HTTP请求的分发。
使用视图集和路由器,可更加简洁、高效地构建RESTful API
,减少重复代码和手动配置的工作量,同时还提供了扩展性和灵活性,允许开发者按需求自定义和组合各种HTTP请求方法。
apifox中常见的Body格式及其含义
Form Data
(表单数据)
模拟HTML
表单提交的数据格式,以键值对形式存在,支持文件上传,数据放在请求体中,按multipart/form-data
或application/x-www-form-urlencoded
格式编码,前者用于含文件的表单,后者用于纯文本键值对,适合用来文件上传、简单表单提交(如登录、注册)
对于非文件字段,可按照如下方式使用:
1 |
|
对于文件可以:
1 |
|
若使用Django REST Framework(DRF)
,可直接通过request.data
获取(自动解析 JSON、Form Data 等):
1 |
|
XML
以 XML 标签格式(
数据放在请求体中,Content-Type
为application/xml或text/xml
。
适用于传统 API、SOAP 协议接口。
需要手动解析XML
内容,使用xml.etree.ElementTree
:
1 |
|
RAW
自定义原始数据(如纯文本、JSON 字符串、XML 字符串等),需手动指定Content-Type
。
数据直接放在请求体中,格式由用户定义(如text/plain
、application/json
等)。
适用于测试自定义格式数据、传递非标准格式内容。
直接读取request.body
,并根据具体实际解析:
1 |
|
Binary
传递二进制文件(如图片、音频),无键值对结构。
请求体中直接存放二进制流,Content-Type
通常为 application/octet-stream
。
适用于纯文件传输。
直接通过request.body
获取:
1 |
|
近乎万能的request.data.get()
在DRF
框架下,request.data.get()
可以处理很多场景,但是一些边界场景还是需要注意。
能处理场景
JSON
数据Form DATA
表单数据- 其他如
application/json-patch+json
、application/vnd.api+json
等DRF
支持的扩展格式.
边界场景
- binary二进制数据
- 自定义的不是
DRF
支持的格式。 URL
查询参数,其是通过 URL 末尾的?key=value&key2=value2
形式传递的数据,常用于筛选、分页、排序等场景。那么应该如何应对这些边界场景呢?
对于前两种可以通过request.body
来直接读取数据,第一个可以直接获取而后一个可能还需要进行编码之类的解析才能使用。
而第三种对于get
方法可以使用request.GET
获取:request.GET.get('page')
获取单参数(如 ?page=1,此处返回的将是字符串)
;request.GET.getlist('ids')
获取多参数(如 ?ids=1&ids=2&ids=3,此处返回的是列表)
对于DRF可以使用request.query_params
来获取:page = request.query_params.get('page', 1) # 第二个参数为默认值
.
对于前端传来的复杂参数(数组、字典等?filters={"name":"test","age":20}
):需要进行反序列化:1
2
3
4filters_str = request.GET.get('filters', '{}')
filters = json.loads(filters_str) # 解析为字典
name = filters.get('name')
age = filters.get('age')这里我不禁有个疑问,就是在我编写的过程中,一些
GET
的方法除了带上了self
和request
参数外常常还多带了一个参数,需要前端传入的方式也类似这里的查询参数,不仅让我好奇这两个是不是一个东西,结果确实不是一个东西,不然为什么不直接用那个参数就行了还要request.GET.get()
一下呢?
参数形式的是路径参数,它是路由的一部分,某种程度上是必需的,例如:https://api.example.com/users/123
中,123 是路径参数(代表用户 ID)
而查询参数位于URL
后以?
开头,参数间以&
分隔,例如:https://api.example.com/books?page=2&size=10
中,page=2
和size=10
是查询参数。
路径参数一般用于”定位”资源,是路由不分割的部分,必须且唯一;但查询参数用于是路由的附加条件,可选且灵活。
Q()
一个构建复杂查询条件的工具类,用来组合多个查询条件,支持(AND、OR、NOT),使编写者像编写SQL
语句一样编写代码。
主要功能
- 实现条件的”或”关系:
Patient.objects.filter(Q(age__gt=30) | Q(gender="女"))
- 组合复杂的嵌套关系
- 与普通查询条件结合使用:
Patient.objects.filter(Q(age__gt=30) | Q(gender="女"), nation="中国")
(注意此时Q()必须在普通条件之前) - 动态构建查询条件:
condition = Q()\for name in name_list: \ condition |= Q(name__icontains=name) # 循环叠加OR条件
- 跨表查询:支持通过双下划线(
__
)关联跨表字段(注意前提是两表中有通过ForeignKey进行关联的字段,通过related_name__att_name来关联),实现多表联合查询。 - 与
exclude()
配合使用,例如:# 排除“年龄>30 且 性别为男”的患者 \ Patient.objects.exclude(Q(age__gt=30) & Q(gender="男"))