Django

帮助处理了Web开发的大部分麻烦,我们只需集中精力开发应用不用重复造轮子。
其包含数十个附加功能,可用于处理如身份验证、内容管理、文件上传和缓存处理等常见开发任务。
非常安全,避免诸如SQL注入、跨站点脚本、跨站点请求伪造和点击劫持等安全错误。

前后端开发

后端提供数据,前端只负责界面,后端只提供接口即可,一接口适配多终端。

后端项目的开发例子

django创建项目及应用

  1. py -m venv venv创建虚拟环境
  2. venv\Scripts\activate激活虚拟环境
  3. pip install django pillow在虚拟环境中安装djangopillow
  4. django-admin startproject project .创建项目,其中.的作用是解构文件夹,即将project中的manage.py展开到文件夹之外。此处创建的文件的作用如下:asgi.py通过asgi异步方式部署时的入口文件,setting.py项目的配置文件,urls.py项目的根路由文件,wsgi.py通过wsgi同步方式部署时的入口文件,manage.py为项目的入口文件。
  5. py manage.py startapp blog创建应用。
  6. 把应用注册到项目的setting.py中。这里放入setting.py注册的是一个应用文件中的apps.py中的XXXXConfig(AppConfig)中的name属性。

应用

通过在应用的命名空间中添加app_name属性来设置应用的命名空间。

视图和路由

视图views

一个简单的Python函数或类,接受Web请求并返回Web响应。无论视图本身的逻辑,都必须返回一个响应,可是一张网页的HTML、一个重定向、一个404错误、一个XML文档或一张图片等。
常被放置在名为view.py的文件之中。

类视图

其继承自django.views.View,方法直接采用HTTP请求名字作为函数名,如getpostpatchdelete等。
第一个参数固定为self,第二个参数必为请求实例对象request,方法必有返回值,为HttpResponse及其子类。
相比于函数视图,其有更高的灵活性和可复用性,尤其对于处理有多种不同HTTP请求方式的视图时。

路由

本质是URL与要为其调用的视图函数间的映射表。
客户端发送请求时,django根据定义的路由规则匹配请求的URL找到对应的视图函数处理该请求。

1
2
3
4
5
<form action="" method="post">
{% csrf_token %}
<input type="text" name="username" id="username">
<input type="submit" value="提交">
</form>

改代码可以用来生成一个发送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对应的是数据库的一些行为的辅助信息及模型的辅助配置。

基本操作

  1. python manage.py makemigrations生成迁移文件(每次模型的修改都需要先执行该命令)
  2. py manage.py migrate迁移数据到数据库(每次模型的修改都需要再执行该命令)
  3. 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元数据,提供一个快速的、以模型为中心的界面,受信任用户可管理网站上的内容,管理推荐使用范围仅限于一个组织的内部管理工具。
管理有很多用于定制的钩子,但注意不要试图专门使用那些钩子。若你要提供一个以流程为中心的接口,抽象掉数据库和字段的实现细节,那么可能是时候编写自己的视图了。

使用

  1. admin.site.register(Model) 简单注册。
  2. 继承自ModelAdmin使用的自定义注册。

模型序列化

内置序列化方法

  • 手动逐个字段序列化dict
  • 使用queryset的查询方法接口values
  • 使用django内置的序列化方法serializers

DRF来构建Web API

其序列化器可将django模型对象序列化为JSON/XML等格式的数据,也可将前端发送的数据反序列化为Django模型对象。
便捷的请求和相应处理机制,方便的对请求进行验证、过滤、转换等操作,及对响应进行格式化、渲染等操作。
djangoorm深度融合,提供很多的高级用发来快速生成增删改查API

安装使用

  1. pip install djangorestframework
  2. rest-framework添加到settings.pyINSTALLED_APPS
  3. 在项目根目录的urls.py中的urlpatterns中添加path('api-auth/', include('rest_framework.urls'))

使用方式

  • 函数式视图,使用方式大致与此前的一致,不过核心是@api_view()装饰器
  • 类视图,继承自APIViewAPIViewDRF提供的所有视图的基类,核心是继承django的基类视图View拓展而来。但DRFRESTful API开发提供了额外的特性。APIView拥有身份认证、权限检查、流量控制等功能。传入视图方法中的是REST frameworkRequest对象,而非DjangoHttpRequest对象;视图方法可返回REST frameworkRequest对象,视图会为响应数据设置符合前端要求的格式;任何APIException异常都会被捕获到,并处理成合适的响应信息。

类视图

通用类视图CenericAPIView

其扩展了APIView,并添加更多通用功能。
引入序列化器和查询集概念,使开发者可更方便处理数据的序列化和反序列化,及数据库查询。
常与一些Mixin(混合类)一起使用,用以实现各种CRUD(创建、读取、更新、删除)操作,如:ListModelMixinCreate~Update~等。

DRF视图集

可将多个视图组合在一起便于管理和维护,并提供了通用的CRUD操作。
与路由(Router)结合使用,可自动生成与视图集方法对应的URL路径。DRF提供了SimpleRouterDefaultRouter两种路由器,它们可根据视图集的方法自动生成URL路径,并处理HTTP请求的分发。
使用视图集和路由器,可更加简洁、高效地构建RESTful API,减少重复代码和手动配置的工作量,同时还提供了扩展性和灵活性,允许开发者按需求自定义和组合各种HTTP请求方法

apifox中常见的Body格式及其含义

Form Data(表单数据)

模拟HTML表单提交的数据格式,以键值对形式存在,支持文件上传,数据放在请求体中,按multipart/form-dataapplication/x-www-form-urlencoded格式编码,前者用于含文件的表单,后者用于纯文本键值对,适合用来文件上传、简单表单提交(如登录、注册)
对于非文件字段,可按照如下方式使用:

1
2
3
4
def my_view(request):
if request.method == 'POST':
username = request.POST.get('username') # 获取表单字段
age = request.POST.get('age')

对于文件可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def upload_file(request):
if request.method == 'POST':
file_obj = request.FILES.get('file') # 获取上传的文件
with open(file_obj.name, 'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
```
#### JSON
以 JSON 格式({"key": "value"})传递结构化数据,支持嵌套对象、数组等复杂结构。数据放在请求体中,`Content-Type`为`application/json`。
适用于RESTful API 接口(如创建、更新资源),传递复杂结构化数据。
获取方式如下:
1. 用`request.body`(原始字节流)+`json.loads`解析
```python
import json

def json_view(request):
if request.method == 'POST':
# 解析JSON数据(注意:request.body是字节类型,需先解码)
data = json.loads(request.body.decode('utf-8'))
username = data.get('username')
hobbies = data.get('hobbies') # 支持数组、嵌套对象等

若使用Django REST Framework(DRF),可直接通过request.data获取(自动解析 JSON、Form Data 等):

1
2
3
4
5
from rest_framework.decorators import api_view

@api_view(['POST'])
def drf_view(request):
username = request.data.get('username') # 自动适配JSON/Form Data

XML

以 XML 标签格式(value)传递数据,适合需要严格格式约束的场景
数据放在请求体中,Content-Typeapplication/xml或text/xml
适用于传统 API、SOAP 协议接口。
需要手动解析XML内容,使用xml.etree.ElementTree

1
2
3
4
5
6
7
import xml.etree.ElementTree as ET

def xml_view(request):
if request.method == 'POST':
xml_data = request.body.decode('utf-8') # 获取XML字符串
root = ET.fromstring(xml_data)
username = root.find('username').text # 提取XML标签内容

RAW

自定义原始数据(如纯文本、JSON 字符串、XML 字符串等),需手动指定Content-Type
数据直接放在请求体中,格式由用户定义(如text/plainapplication/json等)。
适用于测试自定义格式数据、传递非标准格式内容。
直接读取request.body,并根据具体实际解析:

1
2
3
4
def raw_view(request):
if request.method == 'POST':
raw_data = request.body.decode('utf-8') # 转为字符串
# 若为自定义格式,按实际规则解析(如分割字符串、正则匹配等)

Binary

传递二进制文件(如图片、音频),无键值对结构。
请求体中直接存放二进制流,Content-Type通常为 application/octet-stream
适用于纯文件传输。
直接通过request.body获取:

1
2
3
4
5
def binary_view(request):
if request.method == 'POST':
binary_data = request.body # 二进制字节流
with open('received.bin', 'wb') as f:
f.write(binary_data)

近乎万能的request.data.get()

DRF框架下,request.data.get()可以处理很多场景,但是一些边界场景还是需要注意。

能处理场景

  1. JSON数据
  2. Form DATA表单数据
  3. 其他如application/json-patch+jsonapplication/vnd.api+jsonDRF 支持的扩展格式.

边界场景

  1. binary二进制数据
  2. 自定义的不是DRF支持的格式。
  3. 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
    4
    filters_str = request.GET.get('filters', '{}')    
    filters = json.loads(filters_str) # 解析为字典
    name = filters.get('name')
    age = filters.get('age')

    这里我不禁有个疑问,就是在我编写的过程中,一些GET的方法除了带上了selfrequest参数外常常还多带了一个参数,需要前端传入的方式也类似这里的查询参数,不仅让我好奇这两个是不是一个东西,结果确实不是一个东西,不然为什么不直接用那个参数就行了还要request.GET.get()一下呢?
    参数形式的是路径参数,它是路由的一部分,某种程度上是必需的,例如:https://api.example.com/users/123 中,123 是路径参数(代表用户 ID)
    而查询参数位于URL后以?开头,参数间以&分隔,例如:https://api.example.com/books?page=2&size=10 中, page=2size=10 是查询参数。
    路径参数一般用于”定位”资源,是路由不分割的部分,必须且唯一;但查询参数用于是路由的附加条件,可选且灵活。

Q()

一个构建复杂查询条件的工具类,用来组合多个查询条件,支持(AND、OR、NOT),使编写者像编写SQL语句一样编写代码。

主要功能

  1. 实现条件的”或”关系:Patient.objects.filter(Q(age__gt=30) | Q(gender="女"))
  2. 组合复杂的嵌套关系
  3. 与普通查询条件结合使用:Patient.objects.filter(Q(age__gt=30) | Q(gender="女"), nation="中国")(注意此时Q()必须在普通条件之前)
  4. 动态构建查询条件:condition = Q()\for name in name_list: \ condition |= Q(name__icontains=name) # 循环叠加OR条件
  5. 跨表查询:支持通过双下划线(__)关联跨表字段(注意前提是两表中有通过ForeignKey进行关联的字段,通过related_name__att_name来关联),实现多表联合查询。
  6. exclude()配合使用,例如:# 排除“年龄>30 且 性别为男”的患者 \ Patient.objects.exclude(Q(age__gt=30) & Q(gender="男"))