Django之ContentType组件

一、理想表结构设计

1、初始构建

1. 场景
刚过去的双12,很多电商平台都会对他们的商品进行打折促销活动的,
那么我们如果要实现这样的一个场景,改如何设计我们的表?


2. 初始表设计

注释很重要,看看吧

from django.db import models

# Create your models here.


# 食物类
class Food(models.Model):
    '''
    理想中的表数据
    id      title
    1       猪肉
    2       牛肉
    '''
    title = models.CharField(max_length=32)


# 水果类
class Fruit(models.Model):
    '''
    理想中的表数据
    id      title
    1       苹果
    2       榴莲
    '''
    title = models.CharField(max_length=32)


# 双12打折,优惠卷类
class Coupon(models.Model):
    '''
    理想中的数据表
    id        title        food_id      fruit_id    xxx_id  ...
    1       猪肉9.9折          1           null
    2       榴莲11折          null           2
    但是这样构建表结构是不现实的,因为如果我的分类有几十种呢,不就要几十个外键吗
    ########################################################################
    因此,我们想到了另一种方法,再建一张MyTable表,专门用于存放表,我们的优惠表就可以如下设计
    id      title       table_id        object_id
    1       猪肉9.9折       1              1
    2       榴莲11折        2              2

    table_id找到是哪一张表,object_id是找到那种表的某条数据
    '''
    title = models.CharField(max_length=32)
    # 定位表
    table = models.ForeignKey(to='MyTable')
    object_id = models.IntegerField()


class MyTable(models.Model):
    '''
    id      app_name        table_name
    1         Demo              food
    2         Demo              fruit
    3         Demo              coupon
    '''
    app_name = models.CharField(max_length=32)
    table_name = models.CharField(max_length=32)

 

3. 缺点
这样设计的缺点在于,我每次新建一张表,就必须到MyTable表里面记录,
但是不用担心,我们能想到的,Django也帮我们想到了,
Django的ContentType组件就帮我们实现了跟MyTable一样的表结构,而且你每建一张表,都会自动录进去,不需要自己录入。

 

二、ContentType

1、介绍

ContentType是Django的内置的一个应用,可以追踪项目中所有的APP和model的对应关系,并记录在ContentType表中。
当我们的项目做数据迁移后,会有很多django自带的表,其中就有django_content_type表。

ContentType组件应用:
  -- 在model中定义ForeignKey字段,并关联到ContentType表,通常这个字段命名为content_type(使用其他命名反向查询会报错)

  -- 在model中定义PositiveIntergerField字段, 用来存储关联表中的主键,通常我们用object_id

  -- 在model中定义GenericForeignKey字段,传入上面两个字段的名字

  -- 方便反向查询可以定义GenericRelation字段

注意:
  -- 也就是说GenericForeignKey是正向查,GenericRelation是反向查
  -- ContentType表中的对象可以使用model_class()这个方法拿到具体的那张表

 

2、models

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

# Create your models here.


class Food(models.Model):
    '''
    理想中的表数据
    id      title
    1       猪肉
    2       牛肉
    '''
    title = models.CharField(max_length=32)
    # 只用于反向查询,也不生成字段
    coupons = GenericRelation(to='Coupon')


class Fruit(models.Model):
    '''
    理想中的表数据
    id      title
    1       苹果
    2       榴莲
    '''
    title = models.CharField(max_length=32)


# 双12打折,有优惠价
class Coupon(models.Model):
    '''
    理想中的数据表
    id        title        food_id      fruit_id    xxx_id  ...
    1       猪肉9.9折          1           null
    2       榴莲11折          null           2
    但是这样构建表结构是不现实的,因为如果我的分类有几十种呢,不就要几十个外键吗
    ########################################################################
    因此,我们想到了另一种方法,再建一张MyTable表,专门用于存放表,我们的优惠表就可以如下设计
    id      title       table_id        object_id
    1       猪肉9.9折       1              1
    2       榴莲11折        2              2

    table_id找到是哪一张表,object_id是找到那种表的某条数据
    '''
    title = models.CharField(max_length=32)
    # 自己的表
    # table = models.ForeignKey(to='MyTable')
    # object_id = models.IntegerField()
    ########################################################
    # Django提供的表
    # 1.跟ComtentType表绑定关系
    content_type = models.ForeignKey(to=ContentType)
    # 2.对象的id
    object_id = models.PositiveIntegerField()
#------------------------------------------------------#
    # 如果就这样创建表,那么在新建这个表的数据的时候会很麻烦
    # 比如:
    # table_id = ContentType.objects.filter(app_label='Demo', model='food').first().id
    # Coupon.objects.create(title=xxx, content_type_id=table_id, object_id=x)
    # 因此Django也给我们提供了方便的用法,通过表id和object_id不就是为了定位到具体的某个对象吗
    # 使用content_obj,会直接根据这个对象去帮你找到对应的content_type_id和object_id
    # 这样创建表的时候就容易多了:Coupon.object.create(title=xxx, content_obj=obj)
    # 注意:content_obj不是字段,不存在数据库中的,只是Django为了给我们提供方便而帮我们做的一些操作

    # 3.通过外键关系给表及对象id绑定关系,不生成字段
    content_obj = GenericForeignKey('content_type', 'object_id')

 

3、示例

class TestView(views.View):
    def get(self, request):
        # 原始方法给猪肉创建优惠卷
        # ContentType的字段是app_label和model
        content_type_id = ContentType.objects.filter(app_label='Demo', model='food').first().id
        object_id = Food.objects.filter(title='猪肉').first().id
        Coupon.objects.create(title='猪肉9.9折', content_type_id=content_type_id, object_id=object_id)

        # 用ContentType给榴莲创建优惠卷
        obj = Fruit.objects.filter(title='榴莲').first()
        Coupon.objects.create(title='榴莲11折', content_obj=obj)

        # 原始方法查榴莲的所有优惠卷
        content_type = ContentType.objects.filter(app_label='Demo', model='fruit').first()
        ret = Coupon.objects.filter(content_type=content_type, object_id=2).all()
        print(ret[0].title)

        # 用Django提供的ContentType的反向查猪肉的优惠卷
        ret = Food.objects.filter(title='猪肉').first().coupons.all()
        print(ret[0].title)

        # 正向查,优惠卷id=1绑定了哪个对象
        coupon_obj = Coupon.objects.filter(id=1).first()
        print(coupon_obj.content_obj.title)

        # 通过ContentType获得表名
        content = ContentType.objects.filter(app_label='Demo', model='fruit').first()
        # 获得表model对象 相当于models.Fruit
        fruit_model = content.model_class()
        fruit_list = fruit_model.objects.all()
        for fruit in fruit_list:
            print(fruit.title)

        return HttpResponse('ok')

 

posted @ 2018-12-16 16:17  我用python写Bug  阅读(376)  评论(0编辑  收藏  举报