Skip to content

后端操作

0.创建并修改 server 配置文件

shell
cp config_example.yml config.yml
  • a.将config.yml里面的 DB_PASSWORD , REDIS_PASSWORD 取消注释
  • b.生成,并填写 SECRET_KEY, 加密密钥 生产服必须保证唯一性,你必须保证这个值的安全,否则攻击者可以用它来生成自己的签名值
shell
cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 49;echo

将上面的命令生成的字符串填写到config.yml里面的 SECRET_KEY 配置项

shell
# 加密密钥 生产服必须保证唯一性,你必须保证这个值的安全,否则攻击者可以用它来生成自己的签名值
# $ cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 49;echo
SECRET_KEY: django-insecure-mlq6(#a^2vk!1=7=xhp#$i=o5d%namfs=+b26$m#sh_2rco7j^

1.创建 django app

shell
python3 manage.py startapp demo

2.在 config.yml 里面添加我们的app

shell
# 需要将创建的应用写到里面 # 文件位置 config.yml ,否则菜单中,找不到模型关联
XADMIN_APPS:
  - 'demo.apps.DemoConfig'

3.编写models

shell
# 文件位置 demo/models.py

from django.db import models
from django.utils import timezone
from pilkit.processors import ResizeToFill

from common.core.models import DbAuditModel, upload_directory_path
from common.fields.image import ProcessedImageField
from system.models import UserInfo


class Book(DbAuditModel):
    class CategoryChoices(models.IntegerChoices):
        DIRECTORY = 0, "小说"
        MENU = 1, "文学"
        PERMISSION = 2, "哲学"

    # choices 单选
    category = models.SmallIntegerField(choices=CategoryChoices, default=CategoryChoices.DIRECTORY,
                                        verbose_name="书籍类型")

    # ForeignKey  一对多关系
    admin = models.ForeignKey(to=UserInfo, verbose_name="管理员1", on_delete=models.CASCADE)
    admin2 = models.ForeignKey(to=UserInfo, verbose_name="管理员2", on_delete=models.CASCADE,
                               related_name="book_admin2")

    # ManyToManyField 多对多关系
    managers = models.ManyToManyField(to=UserInfo, verbose_name="操作人员1", blank=True, null=True,
                                      related_name="book_managers")
    managers2 = models.ManyToManyField(to=UserInfo, verbose_name="操作人员2", blank=True, null=True,
                                       related_name="book_managers2")
    # 图片上传,原图访问
    cover = models.ImageField(verbose_name="书籍封面原图", null=True, blank=True)

    # 图片上传,压缩访问, 比如库里面存的图片是 xxx/xxx/123.png , 压缩访问路径可以为 xxx/xxx/123_1.jpg
    # 定义了 scales=[1, 2, 3, 4] ,因此有四个压缩链接文件名  123_1.jpg 123_2.jpg 123_3.jpg 123_4.jpg
    # 原图文件名 123.png
    avatar = ProcessedImageField(verbose_name="书籍封面缩略图", null=True, blank=True,
                                 upload_to=upload_directory_path,
                                 processors=[ResizeToFill(512, 512)],  # 默认存储像素大小
                                 scales=[1, 2, 3, 4],  # 缩略图可缩小倍数,
                                 format='png')

    # 文件上传
    book_file = models.FileField(verbose_name="书籍存储", upload_to=upload_directory_path, null=True, blank=True)

    # 普通字段
    name = models.CharField(verbose_name="书籍名称", max_length=100, help_text="书籍名称啊,随便填")
    isbn = models.CharField(verbose_name="标准书号", max_length=20)
    author = models.CharField(verbose_name="书籍作者", max_length=20, help_text="坐着大啊啊士大夫")
    publisher = models.CharField(verbose_name="出版社", max_length=20, default='大宇出版社')
    publication_date = models.DateTimeField(verbose_name="出版日期", default=timezone.now)
    price = models.FloatField(verbose_name="书籍售价", default=999.99)
    is_active = models.BooleanField(verbose_name="是否启用", default=False)

    class Meta:
        verbose_name = '书籍名称'
        verbose_name_plural = verbose_name

    def __str__(self):
        return f"{self.name}"

4.编写序列化器【请务必仔细阅读】

demo目录中,新创建一个utils目录,然后在utils目录中创建serializer.py文件

shell
# 文件位置 demo/utils/serializer.py

from rest_framework import serializers

from common.core.serializers import BaseModelSerializer
from common.fields.utils import input_wrapper
from demo import models


class BookSerializer(BaseModelSerializer):
    class Meta:
        model = models.Book
        ## pk 字段用于前端删除,更新等标识,如果有删除更新等,必须得加上 pk 字段
        ## 数据返回的字段,该字段受字段权限控制
        fields = [
            'pk', 'name', 'isbn', 'category', 'is_active', 'author', 'publisher', 'publication_date', 'price', 'block',
            'created_time', 'admin', 'admin2', 'managers', 'managers2', 'avatar', 'cover', 'book_file', 'updated_time',
        ]
        ## 仅用于前端table表格字段有顺序的展示,如果没定义,默认使用 fields 定义的变量
        ## 为啥要有这个变量? 一般情况下,前端table表格宽度不够,不需要显示太多字段,就可以通过这个变量来控制显示的字段
        table_fields = [
            'pk', 'cover', 'category', 'name', 'is_active', 'isbn', 'author', 'publisher', 'publication_date', 'price',
            'book_file'
        ]

        # fields_unexport = ['pk']  # 导入导出文件时,忽略该字段

        # read_only_fields = ['pk']  # 表示pk字段只读, 和 extra_kwargs 定义的 pk 含义一样

        ## 构建字段的额外参数
        # # extra_kwargs包含了admin 单对多的两种方式,managers 多对多的两种方式,区别在于自定义的input_type,
        # # 观察前端页面变化和 search-columns 请求的数据
        extra_kwargs = {
            'pk': {'read_only': True},  # 表示pk字段只读
            'admin': {
                'attrs': ['pk', 'username'], 'required': True, 'format': "{username}({pk})",
                'input_type': 'api-search-user'
            },
            'admin2': {
                'attrs': ['pk', 'username'], 'required': True, 'format': "{username}({pk})",
            },
            'managers': {
                'attrs': ['pk', 'username'], 'required': True, 'format': "{username}({pk})",
                'input_type': 'api-search-user'
            },
            'managers2': {
                'attrs': ['pk', 'username'], 'required': False, 'format': "{username}({pk})",
            }
        }

    # # 该方法定义了管理字段,和 extra_kwargs 定义的 admin 含义一样,该字段会被序列化为
    # # 定义带有关联关系的字段,比如上面的admin,则额外参数中,input_type 和 attrs 至少存在一个,要不然前端可能会解析失败
    # # { "pk": 2, "username": "admin", "label": "admin(2)" }
    # # attrs 变量,表示展示的字段,有 pk,username 字段, 且 pk 字段是必须的, 比如 'attrs': ['pk']
    # # format 变量,表示label字段展示内容,里面的字段一定是属于 attrs 定义的字段,写错的话,可能会报错
    # # queryset 变量, 表示数据查询对象集合,注意:search-columns 方法中,该字段会有个 choices 变量,并且包含所有queryset数据,
    # #      如果数据量特别大的时候,一定要自定义 input_type, 否则会有问题
    # # input_type 变量, 自定义,如果存在,前端解析定义的类型 api-search-user ,并且 search-columns 方法中,choices变量为 []
    # #      如果数据量特别大的时候,推荐这种写法
    # # 目前,可以注释了,在父类里面,已经定义了 serializer_related_field 字段, 建议写到 extra_kwargs 里面,使用系统会自动生成
    # # 或者 按照下面方法自己定义。
    # # 为啥推荐写到 extra_kwargs ? 写到extra_kwargs里面,系统会自动传一些参数, 可以省略 queryset , label 等参数
    # admin = BasePrimaryKeyRelatedField(attrs=['pk', 'username'], label="管理员", required=True,
    #                                    format="{username}({pk})", queryset=UserInfo.objects,
    #                                    input_type='api-search-user')

    # # 目前,可以注释了,在父类里面,已经定义了 serializer_choice_field 字段, 系统会自动生成
    # category = LabeledChoiceField(choices=models.Book.CategoryChoices.choices,
    #                               default=models.Book.CategoryChoices.DIRECTORY)

    # 自定义 input_type ,设置了 read_only=True 意味着只能通过详情查看,在新增和编辑页面不展示该字段
    # input_type 仅是前端组件渲染识别用, 可以自定义input_type ,但是前端组件得对定义的input_type 进行渲染
    # 前端自定义组件库 src/components/RePlusPage/src/components
    # 渲染组件定义 src/components/RePlusPage/src/utils/columns.tsx
    block = input_wrapper(serializers.SerializerMethodField)(read_only=True, input_type='boolean',
                                                             label="自定义input_type")

    def get_block(self, obj):
        return obj.is_active

5.编写视图

shell
# 文件位置 demo/views.py

from django_filters import rest_framework as filters
from rest_framework.decorators import action

from common.core.filter import BaseFilterSet
from common.core.modelset import BaseModelSet, ImportExportDataAction
from common.core.pagination import DynamicPageNumber
from common.core.response import ApiResponse
from common.utils import get_logger
from demo.models import Book
from demo.serializers.book import BookSerializer

logger = get_logger(__name__)


class BookViewSetFilter(BaseFilterSet):
    name = filters.CharFilter(field_name='name', lookup_expr='icontains')
    author = filters.CharFilter(field_name='author', lookup_expr='icontains')
    publisher = filters.CharFilter(field_name='publisher', lookup_expr='icontains')

    class Meta:
        model = Book
        fields = ['name', 'isbn', 'author', 'publisher', 'is_active', 'publication_date', 'price',
                  'created_time']  # fields用于前端自动生成的搜索表单


class BookViewSet(BaseModelSet, ImportExportDataAction):
    """书籍"""  # 这个 书籍 的注释得写, 否则菜单中可能会显示null,访问日志记录中也可能显示异常

    queryset = Book.objects.all()
    serializer_class = BookSerializer
    ordering_fields = ['created_time']
    filterset_class = BookViewSetFilter
    pagination_class = DynamicPageNumber(1000)  # 表示最大分页数据1000条,如果注释,则默认最大100条数据

    @action(methods=['post'], detail=True)
    def push(self, request, *args, **kwargs):
        """推送到其他服务"""  # 这个 推送到其他服务 的注释得写, 否则菜单中可能会显示null,访问日志记录中也可能显示异常

        # 自定义一个请求为post的 push 路由行为,执行自定义操作, action装饰器有好多参数,可以查看源码自行分析
        instance = self.get_object()
        return ApiResponse(detail=f"{instance.name} 推送成功")

6.新建urls.py路由文件,并添加路由

shell
# 文件位置 demo/urls.py

from rest_framework.routers import SimpleRouter

from demo.views import BookViewSet

app_name = 'demo'

router = SimpleRouter(False)  # 设置为 False ,为了去掉url后面的斜线

router.register('book', BookViewSet, basename='book')

urlpatterns = [
]
urlpatterns += router.urls

7.新建config.py文件,添加相关配置

shell
# 文件位置 demo/config.py
from django.urls import path, include

# 路由配置,当添加APP完成时候,会自动注入路由到总服务
URLPATTERNS = [
    path('api/demo/', include('demo.urls')),
]

# 请求白名单,支持正则表达式,可参考settings.py里面的 PERMISSION_WHITE_URL
PERMISSION_WHITE_REURL = []

8.迁移demo应用

shell
python manage.py makemigrations
python manage.py migrate

9.同步模型字段,或者在web页面字段管理里面,点击重新生成字段数据按钮

shell
python manage.py sync_model_field