跳至主要內容

五、Django 视图-FBV 和 CBV

刘春龙...大约 50 分钟

4.16、Django 模型-聚合与分组查询

class Cook(models.Model):
    name = models.CharField(max_length=32,verbose_name='厨师名')
    level = models.IntegerField(verbose_name='厨艺等级')
    age = models.IntegerField(verbose_name='年龄')
    sect = models.CharField(max_length=32,verbose_name='厨艺派系')
    class Meta:
        db_table='t_cook'

聚合函数

对数据表计算统计值,需要使用 aggregate 方法,提供的参数可以是一个或者多个聚合函数,aggregate 是 QuerySet 的一个子句,它的返回值是一个字典类型

常用的聚合函数有:Avg、Count、Max、Min、Sum

# Max 找出最大的
from django.db.models import
from django.db.models import *
Cook.objects.aggregate(Max('age'))
# 结果是一个字典 {'age__max': 30}
# 可以使用 max=Max('age') 指定 别名为 max,而不使用  age__max
Cook.objects.aggregate(max=Max('age'))

# 多个聚合函数一起使用
Cook.objects.aggregate(Max('age'), Min('age'), Avg('age'))

分组查询

使用 annotate()函数实现分组查询,得配合其他函数:

  • annotate:用于分组,配合 Avg,Count 等聚合函数,如:annotate(max=Max('age'))
  • filter: 用于过滤,在 annotate 之前使用表示 where 条件,在 annotate 之后使用表示 having 条件
  • values:在 annotate 之前使用表示分组字段,在 annotate 之后使用表示取值
# 基本应用
# 以 group_id 分组,找出level的最大值,最小值,和平均值
Cook.objects.values('sect').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))
# 以 group_id 分组 并且 group_id 大于 2 ,找出level的最大值,最小值,和平均值
Cook.objects.values('sect').annotate(max=Max('level'), min=Min('level'), avg=Avg('level')).filter(sect='鲁菜')
# 和下面这句等效
# 推荐使用下面这种方式
Cook.objects.filter(sect='鲁菜').values('sect').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))

4.17、Django 模型-修改数据

  1. 先获取对象,通过对象属性更新数据,再保存 (更新单一数据)
  2. 通过 QuerySet 的 update 函数更新数据 (更新多条数据)
#单条记录修改 save
c = Cook.objects.get(pk=1)
c.name = '安妮'
c.save()


# 更新多个值  update
Cook.objects.filter(sect='粤菜').update(level=5)

Django 模型-刷新对象

通过 refresh_from_db 从数据库中重新获取对象的内容

c = Cook.objects.get(pk=1)
c.refresh_from_db()

4.18、Django 模型-删除数据

  1. 先获取对象,通过对象调用 delete 函数 (删除单一数据)
  2. 通过 QuerySet 的 delete 函数 (删除多条数据)
c = Cook.objects.get(pk=1)
c.delete()


Cook.objects.filter(level=3).delete()

提示

在真实项目中,往往不会真的删除数据,而选择使用逻辑删除!

4.19、Django 模型-Q 对象

ilter() 等方法中的关键字参数查询都是并且('AND')的, 如果需要执行更复杂的查询(例如 or 语句),那么可以使用django.db.models.Q 对象。

Q 对象 (django.db.models.Q) 对象用于封装一组关键字参数,可以使用 & 和 | 操作符组合起来,当一个操作符在两个 Q 对象上使用时,它产生一个新的 Q 对象。

注意

  • Q 对象可以和一般的关键字参数混用, 但是 Q 对象必须在一般关键字参数的前面
  • Q 对象可以多个同时使用
  • Q 对象前面可以增加~用于取反
from food_app.models import Cook
# 查询等级为5的数据
Cook.objects.filter(level=5)
# 查询等级为5,并且派系为川菜的数据
Cook.objects.filter(level=5,sect="川菜")
# 查询等级为5,并且派系为川菜的数据
Cook.objects.filter(level=5).filter(sect="川菜")
# 查询等级为6,或者派系为湘菜的数据,不支持这个写法!!!
Cook.objects.filter(level=6 or sect="湘菜")
# 查询等级为6,或者派系为湘菜的数据,不支持这个写法!!!
Cook.objects.filter(level=6 | sect="湘菜"))


from django.db.models import Q
# 查询等级为6,或者派系为湘菜的数据
Cook.objects.filter(Q(level=6) | Q(sect="湘菜"))
# 查询等级为6,并且派系为湘菜的数据
Cook.objects.filter(level=6,sect="湘菜")
# 查询等级4,并且等级为6,或者派系为湘菜的数据
Cook.objects.filter(Q(id=4),Q(level=6) | Q(sect="湘菜"))
# 查询等级不为4,并且等级为6,或者派系为湘菜的数据
Cook.objects.filter(~Q(id=4),Q(level=6) | Q(sect="湘菜"))
# 查询等级4,并且等级为6,或者派系为湘菜的数据
Cook.objects.filter(Q(level=6) | Q(sect="湘菜"),id=4)
# 查询等级4,并且等级为6,或者派系为湘菜的数据,不支持这个写法!!!
Cook.objects.filter(id=4,Q(level=6) | Q(sect="湘菜"))

4.20、Django 模型-F 对象

作用:模型的属性名出现在操作符的右边,就使用 F 对象进行包裹

class Salary(models.Model):
    name = models.CharField(max_length=32,verbose_name='员工名')
    basic = models.IntegerField(verbose_name='底薪')
    seniority = models.IntegerField(verbose_name='工龄')
    outstand = models.IntegerField(verbose_name='优秀次数')
    class Meta:
        db_table='t_salary'
  • 可以使用模型的 A 属性与 B 属性进行比较

    # 获取工龄等于优秀表现次数的员工# 获取工龄等于优秀表现次数的员工
    Salary.objects.filter(seniority=F("outstand"))
    
  • 支持算数运算

    # 获取平均每年大于1次的员工
    Salary.objects.filter(outstand__gt=F("seniority")+1)
    # 更新员工工作年限+1
    Salary.objects.update(seniority=F('seniority')+1)
    

4.21、Django 模型-使用 SQL 语句

通过模型使用 SQL

通过 raw 函数执行原始 SQL 语句进行查询,主键字段必须包含在查询的字段中,不然会引发错误 :

# 执行 原始 SQL
Cook.objects.raw('SELECT * FROM t_cook')
# 传递参数
name = '黑暗之女'
Cook.objects.raw('SELECT * FROM t_cook WHERE name = %s', [name])
# 返回的结果集一样可以执行切片
first_cook = Cook.objects.raw('SELECT * FROM t_cook')[0]

避开模型使用 SQL

不应用模型,直接使用 SQL 语句进行增删改查

from django.db import connection


cursor = connection.cursor()
cursor.execute("UPDATE t_cook SET level = 1 WHERE id = %s", [1])
cursor.execute("SELECT * FROM t_cook WHERE id = %s", [1])
row = cursor.fetchone()

五、Django 视图-FBV 和 CBV

视图是可以用来调用的,用来处理请求(request)并返回响应(response)

Django 的视图有两种形式 : FBV 与 CBV

FBV 是基于函数的视图 (function base views)

CBV 是基于类的视图(class base views)

from django.shortcuts import render, HttpResponse
from django.views import View


def selectUserFunc(request):
    print("普通的url")
    return HttpResponse("0")


class addUserClass(View):
    def get(self, request):
        return HttpResponse('get OK')

    def post(self, request):
        return HttpResponse('post OK')

loginModule = [
    path('login/', loginFunc)
]
userModule = [
    path('selectUser/', selectUserFunc),
    path('addUser/', addUserClass.as_view()),
]

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', include(loginModule)),  # 登录模块
    path('user/', include(userModule)),  # 用户模块
]

5.1、Django 视图-返回错误响应

返回错误的 3 种方式:

  • 返回 HttpResponseNotFound
  • 返回 HttpResponse 设置 status 状态码
  • 返回 Http404 状态对象
from django.http import Http404, HttpResponseNotFound
from django.shortcuts import render, HttpResponse


def loginFunc(request):
    print("普通的url")
    return HttpResponse("0")

#使用 HttpResponseNotFound
def func1(request):
    return HttpResponseNotFound('<h1>Page not found</h1>')

#还可以直接返回状态码
def func2(request):
    return HttpResponse(status=404)

# 特殊的 404  错误
def func3(request):
    raise Http404("页面没找到")

5.2、Django 视图-视图装饰器

@require_http_methods,要求视图只接收指定的 http 方法

@require_GET():仅仅允许 GET 方法

@require_POST():仅仅允许 POST 方法

@require_safe():仅仅允许 GET 和 HEAD 方法

@login_required

必须登录才能访问装饰的视图函数,

用户未登录,则重定向到 settings.LOGIN_URL,除非指定了 login_url 参数,例如:@login_required(login_url='/polls/login/')

from django.http import Http404, HttpResponseNotFound
from django.shortcuts import render, HttpResponse
from django.views.decorators.http import require_GET, require_POST, require_http_methods


def loginFunc(request):
    print("普通的url")
    return HttpResponse("0")


@require_http_methods(["GET", "POST"])  # 仅仅允许GET和POST方法
def func1(request):
    return HttpResponse("error1")


@require_GET  # 仅仅允许GET方法
def func2(request):
    return HttpResponse("error2")


@require_POST  # 仅仅允许POST方法
def func3(request):
    return HttpResponse("error3")

5.3、Django 视图-请求对象 HttpRequest

每一个用户请求在到达视图函数的同时,Django 会创建一个 HttpRequest 对象并把这个对象当做第一个参数传给要调用的 views 方法。HttpRequest 对象包含了请求的元数据,比如(本次请求所涉及的用户浏览器端数据、服务器端数据等),在 views 里可以通过 request 对象来调用相应的属性

所有视图函数的第一个参数都是 HttpRequest 实例

官网:https://docs.djangoproject.com/zh-hans/4.1/ref/request-response/#django.http.HttpRequestopen in new window

属性(除非另有说明,否则所有属性均应视为只读):

  • HttpRequest.scheme:

    表示请求使用的协议(http 或 https)

  • HttpRequest.body:

    原始 HTTP 请求主体,类型是字节串。处理数据一些非 html 表单的数据类型很有用,譬如:二进制图像,XML 等;

    • 取 form 表单数据,请使用 HttpRequest.POST
    • 取 url 中的参数,用 HttpRequest.GET
  • HttpRequest.path:

    表示请求页面的完整路径的字符串,不包括 scheme 和域名。

    例: "/music/bands/the_beatles/"

  • HttpRequest.method:

    表示请求中使用的 HTTP 方法的字符串,是大写的。例如:

    if request.method == 'GET':
        do_something()
    elif request.method == 'POST':
        do_something_else()
    
  • HttpRequest.encoding:

    表示当前编码的字符串,用于解码表单提交数据(或者 None,表示使用该 DEFAULT_CHARSET 设置)。

    可以设置此属性来更改访问表单数据时使用的编码,修改后,后续的属性访问(例如读取 GET 或 POST)将使用新 encoding 值。

  • HttpRequest.content_type:

    表示请求的 MIME 类型的字符串,从 CONTENT_TYPE 解析 。

  • HttpRequest.content_params:

    包含在 CONTENT_TYPE 标题中的键/值参数字典。

  • HttpRequest.GET:

    包含所有给定的 HTTP GET 参数的类似字典的对象。请参阅 QueryDict 下面的文档。

  • HttpRequest.POST:

    包含所有给定 HTTP POST 参数的类似字典的对象,前提是请求包含表单数据。请参阅 QueryDict 文档。POST 不包含文件信息,文件信息请见 FILES。

  • HttpRequest.COOKIES:

    包含所有 Cookie 的字典,键和值是字符串。

  • HttpRequest.FILES:

    包含所有上传文件的类似字典的对象

  • HttpRequest.META:

    包含所有可用 HTTP meta 的字典

中间件设置的属性:

Django 的 contrib 应用程序中包含的一些中间件在请求中设置了属性。如果在请求中看不到该属性,请确保使用了相应的中间件类 MIDDLEWARE

  • HttpRequest.session:

    来自 SessionMiddleware:代表当前会话的可读写字典对象。

  • HttpRequest.site:

    来自 CurrentSiteMiddleware: 代表当前网站的实例 Site 或 RequestSite 返回 get_current_site()

  • HttpRequest.user:

    来自 AuthenticationMiddleware:AUTH_USER_MODEL 代表当前登录用户的实例

5.4、Django 视图-QueryDict

在一个 HttpRequest 对象中, GET 和 POST 属性是 django.http.QueryDict 的实例,这是一个类似字典的类

  • 获取单个数据

    • queryDict.get(key)
    • queryDict[key]

    注意

    如果键有多个值,获取的值为最后一个值

  • 获取多个数据

    • queryDict.getlist(key)
  • 获取 key 与 value

    • list(queryDict.items())

    注意

    如果键有多个值,获取的值为最后一个值

loginModule = [
    path('login/', loginFunc),
    path('error1/', func1),
]
from django.http import Http404, HttpResponseNotFound
from django.shortcuts import render, HttpResponse
from django.views.decorators.http import require_GET, require_POST, require_http_methods


def loginFunc(request):
    print("普通的url")
    return HttpResponse("0")


@require_http_methods(["GET", "POST"])  # 仅仅允许GET和POST方法
def func1(request):
    params = request.GET
    # 打印params类型
    print(type(params))  # <class 'django.http.request.QueryDict'>
    # 获取单个值
    s_value1 = params.get('a')  # 推荐
    s_value2 = params['a']  # 如果没有值,会报错
    s_value3 = params.get('c')
    # 获取多个值
    m_value = params.getlist('c')
    # 同时获取key 与 value
    kv_value = list(params.items())
    # 返回结果
    return HttpResponse(f'request.GET:{params}<br/>类型是:{type(params)}<br/>s_value1:{s_value1}<br/>s_value2:{s_value2}<br/>  s_value3:{s_value3} <br/> m_value:{m_value}<br/>kv_value:{kv_value}')

访问:http://127.0.0.1:8000/login/error1/?a=1&c=2open in new window

六、Django 响应

6.1、HttpResponse

官网:https://docs.djangoproject.com/zh-hans/4.1/ref/request-response/#django.http.HttpResponseopen in new window

返回给浏览器端的响应对象

from django.http import  HttpResponse
from django.shortcuts import render
from django.views.decorators.http import require_http_methods


def loginFunc(request):
    print("普通的url")
    return HttpResponse("0")


@require_http_methods(["GET", "POST"])  # 仅仅允许GET和POST方法
def func1(request):
    return HttpResponse("error1")

属性

  • HttpResponse.content:

    表示响应的字符串

  • HttpResponse.charset:

    表示响应将被编码的字符集,如果在 HttpResponse 实例化时没有给出,则会从中提取 content_type,如果没有设置 content_type,则使用 settings.DEFAULT_CHARSET

  • HttpResponse.status_code:

    该响应的 HTTP 状态码

  • HttpResponse.reason_phrase:

    响应的 HTTP 原因描述语,使用 HTTP 标准的默认原因描述语 除非明确设置,否则 reason_phrase 由 status_code 决定。

  • HttpResponse.streaming:

    总是 False,中间件通过此属性可以区分流式响应与常规响应

  • HttpResponse.closed:

    如果 response 已经结束,则返回 True,否则返回 False

6.2、JsonResponse

包含 json 格式内容的响应

from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.views.decorators.http import require_http_methods


def loginFunc(request):
    print("普通的url")
    return HttpResponse("0")


@require_http_methods(["GET", "POST"])  # 仅仅允许GET和POST方法
def func1(request):
    return JsonResponse({'foo': 'bar'})

6.3、FileResponse

返回文件内容的响应

import os
from django.conf import settings
from django.http import FileResponse

def resp_file(request):
    file_path = os.path.join(settings.BASE_DIR,'imgs/tu1.png')
    return FileResponse(open(file_path,'rb'),filename='tu1.png')

6.4、Django 响应-重定向

  1. 重定向新地址

    from django.shortcuts import redirect
    def my_view(request):
    	return redirect('https://www.itbaizhan.com/')
    
  2. 通过传递硬编码的 URL 重定向:

    def my_view(request):
    	return redirect('/some/url/')
    
  3. 通过传递一个 URLConf 调度器中配置 path 或 re_path 的名称,以及可选的一些位置或关键字参数,该 URL 将使用 reverse()方法反向解析 :

    def my_view(request):
    	return redirect('polls:index', foo='bar')
    
  4. 默认情况下,redirect()返回一个临时重定向(302)。

    如果要设置永久重定向(301)设置 permanent 参数;为 True 即可:

    def my_view(request):
    	return redirect('polls:index', permanent=True)
    

6.5、Django 响应-查找不到返回 404

数据初始化

from django.db import models

class Salary(models.Model):
    """
    薪资组成
    """
    name = models.CharField(max_length=32,verbose_name='员工名')
    basic = models.IntegerField(verbose_name='底薪')
    seniority = models.IntegerField(verbose_name='工龄')
    outstand = models.IntegerField(verbose_name='优秀次数')
    class Meta:
        db_table='t_salary'

from django.http import Http404
from django.shortcuts import get_object_or_404, HttpResponse
from error_app.models import Salary


def my_view(request):
    poll = get_object_or_404(Salary, pk=1)
    return HttpResponse("有数据")


def my_view1(request):
    poll = get_object_or_404(Salary, pk=2)
    return HttpResponse("无数据")


def my_view2(request):
    poll = Salary.objects.get(pk=2)
    return HttpResponse("无数据")


def my_view3(request):
    try:
        poll = Salary.objects.get(pk=1)
    except Salary.DoesNotExist:
        raise Http404()
    return HttpResponse("无数据")

6.6、Django 响应-内置通用视图

七、Django 会话 Cookie

  • name:cookie 的名称

  • value:cookie 的值

  • domain:该 cookie 的所属域名,具有继承性,只允许本域名及子域名访问

    譬如:test.comopen in new window 这个是顶级域名, 二级域名 aaa.test.comopen in new window 就是 test.comopen in new window 的子域名,三级域名 bbb.aaa.test.comopen in new windowaaa.test.comopen in new window 的子域名

    如果设置一个 cookie: user=terry ,domain = aaa.test.comopen in new window

    那么:

    aaa.test.comopen in new window 这个域名下的 url 都可以访问 该 cookie

    bbb.aaa.test.comopen in new window 下的 url 也可以访问 该 cookie

    但是 test.comopen in new window 下的 url 不可以访问该 cookie,兄弟域名 ccc.test.comopen in new window 也不可以访问该 cookie

  • path:该 cookie 的所属的路径,具有继承性,只允许本路径及子路径域名访问,设置为根路径 path='/' ,则所有路径都可以访问

  • expires/Max-Age:expires 设置为一个失效时间值,HTTP1.1 中,expires 被弃用并且被 Max-Age 替代,设置为 cookie 多久时间之后失效,是整型,表示秒数

    response.set_cookie('num',123,max_age=24*3600)
    response.set_cookie('num',123,expires=(datetime.now()+timedelta(hours=1,seconds=30)))
    
  • size:cookie 的内容大小

  • http:httponly 属性,默认为 False,若此属性为 true,则只有在 http 请求头中会带有此 cookie 的信息,而不能通过 JavaScript(document.cookie)来访问此 cookie

  • secure:默认为 False,设置是否只能通过 https 来传递此 cookie

from django.http import HttpResponse

def loginFunc(request):
    resp = HttpResponse("设置了cookie")
    resp.set_cookie("token", "123", max_age=10)
    return resp
from django.http import HttpResponse
def loginFunc(request):
    info = request.COOKIES.get("token")
    resp = HttpResponse(f"获取了cookie{info}")
    return resp

7.3、会话-3 天免登录

from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect


def loginFunc(request):
    resp = HttpResponse(f"这是login页面")
    return resp

# http://127.0.0.1:8000/login/doLogin/?name=lcl&pwd=123


def doLoginFunc(request):
    data = request.GET
    username = data.get("name")
    password = data.get("pwd")
    print(username, password)
    token = "hweiobcdhoqbcudqbc"
    if username == "lcl" and password == "123":
        resp = HttpResponse("登陆成功")
        resp.set_cookie("token", token, max_age=60*60*24*7)
        resp.set_signed_cookie("pwd", password, max_age=60*60*24*7)
        return resp
    else:
        return redirect("/login/login")

八、Django 会话 Session

Session,在计算机中,尤其是在网络应用中,称为“会话控制”。

Session 对象存储特定用户会话所需的属性及配置信息。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话

当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而且在整个用户会话中一直存在下去。

会话状态仅在支持 cookie 的浏览器中保留**(session 是依赖于 cookie 的)**

Django 框架中的 session 管理允许存储和检索任意数据,它在服务器端存储数据并抽象 cookie 的发送和接收。Cookie 包含 session 的 ID,而不是数据本身(除非使用基于 cookie 的 session 管理类型)

8.1、Session 的配置

官网:https://docs.djangoproject.com/zh-hans/4.1/topics/http/sessions/#using-database-backed-sessionsopen in new window

启用 session

要应用 session,必须开启 session 中间层,在 settings.pyopen in new window 中:

MIDDLEWARE = [
  # 启用 Session 中间层
  'django.contrib.sessions.middleware.SessionMiddleware',
]

五种 session 的引擎

Django 中默认支持 Session,其内部提供了 5 种类型供开发者使用:

  • 数据库
  • 缓存
  • 缓存+数据库
  • 文件
  • Cookie

五种方式的启动配置各异,但是启动完成后,在程序中的使用方式都相同:

  • 数据库方式

    SESSION_ENGINE = 'django.contrib.sessions.backends.db'
    # 数据库类型的session引擎需要开启此应用,启用 sessions 应用
    INSTALLED_APPS = [
      'django.contrib.sessions',
    ]
    
    
  • 缓存

    速度最快,但是由于数据是保存在内存中,所以不是持久性的,服务器重启或者内存满了就会丢失数据

    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
    
  • 缓存+数据库

    速度次于单纯缓存方式,但是实现了持久性,每次写入高速缓存也将写入数据库,并且如果数据尚未存在于缓存中,则使用数据库

    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
    
  • 文件

    SESSION_ENGINE = 'django.contrib.sessions.backends.file'
    # 设置文件位置, 默认是 tempfile.gettempdir(),
    # linux下是:/tmp
    # windows下是: C:\Users\51508\AppData\Local\Temp
    SESSION_FILE_PATH = 'd:\session_dir'
    
  • 加密 cookie

    基于 cookie 的 session,所有数据都保存在 cookie 中,一般情况下不建议使用这种方式

    1. cookie 有长度限制,4096 个字节
    2. cookie 不会因为服务端的注销而无效,那么可能造成攻击者使用已经登出的 cookie 模仿用户继续访问网站
    3. SECRET_KEY 这个配置项绝对不能泄露,否则会让攻击者可以远程执行任意代码
    4. cookie 过大,会影响用户访问速度
    1
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
    

8.2、Session 的使用

官网:https://docs.djangoproject.com/zh-hans/4.1/topics/http/sessions/#using-sessions-in-viewsopen in new window

创建与获取 session

在 django 程序中通过 request.session 来获取 session 对象,并且这个 session 就是一个字典对象,并且 key 只能是 字符串

from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect


def loginFunc(request):
    request.session['name'] = "lcl"    #设置
    value = request.session.get('name')  # 获取
    return HttpResponse(f"session设置好了{value}")

删除 session

# 删除某个key
del request.session['has_commented']
# 从会话中删除当前会话数据并删除会话cookie
flush()
# 设置会话的到期时间
# 如果value是整数,则session将在多少秒不活动后到期
# 如果value是一个datetime或timedelta,该session将在相应的日期/时间到期
# 如果value是0,用户的会话cookie将在用户的Web浏览器关闭时到期
# 如果value是None,则会话将恢复为使用全局会话到期策略
set_expiry(value)

其他 session 方法

# 设置测试cookie以确定用户的浏览器是否支持cookie
set_test_cookie()
# 返回True或者False,取决于用户的浏览器是否接受测试cookie
test_cookie_worked()
# 删除测试cookie
delete_test_cookie()
# 返回此会话到期之前的秒数
# kwargs 为 `modification` 和 `expiry`,一般不指定
# modification:最后一次访问日期,默认当前时间, now
# expiry: 到期剩余秒数,默认全局配置时间
get_expiry_age(**kwargs)
# 返回此会话将过期的日期
# 参数同 get_expiry_age
get_expiry_date(**kwargs)
# 返回True或者False,取决于用户的Web浏览器关闭时用户的会话cookie是否会过期
get_expire_at_browser_close()
# 从会话存储中删除过期的会话,这是个类方法。
clear_expired()
# 在保留当前会话数据的同时创建新的会话密钥
cycle_key()

session 的 settings.pyopen in new window 使用

# Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_NAME = "sessionid"
# Session的cookie保存的路径(默认)
SESSION_COOKIE_PATH = "/"
# Session的cookie保存的域名(默认)
SESSION_COOKIE_DOMAIN = None
# 是否Https传输cookie(默认)
SESSION_COOKIE_SECURE = False
# 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_HTTPONLY = True
# Session的cookie失效日期(2周)(默认)
SESSION_COOKIE_AGE = 1209600
# 是否关闭浏览器使得Session过期(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# 是否每次请求都保存Session,默认修改之后才保存(默认)
SESSION_SAVE_EVERY_REQUEST = False

代码中设置的有效期会覆盖配置文件设置的有效期

九、Django 工具-分页器介绍

在 django 项目中,一般是使用 3 种分页的技术:

  1. 自定义分页功能,所有的分页功能都是自己实现
  2. django 的插件 django-pagination 实现
  3. django 自带的分页器 paginator

分页器相关对象

分页器的对象在 django/core/paginator.py 模块中,主要包括 Paginator 类和 Page 类:

Paginator 类

  1. 初始化方法init(self, object_list, per_page, orphans=0,allow_empty_first_page=True):

    • object_list:可以是 QuerySet、带有 count()或len()方法的列表、元组或其它可切片的对象,如果是 QuerySet,应该进行排序,使用 order_by()子句或者有默认的 ordering
    • per_page:每个页面上显示的项目数目,不包括 orphans 部分
    • orphans:默认为 0,如果最后一页显示的数目小于等于该数值,那么则将最后一页的内容(假设数为 N)添加到倒数第二页中去,这样的话倒数第二页就成了最后一页,显示的数目为:per_page+N
    • allow_empty_first_page:默认为 True,允许第一页显示空白内容,如果设置为 False,那么当 object_list 为空时,抛出 EmptyPage 错误
  2. 方法

    1. get_page(self, number)

      • numer:指定页码数,正确值应该是大于等于 1 的整数

      返回指定 number 的 Page 对象,同时还处理超出范围和无效页码,如果 number 不是数字,则返回第一页,如果 number 为负数或大于最大页数,则返回最后一页。

    2. page(self, number)

      • numer:指定页码数,正确值应该是大于等于 1 的整数

      返回指定 number 的 Page 对象,不处理异常,如果 number 无效,则抛出 InvalidPage 错误

  3. 属性

    1. count:项目总条数,调用该属性时,优先调用 object_list 的 count()方法,没有 count()方法才尝试 len(object_list)方法
    2. num_pages:总页码数
    3. page_range:从 1 开始的页码迭代器,代码:range(1, self.num_pages + 1)

Page 类

一般情况下,不会手动实例化该类,而是通过 Paginator 的 page 或者 get_page 方法获取

  1. 初始化方法init(self, object_list, number, paginator):

    • object_list:当页显示的 object_list 对象,object_list 可以是 QuerySet、带有 count()或len()方法的列表、元组或其它可切片的对象
    • number:页码数
    • paginator:Paginator 类的实例
  2. 方法

    主要的方法都是用来做逻辑判断的,以此来决定页面中显示的诸如:上一页、下一页,首页,末页等

    1. has_next():如果有下一页则返回 True
    2. has_previous():如果有上一页,则返回 True
    3. has_other_pages():如果有上一页或者下一页,则返回 True
    4. next_page_number():返回下一页编号,如果下一页不存在则引发 InvalidPage 错误
    5. previous_page_number():返回上一页编号,如果上一页不存在则引发 InvalidPage 错误
    6. start_index() :返回页面上第一个对象的从 1 开始的索引,相对于分页器列表中的所有对象。例如,当为每页包含 2 个对象的 5 个对象的列表进行分页时,第二个页面 Page 对象的 start_index 返回 3
    7. end_index() :返回页面上最后一个对象的从 1 开始的索引,相对于分页器列表中的所有对象。例如,当为每页包含 2 个对象的 5 个对象的列表进行分页时,第二个页面 Page 对象的 end_index 返回 4
  3. 属性

    其实就是初始化方法中的 3 个参数

    1. object_list:对应的 object_list
    2. number:该对象的所处的页码数,从 1 开始
    3. paginator:关联的 Paginator 实例

十、Django 工具-验证码

十一、中间件

AOP(Aspect Oriented Programming ),面向切面编程,是对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。可以实现在不修改源代码的情况下给程序动态统一添加功能的一种技术

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 的功能将切面植入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事务,日志等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使业务逻辑变得混杂不清

Django 的中间件,就是应用 AOP 技术来实现的,它是 django 请求/响应处理的钩子框架,是一个轻巧的低级“插件”系统,在不修改 django 项目原有代码的基础上,可以全局改变 django 的输入或输出,每个中间件组件负责执行某些特定功能。

提示

因为中间件改变的是全局,所以需要谨慎实用,滥用的话,会影响到服务器的性能

默认中间件

Django 项目默认有一些自带的中间件,每个中间件组件由字符串表示:指向中间件工厂的类或函数名的完整 Python 路径如下:

MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

一般情况下这些中间件都会启用(最少 CommonMiddleware 会启用)

11.1、自定义中间件

官网:https://docs.djangoproject.com/zh-hans/4.1/topics/http/middleware/open in new window

自定义中件间,可以是一个方法,与可以是一个类。但都要有接受 get_response 参数的入口,代码如下:

from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect


def loginFunc(request):
    print("原来的业务逻辑")
    return HttpResponse("index")

def simple_middleware(get_response):
    def middleware(request):
        # 业务处理前
        print(0)
        response = get_response(request)
        # 业务处理后
        print(1)
        return response
    return middleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 自定义中间件
    'middleware.middleware.simple_middleware',
]

十二、Django 日志-概念与配置

日志是程序员经常在代码中使用快速和方便的调试工具。它在调试方面比 print 更加的优雅和灵活

而且日志记录对于调试很有用,可以提供更多,更好的结构化,有关应用程序的状态和运行状况的信息

Django 框架的日志通过 python 内置的 logging 模块实现的,可以记录系统运行中的一些对象数据,还可以记录包括堆栈跟踪、错误代码之类的详细信息

logging 主要组成

logging 主要由 4 部分组成

  • Loggers
    • 记录器是进入日志记录系统的入口点。每个记录器都是一个命名的,可以将消息写入其中进行处理的存储桶
  • Handlers
    • Handler 决定如何处理 logger 中的每条消息。它表示一个特定的日志行为,例如 将消息写入屏幕、文件或网络 Socket
  • Filters
    • 筛选器用于对从 logger 传递给 handler 的哪些日志要做额外控制
  • Formatters
    • 格式化程序描述该文本的确切格式

logger 等级

python 定义了日志的 5 个级别,分别对应 python 程序中日志信息的不同严重性(严重程度从上到下越来越严重,也就是级别越高):

  • DEBUG(10):用于调试的目的的底层系统信息
  • INFO(20):普通的系统信息
  • WARNING(30):一些警告性的信息,发生了一些小问题,这些问题不影响系统的正常运行,但是也不建议出现
  • ERROR(40):系统出现错误了,该错误会影响系统的正常运行,记录错误相关的信息
  • CRITICAL(50):非常严重的问题,譬如可能引起系统崩溃的问题等

提示

  • 写入 logger 的每条消息都是一条日志。每条日志也具有一个日志级别,它表示对应的消息的验证性。每个日志记录还可以包含描述正在打印的事件的元信息
  • 当一条消息传递给 logger 时,消息的日志级别将与 logger 的日志级别进行比较。
    • 如果消息的日志级别大于等于 logger 的日志级别,该消息将会往下继续处理
    • 如果小于,该消息将被忽略
  • Logger 一旦决定消息需要处理,它将传递该消息给一个 Handler

技巧

一般开发环境时,会启用 DEBUG 级别,而在生产环境中,启用 WARNING 或 ERROR 级别

12.1、日志的基本使用

from django.http import HttpResponse
import logging
logger = logging.getLogger(__name__)


def loginFunc(request):
    print("查看日志")
    logger.debug('记录的 debug 日志')
    logger.info('记录的 info 日志')
    logger.warning('记录的 warning 日志')
    logger.error('记录的 error 日志')
    logger.critical('记录的 critical 日志')
    return HttpResponse("查看日志")
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        # 一般应用文件
        'standard': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d:%(funcName)s] %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
    },
    'loggers': {
        '': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}

12.2、Django 日志-Logger

记录器是进入日志记录系统的入口点。每个记录器都是一个命名的,可以将消息写入其中进行处理的存储桶

logger 对应的值是个字典,每一个键都是 logger 的名字,每个值又是一个字典,描述了如何配置对应的 Logger 实例

  • level:指定记录日志的级别,没有配置则处理所有级别的日志
  • propagate:设置该记录器的日志是否传播到父记录器,不设置则是 True
  • filters:指定过滤器列表
  • handlers:指定处理器列表

12.3、Django 日志-Handler

Handler 决定如何处理 logger 中的每条消息。它表示一个特定的日志行为,例如 将消息写入屏幕、文件或网络 Socket

handler 对应的是个字典,每一个键都是一个 handler 的名字,每个值又一个字典,描述了如何配置对应的 handler 实例

  • class(必需):处理程序类的名称
  • level(可选的):处理程序的级别
  • formatter(可选的):处理程序的格式化程序
  • filters(可选的):处理程序的过滤器的列表

12.4、Django 日志-常用的内置 Handler

python 内置了许多 Handler,多部分在以下 2 个目录:

  • logginginit.py
  • logging\handlers.py

内置 Handler

  • StreamHandler:输出到 stream,未指定则使用 sys.stderr 输出到控制台

  • FileHandler:继承自 StreamHandler,输出到文件,默认情况下,文件无限增长

    初始化参数:

    • filename 文件的名字

    • mode ='a' 文件的写入模式

    • encoding = None,

    • delay = False

      delay 如果为 True,那么会延迟到第一次调用 emit 写入数据时才打开文件

12.5、内置 RotatingFileHandler

自动按大小切分的 log 文件, 初始化参数:

  • filename
  • mode ='a'
  • encoding = None
  • delay = False
  • maxBytes:最大字节数,超过时创建新的日志文件,如果 backupCount 或 maxBytes 有一个为 0,那么就一直使用一个文件
  • backupCount:最大文件个数,新文件的扩展名是指定的文件后加序号".1"等,
    • 例如:backupCount=5,基础文件名为:app.log,那么达到指定 maxBytes 之后,会关闭文件 app.log,将 app.log 重命名为 app.log.1,如果 app.log.1 存在,那么就顺推,先将 app.log.1 重命名为 app.log.2,再将现在的 app.log 命名为 app.log.1,最大创建到 app.log.5(旧的 app.log.5 会被删除),然后重新创建 app.log 文件进行日志写入,也就是永远只会对 app.log 文件进行写入

注意

启动项目使用命令:python manager.pyopen in new window runserver --noreaload

12.6、内置 TimedRotatingFileHandler

按时间自动切分的 log 文件,文件后缀 %Y-%m-%d_%H-%M-%S , 初始化参数:

  • filename

  • when='h' 时间间隔类型,不区分大小写

    'S':秒
    'M':分钟
    'H':小时
    'D':天
    'W0'-'W6':星期几(0 = 星期一)
    'midnight':如果atTime未指定,则在 0点0分0秒 新建文件,否则在atTime时间新建文件
    
  • interval=1:间隔的数值

  • backupCount=0: 文件个数

  • encoding=None:编码

  • delay=False:True 是写入文件时才打开文件,默认 False,实例化时即打开文件

  • utc=False:False 则使用当地时间,True 则使用 UTC 时间

  • atTime=None:必须是 datetime.time 实例,指定文件第一次切分的时间,when 设置为 S,M,H,D 时,该设置会被忽略

12.7、Django 日志-内置 SMTPHandler

通过 email 发送日志记录消息, 初始化参数:

  • mailhost:发件人邮箱服务器地址(默认 25 端口)或地址和指定端口的元组,如:('smtp.qq.comopen in new window', 25)
  • fromaddr:发件人邮箱
  • toaddrs:收件人邮箱列表
  • subject:邮件标题
  • credentials:如果邮箱服务器需要登录,则传递(username, password)元组
    • password 是授权码
  • secure:使用 TLS 加密协议

注意

发送邮件的邮箱,开启 SMTP 服务

12.8、Django 日志-Filters 介绍

过滤器用于从 logger 传递给 handler 的哪些日志要做额外控制

默认情况下,满足日志级别的任何消息都将处理。只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加 filter 来给日志处理的过程增加额外条件。例如,可以添加一个 filter 只允许某个特定来源的 ERROR 消息输出

Filters 还可以用于修改将要处理的日志记录的优先级。例如,如果日志记录满足特定的条件,可以编写一个 filter 将日志记录从 ERROR 降为 WARNING

Filters 可以安装在 logger 或者 handler 上,多个 filter 可以链接起来使用,来做多重过滤操作

12.9、最终

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        # 一般应用文件
        'standard': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d:%(funcName)s] %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
        'files': {
            'level': 'WARNING',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'formatter': 'standard',
            'encoding': 'UTF-8',
            'filename': os.path.join(BASE_DIR, 'logs', 'django.log'),
            'when': 'D',  # 按天分割
            'interval': 1,  # 每1天割一次
            'backupCount': 7,
        },
        'email': {
            'level': 'ERROR',
            'class': 'logging.handlers.SMTPHandler',
            'formatter': 'standard',
            'mailhost': ('smtp.qq.com', 25),
            'fromaddr': '486428167@qq.com',
            'toaddrs': ['486428167@qq.com'],
            'subject': '您的网站产生重大漏洞,请及时处理',
            # 密码不是登录的密码,而是授权码
            'credentials': ('486428167@qq.com', 'qkeuybuymayhcacd')
        }
    },
    'loggers': {
        'default': {
            'handlers': ['console', 'files', 'email'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}
from django.http import HttpResponse
import logging
logger = logging.getLogger('default')


def loginFunc(request):
    print("查看日志")
    logger.debug('记录的 debug 日志')
    logger.info('记录的 info 日志')
    logger.warning('记录的 warning 日志')
    logger.error('记录的 error 日志')
    logger.critical('记录的 critical 日志')
    return HttpResponse("查看日志")

十三、阿里云短信验证码

安装依赖模块

pip install alibabacloud_dysmsapi20170525==2.0.23
from django.http import HttpResponse
import sys

from typing import List

from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient


def loginFunc(request):
    class Sample:
        def __init__(self):
            pass

        @staticmethod
        def create_client(
            access_key_id: str,
            access_key_secret: str,
        ) -> Dysmsapi20170525Client:
            config = open_api_models.Config(
                access_key_id=access_key_id,
                access_key_secret=access_key_secret
            )
            config.endpoint = f'dysmsapi.aliyuncs.com'
            return Dysmsapi20170525Client(config)

        @staticmethod
        def main(
            args: List[str],
        ) -> None:
            # 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html
            client = Sample.create_client(args[0], args[1])
            send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
                sign_name='阿里云短信测试',
                template_code='SMS_154950909',
                phone_numbers=args[2],
                template_param='{"code":"'+args[3]+'"}'
            )
            runtime = util_models.RuntimeOptions()
            try:
                # 复制代码运行请自行打印 API 的返回值
                client.send_sms_with_options(send_sms_request, runtime)
            except Exception as error:
                # 如有需要,请打印 error
                UtilClient.assert_as_string(error.message)

        @staticmethod
        async def main_async(
            args: List[str],
        ) -> None:
            # 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html
            client = Sample.create_client(args[0], args[1])
            send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
                sign_name='阿里云短信测试',
                template_code='SMS_154950909',
                phone_numbers=args[2],
                template_param='{"code":"'+args[3]+'"}'
            )
            runtime = util_models.RuntimeOptions()
            try:
                # 复制代码运行请自行打印 API 的返回值
                await client.send_sms_with_options_async(send_sms_request, runtime)
            except Exception as error:
                # 如有需要,请打印 error
                UtilClient.assert_as_string(error.message)
    info = [
        "LTAI5tCtfvDeCH5Kq64BNZzW",
        "C9VZ3G09PnrAN9pcA4gguE3Ca8g5va",
        "17667300429",
        "5678",
    ]
    Sample.main(info)
    return HttpResponse("123")

十四、邮箱验证码

Zmail 的优势

  1. 自动填充大多数导致服务端拒信的头信息(From To LocalHost 之类的)
  2. 将一个字典映射为 email,构造信件就像构造字典一样简单
  3. 自动寻找邮件服务商端口号地址,自动选择合适的协议(经过认证的)

安装

pip install zmail

常用方法与属性

函数名&属性含义
zmail.server(name,password)登录服务器邮件
server.send_mail(address,info)发送邮件

发送的消息以字典发送,包含的 key:

  • subject 邮件主题
  • from 发送人
  • content_text 邮件内容-文本
from django.http import HttpResponse
import zmail


def loginFunc(request):
    # 登录邮箱
    server = zmail.server('486428167@qq.com', 'qkeuybuymayhcacd')
    # 编写内容
    info = {
        'from': '嘉美网络科技',
        'subject': '验证码',
        'content_text': '您的验证码为123456,5分钟内有效'
    }
    # 发送邮件
    server.send_mail('486428167@qq.com', info)
    return HttpResponse("123")

十五、REST framework 环境搭建

安装 Python3.8+

pip install django==4.1.1
pip install djangorestframework==3.14.0

settings 配置

首先新建一个 django 项目

然后如果要启用 REST framework,那么需要将其添加到 INSTALLED_APPS 中

INSTALLED_APPS = [
  ...
  'rest_framework'
]

序列化与反序列化

当前 WEB API 应用中,前端要用到从后台返回的数据来渲染页面的时候,一般都是使用的 json 类型的数据,因为 json 类型简单直观便于理解,那么就需要在 django 框架中,将模型类数据序列化为 json

创建序列化类

在子应用的目录下,新建 app_serializers.py 文件,在其中建立一个对应第一步建立的模型的序列化类:

from rest_framework import serializers
from login.models import *
class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ['id', 'name', 'age','sex']

序列化

from django.shortcuts import render, HttpResponse

from login.models import Student
from login.serializers import StudentSerializer
from rest_framework.renderers import JSONRenderer


def login(request):
    # 得到一个模型实例(序列化单条数据)
    stu = Student.objects.get(pk=1)
    # 得到模型序列化类实例
    stu_ser = StudentSerializer(stu)
    # 得到 模型的字典数据 {"id": 1, ......}
    data_dict = stu_ser.data
    print(data_dict)

    # 得到一个模型实例(序列化多条数据)
    stu1 = Student.objects.all()
    # 得到模型序列化类实例
    stu_ser1 = StudentSerializer(stu1, many=True)
    # 得到 模型的字典数据 {"id": 1, ......}
    data_dict1 = stu_ser1.data
    data_json = JSONRenderer().render(data_dict1)
    print(data_json)

    return HttpResponse(1)

反序列化

from django.shortcuts import render, HttpResponse

from login.models import Student
from login.serializers import StudentSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
import io


def login(request):
    data = b'{"name":"rose", "age":19, "sex":2}'

    stream = io.BytesIO(data)
    # 得到字典数据, {'id': 1,......}
    data_dict = JSONParser().parse(stream)

    # 将字典数据 反序列化
    serializer = StudentSerializer(data=data_dict)

    # 保存到数据库中
    # 必须执行这一步验证, 返回True 执行 save等方法
    serializer.is_valid()
    serializer.save()
    return HttpResponse(1)

一查多

def index2(request, id):  # 一查多
    try:
        # 根据pk获取该班级的学生
        c = Classes.objects.get(id=id)
    except Classes.DoesNotExist:
        return JsonResponse({"message": "班级不存在"}, status=404)
    # 创建序列化对象
    serializer = ClassesSerializer(c)
    # 序列化
    data = serializer.data
    print(serializer)
    return JsonResponse(data, safe=False)
from rest_framework import serializers
from login.models import *


class StudentRelateFiled(serializers.RelatedField):
    # 重写to_representation方法,将外键关联的对象转换为指定的格式
    def to_representation(self, value):
        return {'id': value.id, 'name': value.name, 'age': value.age}


class ClassesSerializer(serializers.ModelSerializer):
    stu = StudentRelateFiled(many=True, read_only=True)

    class Meta:
        model = Classes  # 指定序列化模型
        fields = ['id', 'name', 'stu']  # 指定序列化的字段


class StudentsSerializer(serializers.ModelSerializer):
    classes = ClassesSerializer()  # 多查一的时候将其注释

    class Meta:
        model = Students  # 指定序列化模型
        fields = ['id', 'name', 'age', 'sex', 'classes']  # 指定序列化的字段

多查一

from django.http import JsonResponse
from django.shortcuts import render

from login.models import Student
from login.serializers import StudentsSerializer


def index(request):  # 多查一
    # 获取所有学生信息
    all_stu = Student.objects.all()
    # 创建序列化对象
    serializer = StudentsSerializer(all_stu, many=True)
    # 序列化
    data = serializer.data
    return JsonResponse(data, safe=False)

from rest_framework import serializers
from login.models import *


class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Classes
        fields = '__all__'


class StudentsSerializer(serializers.ModelSerializer):
    classes = ClassSerializer()

    class Meta:
        model = Student  # 指定序列化模型
        fields = ['id', 'name', 'age', 'sex', 'classes']  # 指定序列化的字段

请求和响应对象介绍

REST framework 引入了 2 个新的对象:Request 和 Response

Request

rest_framework.request.Request

该对象扩展了常规的 HttpRequest ,增加了对 REST 框架灵活的请求解析和请求认证的支持

官网:https://www.django-rest-framework.org/api-guide/requests/#requestsopen in new window

主要属性:

  • data

    这个属性类似 django 的 request 的 POST 和 FILES 属性:

    • django 的 request.POST:只能获取 POST 请求的 form 表单数据
    • rest_framework 的 request.data:
      • 包括所有已解析的内容,包括文件和非文件输入
      • 支持'POST', 'PUT' 和'PATCH' 方法
      • 支持 REST 框架的灵活请求解析,而不仅仅支持表单数据。例如,可以传入 JSON 数据
  • query_params

    就是标准的 request.GET 属性,不过从命名角度来说,更加合理,因为不是只有 GET 请求才有 GET 查询参数

Response

rest_framework.response.Response

网站:https://www.django-rest-framework.org/api-guide/responses/#responsesopen in new window

该类是 Django 的 SimpleTemplateResponse 的子类,使用 python 原始对象进行数据初始化,然后,REST 框架使用标准 HTTP 内容协商来确定它应如何呈现最终的响应内容

初始化

Response(data, status=None, template_name=None, headers=None, content_type=None)

  • data:响应的序列化数据,如果是复杂对象,如模型实例,需要在传入之前序列化为原始数据类型(如 dict 等)
  • status:响应的状态代码,默认为 200
  • template_nameHTMLRenderer选择时使用的模板名称
  • headers:要在响应中使用的 HTTP 标头的字典
  • content_type:响应的内容类型。通常,这将由内容协商确定的渲染器自动设置,但在某些情况下您可能需要明确指定内容类型

属性

  • data:响应的未呈现的序列化数据

  • status_code:响应状态码,建议使用 HTTP_200_OK 这样的常量,而不是 200 这样的数字,常量给每个状态代码提供更明确的标识符

    更详细的见:https://www.django-rest-framework.org/api-guide/status-codes/open in new window

  • content:渲染后的响应内容,调用此属性前需要调用.render()方法渲染

  • template_name:指定的模板名

  • accepted_renderer:将用于呈现响应的呈现器实例,在视图返回响应之前由APIView@api_view自动设置

  • accepted_media_type:内容协商阶段选择的媒体类型,在视图返回响应之前由APIView@api_view自动设置

  • renderer_context:需要渲染的上下文字典对象,在视图返回响应之前由APIView@api_view自动设置

视图 API 包装器-@api_view

官网:https://www.django-rest-framework.org/tutorial/2-requests-and-responses/#wrapping-api-viewsopen in new window

框架使用了 2 个包装 API 视图 的包装器:

  • @api_view:用于处理基于函数的视图的装饰器

    rest_framework.decorators.api_view

  • APIView:用于处理基于类的视图

    rest_framework.views.APIView

from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view
from rest_framework.response import Response


@api_view(["GET", "POST"])
@csrf_exempt
def index(request):
    return Response("测试api")

RESTFramework 的 GUI 界面

RESTFrame 的视图包装器与 Response 的结合使用提供的 GUI 界面,可以对数据进行 CRUD,但是后台逻辑操作还是由我们自己开发,具体步骤如如:

  • 实现数据的增删改查操作代码
  • 使用视频包装器包装视图
  • 数据的结果返回 rest_framework.response.Response

没使用之前

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.parsers import JSONParser
'''
数据的增删改查
操作       URL         HTTP方法
添加数据     /students/        POST
修改数据     /student/<int:pk>/    PUT
删除数据     /student/<int:pk>/    DELETE
查询数据     /students/        GET
查询单个数据   /student/<int:pk>/    GET
'''


@api_view(["GET", "POST"])
@csrf_exempt
def index(request):
    if request.method == "GET":
        stu = Student.objects.all()
        serializer = StudentSerializer(stu, many=True)
        data = serializer.data
        return JsonResponse(data, safe=False)
    if request.method == "POST":
        obj = JSONParser().parse(request)
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        else:
            return JsonResponse(serializer.errors, status=400)


@api_view(["GET", "PUT", "DELETE"])
@csrf_exempt
def index1(request, id):
    if request.method == "GET":
        try:
            stu = Student.objects.get(id=id)
        except Student.DoesNotExist:
            return JsonResponse({"ERROR": "数据不存在"}, status=404)
        serializer = StudentSerializer(stu)
        return JsonResponse(serializer.data, status=200)
    if request.method == "PUT":
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return JsonResponse({'error': '暂无数据'}, status=404)
        obj = JSONParser().parse(request)
        serializer = StudentSerializer(stu, data=obj)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=200)
        else:
            return JsonResponse(serializer.errors, status=400)
    if request.method == "DELETE":
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return JsonResponse({'error': '暂无数据'}, status=404)
        stu.delete()
        return JsonResponse({"message": "删除成功"}, status=204)

使用之后

from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view
from rest_framework.response import Response
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.status import Status


@api_view(["GET", "POST"])
@csrf_exempt
def index(request):
    if request.method == "GET":
        stu = Student.objects.all()
        serializer = StudentSerializer(stu, many=True)
        data = serializer.data
        return Response(data)
    if request.method == "POST":
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(data=obj)
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        else:
            return Response(serializer.errors, status=400)


@api_view(["GET", "PUT", "DELETE"])
@csrf_exempt
def index1(request, id):
    if request.method == "GET":
        try:
            stu = Student.objects.get(id=id)
        except Student.DoesNotExist:
            return Response({"ERROR": "数据不存在"}, status=404)
        serializer = StudentSerializer(stu)
        return Response(serializer.data, status=200)
    if request.method == "PUT":
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(stu, data=obj)
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
        else:
            return Response(serializer.errors, status=400)
    if request.method == "DELETE":
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        stu.delete()
        return Response({"message": "删除成功"}, status=204)

格式后缀

为了使我们的响应不再硬连接到单个内容类型这一事实,我们可以将 API 格式后缀添加到 API 之后。使用格式后缀为我们提供了明确引用给定格式的 URL,譬如:http://example.com/api/items/4.jsonopen in new window

官网:https://www.django-rest-framework.org/tutorial/2-requests-and-responses/#adding-optional-format-suffixes-to-our-urlsopen in new window

views:在函数最后添加 format=None 参数,示例:

def students(request, format=None):


def student_detail(request, pk, format=None):

urls:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', index),
    path('index1/<int:id>/', index1),
]
# 可以使用 allowed=['json', 'html'] 参数指定允许的后缀
urlpatterns = format_suffix_patterns(urlpatterns)

http://127.0.0.1:8000/index1/2.jsonopen in new window

http://127.0.0.1:8000/index1/2open in new window

类视图

from rest_framework.views import APIView
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.response import Response


class StudentList(APIView):
    def get(self, request, format=None):
        stu = Student.objects.all()
        serializer = StudentSerializer(stu, many=True)
        data = serializer.data
        return Response(data)

    def post(self, request, format=None):
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        else:
            return Response(serializer.errors, status=400)


class StudentDetail(APIView):
    def get(self, request, id, format=None):
        try:
            stu = Student.objects.get(id=id)
        except Student.DoesNotExist:
            return Response({"ERROR": "数据不存在"}, status=404)
        serializer = StudentSerializer(stu)
        return Response(serializer.data, status=200)

    def put(self, request, id, format=None):
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(stu, data=obj)
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
        else:
            return Response(serializer.errors, status=400)

    def delete(self, request, id, format=None):
        try:
            stu = Student.objects.get(pk=id)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        stu.delete()
        return Response({"message": "删除成功"}, status=204)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', StudentList.as_view()),
    path('index1/<int:id>/', StudentDetail.as_view()),
]

GenericAPIView 的使用

rest_framework.generics.GenericAPIView

继承自 APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面 Mixin 扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个 Mixin 扩展类

属性

  • serializer_class 指明视图使用的序列化器
  • queryset 指明使用的数据查询集

方法

  • get_serializer_class(self) 返回序列化器类
  • get_serializer(self, args, *kwargs) 返回序列化器对象
  • get_queryset(self) 返回视图使用的查询集
  • get_object(self) 返回视图所需的模型类数据对象
from django.contrib import admin
from django.urls import path
from login.views import StudentList, StudentDetail

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', StudentList.as_view()),
    path('index1/<int:pk>/', StudentDetail.as_view()),
]
from rest_framework.views import APIView
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView


class StudentList(GenericAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def get(self, request, format=None):
        # 查询所有数据
        stus = self.get_queryset()
        # 将数据转换为json格式, stus是一个列表,many代表要转换的数据是一个列表
        serializer = self.get_serializer(stus, many=True)
        data = serializer.data
        return Response(data)

    def post(self, request, format=None):
        obj = request.data
        serializer = self.get_serializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        else:
            return Response(serializer.errors, status=400)


class StudentDetail(GenericAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def get(self, request, pk, format=None):
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({"ERROR": "数据不存在"}, status=404)
        serializer = self.get_serializer(stu)
        return Response(serializer.data, status=200)

    def put(self, request, pk, format=None):
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(stu, data=obj)
        obj = request.data
        serializer = self.get_serializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
        else:
            return Response(serializer.errors, status=400)

    def delete(self, request, pk, format=None):
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        stu.delete()
        return Response({"message": "删除成功"}, status=204)

扩展的视图类介绍

rest_framework 提供了几种后端视图(对数据资源进行增删改查)处理流程的实现,如果需要编写的视图属于这几种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量

官网:https://www.django-rest-framework.org/tutorial/3-class-based-views/#using-mixinsopen in new window

rest_framework.mixins

  • ListModelMixin 列表视图扩展类
  • RetrieveModelMixin 详情视图扩展类
  • CreateModelMixin 创建视图扩展类
  • UpdateModelMixin 更新视图扩展类
  • DestroyModelMixin 删除视图扩展类

这五个扩展类需要搭配 GenericAPIView 父类,因为五个扩展类的实现需要调用 GenericAPIView 提供的序列化器与数据库查询的方法

from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.generics import GenericAPIView
from rest_framework import mixins


class StudentList(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def get(self, request, format=None):
        return self.list(request, format=None)

    def post(self, request, format=None):
        return self.create(request, format=None)


class StudentDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def get(self, request, pk, format=None):
        return self.retrieve(request, pk, format=None)

    def put(self, request, pk, format=None):
        return self.update(request, pk, format=None)

    def delete(self, request, pk, format=None):
        return self.destroy(request, pk, format=None)

扩展的视图子类介绍

rest_framework.generics

  • CreateAPIView 提供 post 方法

    继承自: GenericAPIView、CreateModelMixin

  • ListAPIView 提供 get 方法

    继承自:GenericAPIView、ListModelMixin

  • RetrieveAPIView 提供 get 方法

    继承自: GenericAPIView、RetrieveModelMixin

  • DestoryAPIView 提供 delete 方法

    继承自:GenericAPIView、DestoryModelMixin

  • UpdateAPIView 提供 put 和 patch 方法

    继承自:GenericAPIView、UpdateModelMixin

  • ListCreateAPIView 提供 get 和 post 方法

    继承自: GenericAPIView、ListModelMixin、CreateModelMixin

  • RetrieveUpdateAPIView 提供 get、put、patch 方法

    继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

  • RetrieveDestroyAPIView 提供 get、delete 方法

    继承自:GenericAPIView、RetrieveModelMixin、DestroyModelMixin

  • RetrieveUpdateDestroyAPIView 提供 get、put、patch、delete 方法

    继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin

from login.models import Student
from login.serializer import StudentSerializer
from rest_framework import generics


class StudentList(generics.ListCreateAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer


class StudentDetail(generics.RetrieveUpdateDestroyAPIView):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

视图集介绍

est_framework 提供了视图集类,更加简化了开发的工作量,与视图类不同的点在于 2 点:

  • 视图中定义方法不在按照请求方式定义
  • 路由匹配规则

具体分类

rest_framework. viewsets

2 个基本视图集

  • ViewSet

    继承的 APIView

  • GenericViewSet

    继承的 GenericAPIView

2 个扩展视图集

  • ModelViewSet

    继承的 GenericAPIView,CreateModelMixin、DestoryModelMixin、UpdateModelMixin、ListModelMixin、RetrieveModelMixin

  • ReadOnlyModelViewSet

    继承 GenericAPIView、ListModelMixin、RetrieveModelMixin

ViewSet 类的使用

使用视图集 ViewSet,可以将一系列逻辑相关的动作放到一个类中:

  • list() 提供一组数据
  • retrieve() 提供单个数据
  • create() 创建数据
  • update() 保存数据
  • destory() 删除数据

与 APIView 的区别

  • 视图中定义方法不在按照请求方式定义
  • 路由匹配规则
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', StudentList.as_view({'get': 'list', 'post': 'create'})),
    path('index1/<int:pk>/',StudentDetail.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from login.models import Student
from login.serializer import StudentSerializer


class StudentList(ViewSet):
    def list(self, request, format=None):
        # 查询全部数据
        stu = Student.objects.all()
        serializer = StudentSerializer(stu, many=True)
        data = serializer.data
        return Response(data)

    def create(self, request, format=None):
        # 增加数据
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        else:
            return Response(serializer.errors, status=400)


class StudentDetail(ViewSet):

    def retrieve(self, request, pk, format=None):
        # 查询单个数据
        try:
            stu = Student.objects.get(id=pk)
        except Student.DoesNotExist:
            return Response({"ERROR": "数据不存在"}, status=404)
        serializer = StudentSerializer(stu)
        return Response(serializer.data, status=200)

    def update(self, request, pk, format=None):
        # 修改数据
        try:
            stu = Student.objects.get(pk=pk)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(stu, data=obj)
        obj = request.data
        serializer = StudentSerializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
        else:
            return Response(serializer.errors, status=400)

    def destroy(self, request, pk, format=None):
        # 删除数据
        try:
            stu = Student.objects.get(pk=pk)
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        stu.delete()
        return Response({"message": "删除成功"}, status=204)

GenericViewSet 类的使用

from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from login.models import Student
from login.serializer import StudentSerializer


class StudentList(GenericViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def list(self, request, format=None):
        # 查询所有数据
        stus = self.get_queryset()
        # 将数据转换为json格式, stus是一个列表,many代表要转换的数据是一个列表
        serializer = self.get_serializer(stus, many=True)
        data = serializer.data
        return Response(data)

    def create(self, request, format=None):
        # 增加数据
        obj = request.data
        serializer = self.get_serializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        else:
            return Response(serializer.errors, status=400)


class StudentDetail(GenericViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

    def retrieve(self, request, pk, format=None):
        # 查询单个数据
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({"ERROR": "数据不存在"}, status=404)
        serializer = self.get_serializer(stu)
        return Response(serializer.data, status=200)

    def update(self, request, pk, format=None):
        # 修改数据
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        # obj = JSONParser().parse(request)
        # serializer = StudentSerializer(stu, data=obj)
        obj = request.data
        serializer = self.get_serializer(data=obj)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
        else:
            return Response(serializer.errors, status=400)

    def destroy(self, request, pk, format=None):
        # 删除数据
        try:
            stu = self.get_object()
        except Student.DoesNotExist:
            return Response({'error': '暂无数据'}, status=404)
        stu.delete()
        return Response({"message": "删除成功"}, status=204)

ModelViewSet 类的使用

ModelViewSet 继承自GenericAPIVIew,同时包括了 ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin

ReadOnlyModelViewSet 继承自GenericAPIVIew,同时包括了 ListModelMixin、RetrieveModelMixin

from django.contrib import admin
from django.urls import path
from login.views import StudentList

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', StudentList.as_view({'get': 'list', 'post': 'create'})),
    path('index/<int:pk>/',
         StudentList.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from login.models import Student
from login.serializer import StudentSerializer


class StudentList(ModelViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

SimpleRouter 的使用

REST Framework 中不仅提供了业务逻辑代码的功能 ,还提供了 Router 的生成功能,具体使用 DefaultRouter 类

官网:https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/#using-routersopen in new window

rest_framework.routers.SimpleRouter

注意

只能应用到视图集上

from django.contrib import admin
from django.urls import include, path
from login.views import StudentList
from rest_framework.routers import SimpleRouter
# 创建路由生成器对象
router = SimpleRouter()
# 注册路由  1. url的地址  2. 视图集 3. 路由的命名
router.register('index1', StudentList, basename='student')
urlpatterns = [
    path('admin/', admin.site.urls),
    pat
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from login.models import Student
from login.serializer import StudentSerializer


class StudentList(ModelViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer

action 装饰器的使用

REST Framework 的对于视图集的自动生成路由,默认是识别不了自定义的方法的。若想使用,去要通过 action 去指定

rest_framework.decorators.action

from rest_framework.decorators import action
@action(methods=['get'],detail=True)
def lastdata(self,request,pk):
  print(self.action)
  stu=Student.objects.get(id=pk)
  ser=self.get_serializer(stu)
  return Response(ser.data)

身份验证和权限

到目前为止,程序的 API 对任何人都可以编辑或删除,没有任何限制。我们希望有一些更高级的行为,进行身份验证和权限分配,以确保:

  • 数据始终与创建者相关联
  • 只有经过身份验证的用户才能创建数据
  • 只有数据的创建者可以更新或删除
  • 未经身份验证的请求只有只读访问权限

提示

往往用户验证与权限分配一起配合使用

官网:https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/open in new window

配置权限

全局配置

settings.pyopen in new window 文件,增加代码

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',  # 基本认证
        'rest_framework.authentication.SessionAuthentication',  # session认证
    )
}

局部配置

views.pyopen in new window 文件,增加代码

from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView


class ExampleView(APIView):
  permission_classes = (IsAuthenticated,)
  ...

提供的权限

  • AllowAny 允许所有用户
  • IsAuthenticated 仅通过认证的用户
  • IsAdminUser 仅管理员用户
  • IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能 get 读取

配置认证

全局配置

settings.pyopen in new window 文件,增加代码

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',  # 基本认证
        'rest_framework.authentication.SessionAuthentication',  # session认证
    )
}

局部配置

views.pyopen in new window 中,增加代码

from rest_framework.viewsets import ModelViewSet
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication, BasicAuthentication


class StudentList(ModelViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer
    # 设置用户权限
    permission_classes = [IsAuthenticated]
    # 设置认证
    authentication_classes = [SessionAuthentication, BasicAuthentication]

认证返回值

  • 401 Unauthorized 未认证
  • 403 Permission Denied 权限被禁止

注意

只针对与 REST FrameWork 的视图有效

使用 admin 应用的 User

  • 配置好 settings 中的数据库配置
  • 将 admin 应用的数据库进行迁移
python manage.py migrate
  • 使用 createsuperuser 创建用户
python manage.py createsuperuser

限流

限流最主要功能就是限制访问。就是一个用户多次发送一个请求(页面或链接)的时候,单位时间内有允许访问次数限制,超过限制就会出现访问受限,提示譬如:离下一次访问还有多久之类等的字样

REST FrameWork 里面设置限流分为 3 种:

rest_framework.throttling

  • AnonRateThrottle 限制所有匿名未认证用户,使用 IP 区分用户
  • UserRateThrottle 限制认证用户,使用 User id 来区分
  • ScopedRateThrottle 限制用户对于每个视图的访问频次,使用 ip 或 user id

配置用户限流

settings.pyopen in new window 中,增加代码

REST_FRAMEWORK = {
    # 'DEFAULT_PERMISSION_CLASSES': (
    #     'rest_framework.permissions.IsAuthenticated',
    # ),
    # 'DEFAULT_AUTHENTICATION_CLASSES': (
    #     'rest_framework.authentication.BasicAuthentication',  # 基本认证
    #     'rest_framework.authentication.SessionAuthentication',  # session认证
    # ),
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',  # 匿名
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/day',
        'user': '10/day'
    }
}

说明:

  • DEFAULT_THROTTLE_CLASSES:配置默认的节流类,列表,可以配置多个
  • DEFAULT_THROTTLE_RATES: 节流频率的规则,字典,key 是第一步中节流类的 scope 属性,值是 '5/day':
    • 5 是指允许的访问次数
    • day 是指单位时间,总共 4 种,后面的数字表示秒数,官方文档是用:second,minute,hour 或 day,其实从代码可以看出,只有第一个字符起作用

配置视图限流

# views.py
class ContactListView(APIView):
  throttle_scope = 'contacts'

# settings.py
REST_FRAMEWORK = {
  'DEFAULT_THROTTLE_CLASSES': (
    'rest_framework.throttling.ScopedRateThrottle',
   ),
  'DEFAULT_THROTTLE_RATES': {
    'contacts': '10/day'
   }
}

过滤 Filter

在日常操作中,我们需要获取指定条件的数据,可以根据字段进行过滤。

django-filter 库包含一个 DjangoFilterBackend 类,该类支持 REST 框架的高度可定制的字段过滤。

官网:https://django-filter.readthedocs.io/en/main/index.htmlopen in new window

  1. 安装 django-filter

    pip install django-filter==22.1
    
  2. 把 django_filters 添加到 INSTALLED_APPS

    INSTALLED_APPS = [
      'rest_framework',
      'django_filters',
      '...'
    ]
    
  3. setting.pyopen in new window 中指定过滤后端

    REST_FRAMEWORK = {
      #指定过滤后端
      "DEFAULT_FILTER_BACKENDS":['django_filters.rest_framework.DjangoFilterBackend'],
    }
    
  4. 在视图类中添加 filter_fields 属性,指定可以过滤的字段

    往往使用在直接或者间接继承了 ListAPIView 的视图

    from rest_framework.viewsets import ModelViewSet
    from login.models import Student
    from login.serializer import StudentSerializer
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.authentication import SessionAuthentication, BasicAuthentication
    
    
    class StudentList(ModelViewSet):
        # 指定查询集
        queryset = Student.objects.all()
        # 指定序列化器,不用创建对象
        serializer_class = StudentSerializer
        # 添加过滤字段
        filterset_fields = ('name', 'sex', "age")
    

http://127.0.0.1:8000/rest_app/students/?sex=1&name=roseopen in new window

排序

在 REST framework 提供了 OrderingFilter 过滤器来帮助实现按指定字段进行排序。

rest_framework.filter.OrderingFilter

官网地址:https://www.django-rest-framework.org/api-guide/filtering/#orderingfilteropen in new window

REST framework 会在请求的查询字符串参数中检查是否包含了 ordering 参数,如果包含了 ordering 参数,则按照 ordering 参数指明的排序字段对数据进行排序

  • 在视图类中通过 order_fieldes 指定要过滤的字段
  • 在视图类中通过 filter_backends 指定过滤器类
from rest_framework.viewsets import ModelViewSet
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.filters import OrderingFilter


class StudentList(ModelViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer
    # 设置排序的功能类
    filter_backends = (OrderingFilter,)
    # 设置可以排序的字段
    ordering_fields = ('sex', 'age')
http://127.0.0.1:8000/rest_app/students/?ordering=-age
http://127.0.0.1:8000/rest_app/students/?ordering=id,-age

分页-PageNumberPagination 类

REST framework 提供了分页的支持

官网:https://www.django-rest-framework.org/api-guide/pagination/open in new window

全局设置

# settings.py
REST_FRAMEWORK = {
  'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
  'PAGE_SIZE': 100 # 每页数目
}

提示

不推荐使用全局,因为全局配置会让项目的所有视图(复合使用规范)采用分页。

但往往并不是所有视图都需要分页!!

单独设置

视图类自定义 Pagination 类

from rest_framework.viewsets import ModelViewSet
from login.models import Student
from login.serializer import StudentSerializer
from rest_framework.filters import OrderingFilter
from rest_framework.pagination import PageNumberPagination


class LargeResultsSetPagiation(PageNumberPagination):
    page_size = 2  # 默认每页显示多少条数据
    max_page_size = 5  # 前端在控制每页显示多少条时,最多不能超过5
    page_query_param = 'page'  # 前端在查询字符串的关键字 指定显示第几页的名字,不指定默认时page
    page_size_query_param = 'page_size'  # 前端在查询字符串的关键字名字,是用来控制每页显示多少条的关键字


class StudentList(ModelViewSet):
    # 指定查询集
    queryset = Student.objects.all()
    # 指定序列化器,不用创建对象
    serializer_class = StudentSerializer
    # 指定分页类
    pagination_class = LargeResultsSetPagiation

视图类 view.pyopen in new window,指定分页类

分页-LimitOffsetPagination 类

在分页还可以使用一个类:LimitOffsetPagination

前端访问网址形式:

1
GET http://api.example.org/products/?limit=100&offset=400

可以在子类中定义的属性:

  • default_limit 默认限制,默认值与 PAGE_SIZE 设置一样
  • limit_query_param limit 参数名,默认'limit'
  • offset_query_param offset 参数名,默认'offset'
  • max_limit 最大 limit 限制,默认 None
class LimitOffset(LimitOffsetPagination):
  default_limit=2 #不指定看几条,默认就看2条
  max_limit = 5 #限制前端每次最多能看几条
  limit_query_param = 'limit'
  offset_query_param = 'offset'


class StudentView(generics.ListCreateAPIView):
  queryset = Student.objects.all()
  serializer_class = StudentSerializer
  #分页
  pagination_class = LimitOffset

测试

http://127.0.0.1:8000/rest_app/students/
http://127.0.0.1:8000/rest_app/student/?limit=3&offset=3

文件上传和下载

pip install pillow==9.3.0
class UploadFile(models.Model):
    file = models.FileField(upload_to='files/')
    img = models.ImageField(upload_to='imgs/')
    desc = models.CharField(max_length=100)

配置多媒体路径

# 设置获取的文件的路径
MEDIA_URL = '/media/'
# 设置文件要存储的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

注册模型类

# admin.py
from django.contrib import admin
from filesup.models import UploadFile
admin.site.register(UploadFile)
  • 创建 admin 用户

    命令行执行

    python manage.py createsuperuser
    
  • 测试

    http://127.0.0.1:8000/admin
    
from django.http import JsonResponse

from filesup.models import UploadFile
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def index(request):
    if request.method == 'POST':
        file = request.FILES.get('file')
        img = request.FILES.get('img')
        desc = request.POST.get('desc')
        print(file, img, desc)
        # 实例化一个Upload对象
        upload = UploadFile()
        # 给Upload对象的file属性赋值
        upload.file = file
        upload.img = img
        upload.desc = desc
        # 保存
        upload.save()
        return JsonResponse({"message": "yes"})

自定义上传文件重命名

import os
from uuid import uuid4
from django.http import JsonResponse

from filesup.models import UploadFile
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def index(request):
    if request.method == 'POST':
        file = request.FILES.get('file')
        img = request.FILES.get('img')
        desc = request.POST.get('desc')
        # 重命名
        if file:
            file.name = generate(file.name)
        if img:
            img.name = generate(img.name)
        # 实例化一个Upload对象
        upload = UploadFile()
        # 给Upload对象的file属性赋值
        upload.file = file
        upload.img = img
        upload.desc = desc
        # 保存
        upload.save()
        return JsonResponse({"message": "yes"})

# 随机生成一个文件名


def generate(filename):
    ext = os.path.splitext(filename)[-1]
    name = uuid4().hex
    return f'{name}.{ext}'

Celery 的介

上次编辑于:
贡献者: 刘春龙
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.7