路飞学城之多方式登录、短信验证码、课程

一、多方式登录

1、思路分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 分析
    前端携带的数据:{username:用户名 或手机号 或邮箱,password:md5(密码)}
    注册的时候:密码也是md5加密后带过来的
    我们登录的时候,带的密码是md5加密的---》后端校验通不过
   
# 后端:
    -request.data中取出来
    -校验用户名密码是否正确---》逻辑写到 序列类中
    -配合序列化类---》全局钩子中写逻辑,签发token
    -返回给前端
     
# 总结:
 1 序列化类实例化得到对象时要ser=UserLoginSerializer(data=request.data)    data=request.data  不能传给第一个位置
 2 被 APIResponse 序列化的数据类型,必须是 数字,字符串,列表,字典,不能是其他对象类型
 3 配置文件中写了个 后台项目地址

2、序列化类

字段自己检验,需要重写相应的字段

全局钩子做主逻辑处理:签发token、多方式登录验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from .models import User
from rest_framework import serializers
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
from django.conf import settings
 
 
# 只用来做校验
class UserLoginSerializer(serializers.Serializer):
    # 字段自己的校验规则会限制,不通过,因为是unique的
    username = serializers.CharField()
    password = serializers.CharField()
 
    def validate(self, attrs):
        # 1 校验用户名密码是否正确
        user = self._get_user(attrs)
         
        # 2 签发token
        token = self._get_token(user)
        
        # 3 把签发的token和username放到context中
        self.context['username'] = user.username
        self.context['token'] = token
        self.context['icon'] = settings.BACKEND_URL + '/media/' + str(user.icon)
         
        # 4 返回attrs
        return attrs
 
    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
 
        # 手机号码
        if re.match(r'^1[3-9][0-9]{9}$', username):
            # 因为这个password是明文---》在数据库中存了密文,必须要使用  user.check_password校验用户秘钥
            # user=User.objects.filter(mobile=username,password=password)
            user = User.objects.filter(mobile=username).first()
         
        # 邮箱
        elif re.match(r'^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', username):
            user = User.objects.filter(email=username).first()
         
        # 用户名
        else:
            user = User.objects.filter(username=username).first()
 
        if user and user.check_password(password):
            return user
        else:
            raise ValidationError('用户名或密码错误')
 
    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token                                                       

3、视图类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UserLoginView(GenericViewSet):
    serializer_class = UserLoginSerializer
 
    # 没有用,就不需要写
    # queryset = None
    @action(methods=['POST'], detail=False)
    def mul_login(self, request, *args, **kwargs):
        ser = self.get_serializer(data=request.data)
        # ser=UserLoginSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        username = ser.context.get('username')
        token = ser.context.get('token')
        icon = ser.context.get('icon')
        # icon 必须是字符串形式,不能是对象相似
        # {code:100,msg:成功,token:asdfasf,icon:asdfasdf,username:asdfasd}
        return APIResponse(username=username, token=token, icon=icon)
        # {code:100,msg:成功,token:asdfasf,user:{id:1,username:xxx,icon:ssss}}
        # return APIResponse(token=token, user=ser.data) # 如果执行ser.data,就会走序列化

4、路由

1
2
# 127.0.0.1:8000/api/v1/user/login/mul_login/   ---post 请求
router.register('login', UserLoginView, 'login')

二、腾讯云短信申请

1、申请腾讯云短信服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 发送短信功能
    -网上会有第三方短信平台,为我们提供api,花钱,向它的某个地址发送请求,携带手机号,内容---》它替我们发送短信
     
    -腾讯云短信---》以这个为例
    -阿里 大于短信
    -容联云通信
         
#1 申请一个公众号---》自行百度---》个人账号
 
#2  如何申请腾讯云短信
    -1 地址:https://cloud.tencent.com/act/pro/csms
    -2 登录后,进入控制台,搜短信https://console.cloud.tencent.com/smsv2
    -3 创建签名:使用公众号
        -身份证,照片
    -4 模板创建
    -5 发送短信
        - 使用腾讯提供的sdk发送
        - https://cloud.tencent.com/document/product/382/43196

2、API和sdk的区别

短信 Python SDK-SDK 文档-文档中心-腾讯云 (tencent.com)

1
2
3
4
5
6
7
8
9
10
-API: 网络地址,有请求方式,向这个地址按照规则发送请求,就能完成某些操作---》以后只要使用第三方服务,大概率会提供给你api
 
-sdk:集成开发工具包,第三方平台,用不同语言对api接口进行封装---》只要按照它的使用规则---》直接导入使用接口
        - 可能没提供所有语言的sdk,不同语言要单独写
        - python的形式就是一个包,把包下载下来
         
    -以后使用第三方,如果有sdk,优先用sdk,如果没有,只能用api
     
# 下载sdk
pip install --upgrade tencentcloud-sdk-python

发送短信代码:

3、发送短信封装(重点)

3.1 目录结构

settings.py

1
2
3
4
5
6
SECRET_ID = 'xxxFF6rRr8Qd'
SECRET_KEY = 'xxxG3kHcjnfbQ'
 
APPID = 'xxx1075'
SIGN_NAME = '爱瞌睡的老头公众号'
TEMPLATE_ID = 'xxx36'

注意

APPID在应用管理里面,需要自己创建一个应用

3.2 sms.py

3.3  短信验证码接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MobileView(ViewSet):
 
    @action(methods=['POST'], detail=False)
    def send_sms(self, request, *args, **kwargs):
        # 1 给谁发,手机号是从前端传入的,{mobile:18923434,code:'验证码'}  ---》我们的:{mobile:18923434}
        mobile = request.data.get('mobile')
        # 2 生成数字验证码
        code = get_code()
        # 3 数字验证码保存---》保存到哪?后续还能拿到---》放到缓存中---》默认放在内存中
        cache.set('cache_mobile_%s' % mobile, code)  # key 一定要唯一,后续还能取出来,就用手机号
        # 4 同步 发送短信---》同步发送--》可能前端会一直等待,耗时
        # res = send_sms_mobile(code, mobile)
        # if res:
        #     return APIResponse(msg='发送成功')
        # else:
        #     return APIResponse(code=101, msg='发送失败,请稍后再试')
        #
        # 5 发送短信--》异步操作,使用多线程,无法知道短信是否成功了,不需要关注是否成功
        t=Thread(target=send_sms_mobile,args=[code,mobile])
        t.start()
        return APIResponse(msg='发送已发送')

4、短信登录接口

4.1 分析

1
2
3
4
5
6
# 分析:
    前端携带的数据---》{mobile:11111,code:8888}
    后端:
        -取出手机号验证码,验证验证码是否正确,如果正确
        -签发token
        -返回给前端

4.2 视图类

4.3 序列化类

遗留:如果项目中配了,优先用项目中的,如果没配,用自己的

参照:

libs/lqz_jwt · liuqingzheng/rbac_manager - 码云 - 开源中国 (gitee.com)

三、注册功能

1、分析

1
2
3
4
前端:携带数据格式 {mobile:,code:,password}
    后端:
        -1 视图类---》注册方法
        -2 序列化类---》校验,保存(表中字段多,传的少---》随机,按某种格式生成---》后期修改)

2、视图类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserRegisterView(GenericViewSet, CreateModelMixin):
    serializer_class = UserRegisterSerializer
 
    # @action(methods=['POST'], detail=False)
    # def register(self, request, *args, **kwargs):
    #     ser = self.get_serializer(data=request.data)
    #     ser.is_valid(raise_exception=True)
    #     ser.save()
    #     return APIResponse(msg='恭喜您注册成功')
    @action(methods=['POST'], detail=False)
    def register(self, request, *args, **kwargs):
        # 内部执行了它:serializer.data---》走序列化---》基于create返回的user做序列化---》按照'mobile', 'code', 'password' 做序列化---》code不是表中字段,就报错了
        res = super().create(request, *args, **kwargs)
        return APIResponse(msg='注册成功')

3、序列化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class UserRegisterSerializer(serializers.ModelSerializer):
    # code 不是表中字段
    code = serializers.CharField(max_length=4, min_length=4, write_only=True)
 
    class Meta:
        model = User
        fields = ['mobile', 'code', 'password']
 
    def validate(self, attrs):
        # 1 校验验证码是否正确
        mobile = attrs.get('mobile')
        code = attrs.get('code')
        old_code = cache.get('cache_mobile_%s' % mobile)
        if code == old_code or code == '8888'# 测试阶段,万能验证码
            # 2 组装数据 :username必填,但是没有,code不是表的字段
            attrs['username'] = mobile
            attrs.pop('code')
        # 3 返回
        return attrs
 
    def create(self, validated_data):  # 密码是加密的,如果重写,密码是明文的  validated_data=mobile,password,username
        user = User.objects.create_user(**validated_data)
        return user

补充:

4、前端注册页面

1
2
3
4
# 登录,注册,都写成组件----》在任意页面中,都能点击显示登录模态框
<br># 写好的组件,应该放在那个组件中----》不是页面组件(小组件)
<br># 点击登录按钮,把Login.vue 通过定位,占满全屏,透明度设为 0.5 ,纯黑色悲剧,覆盖在组件上
<br># 在Login.vue点关闭,要把Login.vue隐藏起来,父子通信之子传父,自定义事件

Header.vue

Login.vue

Register.vue

注意:新用户注册界面,有用到校验手机号是否存在的接口,返回的code状态码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
class MobileView(ViewSet):  # ViewSet = ViewSetMixin + APIView
    @action(methods=['GET'], detail=False)
    def check_mobile(self, request, *args, **kwargs):
        try:
            # 取出前端传入手机号
            mobile = request.query_params['mobile']
            User.objects.get(mobile=mobile)  # 有且只有一个才不报错,否则报错
        except MultiValueDictKeyError as e:
            return APIResponse(code=101, msg='xxx')
        except Exception as e:
            return APIResponse(code=102, msg='yyy')
        return APIResponse(msg='手机号存在')

四、课程相关

1、课程列表页前端

 三种课程类型设置为三个组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#1 前端  新建三个组件
    LightCourse.vue
    FreeCourse.vue
    ActualCourse.vue
     
# 2 配置路由
    import FreeCourse from "@/views/FreeCourse";
    import ActualCourse from "@/views/ActualCourse";
    import LightCourse from "@/views/LightCourse";
     
     {
        path: '/free-course',
        name: 'free-course',
        component: FreeCourse
    },
    {
        path: '/actual-course',
        name: 'actual-course',
        component: ActualCourse
    },
    {
        path: '/light-course',
        name: 'light-course',
        component: LightCourse
    },

ActualCourse

2、课程相关表设计

新建一个course app 并注册到配置中

五个表

1
2
3
4
5
6
7
8
#1  课程分类表:跟课程一对多:一个课程分类,有多个课程
#2  课程表(实战课课表)
    -多种课程:免费,实战,轻课---》使用同一个表?使用三个表
    -多种课程,字段可能有一样的,也有不一样的
    -我们的设计是:多个课程,多个表,以后再加别的课程,再加表即可
# 3 章节表:一个课程,有多个章节 
# 4 课时表:一个章节,有多个课时
# 5 老师表:实战课,一门课,就是一个老师讲, 一对多,一个老师可以讲多门课

models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 创建表
from django.db import models
from utils.common_model import BaseModel
 
 
# Create your models here.
## 课程分类
class CourseCategory(BaseModel):
    """分类"""
    name = models.CharField(max_length=64, unique=True, verbose_name="分类名称")
 
    class Meta:
        db_table = "luffy_course_category"
        verbose_name = "分类"
        verbose_name_plural = verbose_name
 
    def __str__(self):
        return "%s" % self.name
 
 
### 课程表
class Course(BaseModel):
    """课程"""
    course_type = (
        (0, '付费'),
        (1, 'VIP专享'),
        (2, '学位课程')
    )
    level_choices = (
        (0, '初级'),
        (1, '中级'),
        (2, '高级'),
    )
    status_choices = (
        (0, '上线'),
        (1, '下线'),
        (2, '预上线'),
    )
    name = models.CharField(max_length=128, verbose_name="课程名称")
    course_img = models.ImageField(upload_to="courses", max_length=255, verbose_name="封面图片", blank=True, null=True)
    course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付费类型")
    brief = models.TextField(max_length=2048, verbose_name="详情介绍", null=True, blank=True)
    level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name="难度等级")
    pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)
    period = models.IntegerField(verbose_name="建议学习周期(day)", default=7)
    attachment_path = models.FileField(upload_to="attachment", max_length=128, verbose_name="课件路径", blank=True,null=True)
    status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态")
    students = models.IntegerField(verbose_name="学习人数", default=0)
    sections = models.IntegerField(verbose_name="总课时数量", default=0)
    pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0)
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0)
 
 
    # 删除某个课程分类,如果使用models.CASCADE,所有课程都删除,很危险
    # 删除某个课程分类models.SET_NULL,跟它相关联的课程,置为空
    # db_constraint=False  逻辑外键,不建立物理外键  没有外键约束
    course_category = models.ForeignKey("CourseCategory", on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True,verbose_name="课程分类")
    teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师")
 
    class Meta:
        db_table = "luffy_course"
        verbose_name = "课程"
        verbose_name_plural = "课程"
 
    def __str__(self):
        return "%s" % self.name
 
 
### 章节表
class CourseChapter(BaseModel):
    """章节"""
    course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE, verbose_name="课程名称")
    chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
    name = models.CharField(max_length=128, verbose_name="章节标题")
    summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
    pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)
 
    class Meta:
        db_table = "luffy_course_chapter"
        verbose_name = "章节"
        verbose_name_plural = verbose_name
 
    def __str__(self):
        return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)
 
 
#课时表
class CourseSection(BaseModel):
    """课时"""
    section_type_choices = (
        (0, '文档'),
        (1, '练习'),
        (2, '视频')
    )
    chapter = models.ForeignKey("CourseChapter", related_name='coursesections', on_delete=models.CASCADE,verbose_name="课程章节")
    name = models.CharField(max_length=128, verbose_name="课时标题")
    orders = models.PositiveSmallIntegerField(verbose_name="课时排序")
    section_type = models.SmallIntegerField(default=2, choices=section_type_choices, verbose_name="课时种类")
    section_link = models.CharField(max_length=255, blank=True, null=True, verbose_name="课时链接",help_text="若是video,填vid,若是文档,填link")
    duration = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32# 仅在前端展示使用
    pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
    free_trail = models.BooleanField(verbose_name="是否可试看", default=False)
 
    class Meta:
        db_table = "luffy_course_section"
        verbose_name = "课时"
        verbose_name_plural = verbose_name
 
    def __str__(self):
        return "%s-%s" % (self.chapter, self.name)
 
#老师表
class Teacher(BaseModel):
    """导师"""
    role_choices = (
        (0, '讲师'),
        (1, '导师'),
        (2, '班主任'),
    )
    name = models.CharField(max_length=32, verbose_name="导师名")
    role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="导师身份")
    title = models.CharField(max_length=64, verbose_name="职位、职称")
    signature = models.CharField(max_length=255, verbose_name="导师签名", help_text="导师签名", blank=True, null=True)
    image = models.ImageField(upload_to="teacher", null=True, verbose_name="导师封面")
    brief = models.TextField(max_length=1024, verbose_name="导师描述")
 
    class Meta:
        db_table = "luffy_teacher"
        verbose_name = "导师"
        verbose_name_plural = verbose_name
 
    def __str__(self):
        return "%s" % self.name

补充:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#1  ForeignKey 的on_delete 参数
'''on_delete 可以选择的参数
    models.CASCADE    # 级联删除,注意使用时机,用户和用户详情   课程和课程章节
    models.SET_NULL  # 删除分类,课程这个字段设置为空,要有null=True参数的配合
    models.SET_DEFAULT      # 删除分类,课程这个字段设置为默认值,要有 defalut=xx参数的配合
    models.DO_NOTHING      # 删除分类,课程这个字段什么都不做
    models.SET(值,函数内存地址) # 删除分类,课程这个字段设置为写的值,或者执行内部传入的函数,函数返回值放在这
'''
 
# 2 ForeignKey 的db_constraint=False 
     如果 db_constraint=False  只有逻辑外键,不建立物理外键  没有外键约束,如果这样设置,修改,新增速度快,可能会出现脏数据(程序层面控制)
    连表查询没有任何区别,其他操作完全一致
    
# 3 ForeignKey 的 related_name='coursechapters'
    # 基于对象的跨表查:
        正向: 对象.字段  course.course_category 根据课程拿到了课程分类对象, course_category在表内部,就是正向
      反向: 对象.表名小写  对象.表名小写_set.all()  字段在表中没有,但是有关联关系
    related_name:
        为了替换原来基于对象的跨表查反向查询  表名小写 或 表名小写_set.all() 的
    related_query_name
        为了替换原来__连表查询,中表名小写,现在直接用related_query_name指定的字符串即可

数据录入:

3、课程列表页接口

1
2
3
4
5
6
# 1 课程分类接口
 
# 2 查询所有课程
    -1 可以按人气(学生人数)和价格排序
    -2 可以按课程分类过滤
    -3 带分页(基本分页)

查询所有分类接口

1
2
3
4
5
6
7
8
9
10
11
12
13
#### 路由
router.register('category', CourseCategoryView, 'category')
 
### 视图类
class CourseCategoryView(GenericViewSet, CommonListModelMixin):
    queryset = CourseCategory.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = CourseCategorySerializer
       
### 序列化类
class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['id', 'image', 'link']

路由

1
2
# http://127.0.0.1:8000/api/v1/courses/actual/--- get
router.register('actual', CourseView, 'actual')

视图类

1
2
3
4
5
6
7
8
9
class CourseView(GenericViewSet, CommonListModelMixin):
    queryset = Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = CourseSerializer
 
    filter_backends = [OrderingFilter, DjangoFilterBackend]
    ordering_fields = ['students', 'price']
    filterset_fields = ['course_category']
    # 分页
    pagination_class = CommonPageNumberPagination

序列化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ['id', 'name', 'role_name', 'title', 'signature', 'image', 'brief']
class CourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = [
            'id',
            'name',
            'course_img',
            'brief',
            'attachment_path',
            'pub_sections',
            'price',
            'students',
            'period',
            'sections',
 
            'course_type_name',
            'level_name',
            'status_name',
 
            'teacher',
            'section_list',
        ]
 
    # 定制,返回课程的老师,课程章节及章节下的课时(总共课时最多显示4条,不足4条,就全部显示)
    # 子序列化      表模型中写,在序列化类中
    teacher = TeacherSerializer()

表模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class Course(BaseModel):
    """课程"""
    course_type = (
        (0, '付费'),
        (1, 'VIP专享'),
        (2, '学位课程')
    )
    level_choices = (
        (0, '初级'),
        (1, '中级'),
        (2, '高级'),
    )
    status_choices = (
        (0, '上线'),
        (1, '下线'),
        (2, '预上线'),
    )
    name = models.CharField(max_length=128, verbose_name="课程名称")
    course_img = models.ImageField(upload_to="courses", max_length=255, verbose_name="封面图片", blank=True, null=True)
    course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付费类型")
    brief = models.TextField(max_length=2048, verbose_name="详情介绍", null=True, blank=True)
    level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name="难度等级")
    pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)
    period = models.IntegerField(verbose_name="建议学习周期(day)", default=7)
    attachment_path = models.FileField(upload_to="attachment", max_length=128, verbose_name="课件路径", blank=True,
                                       null=True)
    status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态")
    students = models.IntegerField(verbose_name="学习人数", default=0)
    sections = models.IntegerField(verbose_name="总课时数量", default=0)
    pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0)
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0)
 
    # 删除某个课程分类,如果使用models.CASCADE,所有课程都删除,很危险
    # 删除某个课程分类models.SET_NULL,跟它相关联的课程,置为空
    '''on_delete 可以选择的参数
    models.CASCADE    # 级联删除,注意使用时机,用户和用户详情   课程和课程章节
    models.SET_NULL  # 删除分类,课程这个字段设置为空,要有null=True参数的配合
    models.SET_DEFAULT      # 删除分类,课程这个字段设置为默认值,要有 defalut=xx参数的配合
    models.DO_NOTHING      # 删除分类,课程这个字段什么都不做
    models.SET(值,函数内存地址) # 删除分类,课程这个字段设置为写的值,或者执行内部传入的函数,函数返回值放在这
    '''
 
    # db_constraint=False  逻辑外键,不建立物理外键  没有外键约束,如果这样设置,修改,新增速度快,可能会出现脏数据(程序层面控制)
    # 连表查询没有任何区别,其他操作完全一致
    course_category = models.ForeignKey("CourseCategory", on_delete=models.SET_NULL, db_constraint=False, null=True,
                                        blank=True, verbose_name="课程分类")
    teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师")
 
    class Meta:
        db_table = "luffy_course"
        verbose_name = "课程"
        verbose_name_plural = "课程"
 
    def __str__(self):
        return "%s" % self.name
 
    # choice字段,返回中文
    def course_type_name(self):
        return self.get_course_type_display()
 
    def level_name(self):
        return self.get_level_display()
 
    def status_name(self):
        return self.get_status_display()
 
    def section_list(self):
        l = []
        # 先取出课程下所有章节
        course_chapter_list = self.coursechapters.all()
        for course_chapter in course_chapter_list:
            # 拿出这个章节下所有课时
            course_section_list = course_chapter.coursesections.all()
            for course_section in course_section_list:
                l.append({'id': course_section.id,
                          'name': course_section.name,
                          'section_type': course_section.section_type,
                          'section_link': course_section.section_link,
                          'duration': course_section.duration,
                          'free_trail': course_section.free_trail,
 
                          })
                if len(l) >= 4:
                    return l
            # 循环章节,取出每个章节下的课时,追加到l中,如果超过4条,就结束了
        return l

分页

1
2
3
4
5
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2
    page_query_param = 'page'
    page_size_query_param = 'size'
    max_page_size = 5

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

posted @   凡人半睁眼  阅读(136)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
历史上的今天:
2020-10-16 mysql字符集、隔离等级
2019-10-16 网络学习之路由

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示