about
python3.11.7 + django5.0.1
contenttypes组件
Django项目中默认开启了内置组件contenttypes
,参考django5.0官网:https://docs.djangoproject.com/zh-hans/5.0/ref/contrib/contenttypes/
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 | 课程 | 价格 |
---|---|---|
1 | python | 600 |
2 | linux | 600 |
3 | java | 600 |
这种方式的确简单省事儿,但不够灵活,无法自定制各种价格策略信息。
如果不同的课程根据分类不同,不能放到一个表中;同时不同的课程也有不同的价格策略该如何设计表结构呢?实现方式其实有多种。
实现方式1:一个课程分类,对应一个价格策略
这种方式省时省力,就是要实现表结构有点多,一个分类课程,就要对应一张价格策略表。
PS:下面表格中包含多个表结构,你觉得看着不够清晰方便的话,可以把这个在Excel表中列出来一份,观看就比较直观了....
一对一全栈课 | 全栈课价格策略表 | |||||
---|---|---|---|---|---|---|
ID | 课程 | ID | 周期(天) | 价格(元) | 对应的课程ID | |
1 | python | 1 | 60 | 600 | 1 | |
2 | linux | 2 | 90 | 1000 | 1 | |
3 | java | 3 | 180 | 1500 | 1 | |
4 | 60 | 500 | 2 | |||
5 | 90 | 700 | 2 | |||
6 | 60 | 300 | 3 | |||
7 | 180 | 600 | 3 | |||
web专题课 | web专题课价格策略表 | 对应的课程ID | ||||
ID | 课程 | ID | 周期(天) | 价格(元) | 1 | |
1 | Django实战 | 1 | 60 | 300 | 1 | |
2 | flask实战 | 2 | 90 | 500 | 1 | |
3 | fastapi实战 | 3 | 180 | 700 | 2 | |
4 | 90 | 700 | 2 | |||
5 | 60 | 300 | 3 | |||
6 | 180 | 600 | 3 | |||
轻课课程 | 轻课价格策略表 | 对应的课程ID | ||||
ID | 课程 | ID | 周期(天) | 价格(元) | 1 | |
1 | js逆向 | 1 | 60 | 300 | 1 | |
2 | 网络攻防 | 2 | 90 | 500 | 1 | |
3 | app逆向 | 3 | 180 | 700 | 2 | |
4 | 90 | 700 | 2 | |||
5 | 60 | 300 | 3 | |||
6 | 180 | 600 | 3 |
上面这种方式,简单清晰,就是表有点多。
实现方式2:每个课程分类都添加到价格策略表中去
这种就是优化了价格策略表,每个分类的价格策略只需要填写到自己的字段中就行,其他分类字段值保留为空或者设置为null就行了。
一对一全栈课 | 价格策略表 | |||||||
---|---|---|---|---|---|---|---|---|
ID | 课程 | ID | 周期(天) | 价格(元) | 全栈表ID | web专题表ID | 轻课表ID | |
1 | python | 1 | 60 | 600 | 1 | |||
2 | linux | 2 | 90 | 1000 | 1 | |||
3 | java | 3 | 180 | 1500 | 1 | |||
4 | 90 | 1000 | 2 | |||||
web专题课 | 5 | 180 | 1500 | 2 | ||||
ID | 课程 | 6 | 180 | 1500 | 3 | |||
1 | Django实战 | 7 | 60 | 500 | 1 | |||
2 | flask实战 | 8 | 90 | 700 | 1 | |||
3 | fastapi实战 | 9 | 60 | 300 | 2 | |||
10 | 180 | 600 | 3 | |||||
轻课课程 | 11 | 60 | 500 | 1 | ||||
ID | 课程 | 12 | 90 | 700 | 2 | |||
1 | js逆向 | 13 | 60 | 300 | 3 | |||
2 | 网络攻防 | 14 | 180 | 600 | 3 | |||
3 | app逆向 | 15 | 360 | 1000 | 3 |
实现方式3:在分类课程和价格策略这两张表的基础上,引入第三张表
这第三张表专门用于记录各个表的表名的,来看如何设计这三个表的表关系吧。
主要理解价格策略表就行,如它第一行记录,表示了这样的一个条记录:一个周期为60天、价格为600元的全栈课程的Python课程价格策略记录。
一对一全栈课 | 表记录表 | ||||||
---|---|---|---|---|---|---|---|
ID | 课程 | ID | 表名 | ||||
1 | python | 1 | 全栈课 | ||||
2 | linux | 2 | web专题课 | ||||
3 | java | 3 | 轻课 | ||||
价格策略表 | |||||||
ID | 周期(天) | 价格(元) | 对应表ID(来自表记录表) | 对应表记录ID(来自各个分类课) | |||
web专题课 | 1 | 60 | 600 | 1 | 1 | ||
ID | 课程 | 2 | 90 | 1000 | 1 | 1 | |
1 | Django实战 | 3 | 180 | 1500 | 1 | 1 | |
2 | flask实战 | 4 | 90 | 1000 | 1 | 2 | |
3 | fastapi实战 | 5 | 180 | 1500 | 1 | 2 | |
6 | 180 | 1500 | 1 | 3 | |||
7 | 60 | 500 | 2 | 1 | |||
8 | 90 | 700 | 2 | 1 | |||
9 | 60 | 300 | 2 | 2 | |||
轻课课程 | 10 | 180 | 600 | 2 | 3 | ||
ID | 课程 | 11 | 60 | 500 | 3 | 1 | |
1 | js逆向 | 12 | 90 | 700 | 3 | 1 | |
2 | 网络攻防 | 13 | 60 | 300 | 3 | 2 | |
3 | app逆向 | 14 | 180 | 600 | 3 | 3 |
接下来我们主要了解这种实现方式,因为Django的contenttypes内置组件扮演的就是上面三张表中表记录表的角色。
基本使用
首先要保证启用contenttypes内置组件
settings.py
配置文件中:
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',
]
然后配置好了数据库配置,并执行了数据库迁移的两条命令:
python manage.py makemigrations
python manage.py migrate
然后就可以实操了。
实操
这里以FullStackCourse表和PricePolicy来演示怎么用,其它课程表的操作一致,没必要重复写了。
首先创建一些数据:
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)
# -*- 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()
生成一些数据之后,各个表如下:
增删改查操作
这里模型类不变,所以只列出来脚本文件即可:
# -*- 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()