zoukankan      html  css  js  c++  java
  • 0110 celery接口缓存与luffy项目分类主页群查

    昨日回顾

    """
    1、redis
    	1) 优点:内存数据库,IO操作快;nosql数据库,key-value操作数据便捷;支持数据持久化;高并发;支持五种数据类型;
    	2) 基本命令:
    		启动:redis-server /配置文件路径 --service-start
    		连接:redis-cli -h 地址 -p 端口 -a 密码 -n 编号
    		关闭: redis-cli shutdown
    		持久化: redis-cli -> save
    		切换:redis-cli -> select 编号
    	3) 数据类型:
    		字符串:set k v | setex k e v
    		列表: rpush k v1 v2 ...
    		哈希: hmset k f1 v1 f2 v2 ...
    		集合:sadd k m1 m2 ...
    		有序集合:zadd k g1 m1 g2 m2 ...
    	4) python使用redis:redis
    		import redis
    		rdb = redis.Redis(host='127.0.0.1', port=6379, db=0, password=None, decode_responses=True)
    		
    		pool = redis.ConnectionPool(max_connections=100, host='127.0.0.1', port=6379, db=0, password=None, decode_responses=True)
    		rdb = redis.Redis(connection_pool=pool)
    	
    	5) django使用redis:django-redis
    		配置:CACHES
    		使用:通过django的cache对象使用redis
    		
    2、接口缓存
    	1) 优先从缓存中取数据,如果没有,再从数据库中取(同步到缓存)
        def list(self, request, *args, **kwargs):
            data = cache.get('banner_cache')
            if not data:
                print('走了数据库')
                response = super().list(request, *args, **kwargs)
                # 不设置过期时间,缓存的更新在后台异步更新(celery异步框架)
                cache.set('banner_cache', response.data)  
                return response
            return Response(data)
    """
    

    Celery

    Celery 官网:http://www.celeryproject.org/

    Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html

    Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/

    1. 简介

    1.1 Celery架构

    Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(task result store)组成。

    • 消息中间件

    Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等

    • 任务执行单元

    Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。

    • 任务结果存储

    Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等

    1.2 使用场景

    异步执行:解决耗时任务

    延迟执行:解决延迟任务

    定时执行:解决周期(周期)任务

    1.3 Celery的安装配置

    pip install celery
    
    消息中间件:RabbitMQ/Redis
    
    app=Celery('任务名', broker='xxx', backend='xxx')
    

    2. Celery使用

    2.1 Celery执行异步任务

    包架构封装

    project
        ├── celery_task  	# celery包
        │   ├── __init__.py # 包文件
        │   ├── celery.py   # celery连接和配置相关文件,且名字必须交celery.py
        │   └── tasks.py    # 所有任务函数
        ├── add_task.py  	# 添加任务
        └── get_result.py   # 获取结果
    

    2.2 基本使用 (定时任务)

    celery.py
    # 1)创建app + 任务
    
    # 2)启动celery(app)服务:
    # 非windows
    # 命令:celery worker -A celery_task -l info
    # windows:
    # pip3 install eventlet
    # celery worker -A celery_task -l info -P eventlet
    
    # 3)添加任务:手动添加,要自定义添加任务的脚本,右键执行脚本
    
    # 4)获取结果:手动获取,要自定义获取任务的脚本,右键执行脚本
    
    
    from celery import Celery
    broker = 'redis://127.0.0.1:6379/1'
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    
    
    
    
    ---------------------------------------------------------------------------------------------
    
    # 基本使用
    # 1.安装celery
    # 2.安装eventlet,
    # 3.添加任务 .delay()  异步任务
    # 4.执行worker : celery worker -A celery_task -l info -P eventlet
    
    from celery import Celery
    
    # broker提交任务(使用redis0数据库存储)
    broker = 'redis://127.0.0.1:6379/0'
    
    # 提供backend用于存储任务结果
    backend = 'redis://127.0.0.1:6379/1'
    
    # worker任务执行分发
    app = Celery(broker=broker,backend=backend)
    
    # 设定任务@app.task
    # 任务就是一个功能函数,执行任务就是执行函数,任务的结果就是任务的返回值
    @app.task
    def add(a,b):
        res = a + b
        print('a + b = %s' % res)
        return res
    
    @app.task
    def low(a,b):
        res = a - b
        print('a - b = %s' % res)
        return res
    
    # 添加任务到worker,自定义添加任务脚本
    # 获取任务的脚本
    
    
    """
    celery框架工作流程
    1)创建Celery框架对象app,配置broker和backend,得到的app就是worker
    2)给worker对应的app添加可处理的任务函数
    3)启动celery服务,运行worker
    4)书写添加任务的脚本,执行脚本添加任务到broker,worker会自己异步从broker中拿任务执行,执行结果放在backend中
    5) 书写获取任务结果的脚本,明确任务id与执行的app,获取任务结果
    """
    
    
    
    tasks.py
    from .celery import app
    import time
    @app.task
    def add(n, m):
        print(n)
        print(m)
        time.sleep(10)
        print('n+m的结果:%s' % (n + m))
        return n + m
    
    @app.task
    def low(n, m):
        print(n)
        print(m)
        print('n-m的结果:%s' % (n - m))
        return n - m
    
    add_task.py
    from celery_task import tasks
    
    # 添加立即执行任务
    t1 = tasks.add.delay(10, 20)
    t2 = tasks.low.delay(100, 50)
    print(t1.id)
    
    
    # 添加延迟任务
    from datetime import datetime, timedelta
    eta=datetime.utcnow() + timedelta(seconds=10)
    tasks.low.apply_async(args=(200, 50), eta=eta)
    
    
    
    
    ---------------------------------------------------------------------------------------------
    
    # 跟celery任务框架没关系,只是一个手动添加任务的脚本文件
    from tasks import add, low
    
    # # 导入任务执行函数
    # res = add(10,20)
    # print(res)
    
    # worker添加任务  .delay()
    res = add.delay(20,30)
    # 结果自动保存到了redis数据库中
    print(res)  # 83f9e85f-109c-404b-aa63-6a13e57f1bcd
    
    
    
    get_result.py
    from celery_task.celery import app
    
    from celery.result import AsyncResult
    
    id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
    if __name__ == '__main__':
        async = AsyncResult(id=id, app=app)
        if async.successful():
            result = async.get()
            print(result)
        elif async.failed():
            print('任务失败')
        elif async.status == 'PENDING':
            print('任务等待中被执行')
        elif async.status == 'RETRY':
            print('任务异常后正在重试')
        elif async.status == 'STARTED':
            print('任务已经开始被执行')
            
            ---------------------------------------------------------------------------------------
            
            
    from tasks import app
    from celery.result import AsyncResult
    id = 'ad4ca85d-998a-4cc9-9d24-655986a169b3'
    
    # async = AsyncResult(id=id, app=app)
    # res = async.get()
    # print(res)
    
    # """
    if __name__ == '__main__':
        # 获取async对象
        async = AsyncResult(id=id, app=app)
        # 判断是否成功
        if async.successful():
            # .get() 获取任务对象
            result = async.get()
            print(result)
        # 失败判断
        elif async.failed():
            print('任务失败')
        elif async.status == 'PENDING':
            print('任务等待中被执行')
        elif async.status == 'RETRY':
            print('任务异常后正在重试')
        elif async.status == 'STARTED':
            print('任务已经开始被执行')
    # """
    

    2.3 高级使用 (定时任务)

    celery.py
    # 1)创建app + 任务
    
    # 2)启动celery(app)服务:
    # 非windows
    # 命令:celery worker -A celery_task -l info
    # windows:
    # pip3 install eventlet
    # celery worker -A celery_task -l info -P eventlet
    
    # 3)添加任务:自动添加任务,所以要启动一个添加任务的服务
    # 命令:celery beat -A celery_task -l info
    
    # 4)获取结果
    
    
    from celery import Celery
    
    broker = 'redis://127.0.0.1:6379/1'
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    
    
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
    
    # 任务的定时配置
    from datetime import timedelta
    from celery.schedules import crontab
    app.conf.beat_schedule = {
        'low-task': {
            'task': 'celery_task.tasks.low',
            'schedule': timedelta(seconds=3),
            # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
            'args': (300, 150),
        }
    }
    
    
    --------------------------------------------------------------------------------------------------
    
    # 启动分发
    
    from celery import  Celery
    
    broker = 'redis://127.0.0.1:6379/0'
    backend = 'redis://127.0.0.1:6379/1'
    
    # worker 使用include将任务文件函数包含在内
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    
    
    
    # 设置定时任务需要配置
    # 延时时间
    from datetime import timedelta
    
    # 定时至某一时刻
    from celery.schedules import crontab
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
    
    app.conf.beat_schedule = {
        # 任务的名字
        'add-task': {
            # 添加任务地址
            'task': 'celery_task.tasks.add',
            # 定时任务的时间间隔
            'schedule': timedelta(seconds=3), # 延时3s
            # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
            # 任务的参数
            'args':(20, 10),
            # 关键字参数
            # 'kwargs': (),
        },
        # 添加第二个任务
        'low-task': {
            'task': 'celery_task.tasks.low',
            'schedule': timedelta(seconds=6),  # 延时6s
            'args': (20, 10),
        },
    }
    
    
    添加任务使用socket自动添加
    # 添加任务:自动添加任务,所以要启动一个添加任务的服务
    # 命令:celery beat -A celery_task -l info
    
    
    
    """
    celery框架工作流程
    1)创建Celery框架对象app,配置broker和backend,得到的app就是worker
    2)给worker对应的app添加可处理的任务函数,用include配置给worker的app
    3)完成提供的任务的定时配置app.conf.beat_schedule
    4)启动celery服务,运行worker,执行任务
    5)启动beat服务,运行beat,添加任务
    """
    
    tasks.py
    from .celery import app
    
    import time
    @app.task
    def add(n, m):
        print(n)
        print(m)
        time.sleep(10)
        print('n+m的结果:%s' % (n + m))
        return n + m
    
    
    @app.task
    def low(n, m):
        print(n)
        print(m)
        print('n-m的结果:%s' % (n - m))
        return n - m
    
    get_result.py
    from celery_task.celery import app
    
    from celery.result import AsyncResult
    
    id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
    if __name__ == '__main__':
        async = AsyncResult(id=id, app=app)
        if async.successful():
            result = async.get()
            print(result)
        elif async.failed():
            print('任务失败')
        elif async.status == 'PENDING':
            print('任务等待中被执行')
        elif async.status == 'RETRY':
            print('任务异常后正在重试')
        elif async.status == 'STARTED':
            print('任务已经开始被执行')
    

    2.4 django中使用

    celery.py
    """
    celery框架django项目工作流程
    1)加载django配置环境
    2)创建Celery框架对象app,配置broker和backend,得到的app就是worker
    3)给worker对应的app添加可处理的任务函数,用include配置给worker的app
    4)完成提供的任务的定时配置app.conf.beat_schedule
    5)启动celery服务,运行worker,执行任务
    6)启动beat服务,运行beat,添加任务
    
    重点:由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置项目的根目录下
    """
    
    # 一、加载django配置环境
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffyapi.settings.dev")
    
    # 二、加载celery配置环境
    from celery import Celery
    # broker
    broker = 'redis://127.0.0.1:6379/0'
    # backend
    backend = 'redis://127.0.0.1:6379/1'
    # worker
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    
    
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
    
    # 任务的定时配置
    from datetime import timedelta
    from celery.schedules import crontab
    app.conf.beat_schedule = {
        'django-task': {
            'task': 'celery_task.tasks.test_django_celery',
            'schedule': timedelta(seconds=3),
            'args': (),
        }
    }
    
    tasks.py
    from .celery import app
    # 获取项目中的模型类
    from api.models import Banner
    @app.task
    def test_django_celery():
        banner_query = Banner.objects.filter(is_delete=False).all()
        print(banner_query)
    

    luffy项目

    前台

    freecourse免费课的课程页

    <template>
        <div class="course">
            <Header></Header>
            <div class="main">
                <!-- 筛选条件 -->
                <div class="condition">
                    <ul class="cate-list">
                        <li class="title">课程分类:</li>
                        <li class="this">全部</li>
                        <li>Python</li>
                        <li>Linux运维</li>
                        <li>Python进阶</li>
                        <li>开发工具</li>
                        <li>Go语言</li>
                        <li>机器学习</li>
                        <li>技术生涯</li>
                    </ul>
    
                    <div class="ordering">
                        <ul>
                            <li class="title">筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>
                            <li class="default this">默认</li>
                            <li class="hot">人气</li>
                            <li class="price">价格</li>
                        </ul>
                        <p class="condition-result">共21个课程</p>
                    </div>
    
                </div>
                <!-- 课程列表 -->
                <div class="course-list">
                    <div class="course-item">
                        <div class="course-image">
                            <img src="@/assets/img/course-cover.jpeg" alt="">
                        </div>
                        <div class="course-info">
                            <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3>
                            <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>
                            <ul class="lesson-list">
                                <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li>
                            </ul>
                            <div class="pay-box">
                                <span class="discount-type">限时免费</span>
                                <span class="discount-price">¥0.00元</span>
                                <span class="original-price">原价:9.00元</span>
                                <span class="buy-now">立即购买</span>
                            </div>
                        </div>
                    </div>
                    <div class="course-item">
                        <div class="course-image">
                            <img src="@/assets/img/course-cover.jpeg" alt="">
                        </div>
                        <div class="course-info">
                            <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3>
                            <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>
                            <ul class="lesson-list">
                                <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li>
                            </ul>
                            <div class="pay-box">
                                <span class="discount-type">限时免费</span>
                                <span class="discount-price">¥0.00元</span>
                                <span class="original-price">原价:9.00元</span>
                                <span class="buy-now">立即购买</span>
                            </div>
                        </div>
                    </div>
                    <div class="course-item">
                        <div class="course-image">
                            <img src="@/assets/img/course-cover.jpeg" alt="">
                        </div>
                        <div class="course-info">
                            <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3>
                            <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>
                            <ul class="lesson-list">
                                <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li>
                            </ul>
                            <div class="pay-box">
                                <span class="discount-type">限时免费</span>
                                <span class="discount-price">¥0.00元</span>
                                <span class="original-price">原价:9.00元</span>
                                <span class="buy-now">立即购买</span>
                            </div>
                        </div>
                    </div>
                    <div class="course-item">
                        <div class="course-image">
                            <img src="@/assets/img/course-cover.jpeg" alt="">
                        </div>
                        <div class="course-info">
                            <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3>
                            <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>
                            <ul class="lesson-list">
                                <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码</span></li>
                                <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li>
                            </ul>
                            <div class="pay-box">
                                <span class="discount-type">限时免费</span>
                                <span class="discount-price">¥0.00元</span>
                                <span class="original-price">原价:9.00元</span>
                                <span class="buy-now">立即购买</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <!--<Footer></Footer>-->
        </div>
    </template>
    
    <script>
        import Header from "@/components/Header"
        // import Footer from "@/components/Footer"
    
        export default {
            name: "Course",
            data() {
                return {
                    category: 0,
                }
            },
            components: {
                Header,
                // Footer,
            }
        }
    </script>
    
    <style scoped>
        .course {
            background: #f6f6f6;
        }
    
        .course .main {
             1100px;
            margin: 35px auto 0;
        }
    
        .course .condition {
            margin-bottom: 35px;
            padding: 25px 30px 25px 20px;
            background: #fff;
            border-radius: 4px;
            box-shadow: 0 2px 4px 0 #f0f0f0;
        }
    
        .course .cate-list {
            border-bottom: 1px solid #333;
            border-bottom-color: rgba(51, 51, 51, .05);
            padding-bottom: 18px;
            margin-bottom: 17px;
        }
    
        .course .cate-list::after {
            content: "";
            display: block;
            clear: both;
        }
    
        .course .cate-list li {
            float: left;
            font-size: 16px;
            padding: 6px 15px;
            line-height: 16px;
            margin-left: 14px;
            position: relative;
            transition: all .3s ease;
            cursor: pointer;
            color: #4a4a4a;
            border: 1px solid transparent; /* transparent 透明 */
        }
    
        .course .cate-list .title {
            color: #888;
            margin-left: 0;
            letter-spacing: .36px;
            padding: 0;
            line-height: 28px;
        }
    
        .course .cate-list .this {
            color: #ffc210;
            border: 1px solid #ffc210 !important;
            border-radius: 30px;
        }
    
        .course .ordering::after {
            content: "";
            display: block;
            clear: both;
        }
    
        .course .ordering ul {
            float: left;
        }
    
        .course .ordering ul::after {
            content: "";
            display: block;
            clear: both;
        }
    
        .course .ordering .condition-result {
            float: right;
            font-size: 14px;
            color: #9b9b9b;
            line-height: 28px;
        }
    
        .course .ordering ul li {
            float: left;
            padding: 6px 15px;
            line-height: 16px;
            margin-left: 14px;
            position: relative;
            transition: all .3s ease;
            cursor: pointer;
            color: #4a4a4a;
        }
    
        .course .ordering .title {
            font-size: 16px;
            color: #888;
            letter-spacing: .36px;
            margin-left: 0;
            padding: 0;
            line-height: 28px;
        }
    
        .course .ordering .this {
            color: #ffc210;
        }
    
        .course .ordering .price {
            position: relative;
        }
    
        .course .ordering .price::before,
        .course .ordering .price::after {
            cursor: pointer;
            content: "";
            display: block;
             0px;
            height: 0px;
            border: 5px solid transparent;
            position: absolute;
            right: 0;
        }
    
        .course .ordering .price::before {
            border-bottom: 5px solid #aaa;
            margin-bottom: 2px;
            top: 2px;
        }
    
        .course .ordering .price::after {
            border-top: 5px solid #aaa;
            bottom: 2px;
        }
    
        .course .course-item:hover {
            box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
        }
    
        .course .course-item {
             1100px;
            background: #fff;
            padding: 20px 30px 20px 20px;
            margin-bottom: 35px;
            border-radius: 2px;
            cursor: pointer;
            box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
            /* css3.0 过渡动画 hover 事件操作 */
            transition: all .2s ease;
        }
    
        .course .course-item::after {
            content: "";
            display: block;
            clear: both;
        }
    
        /* 顶级元素 父级元素  当前元素{} */
        .course .course-item .course-image {
            float: left;
             423px;
            height: 210px;
            margin-right: 30px;
        }
    
        .course .course-item .course-image img {
             100%;
        }
    
        .course .course-item .course-info {
            float: left;
             596px;
        }
    
        .course-item .course-info h3 {
            font-size: 26px;
            color: #333;
            font-weight: normal;
            margin-bottom: 8px;
        }
    
        .course-item .course-info h3 span {
            font-size: 14px;
            color: #9b9b9b;
            float: right;
            margin-top: 14px;
        }
    
        .course-item .course-info h3 span img {
             11px;
            height: auto;
            margin-right: 7px;
        }
    
        .course-item .course-info .teather-info {
            font-size: 14px;
            color: #9b9b9b;
            margin-bottom: 14px;
            padding-bottom: 14px;
            border-bottom: 1px solid #333;
            border-bottom-color: rgba(51, 51, 51, .05);
        }
    
        .course-item .course-info .teather-info span {
            float: right;
        }
    
        .course-item .lesson-list::after {
            content: "";
            display: block;
            clear: both;
        }
    
        .course-item .lesson-list li {
            float: left;
             44%;
            font-size: 14px;
            color: #666;
            padding-left: 22px;
            /* background: url("路径") 是否平铺 x轴位置 y轴位置 */
            background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
            margin-bottom: 15px;
        }
    
        .course-item .lesson-list li .lesson-title {
            /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
            text-overflow: ellipsis;
            overflow: hidden;
            white-space: nowrap;
            display: inline-block;
            max- 200px;
        }
    
        .course-item .lesson-list li:hover {
            background-image: url("/src/assets/img/play-icon-yellow.svg");
            color: #ffc210;
        }
    
        .course-item .lesson-list li .free {
             34px;
            height: 20px;
            color: #fd7b4d;
            vertical-align: super;
            margin-left: 10px;
            border: 1px solid #fd7b4d;
            border-radius: 2px;
            text-align: center;
            font-size: 13px;
            white-space: nowrap;
        }
    
        .course-item .lesson-list li:hover .free {
            color: #ffc210;
            border-color: #ffc210;
        }
    
        .course-item .pay-box::after {
            content: "";
            display: block;
            clear: both;
        }
    
        .course-item .pay-box .discount-type {
            padding: 6px 10px;
            font-size: 16px;
            color: #fff;
            text-align: center;
            margin-right: 8px;
            background: #fa6240;
            border: 1px solid #fa6240;
            border-radius: 10px 0 10px 0;
            float: left;
        }
    
        .course-item .pay-box .discount-price {
            font-size: 24px;
            color: #fa6240;
            float: left;
        }
    
        .course-item .pay-box .original-price {
            text-decoration: line-through;
            font-size: 14px;
            color: #9b9b9b;
            margin-left: 10px;
            float: left;
            margin-top: 10px;
        }
    
        .course-item .pay-box .buy-now {
             120px;
            height: 38px;
            background: transparent;
            color: #fa6240;
            font-size: 16px;
            border: 1px solid #fd7b4d;
            border-radius: 3px;
            transition: all .2s ease-in-out;
            float: right;
            text-align: center;
            line-height: 38px;
        }
    
        .course-item .pay-box .buy-now:hover {
            color: #fff;
            background: #ffc210;
            border: 1px solid #ffc210;
        }
    </style>
    
    

    后台

    course的models

    '''
    from django.db import models
    class Course(models.Model):
        # 基础字段
        name = models.CharField(max_length=64)
        title = models.CharField(max_length=64)
        level = models.IntegerField(choices=((0, '入门'), (1, '进阶')), default=0)
        detail = models.TextField()  # 可以关联详情表
        type = models.IntegerField(choices=((0, 'Python'), (1, 'Linux')), default=0)
        is_show = models.BooleanField(default=False)
    
        # 优化字段
        students = models.IntegerField(default=0)   # 热度
        time = models.IntegerField(default=0)   # 总时长
        sections = models.IntegerField(default=0)   # 总课时
    
        # 虚拟表基类
        class Meta:
            abstract = True
    
    # 免费课
    class FreeCourse(Course):
        image = models.ImageField(upload_to='course/free')
        attachment = models.FileField(upload_to='attachment')
    
    # 实战课
    class ActualCourse(Course):
        image = models.ImageField(upload_to='course/actual')
        price = models.DecimalField(max_digits=7, decimal_places=2)
        cost = models.DecimalField(max_digits=7, decimal_places=2)
    
    # 轻课
    class LightCourse(Course):
        image = models.ImageField(upload_to='course/light')
        price = models.DecimalField(max_digits=7, decimal_places=2)
        cost = models.DecimalField(max_digits=7, decimal_places=2)
        period = models.IntegerField(verbose_name='学习建议周期(month)', default=0)
    
    # 评论表:分三个表、(id,ctx,date,user_id,free_course_id, comment_id)
    # 老师表:在课程表建立多对一外键
    # 章节表:在章节表建立多对一外键关联课程
    # 课时表:在课时表建立多对一外键关联章节
    
    '''
    
    
    from django.db import models
    # 课程分类
    from utils.model import BaseModel
    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="课程状态")
        pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0)
        price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0)
    
        students = models.IntegerField(verbose_name="学习人数", default=0)
        sections = models.IntegerField(verbose_name="总课时数量", default=0)
    
        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="授课老师")
    
        @property
        def level_name(self):
            return self.get_level_display()
    
        # 最多显示本课程前4节
        @property
        def section_list(self):
            section_list_temp = []
    
            for chapter in self.coursechapters.all():
                for section in chapter.coursesections.all():
                    section_list_temp.append({
                        'chapter': chapter.chapter,
                        'orders': section.orders,
                        'name': section.name
                    })
                    if len(section_list_temp) >= 4:
                        return section_list_temp
            # 不足4条
            return section_list_temp
    
    
    
        class Meta:
            db_table = "luffy_course"
            verbose_name = "课程"
            verbose_name_plural = "课程"
    
        def __str__(self):
            return "%s" % 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="导师描述")
    
        @property
        def role_name(self):
            return self.get_role_display()
    
        class Meta:
            db_table = "luffy_teacher"
            verbose_name = "导师"
            verbose_name_plural = verbose_name
    
        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):
            try:
                return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)
            except:
                return super().__str__()
    
    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="课时标题")
        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):
            try:
                return "%s-%s" % (self.chapter, self.name)
            except:
                return super().__str__()
    
    

    使用xadmin管理表

    1.更改admin为adminx
    
    
    import xadmin
    from . import models
    # 使用xadmin管理表
    xadmin.site.register(models.Course)
    xadmin.site.register(models.CourseCategory)
    xadmin.site.register(models.CourseChapter)
    xadmin.site.register(models.CourseSection)
    xadmin.site.register(models.Teacher)
    

    执行数据库迁移

    python ../../manage.py makemigrations
    
    python ../../manage.py migrate
    

    更改显示信息

    course

    # init.py
    
    default_app_config = "course.apps.CourseConfig"
    
    
    # apps.py
    
    from django.apps import AppConfig
    
    class CourseConfig(AppConfig):
        name = 'course'
        verbose_name = '课程业务'
    

    课程表数据录入

    资源手动迁移

    # 头像图片放在 media/teacher 文件夹下
    # 课程图片放在 media/course 文件夹下
    

    老师表

    INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (1, 1, 1, 0, '2019-07-14 13:44:19.661327', '2019-07-14 13:46:54.246271', 'Alex', 1, '老男孩Python教学总监', '金角大王', 'teacher/alex_icon.png', '老男孩教育CTO & CO-FOUNDER 国内知名PYTHON语言推广者 51CTO学院20162017年度最受学员喜爱10大讲师之一 多款开源软件作者 曾任职公安部、飞信、中金公司、NOKIA中国研究院、华尔街英语、ADVENT、汽车之家等公司');
    
    INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (2, 2, 1, 0, '2019-07-14 13:45:25.092902', '2019-07-14 13:45:25.092936', 'Mjj', 0, '前美团前端项目组架构师', NULL, 'teacher/mjj_icon.png', '是马JJ老师, 一个集美貌与才华于一身的男人,搞过几年IOS,又转了前端开发几年,曾就职于美团网任高级前端开发,后来因为不同意王兴(美团老板)的战略布局而出家做老师去了,有丰富的教学经验,开起车来也毫不含糊。一直专注在前端的前沿技术领域。同时,爱好抽烟、喝酒、烫头(锡纸烫)。 我的最爱是前端,因为前端妹子多。');
    
    INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (3, 3, 1, 0, '2019-07-14 13:46:21.997846', '2019-07-14 13:46:21.997880', 'Lyy', 0, '老男孩Linux学科带头人', NULL, 'teacher/lyy_icon.png', 'Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸');
    

    分类表

    INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (1, 1, 1, 0, '2019-07-14 13:40:58.690413', '2019-07-14 13:40:58.690477', 'Python');
    
    INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (2, 2, 1, 0, '2019-07-14 13:41:08.249735', '2019-07-14 13:41:08.249817', 'Linux');
    

    课程表

    INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (1, 1, 1, 0, '2019-07-14 13:54:33.095201', '2019-07-14 13:54:33.095238', 'Python开发21天入门', 'courses/alex_python.png', 0, 'Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土', 0, '2019-07-14', 21, '', 0, 231, 120, 120, 0.00, 1, 1);
    
    INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (2, 2, 1, 0, '2019-07-14 13:56:05.051103', '2019-07-14 13:56:05.051142', 'Python项目实战', 'courses/mjj_python.png', 0, '', 1, '2019-07-14', 30, '', 0, 340, 120, 120, 99.00, 1, 2);
    
    INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (3, 3, 1, 0, '2019-07-14 13:57:21.190053', '2019-07-14 13:57:21.190095', 'Linux系统基础5周入门精讲', 'courses/lyy_linux.png', 0, '', 0, '2019-07-14', 25, '', 0, 219, 100, 100, 39.00, 2, 3);
    

    章节表

    INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (1, 1, 1, 0, '2019-07-14 13:58:34.867005', '2019-07-14 14:00:58.276541', 1, '计算机原理', '', '2019-07-14', 1);
    
    INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (2, 2, 1, 0, '2019-07-14 13:58:48.051543', '2019-07-14 14:01:22.024206', 2, '环境搭建', '', '2019-07-14', 1);
    
    INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (3, 3, 1, 0, '2019-07-14 13:59:09.878183', '2019-07-14 14:01:40.048608', 1, '项目创建', '', '2019-07-14', 2);
    
    INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (4, 4, 1, 0, '2019-07-14 13:59:37.448626', '2019-07-14 14:01:58.709652', 1, 'Linux环境创建', '', '2019-07-14', 3);
    

    课时表

    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (1, 1, 0, '2019-07-14 14:02:33.779098', '2019-07-14 14:02:33.779135', '计算机原理上', 1, 2, NULL, NULL, '2019-07-14 14:02:33.779193', 1, 1);
    
    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (2, 1, 0, '2019-07-14 14:02:56.657134', '2019-07-14 14:02:56.657173', '计算机原理下', 2, 2, NULL, NULL, '2019-07-14 14:02:56.657227', 1, 1);
    
    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (3, 1, 0, '2019-07-14 14:03:20.493324', '2019-07-14 14:03:52.329394', '环境搭建上', 1, 2, NULL, NULL, '2019-07-14 14:03:20.493420', 0, 2);
    
    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (4, 1, 0, '2019-07-14 14:03:36.472742', '2019-07-14 14:03:36.472779', '环境搭建下', 2, 2, NULL, NULL, '2019-07-14 14:03:36.472831', 0, 2);
    
    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (5, 1, 0, '2019-07-14 14:04:19.338153', '2019-07-14 14:04:19.338192', 'web项目的创建', 1, 2, NULL, NULL, '2019-07-14 14:04:19.338252', 1, 3);
    
    INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (6, 1, 0, '2019-07-14 14:04:52.895855', '2019-07-14 14:04:52.895890', 'Linux的环境搭建', 1, 2, NULL, NULL, '2019-07-14 14:04:52.895942', 1, 4);
    

    分类群查接口与课程群查接口

    urls

    from django.urls import path,re_path,include
    from . import views
    
    from utils.router import router
    # 群查分类
    router.register('categories',views.CategoryListViewSet,'category')
    # 群查课程主页
    router.register('free',views.FreeCourseListViewSet,'free-course')
    
    urlpatterns = [
        path('',include(router.urls)),
    ]
    
    

    views

    from  rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    from . import models, serializers
    
    
    # 分类群查
    class CategoryListViewSet(ListModelMixin, GenericViewSet):
        queryset = models.CourseCategory.objects.filter(is_delete=False,is_show=True).all()
        serializer_class = serializers.CategoryModelSerializer
    
    
    # 课程群查接口
    class FreeCourseListViewSet(ListModelMixin, GenericViewSet):
        queryset = models.Course.objects.filter(is_delete=False,is_show=True).all()
        serializer_class = serializers.CourseModelSerializer
    

    Serializer

    from rest_framework import serializers
    
    from . import models
    class CategoryModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.CourseCategory
            fields = ('name', )
    
    
    # 子序列化类
    class TeacherModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Teacher
            fields = (
                'name',
                'title',
                'signature',
                'image',
                'brief',
                'role_name',
            )
    
    class CourseModelSerializer(serializers.ModelSerializer):
        teacher = TeacherModelSerializer()
        class Meta:
            model = models.Course
            fields = (
                'name',
                'course_img',
                'level_name',
                'period',
                'price',
                'students',
                'sections',
                'teacher',
                'section_list',
            )
    
    

    models

    '''
    from django.db import models
    class Course(models.Model):
        # 基础字段
        name = models.CharField(max_length=64)
        title = models.CharField(max_length=64)
        level = models.IntegerField(choices=((0, '入门'), (1, '进阶')), default=0)
        detail = models.TextField()  # 可以关联详情表
        type = models.IntegerField(choices=((0, 'Python'), (1, 'Linux')), default=0)
        is_show = models.BooleanField(default=False)
    
        # 优化字段
        students = models.IntegerField(default=0)   # 热度
        time = models.IntegerField(default=0)   # 总时长
        sections = models.IntegerField(default=0)   # 总课时
    
        # 虚拟表基类
        class Meta:
            abstract = True
    
    # 免费课
    class FreeCourse(Course):
        image = models.ImageField(upload_to='course/free')
        attachment = models.FileField(upload_to='attachment')
    
    # 实战课
    class ActualCourse(Course):
        image = models.ImageField(upload_to='course/actual')
        price = models.DecimalField(max_digits=7, decimal_places=2)
        cost = models.DecimalField(max_digits=7, decimal_places=2)
    
    # 轻课
    class LightCourse(Course):
        image = models.ImageField(upload_to='course/light')
        price = models.DecimalField(max_digits=7, decimal_places=2)
        cost = models.DecimalField(max_digits=7, decimal_places=2)
        period = models.IntegerField(verbose_name='学习建议周期(month)', default=0)
    
    # 评论表:分三个表、(id,ctx,date,user_id,free_course_id, comment_id)
    # 老师表:在课程表建立多对一外键
    # 章节表:在章节表建立多对一外键关联课程
    # 课时表:在课时表建立多对一外键关联章节
    
    '''
    
    
    from django.db import models
    # 课程分类
    from utils.model import BaseModel
    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="课程状态")
        pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0)
        price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0)
    
        students = models.IntegerField(verbose_name="学习人数", default=0)
        sections = models.IntegerField(verbose_name="总课时数量", default=0)
    
        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="授课老师")
    
        # 等级
        @property
        def level_name(self):
            return self.get_level_display()
    
        # 最多显示本课程前4节
        @property
        def section_list(self):
            section_list_temp = []
    
            for chapter in self.coursechapters.all():
                for section in chapter.coursesections.all():
                    section_list_temp.append({
                        'chapter': chapter.chapter,
                        'orders': section.orders,
                        'name': section.name
                    })
                    if len(section_list_temp) >= 4:
                        return section_list_temp
            # 不足4条
            return section_list_temp
    
    
    
        class Meta:
            db_table = "luffy_course"
            verbose_name = "课程"
            verbose_name_plural = "课程"
    
        def __str__(self):
            return "%s" % 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="导师描述")
    
        @property
        def role_name(self):
            return self.get_role_display()
    
        class Meta:
            db_table = "luffy_teacher"
            verbose_name = "导师"
            verbose_name_plural = verbose_name
    
        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):
            try:
                return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)
            except:
                return super().__str__()
    
    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="课时标题")
        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):
            try:
                return "%s-%s" % (self.chapter, self.name)
            except:
                return super().__str__()
    
    
  • 相关阅读:
    Javascript FP-ramdajs
    微信小程序开发
    SPA for HTML5
    One Liners to Impress Your Friends
    Sass (Syntactically Awesome StyleSheets)
    iOS App Icon Template 5.0
    React Native Life Cycle and Communication
    Meteor framework
    RESTful Mongodb
    Server-sent Events
  • 原文地址:https://www.cnblogs.com/fwzzz/p/12184507.html
Copyright © 2011-2022 走看看