DRF3

为了方便接下来的学习,我们创建一个新的子应用 component

python manage.py startapp component

component/urls.py,子路由代码:

from django.urls import path
from . import views

urlpatterns = [
    
]

注册子应用,settings.py,代码:

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',

    'stuapi',    # 不使用drf编写api接口
    'students',  # 使用了drf编写api接口
    'sers',      # 演示序列化的使用
    'req',       # drf提供的http请求与响应
    'demo',      # drf提供的提供的视图类
    'component', # drf提供的组件功能
]

总路由,代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include("stuapi.urls")),
    path('api/', include("students.urls")),
    path('sers/', include("sers.urls")),
    path("req/", include("req.urls")),
    path("demo/", include("demo.urls")),
    path("component/", include("component.urls")),
]

因为接下来的认证组件中需要使用到登陆功能,所以我们使用django内置admin站点并创建一个管理员.

admin运营站点的访问地址:http://127.0.0.1:8000/admin

python manage.py createsuperuser
# 如果之前有账号,但是忘了,可以通过终端下的命令修改指定用户的密码,这里的密码必须8位长度以上的
python manage.py changepassword 用户名

1557276390641

创建管理员以后,访问admin站点,先修改站点的语言配置

settings.py

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

1553043081445

访问admin 站点效果:

1553043054133

1. 认证Authentication

可以在配置文件中配置全局默认的认证方式/认证方案。

开发中常见的认证方式:cookie、sessiontoken

rest_framework/settings.py 默认配置文件

REST_FRAMEWORK = {
    # 配置认证方式的选项
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    )
}

可以在具体的视图类中通过设置类属性authentication_classess来设置单独的不同的认证方式

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication, BasicAuthentication

# Create your views here.
class AuthenticationAPIView(APIView):
    # 局部的认证方式,支持多个认证方法
    authentication_classes = [SessionAuthentication]
    def get(self,request):
        print(request.user)
        # AnonymousUser 就是在request.user无法识别当前访问的客户端时的游客用户对象
        return Response("ok")

认证失败会有两种可能的返回值,这个需要我们配合权限组件来使用:

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

自定义认证,component/authentication.py,代码:

from rest_framework.authentication import BaseAuthentication
from django.contrib.auth import get_user_model  # 自动识别当前django系统中的系统用户模型


class CustomAuthentication(BaseAuthentication):
    """
    自定义认证方式
    """
    def authenticate(self, request):
        """核心认证方法"""
        user = request.query_params.get("user")
        pwd = request.query_params.get("pwd")
        if user != "root" or pwd != "houmen":
            return None

        # get_user_model获取当前系统中用户表对应的用户模型类
        user = get_user_model().objects.filter(is_superuser=1, is_active=1).first()
        return (user, None)  # 按照固定的返回格式填写 (用户模型对象, None)

视图调用自定义认证,视图代码:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from .authentication import CustomAuthentication

# Create your views here.
class AuthenticationAPIView(APIView):
    # 局部的认证方式,支持多个认证方法
    authentication_classes = [CustomAuthentication]
    def get(self,request):
        print(request.user)
        # AnonymousUser 就是在request.user无法识别当前访问的客户端时的游客用户对象
        return Response("ok")

当然也可以注释掉上面视图中的配置,改成全局配置。settings.py,代码:

"""drf配置信息必须全部写在REST_FRAMEWORK配置项中"""
REST_FRAMEWORK = {
    # 配置认证方式的选项【drf的认证是内部循环遍历每一个注册的认证类,一旦认证通过识别到用户身份,则不会继续循环】
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'drfdemo.authentication.CustomAuthentication',            # 自定义认证
        'rest_framework.authentication.SessionAuthentication',  # session认证
        'rest_framework.authentication.BasicAuthentication',      # 基本认证
    )
}

2. 权限Permissions

权限控制可以限制用户对于视图的访问和对于具有模型对象的访问。

  • 在APIView视图的dispatch()方法中调用initial方法中先进行视图访问权限的判断

    self.check_permissions(request)

  • 在GenericAPIView通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断

    self.check_object_permissions(self.request, obj)

使用

可以在配置文件restframework/settings.py中默认的全局设置了权限管理类,源码:

REST_FRAMEWORK = {
    ....
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # AllowAny 表示允许任何用户访问站点视图
    ],
}

如果要在项目覆盖默认配置rest_framework/settings.py的设置,则可以在项目配置文件中,settings.py,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # 配置权限的选项[全局配置]
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

也可以在具体的视图类中通过类属性permission_classes来进行局部设置,如

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, AllowAny
from demo.serializers import StudentModelSerializer, Student
class PermissionAPIView(ModelViewSet):
    authentication_classes = [CustomAuthentication]
    permission_classes = [AllowAny]  # 针对封闭内部系统,登录页面时谁都可以访问。
    # permission_classes = [IsAuthenticated]
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

提供的权限

  • AllowAny 允许所有用户进行操作访问,默认权限
  • IsAuthenticated 仅通过登录认证的用户进行操作访问
  • IsAdminUser 仅允许管理员用户进行操作访问
  • IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的游客只能查看数据。

打开浏览器的无痕模式:Ctrl+Shift+N

举例

视图代码:

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from demo.serializers import StudentModelSerializer, Student
class PermissionAPIView(ModelViewSet):
    # authentication_classes = [CustomAuthentication]
    # permission_classes = [AllowAny]  # 针对封闭内部系统,登录页面时谁都可以访问。
    permission_classes = [IsAdminUser]  # 设置当前视图,只有是管理员可以访问操作。
    # permission_classes = [IsAuthenticatedOrReadOnly]  # 登录用户可以操作,而游客只能查看数据
    # permission_classes = [IsAuthenticated]
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

路由代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    path("auth", views.AuthenticationAPIView.as_view()),
    path("pess/", views.PermissionAPIView.as_view({"get": "list", "post": "create"})),
    re_path("^pess/(?P<pk>\d+)/$", views.PermissionAPIView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

自定义权限

如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部

  • has_permission(self, request, view)

    是否可以访问视图, view表示当前视图对象,request可以通过user属性获取当前用户

  • has_object_permission(self, request, view, obj)

    是否可以访问模型对象, view表示当前视图, obj为模型数据对象,request可以通过user属性获取当前用户

例如:在当前子应用下,创建一个权限文件permissions.py中声明自定义权限类:

from rest_framework.permissions import BasePermission


class VVIPPermission(BasePermission):
    """
    VVIP权限
    自定义权限,可用于全局配置,也可以用于局部配置
    """

    def has_permission(self, request, view):
        """
        视图权限
        返回结果未True则表示允许访问视图类
        request: 本次客户端提交的请求对象
        view: 本次客户端访问的视图类
        """
        # # 写在自己要实现认证的代码过程。
        identity = request.query_params.get("identity")
        # # 返回值为True,则表示通行
        return identity == "vvip"

    def has_object_permission(self, request, view, obj):
        """
        模型权限,写了视图权限(has_permission)方法,一般就不需要写这个了。
        返回结果未True则表示允许操作模型对象
        """
        from stuapi.models import Student
        if isinstance(obj, Student):
            # 限制只有小明才能操作Student模型
            identity = request.query_params.get("identity")
            return identity == "vvip"  # 如果身份不是vvip,返回值为False,不能操作
        else:
            # 操作其他模型,直接放行
            return True

局部配置自定义权限,视图代码:

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from demo.serializers import StudentModelSerializer, Student
from .permissions import VVIPPermission


class PermissionAPIView(ModelViewSet):
    # authentication_classes = [CustomAuthentication]
    # permission_classes = [AllowAny]  # 针对封闭内部系统,登录页面时谁都可以访问。
    # permission_classes = [IsAdminUser]  # 设置当前视图,只有是管理员可以访问操作。
    # permission_classes = [IsAuthenticatedOrReadOnly]  # 登录用户可以操作,而游客只能查看数据
    # permission_classes = [IsAuthenticated]
    permission_classes = [VVIPPermission]
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

settings.py,全局配置,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # 配置权限的选项[全局配置]
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.IsAuthenticated',  # 如果将来的站点时封闭内部系统,则设置IsAuthenticated
        # 'rest_framework.permissions.AllowAny',         # 如果将来的站点时开放外部系统,则设置AllowAny
        'component.permissions.VVIPPermission'
    ]
}

访问地址:http://127.0.0.1:8000/component/pess/?identity=vvip

访问地址:http://127.0.0.1:8000/component/pess/

注意

认证主要的作用就是识别客户端的访问者的身份,但是不能拦截客户端的访问。

权限是基于认证来实现的,但是权限可以针对不同身份的用户,进行拦截用户对视图、模型的访问操作。

3. 限流 Throttling

可以对接口访问的频次进行限制,以减轻数据库的查询压力,或者实现特定的业务。

一般用于付费购买次数,投票等场景使用。

基本使用

可以在配置文件中,使用DEFAULT_THROTTLE_CLASSESDEFAULT_THROTTLE_RATES进行全局配置,

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # # 配置权限的选项[全局配置]
    # 'DEFAULT_PERMISSION_CLASSES': [
    #     # 'rest_framework.permissions.IsAuthenticated',  # 如果将来的站点时封闭内部系统,则设置IsAuthenticated
    #     # 'rest_framework.permissions.AllowAny',         # 如果将来的站点时开放外部系统,则设置AllowAny
    #     'component.permissions.VVIPPermission'
    # ],
    # 配置限流[全局配置]
    'DEFAULT_THROTTLE_CLASSES':[ # 限流配置类
        'rest_framework.throttling.AnonRateThrottle',  # 未登录认证的用户
        'rest_framework.throttling.UserRateThrottle',  # 已登录认证的用户
    ],
    'DEFAULT_THROTTLE_RATES': {  # 访问频率的全局配置
        'anon': '2/day',  # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
        'user': '5/day',  # 针对会员的访问频率进行限制,
    }
}

DEFAULT_THROTTLE_RATES限流配置可以支持使用 s(秒), m(分), h(时) 或d(天)来指明限流周期,对应的设置是在rest_framework/throttling.py的SimpleRateThrottle类中的parse_rate方法中设置的。源代码如下:

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]   # 核心代码
        return (num_requests, duration)

限流配置也可以在具体视图类中通过类属性throttle_classess来局部配置,views.py,代码:

from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class ThorttlingAPIView(APIView):
    throttle_classes = [UserRateThrottle, AnonRateThrottle]
    def get(self,request):
        return Response("ok")

可选限流类

限流类
AnonRateThrottle 限制所有匿名未认证用户,使用IP区分用户。 使用配置项DEFAULT_THROTTLE_RATES['anon'] 来设置频次
UserRateThrottle 限制认证用户,使用User模型的 id主键 来区分。 使用配置项DEFAULT_THROTTLE_RATES['user'] 来设置频次
ScopedRateThrottle 限制用户对于每个视图类的访问频次,使用ip或user id。 使用视图类中的throttle_scope设置限流频次的变量名,假设是a,则可以使用配置项DEFAULT_THROTTLE_RATES['a']来设置频次

settings.py,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # # 配置权限的选项[全局配置]
    # 'DEFAULT_PERMISSION_CLASSES': [
    #     # 'rest_framework.permissions.IsAuthenticated',  # 如果将来的站点时封闭内部系统,则设置IsAuthenticated
    #     # 'rest_framework.permissions.AllowAny',         # 如果将来的站点时开放外部系统,则设置AllowAny
    #     'component.permissions.VVIPPermission'
    # ],
    # 配置限流[全局配置]
    'DEFAULT_THROTTLE_CLASSES':[ # 限流配置类
        # 'rest_framework.throttling.AnonRateThrottle',    # 未登录认证的用户
        # 'rest_framework.throttling.UserRateThrottle',    # 已登录认证的用户
        'rest_framework.throttling.ScopedRateThrottle',  # 已视图作为识别单位进行限流
    ],
    'DEFAULT_THROTTLE_RATES': {  # 频率配置
        'anon': '3/h',   # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
        'user': '10/h',  # 针对会员的访问频率进行限制,
        'thorttle': '5/h',  # 已视图中的throttle_scope进行设置限流频率
    }
}

视图代码:

from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class ThorttlingAPIView(APIView):
    # throttle_classes = [UserRateThrottle, AnonRateThrottle]
    throttle_scope = "thorttle"

    def get(self,request):
        return Response("ok")

class Thorttling2APIView(APIView):
    # throttle_classes = [UserRateThrottle, AnonRateThrottle]
    throttle_scope = "thorttle"

    def get(self,request):
        return Response("ok")

urls.py,代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    path("auth/", views.AuthenticationAPIView.as_view()),
    path("pess/", views.PermissionAPIView.as_view({"get": "list", "post": "create"})),
    re_path("^pess/(?P<pk>\d+)/$", views.PermissionAPIView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    path("throttle/", views.ThorttlingAPIView.as_view()),
    path("throttle2/", views.Thorttling2APIView.as_view()),
]

4. 过滤Filtering

Github:https://github.com/carltongibson/django-filter

使用文档:https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html

对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展模块来增强支持。

conda install django-filter

settings.py,代码:

INSTALLED_APPS = [
    # ....
    'django_filters',
]

在配置文件中增加过滤器类的全局设置:

"""drf配置信息必须全部写在REST_FRAMEWORK配置项中"""
REST_FRAMEWORK = {
    # ....代码省略。。。
    
    # 过滤查询,全局配置
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend'
    ],
}

在具体的视图类中添加类属性filterset_fields,指定可以过滤的字段

from rest_framework.generics import ListAPIView

class FilterAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 设置当前列表视图的过滤字段
    filterset_fields = ["sex", "classmate", "age"]
    
# 单个字段过滤
# http://127.0.0.1:8000/component/list/?classmate=303
# http://127.0.0.1:8000/component/list/?sex=1
# 多个字段过滤
# http://127.0.0.1:8000/component/list/?age=20&classmate=303

路由代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    path("auth/", views.AuthenticationAPIView.as_view()),
    path("pess/", views.PermissionAPIView.as_view({"get": "list", "post": "create"})),
    re_path("^pess/(?P<pk>\d+)/$", views.PermissionAPIView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    path("throttle/", views.ThorttlingAPIView.as_view()),
    path("throttle2/", views.Thorttling2APIView.as_view()),
    path("list/", views.FilterAPIView.as_view()),
]

局部设置,就是直接在视图类中设置类属性filter_backends调用的过滤器类

from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend

class FilterAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 设置过滤器类,局部设置
    filter_backends = [DjangoFilterBackend]
    # 设置当前列表视图的过滤字段
    filterset_fields = ["sex", "classmate", "age"]

5. 排序Ordering

对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。

全局配置,settings.py,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
   # 中间代码省略。。。。。
   # 中间代码省略。。。。。

    # # 查询过滤[全局配置]
    'DEFAULT_FILTER_BACKENDS': [
        # 'django_filters.rest_framework.DjangoFilterBackend',  # 过滤
        'rest_framework.filters.OrderingFilter',  # 排序
    ],
}


视图代码:

class OrderAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 设置当前列表视图的排序字段
    ordering_fields = ['id', 'age']

# http://127.0.0.1:8000/component/order/?ordering=-id
# -id 表示针对id字段进行倒序排序
# id  表示针对id字段进行升序排序

urls.py,代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    path("auth/", views.AuthenticationAPIView.as_view()),
    path("pess/", views.PermissionAPIView.as_view({"get": "list", "post": "create"})),
    re_path("^pess/(?P<pk>\d+)/$", views.PermissionAPIView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    path("throttle/", views.ThorttlingAPIView.as_view()),
    path("throttle2/", views.Thorttling2APIView.as_view()),
    path("list/", views.FilterAPIView.as_view()),
    path("order/", views.OrderAPIView.as_view()),
]

局部设置,在视图类中使用filter_backends设置当前视图类中使用的排序类,views.py,代码:

from rest_framework.filters import OrderingFilter
class OrderAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 局部设置当前列表视图的排序类[如果使用局部配置项filter_backends会自动覆盖全局配置的DEFAULT_FILTER_BACKENDS]
    filter_backends = [OrderingFilter]
    # 设置当前列表视图的排序字段
    ordering_fields = ['id', 'age']

上面提到,因为过滤和排序公用了一个配置项filter_backends,所以如果排序和过滤要一起使用的话则必须整个项目,要么一起全局过滤排序,要么一起局部过滤排序。绝不能出现,一个全局,一个局部的这种情况,局部配置项filter_backends会自动覆盖全局配置的DEFAULT_FILTER_BACKENDS。

from rest_framework.viewsets import ModelViewSet
from students.models import Student
from students.serializers import StudentModelSerializer
# Create your views here.
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
class StuListAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 局部设置过滤器类与排序类
    filter_backends = [DjangoFilterBackend, OrderingFilter]
    # 设置当前列表视图的过滤字段
    filterset_fields = ["id", "classmate", "sex"]
    # 设置当前列表视图的排序字段
    ordering_fields = ['id', 'age']

# http://127.0.0.1:8000/component/stu/?ordering=-id&classmate=301

路由,代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    # 中间代码省略。。。。
    # 中间代码省略。。。。
    path("stu/", views.StuListAPIView.as_view()),
]

6. 分页Pagination

因为django默认提供的分页器主要使用于前后端不分离的业务场景,所以REST framework也提供了针对接口数据的分页支持。

我们可以在配置文件settings.py中进行全局分页配置,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 中间代码省略......
    # 中间代码省略......
    # 列表分页[全局配置,对整站所有的列表页视图都会进行分页处理]
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 以page参数作为分页参数
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',              # 以limit和offset作为分页参数
    'PAGE_SIZE': 2,  # 每页数目,如果不设置,则没有进行分配
}

上面直接访问任意一个列表页视图的api接口,可以看到如下效果:

{
    "count": 16,   // 本次分页的总数据量
    "next": "http://127.0.0.1:8000/component/page/?page=3",  // 下一页数据所在的地址
    "previous": "http://127.0.0.1:8000/component/page/",         // 上一页数据所在的地址
    "results": [    // 当前页数据的列表项
        {
            "id": 6,
            "name": "xiaoming",
            "sex": true,
            "age": 20,
            "classmate": "303",
            "description": "hello world"
        },
       // .....
    ]
}

如果在settings.py配置文件中设置了全局分页,那么在drf中凡是调用了ListModelMixin的list()都会自动分页。如果项目中出现大量需要分页的数据,只有少数部分的不需要分页,则可以在少部分的视图类中关闭分页功能。可以在视图类中设置如下:

class PageAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    pagination_class = None  # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页

也可通过自定义Pagination类来为视图添加不同分页行为。在视图类中通过pagination_classs属性来指明。

可选分页器

PageNumberPagination

前端访问网址形式:

GET  http://127.0.0.1:8000/component/page/?page=2

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

参数名 描述
page_size 每页数据量的数量,默认是没有设置的,我们可以在配置分页时通过PAGE_SIZE设置设置
page_query_param url地址栏上当前页码的参数名,默认为"page"
page_size_query_param url地址栏上代表每一页数据量的参数名,默认是None,也就是不允许地址栏修改每一页数据量。
max_page_size 限制url地址栏上设置每一页数据量的最大值,前提是已经设置了page_size_query_param

例如在当前子应用下的paginations.py创建一个自定义分页器类,代码:

from rest_framework.pagination import PageNumberPagination
# PageNumberPagination,以页码作为分页条件
# page=1&page_size=10      第1页
# page=2&page_size=10      第2页
# ...
class StudentPageNumberPagination(PageNumberPagination):
    page_size = 5  # 每一页默认实现的数据量
    page_query_param = 'page'  # 页码
    page_size_query_param = 'page_size'  # 每一页数据量的设置变量
    max_page_size = 10  # 限制 page_size_query_param 的最大值

视图中使用自定义分页类,视图代码:

from .paginations import StudentPageNumberPagination

class PageAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # pagination_class = None  # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页
    pagination_class = StudentPageNumberPagination
from  rest_framework.pagination import PageNumberPagination,LimitOffsetPagination

# LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件
# limit=10&offset=0   第1页
# limit=10&offset=10  第2页
# ...

LimitOffsetPagination

前端访问网址形式:

GET http://127.0.0.1:8000/component/page/?limit=5&offset=10

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

参数 描述
default_limit 默认每一页展示数据的数量,默认值与PAGE_SIZE一样的
limit_query_param 默认'limit',default_limit的地址栏参数名
offset_query_param 查询数据的开始偏移量,相当于上面的page参数,默认'offset'
max_limit 限制地址栏对每一页数据展示的最大数量。默认None

子定义分页类,paginations.py,代码:

from  rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
# 中间省略。。。
# LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件
# limit=10&offset=0   第1页
# limit=10&offset=10  第2页

# 中间省略。。。

class Student1LimitOffsetPagination(LimitOffsetPagination):
    default_limit = 5 # 每一页的数据量
    offset_query_param = "offset" # 查询字符串中代表页码的变量名
    limit_query_param = "limit" # 查询字符串中代表每一页数据的变量名
    max_limit = 10   # 允许客户端通过查询字符串调整的最大单页数据量

视图,views.py,代码:

from .paginations import StudentPageNumberPagination, Student1LimitOffsetPagination

class PageAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # pagination_class = None  # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页
    # pagination_class = StudentPageNumberPagination
    pagination_class = Student1LimitOffsetPagination
    
  # 访问地址:http://127.0.0.1:8000/component/page/

7. 异常处理 Exceptions

REST framework本身在APIView提供了异常处理,但是仅针对drf内部现有的接口开发相关的异常进行格式处理,但是开发中我们还会使用到各种的数据或者进行各种网络请求,这些都有可能导致出现异常,这些异常在drf中是没有进行处理的,所以就会冒泡给django框架了,django框架会进行组织错误信息,作为html页面返回给客户端,所在在前后端分离项目中,可能js的ajax无法理解或者无法接收到这种数据,甚至导致js出现错误的情况。因此为了避免出现这种情况,我们可以自定义一个属于自己的异常处理函数,对于drf无法处理的异常,我们自己编写异常处理的代码逻辑。

针对于现有的drf的异常处理进行额外添加属于开发者自己的逻辑代码,一般我们编写的异常处理函数,会写一个公共的目录下或者主应用目录下。这里主应用下直接创建一个excepitions.py,代码:

from django.core.exceptions import ObjectDoesNotExist
from rest_framework.views import exception_handler, Response, status



def custom_exception_handler(exc, context):
    """
    自定义异常函数
    exc: 异常实例对象,发生异常时实例化出来的
    context: 字典,异常发生时python解释器会自动收集异常的执行上下文信息。
             所谓的执行上下文context就是python解释器在执行代码时保存在内存中的变量、函数、类、对象、模块、以及异常出现的路径,代码的行号等,等一系列的信息组成的环境信息。
    """
    # 1. 先让drf处理它能处理的异常
    response = exception_handler(exc, context)
    if response is None:
        """response的值为None,则表示当前异常无法drf处理"""
        if isinstance(exc, ZeroDivisionError):
            response = Response({"detail": "数学老师还有30秒达到战场,0不能作为除数!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        if isinstance(exc, ObjectDoesNotExist):
            response = Response({"detail": "当前模型对象不存在!"}, status=status.HTTP_404_NOT_FOUND)

    return response

在配置文件中声明自定义的异常处理,settings.py,代码:

REST_FRAMEWORK = {
    # 异常配置
    'EXCEPTION_HANDLER': 'drfdemo.exceptions.custom_exception_handler',
}

如果未声明自定义异常的话,drf会采用默认的方式,使用自己封装的异常处理函数,rest_framework/settings.py

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',  # from rest_framework.views import exception_handler
}

视图代码:

class ErrorAPIView(APIView):
    def get(self,request):
        # 1/0
        Student.objects.get(pk=1000)
        return Response("ok")

urls.py,代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
    path("auth/", views.AuthenticationAPIView.as_view()),
    path("pess/", views.PermissionAPIView.as_view({"get": "list", "post": "create"})),
    re_path("^pess/(?P<pk>\d+)/$", views.PermissionAPIView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    path("throttle/", views.ThorttlingAPIView.as_view()),
    path("throttle2/", views.Thorttling2APIView.as_view()),
    path("list/", views.FilterAPIView.as_view()),
    path("order/", views.OrderAPIView.as_view()),
    path("stu/", views.StuListAPIView.as_view()),
    path("page/", views.PageAPIView.as_view()),
    path("err/", views.ErrorAPIView.as_view()),
]

REST framework内置的异常类

异常类 描述
APIException drf的所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限拒绝
NotFound 404 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
UnsupportedMediaType 不支持的媒体格式
Throttled 超过限流次数
ValidationError 校验失败

也就是说很多的没有在上面列出来的异常,就需要我们在自定义异常函数中自己处理了。

8. 自动生成接口文档

REST framework可以自动帮助我们生成接口文档。drf的接口文档多数以网页的方式呈现,自动接口文档能生成的是继承自APIView及其子类的视图。

coreapi

安装依赖

REST framewrok生成接口文档需要coreapi库的支持。

conda install coreapi

设置接口文档访问路径

在settings.py中配置接口文档的模块到项目中。

INSTALLED_APPS = [
 
    'coreapi',


]
REST_FRAMEWORK = {
    # 。。。 其他选项
    # 配置自动生成接口文档的模式
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}

在总路由中添加接口文档路径。

文档路由对应的视图配置为rest_framework.documentation.include_docs_urls

参数title为接口文档网站的标题。总路由urls.py,代码:

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title='站点页面标题'))
]

8.3. 文档描述说明的定义位置

1) 单一方法的视图,可直接使用类视图的文档字符串,如

class StudentListView(ListAPIView):
    """
    返回所有学生信息
    """

2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如

from rest_framework.generics import ListCreateAPIView
class DocumentAPIView(ListCreateAPIView):
    """
    get: 获取所有学生信息
    post: 添加学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class Document1APIView(RetrieveUpdateDestroyAPIView):
    """
    get: 查询一个学生信息
    put: 更新一个学生信息
    patch: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

3)对于视图集ViewSet,在类视图的文档字符串中定义,但是应使用action名称区分,如

from rest_framework.decorators import action
from demo.serializers import StudentLoginModelSerializer

class DocumentAPIView(ModelViewSet):
    """
    list: 获取所有学生信息
    create: 添加学生信息
    read: 查询一个学生信息
    update: 更新一个学生信息
    partial_update: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    login: 学生登录
    """
    queryset = Student.objects.all()

    def get_serializer_class(self):
        if self.action == "login":
            return StudentLoginModelSerializer
        return StudentModelSerializer
    
    @action(methods=["POST"], detail=False)
    def login(self, request):
        return Response("ok")

serializers.py,代码:

from rest_framework import serializers
from stuapi.models import Student
from .models import Course

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"


class StudentLoginModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ["name", "age"]

class CourseModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = "__all__"

stuapi/modes.py,代码:

from django.db import models


# Create your models here.
class Student(models.Model):
    """学生信息"""
    # help_text 提供给接口文档中显示当前字段的意义的
    name = models.CharField(max_length=255, verbose_name="姓名", help_text="姓名")
    sex = models.BooleanField(default=True, verbose_name="性别" ,help_text="性别")
    age = models.IntegerField(verbose_name="年龄", help_text="年龄")
    classmate = models.CharField(db_column="class", max_length=5, verbose_name="班级", help_text="班级编号为3个数字组成")
    description = models.TextField(max_length=1000, null=True, blank=True, verbose_name="个性签名", help_text="个性签名")

    class Meta:
        db_table = "tb_student"
        verbose_name = "学生"
        verbose_name_plural = verbose_name

urls.py,代码:

from django.urls import path, re_path
from . import views

urlpatterns = [
   # ..中间代码省略
]

from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("doc", views.DocumentAPIView, basename="doc")
urlpatterns += router.urls

访问接口文档网页

浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。

接口文档网页

两点说明:

1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read

2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:

class Student(models.Model):
    ...
    age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
    ...

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
        extra_kwargs = {
            'classmate': {
                'help_text': '班级'
            }
        }

yasg

Swagger是一个规范和完整的自动化接口开发框架,用于生成、描述、调用和可视化RESTful风格的Web服务。总体目标是使客户端和文件系统源代码作为服务器以同样的速度来更新。当接口有变动时,对应的接口文档也会自动更新。如:接口测试站点(http://httpbin.org/),也是利用Swagger来生成接口文档。

Swagger在线编辑器:https://editor.swagger.io/

官方文档:https://drf-yasg.readthedocs.io/en/stable/

Github:https://github.com/axnsan12/drf-yasg

安装

conda install drf-yasg

配置,settings.py,代码:

INSTALLED_APPS = [

    'drf_yasg',  # 接口文档drf_yasg

]

总路由,

"""drfdemo URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls


# yasg的视图配置类,用于生成a'pi
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

# 接口文档的视图配置
schema_view = get_schema_view(
    openapi.Info(
        title="drf接口文档",  # 站点标题,必填
        default_version='v1.0,0',  # api版本,必填
        description="描述信息",  # 站点描述
        terms_of_service='htttp://www.moluo.net/',   # 团队博客网址
        contact=openapi.Contact(name="墨落", url="htttp://www.moluo.net/", email="649641514@qq.com"), # 联系邮箱地址
        license=openapi.License(name="开源协议名称", url="开源协议网地") # 协议
    ),
    public=True, # 是否外部站点
    # permission_classes=(rest_framework.permissions.AllowAny)  # 权限类
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('doc/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger'),
    path('docs/', include_docs_urls(title='公司项目名')),
    path('api/', include("stuapi.urls")),
    path('api/', include("students.urls")),
    path('sers/', include("sers.urls")),
    path("req/", include("req.urls")),
    path("demo/", include("demo.urls")),
    path("component/", include("component.urls")),
]

http://127.0.0.1:8000/doc/,访问效果:

image-20210901182801990

image-20210901182739563

9.Admin

django内置了一个强大的组件叫Admin,提供给网站管理员快速开发运营后台的管理站点。

提醒:虽然django内置的运营站点功能齐全,但是在实际工作中如果要实现高定制性后台运营站点,很多公司都是自己另行自己从0开始搭建的或使用第三方组件对Admin进行增强美化。

站点文档: https://docs.djangoproject.com/zh-hans/4.0/ref/contrib/admin/

注意:要使用Admin,必须先创建超级管理员。
python manage.py createsuperuser

访问地址:http://127.0.0.1:8000/admin,访问效果如下:

1585638280577

admin站点默认并没有提供其他的操作给我们,所以一切功能都需要我们进行配置,在项目中,我们每次创建子应用的时候都会存在一个admin.py文件,这个文件就是用于配置admin站点功能的文件。这些admin.py最终都会被项目运营的时候被django所加载并识别。

一般而言,我们在开发中经常以下几个术语:
1. 前台站点
   一个独立站点,这个站点和后台站点共用了一个数据库,甚至在一个目录下,大家一起运行。
   前台站点一般是允许任何人进行注册,登录并访问。我们在网络中经常访问的网站基本都是各种网站的前台站台。
   例如:京东、淘宝、

2. 后台站点
   也是一个独立站点,这个站点和前台站点共用了一个数据库,甚至在一个目录下,大家一起运行。
   后台一般是提供公司内部不懂代码的工作人员,方便这些人更好的维护或对站点已有的数据库中的内容进行增删查改用的

在绝大部分情况下,我们所说的一个项目,往往都会涵盖了前台和后台站点,除了OA,CRM,ERP类似这种内部企业项目以外。
不管是前台站点或者后台站点,都是由前端代码(html,css,js)和后端代码(python,java,c++,go,php,.net,C#)所实现的。

3. 前端开发
   指代就是客户端开发: web前端,移动端开发,游戏客户端开发
   web前端: 使用基于http获取服务端数据提供给客户端展示的这一类的开发。目前来说,绝大部分指代的就是js,....
   移动端开发:js,andorid,IOS。。
   游戏客户端:C++,js,java,。。

4. 后端开发[服务端开发]
   python,java,c++,go,php,.net,C#,rust

django的admin站点在没有经过任何配置的之前,就已经实现了目前业内来说比较完善的用户认证机制和权限分配系统了。

用户认证机制:基于django.contrib.auth子应用对外提供的。里面有内置的视图,模板,模型等等。

​ 已经实现了关于管理员的登录,退出,修改密码等等。

权限认证系统:基于django.contrib.auth子应用对外提供的。里面基于模型完成了基于RBAC的权限认证机制.

RBAC权限认证机制

实现了权限认证机制以后,我们可以让不同的用户得到不同的权限,基于用户拥有的权限不同,能操作的功能或者能看到的站点内容也会产生不一样。

RBAC(Role-Base Access Control 的缩写),译作:基于角色分配的访问控制机制。

在开发中,我们一般用于项目权限的分配机制无非3种:RBAC,OAuth授权认证、RLS(Row Level Security的缩写、译作:行级数据安全)。

在网站后台运营站点这种单个站点内部,单个站点集群场景下,一般使用的都是RBAC。

在对外开发的业务站点中,基于不同的渠道,不同的领域,不同站点之间,一般都是使用OAuth2.0授权认证。

在对外开发的站点服务,如果配置多台前后台的租赁模式,多数使用RLS权限机制。

RBAC的实现

在实现过程中,因为项目业务的复杂程度不一致。所以存在有3表RBAC或5表RBAC的实现方案。

django的admin站点实际上就是基于5表RBAC的实现方案扩展出来的6表RBAC认证机制。

3表RBAC认证

顾名思义,就是使用了3张表保存了权限相关的所有数据,这3张表分别是用户表(user),角色表(role/group/department),权限表(permission/auth)

image-20210622092906059

常见的场景:单个网站:小论坛,小商城,普通的后台站点。

5表RBAC认证

顾名思义,就是在3表的基础上新增了2张关系表保存权限相关的所有数据,这5张表分别是用户表(user),角色表(role/group),权限表(permission/auth),以及新增的2张关系表:用户与角色之间的关系表(user_group)和角色与权限的关系表(group_permission)。

image-20211025092728749

常见的业务场景:大型综合论坛,大型的商城(商家入驻),有分公司的企业内部站点,OA、ORM、ERP、权限系统。

Django的RBAC

是在传统的5表RBAC基础上,增加了一个关系表,用户与权限之间的关系表(user_permission)。

因为RBAC的本质是用户随着角色不同,而拥有不同的权限,而django的admin站点中,允许针对某一个用户,可以单独分配权限的。

image-20211025092944971

admin.py里面允许我们编写的代码一共可以分成2部分:

列表页配置

主要用于针对项目中各个子应用里面的models.py里面的模型,根据这些模型自动生成后台运营站点的管理功能。

stuapi/admin.py,代码:

from django.contrib import admin
from .models import Student
# Register your models here.


class StudentModelAdmin(admin.ModelAdmin):
    """学生的模型管理器"""
    pass

# admin.site.register(模型类, 模型管理类)
admin.site.register(Student, StudentModelAdmin)

子应用的英文名改成中文显示,stuapi/apps.py,代码:

from django.apps import AppConfig


class StuapiConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'stuapi'
    verbose_name = "学生管理"
    verbose_name_plural = verbose_name

关于列表页的配置,stuapi/admin.py,代码:

from django.contrib import admin
from .models import Student

class StudentModelAdmin(admin.ModelAdmin):
    """列表页配置"""
    # 列表列显示的数据字段[值可以是模型的字段,也可以是模型的方法名(方法不能有参数)]
    list_display = ["id", "name", "classmate", "age", "sex", "sex_text", "born"]
    # 设置点击哪些字段可以跳转到详情页
    list_display_links = ["id"]

    # 默认排序[值只能是模型字段,不能是模型方法]
    ordering = ['-age','id']
    # 动作栏是否在上下方显示
    actions_on_top = True     # 上方控制栏是否显示,默认False表示隐藏
    actions_on_bottom = True  # 下方控制栏是否显示,默认False表示隐藏

    # 设置过滤器的字段条件[值只能是模型字段,不能是模型方法]
    list_filter = ["classmate", "sex"]  # 过滤器,按指定字段的不同值来进行展示

    # 允许直接点击列表页进入编辑的字段[值只能是模型字段,不能是模型方法]
    list_editable = ["age"]

    # 分页显示列表页数据[单页数据量]
    list_per_page = 5

    # 搜索框搜索字段条件[值只能是模型字段,不能是模型方法]
    search_fields = ["name", "classmate"]  # 搜索内容

    # 日期筛选功能[值只能是模型中的日期时间字段,不能是模型方法,也不能时其他格式字段]
    date_hierarchy = "created_time"

    """自定义方法字段"""
    def sex_text(self, obj):
        return "男" if obj.sex else "女"

    # 自定义字段列的文本描述
    sex_text.short_description = "性别"
    sex_text.admin_order_field = "sex"

    @admin.display(description="出生年份", ordering="age")
    def born(self, obj):
        from datetime import datetime
        return datetime.now().year - obj.age


admin.site.register(Student, StudentModelAdmin)

stuapi/models.py,代码:

from django.db import models


# Create your models here.
class Student(models.Model):
    """学生信息"""
    name = models.CharField(max_length=255, verbose_name="姓名", help_text="姓名")
    sex = models.BooleanField(default=True, verbose_name="性别" ,help_text="性别")
    age = models.IntegerField(verbose_name="年龄", help_text="年龄")
    classmate = models.CharField(db_column="class", max_length=5, verbose_name="班级", help_text="班级编号为3个数字组成")
    description = models.TextField(max_length=1000, null=True, blank=True, verbose_name="个性签名", help_text="个性签名")
    created_time = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="注册日期")

    class Meta:
        db_table = "tb_student"
        verbose_name = "学生信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

    """自定义方法字段[如果在admin.py中设置了,就不要在这边编写了]"""
    # def sex_text(self):
    #     return "男" if self.sex else "女"
    #
    # # 自定义字段列的文本描述
    # sex_text.short_description = "性别"
    # sex_text.admin_order_field = "sex"
    #
    # def born(self):
    #     from datetime import datetime
    #     return datetime.now().year - self.age
    #
    # # 自定义字段列的文本描述
    # born.short_description = "出生年份"
    # born.admin_order_field = "age"

数据库和本地Django的时区有冲突,所以需要根据实际开发过程中的业务来考虑是否要修改时区关闭USE_TZ,settings.py,代码:

USE_TZ = False

详情页配置

from django.contrib import admin
from .models import Student

class StudentModelAdmin(admin.ModelAdmin):
    """列表页配置"""
    # 列表列显示的数据字段[值可以是模型的字段,也可以是模型的方法名(方法不能有参数)]
    list_display = ["id", "name", "classmate", "age", "sex", "sex_text", "born"]
    # 设置点击哪些字段可以跳转到详情页
    list_display_links = ["id"]

    # 默认排序[值只能是模型字段,不能是模型方法]
    ordering = ['-age','id']
    # 动作栏是否在上下方显示
    actions_on_top = True     # 上方控制栏是否显示,默认False表示隐藏
    actions_on_bottom = True  # 下方控制栏是否显示,默认False表示隐藏

    # 设置过滤器的字段条件[值只能是模型字段,不能是模型方法]
    list_filter = ["classmate", "sex"]  # 过滤器,按指定字段的不同值来进行展示

    # 允许直接点击列表页进入编辑的字段[值只能是模型字段,不能是模型方法]
    list_editable = ["age"]

    # 分页显示列表页数据[单页数据量]
    list_per_page = 5

    # 搜索框搜索字段条件[值只能是模型字段,不能是模型方法]
    search_fields = ["name", "classmate"]  # 搜索内容

    # 日期筛选功能[值只能是模型中的日期时间字段,不能是模型方法,也不能时其他格式字段]
    date_hierarchy = "created_time"

    # 自定义方法字段
    def sex_text(self, obj):
        return "男" if obj.sex else "女"

    # 自定义字段列的文本描述
    sex_text.short_description = "性别"
    sex_text.admin_order_field = "sex"

    @admin.display(description="出生年份", ordering="age")
    def born(self, obj):
        from datetime import datetime
        return datetime.now().year - obj.age


    """详情页配置"""
    # # 设置修改或添加页面的表单中显示的字段列表
    # fields = ['name', 'sex', 'age', 'classmate', "description"]

    # # 设置修改或添加页面的表单中不显示的字段列表
    # exclude = ["classmate"]

    # 字段分组展示,字段分组和上面的字段列表配置(fiedls或exclude)是互斥的。
    fieldsets = (
        ("必填项", {
            'fields': ('name', 'age', 'description')
        }),
        ('可选项', {
            'classes': ('collapse',),  # 折叠样式
            'fields': ('sex', 'classmate'),
        }),
    )

    # 添加数据的字段列配置
    add_fieldsets = (
        ("必填项", {
            'classes': ('wide',),
            'fields': ('name', 'age', 'classmate'),
        }),
        ('可选项', {
            'classes': ('collapse',),  # 折叠样式
            'fields': ('sex', 'description'),
        }),
    )


    # 识别当前操作是添加数据还是更新数据,方便加载不同的fieldsets配置
    def get_fieldsets(self, request, obj=None):
        """
        :request 本次客户端的请求处理对象
        :obj     本次客户端更新的模型对象[如果本次操作属于添加数据操作,则obj的值为None]
        """
        if obj:
            # 修改数据的操作
            return self.fieldsets
        # 添加数据的操作
        return self.add_fieldsets

    def save_model(self, request, obj, form, change):
        """
        当站点保存(添加、编辑)当前模型时自动执行的钩子方法
        区分:依靠ID来进行区分,obj是否有id
        :request 本次客户端的请求处理对象
        :obj     本次客户端操作的模型对象[如果本次操作属于添加数据操作,则obj的值为None]
        :form    本次客户端提交的表单信息
        :change  本次客户端更新数据后的表单信息
        """
        # 验证数据或者补充代码操作
        if obj.age > 100 or obj.age < 1:
            raise TypeError("年龄必须是正常人类的年龄!")

        if obj.id:
            print("更新模型操作")
        else:
            print("添加模型操作")

        return super().save_model(request, obj, form, change)  # 让当前模型继续保存数据,如果上面代码已经完成操作,这一句代码可以注释掉了。


    def delete_model(self, request, obj):
        """详情页删除操作"""
        print("详情页删除模型操作")
        return super().delete_model(request, obj)

    def delete_queryset(self, request, queryset):
        """列表页删除操作"""
        print("列表页删除模型操作")
        return super().delete_queryset(request, queryset)

admin.site.register(Student, StudentModelAdmin)

django提供的admin站点已经非常强大了。但是定制性仅限于它提供的配置,要深度定制,只能自己开发。

一般可以借助ant design(antd)、element-plus 前端的UI框架。

然后,当然如果使用admin内置运营站点,也可以在admin基础上进行站点外观美化,或者功能的扩展。

一般站点美化模块: xadmin(django2.x)、simpleui(django3.0,增强版:simplepro)

simpleUI的官方站点:https://simpleui.72wo.com/

安装:

pip install django-simpleui

settings.py,注册simpleUI,代码:

INSTALLED_APPS = [
    'simpleui', # admin界面美化,必须写在admin上面
    'django.contrib.admin', # 内置的admin运营站点
    # ...
]

注册完成以后,直接访问原来的站点即可查看效果:

image-20210622120344117

simpleUI的基本配置,settings.py,代码:

"""simpleUI的配置"""
SIMPLEUI_LOGIN_PARTICLES = False
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'
# SIMPLEUI_HOME_PAGE = 'https://www.163.com'
# SIMPLEUI_HOME_TITLE = '百度一下你就知道'

from django.contrib.admin import AdminSite

AdminSite.site_header = "北京xxx科技有限公司"
AdminSite.site_title = "北京xxx科技有限公司"

10.Xadmin

xadmin是Django2.0时代最流行的admin站点第三方扩展,是一个比Django的admin站点使用更方便的后台站点。

构建于admin站点之上。除了Xadmin以外,django2.0时代也有Simpleui等。

文档:http://sshwsfc.github.io/xadmin/

https://xadmin.readthedocs.io/en/latest/index.html

1.1. 安装

通过如下命令安装xadmin的2.0版本【注意:前提是当前使用的django是2.x版本】

pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2

在配置文件settings.py中注册如下应用

INSTALLED_APPS = [
    # ...
    'xadmin',
    'crispy_forms',
    'reversion',
    # ...
]

# 修改使用中文界面
LANGUAGE_CODE = 'zh-Hans'

# 修改时区
TIME_ZONE = 'Asia/Shanghai'

如果本地的django版本的3.0以上版本,那么xadmin在注册到项目以后,会出现非常多的异常。

解决版本带来的异常

注意:如果是django2.0版本则没有以下异常

1. six缺失

错误信息:

image-20210622121107777

pip install six
# 安装完成以后,six模块就是一个six.py文件。记住这个安装的位置,一般就是python解析器对应的site-packages包中。我们需要把six.py文件复制到报six模块缺失的位置。

image-20210622121236908

Ctrl+C->Ctrl+V 复制文件,粘贴文件

image-20210622121321342

image-20210622121451315

2. python_2_unicode_compatible 缺失

错误信息如下:

image-20210622121922932

解决:

# 使用
from six import python_2_unicode_compatible

# 替代
from django.utils.encoding import python_2_unicode_compatible

3. pretty_name缺失

报错信息:

image-20210622122257967

解决:

# 使用
from django.forms import forms

# 替代:
from django.forms.forms import pretty_name

4.templatetags缺失

报错信息:

image-20210622122404767

解决:

使用:
from django.templatetags.static import static
替换:
from django.contrib.staticfiles.templatetags.staticfiles import static

5. FieldDoesNotExist缺失

报错信息:

image-20210622122600879

解决:

使用:
from django.core.exceptions import FieldDoesNotExist
替换:
from django.db.models.fields import FieldDoesNotExist

6. SKIP_ADMIN_LOG缺失

报错信息:

image-20210622122949840

解决:

# 去掉
from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS
# 改成
from import_export.admin import DEFAULT_FORMATS, ImportMixin,ImportExportMixinBase

# 找到当前文件中的109行和115行左右位置,改动代码如下:
    def get_skip_admin_log(self):
        if self.skip_admin_log is None:
            # return SKIP_ADMIN_LOG
            return ImportMixin(ImportExportMixinBase).get_skip_admin_log()
        else:
            return self.skip_admin_log

    def get_tmp_storage_class(self):
        if self.tmp_storage_class is None:
            # return TMP_STORAGE_CLASS
            return ImportMixin(ImportExportMixinBase).get_tmp_storage_class()
        else:
            return self.tmp_storage_class

完成了上面操作以后,xadmin就不在报错了。接下来就可以正常使用Xadmin了。

初始化

xadmin有建立自己的数据库模型类,需要进行数据库迁移

python manage.py makemigrations
python manage.py migrate

在总路由中添加xadmin的路由信息

import xadmin
xadmin.autodiscover()

# version模块自动注册需要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()

urlpatterns = [
    path('xadmin/', xadmin.site.urls),
]

如果没有创建超级用户,则需要创建用户,如果之前创建了, 是可以共享一个数据库的用户的。

python manage.py createsuperuser

1.2. 使用

  • xadmin不再使用Django的admin.py进行功能配置了,而是需要编写代码在adminx.py文件中。所以我们需要在每一个子应用下单独创建adminx.py配置文件。
  • xadmin的模型管理类不用继承admin.ModelAdmin,而是普通类即可。

例如:在子应用students中创建adminx.py文件。

1.2.1 站点的全局配置

import xadmin
from xadmin import views

class BaseSetting(object):
    """xadmin的基本配置"""
    enable_themes = True  # 开启主题切换功能
    use_bootswatch = True

xadmin.site.register(views.BaseAdminView, BaseSetting)

class GlobalSettings(object):
    """xadmin的全局配置"""
    site_title = "浮光学城"  # 设置站点标题
    site_footer = "浮光学城有限公司"  # 设置站点的页脚
    menu_style = "accordion"  # 设置菜单折叠

xadmin.site.register(views.CommAdminView, GlobalSettings)

1.2.2 站点Model管理

xadmin可以使用的页面样式控制基本与Django原生的admin一致。

  • list_display 控制列表展示的字段

    list_display = ['id', 'title', 'read', 'comment']
    
  • search_fields 控制可以通过搜索框搜索的字段名称,xadmin使用的是模糊查询

    search_fields = ['id','title']
    
  • list_filter 可以进行过滤操作的列,对于分类、性别、状态

    list_filter = ['is_delete']
    
  • ordering 默认排序的字段

  • readonly_fields 在编辑页面的只读字段

  • exclude 在编辑页面隐藏的字段

  • list_editable 在列表页可以快速直接编辑的字段

  • show_detail_fields 在列表页提供快速显示详情信息

  • refresh_times 指定列表页的定时刷新

    refresh_times = [5, 10,30,60]  # 设置允许后端管理人员按多长时间(秒)刷新页面
    
  • list_export 控制列表页导出数据的可选格式

    list_export = ('xls', 'xml', 'json')   list_export设置为None来禁用数据导出功能
    list_export_fields = ('id', 'title', 'pub_date') # 允许导出的字段
    
  • show_bookmarks 控制是否显示书签功能

    show_bookmarks = True
    
  • data_charts 控制显示图表的样式

    data_charts = {
            "order_amount": {
              'title': '图书发布日期表', 
              "x-field": "bpub_date", 
              "y-field": ('btitle',),
              "order": ('id',)
            },
        #    支持生成多个不同的图表
        #    "order_amount": {
        #      'title': '图书发布日期表', 
        #      "x-field": "bpub_date", 
        #      "y-field": ('btitle',),
        #      "order": ('id',)
        #    },
        }
    
    • title 控制图标名称
    • x-field 控制x轴字段
    • y-field 控制y轴字段,可以是多个值
    • order 控制默认排序
  • model_icon 控制菜单的图标

    这里使用的图标是来自bootstrap3的图标。https://v3.bootcss.com/components/

    class BookInfoAdmin(object):
        model_icon = 'fa fa-gift'
    
    xadmin.site.register(models.BookInfo, BookInfodmin)
    

django3.0以前的版本,修改admin或者xadmin站点下的子应用成中文内容。

# 在子应用的apps.py下面的配置中,新增一个属性verbose_name
from django.apps import AppConfig

class StudentsConfig(AppConfig):
    name = 'students'
    verbose_name = "学生管理"

# 然后在当前子应用的__init__.py里面新增一下代码:
default_app_config = "students.apps.StudentsConfig"

11.作业

给以下表格设计5个基本API接口。

table:tb_course

id name description outtime ontime duration price
1 python入门 python入门学习课程基于xxx开发出来的 12:30 9:30 90 1000
2 python进阶 python进阶包括了xxx内容。。。 14:30 18:30 60 1200
3 pythonweb开发 python常见用的webxxxxx 12:30 9:30 40 1300
1. 给上面表格设计一个模型
2. 基于APIView编写基本的5个API接口
3. 基于GenericAPIView编写5个API接口
4. 基于GenericAPIView+Mixins编写5个API接口

创建子应用

python manage.py startapp homework

把子应用注册项目中,settings.py,代码:


为当前子应用创建子路由,home/urls.py,代码:


把子应用的路由文件,注册到总路头,urls.py,代码:


根据上面表格的内容,创建模型,homework/models.py,代码:


终端下,执行数据迁移:


序列化器,homework/serializers.py,代码:


视图代码:


posted @ 2023-07-22 19:35  凫弥  阅读(71)  评论(0编辑  收藏  举报