Skip to content

about

python3.11.7 + django5.0.1

contenttypes组件

Django项目中默认开启了内置组件contenttypes,参考django5.0官网:https://docs.djangoproject.com/zh-hans/5.0/ref/contrib/contenttypes/

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',  # 就它
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.ctts.apps.CttsConfig',
    'apps.drf.apps.DrfConfig',
    'rest_framework',
]

想要了解它的主要作用,我们需要探讨一下工作中常见的场景中,表结构是如何设计的:

  • 旅游:北京三天精品游、云南7日畅享游、欧洲15天尽兴游.....各种旅游套餐,以及它们的定价策略的表结构设计。
  • 线上教育培训卖的各种课程,各种不同的课程种类,以及不同的时长套餐,以及它们的定价策略的表结构设计。
  • ......

我们来搞个场景分析下。

需求场景:设计课程和价格价格策略表结构

那么你可能会想到:

ID课程价格
1python600
2linux600
3java600

这种方式的确简单省事儿,但不够灵活,无法自定制各种价格策略信息。

如果不同的课程根据分类不同,不能放到一个表中;同时不同的课程也有不同的价格策略该如何设计表结构呢?实现方式其实有多种。

实现方式1:一个课程分类,对应一个价格策略

这种方式省时省力,就是要实现表结构有点多,一个分类课程,就要对应一张价格策略表。

PS:下面表格中包含多个表结构,你觉得看着不够清晰方便的话,可以把这个在Excel表中列出来一份,观看就比较直观了....

一对一全栈课全栈课价格策略表
ID课程ID周期(天)价格(元)对应的课程ID
1python1606001
2linux29010001
3java318015001
4605002
5907002
6603003
71806003
web专题课web专题课价格策略表对应的课程ID
ID课程ID周期(天)价格(元)1
1Django实战1603001
2flask实战2905001
3fastapi实战31807002
4907002
5603003
61806003
轻课课程轻课价格策略表对应的课程ID
ID课程ID周期(天)价格(元)1
1js逆向1603001
2网络攻防2905001
3app逆向31807002
4907002
5603003
61806003

上面这种方式,简单清晰,就是表有点多。

实现方式2:每个课程分类都添加到价格策略表中去

这种就是优化了价格策略表,每个分类的价格策略只需要填写到自己的字段中就行,其他分类字段值保留为空或者设置为null就行了。

一对一全栈课价格策略表
ID课程ID周期(天)价格(元)全栈表IDweb专题表ID轻课表ID
1python1606001
2linux29010001
3java318015001
49010002
web专题课518015002
ID课程618015003
1Django实战7605001
2flask实战8907001
3fastapi实战9603002
101806003
轻课课程11605001
ID课程12907002
1js逆向13603003
2网络攻防141806003
3app逆向1536010003

实现方式3:在分类课程和价格策略这两张表的基础上,引入第三张表

这第三张表专门用于记录各个表的表名的,来看如何设计这三个表的表关系吧。

主要理解价格策略表就行,如它第一行记录,表示了这样的一个条记录:一个周期为60天、价格为600元的全栈课程的Python课程价格策略记录。

一对一全栈课表记录表
ID课程ID表名
1python1全栈课
2linux2web专题课
3java3轻课
价格策略表
ID周期(天)价格(元)对应表ID(来自表记录表)对应表记录ID(来自各个分类课)
web专题课16060011
ID课程290100011
1Django实战3180150011
2flask实战490100012
3fastapi实战5180150012
6180150013
76050021
89070021
96030022
轻课课程1018060023
ID课程116050031
1js逆向129070031
2网络攻防136030032
3app逆向1418060033

接下来我们主要了解这种实现方式,因为Django的contenttypes内置组件扮演的就是上面三张表中表记录表的角色。

基本使用

首先要保证启用contenttypes内置组件

settings.py配置文件中:

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',  # 这个不能注释
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.ctts.apps.CttsConfig',
    'apps.drf.apps.DrfConfig',
    'rest_framework',
]

然后配置好了数据库配置,并执行了数据库迁移的两条命令:

bash
python manage.py makemigrations
python manage.py migrate

然后就可以实操了。

实操

这里以FullStackCourse表和PricePolicy来演示怎么用,其它课程表的操作一致,没必要重复写了。

首先创建一些数据:

python
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType


class FullStackCourse(models.Model):
    name = models.CharField(max_length=64, verbose_name='课程名称')

    # 用于GenericForeignKey反向查询,不会生成表字段
    fullstack_price_policy = GenericRelation('PricePolicy', related_query_name='fullstack_price_policy')

    def __str__(self):
        return self.name


class WebCourse(models.Model):
    name = models.CharField(max_length=64, verbose_name='课程名称')
    # 用于GenericForeignKey反向查询,不会生成表字段
    web_price_policy = GenericRelation('PricePolicy', related_query_name='web_price_policy')

    def __str__(self):
        return self.name


class LightLessonCourse(models.Model):
    name = models.CharField(max_length=64, verbose_name='课程名称')
    # 用于GenericForeignKey反向查询,不会生成表字段
    lightlesson_price_policy = GenericRelation('PricePolicy', related_query_name='lightlesson_price_policy')

    def __str__(self):
        return self.name


class PricePolicy(models.Model):
    """ 结合Django内置的Contenttypes组件实现的价格策略表 """
    price = models.FloatField(verbose_name='价格(元)')
    valid_period = models.PositiveIntegerField(verbose_name='有效期(天)')
    
    # 下面三行是固定写法,与各课程进行关联,方便进行增删改查操作
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    def __str__(self):
        return '%s%s' % (self.content_object, self.price)
python
# -*- coding = utf-8 -*-
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj5.settings")  # orderapi:项目名称
django.setup()  # 这一步执行完就在脚本中加载了Django环境

from apps.ctts.models import WebCourse, FullStackCourse, LightLessonCourse, PricePolicy


def init_data():
    # 创建课程
    FullStackCourse.objects.bulk_create([
        FullStackCourse(name='python'),
        FullStackCourse(name='linux'),
        FullStackCourse(name='java')
    ])

    # 创建价格策略表记录
    PricePolicy.objects.bulk_create([
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=600, valid_period=60),
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=1000, valid_period=90),
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=1500, valid_period=180),
        PricePolicy(content_object=FullStackCourse.objects.get(name='linux'), price=500, valid_period=90),
        PricePolicy(content_object=FullStackCourse.objects.get(name='linux'), price=1500, valid_period=180),
        PricePolicy(content_object=FullStackCourse.objects.get(name='java'), price=500, valid_period=60),
        PricePolicy(content_object=FullStackCourse.objects.get(name='java'), price=1500, valid_period=180),
    ])


if __name__ == '__main__':
    init_data()

生成一些数据之后,各个表如下:

1832669373580443648.png

增删改查操作

这里模型类不变,所以只列出来脚本文件即可:

python
# -*- coding = utf-8 -*-
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj5.settings")  # orderapi:项目名称
django.setup()  # 这一步执行完就在脚本中加载了Django环境

from apps.ctts.models import WebCourse, FullStackCourse, LightLessonCourse, PricePolicy


def init_data():
    # 创建课程
    FullStackCourse.objects.bulk_create([
        FullStackCourse(name='python'),
        FullStackCourse(name='linux'),
        FullStackCourse(name='java')
    ])

    # 创建价格策略表记录
    PricePolicy.objects.bulk_create([
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=600, valid_period=60),
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=1000, valid_period=90),
        PricePolicy(content_object=FullStackCourse.objects.get(name='python'), price=1500, valid_period=180),
        PricePolicy(content_object=FullStackCourse.objects.get(name='linux'), price=500, valid_period=90),
        PricePolicy(content_object=FullStackCourse.objects.get(name='linux'), price=1500, valid_period=180),
        PricePolicy(content_object=FullStackCourse.objects.get(name='java'), price=500, valid_period=60),
        PricePolicy(content_object=FullStackCourse.objects.get(name='java'), price=1500, valid_period=180),
    ])


def query_data():
    # ------------ 增/删/改 ------------
    # 通过价格策略表进行增删改都按照单表操作即可,但不建议这么做,通常你可以通过课程对象来完成其对应价格策略的增删改查
    # PricePolicy.objects.create(content_object=FullStackCourse.objects.get(name='python'), price=1800, valid_period=360)


    # 通过为课程表中的某个课程添加价格策略
    # fs_obj = FullStackCourse(name='go')
    # fs_obj.save()  # 注意,这里必须先save一下,把课程对象的记录创建出来,然后才能添加一个对应的价格策略记录
    # fs_obj.fullstack_price_policy.create(price=1800, valid_period=360)
    # fs_obj.save()

    # 先整出来两个价格策略记录
    # p1 = PricePolicy.objects.create(content_object=FullStackCourse.objects.get(name='go'), price=1000, valid_period=90)
    # p2 = PricePolicy.objects.create(content_object=FullStackCourse.objects.get(name='go'), price=1500, valid_period=180)

    # 在原有的价格策略上添加新的策略
    # fs_obj = FullStackCourse.objects.get(name='go')
    # fs_obj.fullstack_price_policy.add(p1, p2)
    # fs_obj.save()
    # print(fs_obj.fullstack_price_policy.all())

    # 直接替换原有的价格策略,相当于是覆盖更新
    # fs_obj = FullStackCourse.objects.get(name='go')
    # fs_obj.fullstack_price_policy.set([p1, p2])
    # fs_obj.save()
    # print(fs_obj.fullstack_price_policy.all())

    # 删除掉指定的某条价格策略,注意,remove中必须是一个价格策略对象,不能是个queryset
    # fs_obj = FullStackCourse.objects.get(name='go')
    # fs_obj.fullstack_price_policy.remove(PricePolicy.objects.filter(price=1000, valid_period=90, object_id=4).first())
    # fs_obj.save()
    # print(fs_obj.fullstack_price_policy.all())

    # 清空掉当前课程的所有价格策略
    # fs_obj = FullStackCourse.objects.get(name='go')
    # fs_obj.fullstack_price_policy.clear()
    # fs_obj.save()
    # print(print(fs_obj.fullstack_price_policy.all()))

    # ------------ 查询 ------------
    #  查询课程信息 查询课程信息
    # for course in FullStackCourse.objects.all():
    #     # 对于每个课程对应的所有价格策略,直接通过fullstack_price_policy外键字段获取即可
    #     print(course.name, course.fullstack_price_policy.all())
    """
    python <QuerySet [<PricePolicy: python:600.0>, <PricePolicy: python:1000.0>, <PricePolicy: python:1500.0>]>
    linux <QuerySet [<PricePolicy: linux:500.0>, <PricePolicy: linux:1500.0>]>
    java <QuerySet [<PricePolicy: java:500.0>, <PricePolicy: java:1500.0>]>
    """
    # 查询价格策略信息
    # for policy in PricePolicy.objects.all():
    #     # 对应的课程表记录,直接通过content_object直接能拿到课程表的对象,然后想要啥字段你点啥字段
    #     print(policy.content_object.name, policy.price, policy.valid_period)
    """
    python 600.0 60
    python 1000.0 90
    python 1500.0 180
    linux 500.0 90
    linux 1500.0 180
    java 500.0 60
    java 1500.0 180
    """


if __name__ == '__main__':
    # init_data()
    query_data()