django內置組件——ContentTypes


一、什么是Django ContentTypes?

  Django ContentTypes是由Django框架提供的一個核心功能,它對當前項目中所有基於Django驅動的model提供了更高層次的抽象接口。主要用來創建模型間的通用關系(generic relation)。

  進一步了解ContentTypes可以直接查閱以下這兩個鏈接:

二、Django ContentTypes做了什么?

  當創建一個django項目時,可以看到在默認的INSTALL_APPS已經包含了django.contrib.contenttypes。

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

  注意:django.contrib.contenttypes是在django.contrib.auth之后,這是因為auth中的permission系統是根據contenttypes來實現的

  導入contenttypes組件:

from django.contrib.contenttypes.models import ContentType

  查看django.contrib.contenttypes.models.ContentType類的內容:

class ContentType(models.Model):
    app_label = models.CharField(max_length=100)
    model = models.CharField(_('python model class name'), max_length=100)
    objects = ContentTypeManager()

    class Meta:
        verbose_name = _('content type')
        verbose_name_plural = _('content types')
        db_table = 'django_content_type'
        unique_together = (('app_label', 'model'),)

    def __str__(self):
        return self.name

  可以看到ContentType就是一個簡單的django model,而且它在數據庫中的表的名字為django_content_type。 

  在第一次對Django的model進行migrate之后,就可以發現在數據庫中出現了一張默認生成的名為django_content_type的表。 
如果沒有建立任何的model,默認django_content_type是前六項:

  

  django_content_type記錄了當前的Django項目中所有model所屬的app(即app_label屬性)以及model的名字(即model屬性)。 

  django_content_type並不只是記錄屬性這么簡單.了contenttypes是對model的一次封裝,因此可以通過contenttypes動態的訪問model類型,而不需要每次import具體的model類型。

1、ContentType實例提供的接口

  • ContentType.model_class() 

    獲取當前ContentType類型所代表的模型類

  • ContentType.get_object_for_this_type() 

    使用當前ContentType類型所代表的模型類做一次get查詢

2、ContentType管理器(manager)提供的j接口

  • ContentType.objects.get_for_id()
    • 通過id尋找ContentType類型,這個跟傳統的get方法的區別就是它跟get_for_model共享一個緩存,因此更為推薦。
  • ContentType.objects.get_for_model()
    • 通過model或者model的實例來尋找ContentType類型

 三、Django ContentTypes框架使用場景

1、設計模型(創建表結構)

  假設我們創建如下模型,里面包含學位課程、專題課程、價格策略。

  價格策略既可以是專題課程的價格策略,也可以是學位課程的價格策略。需要在pricepolicy對象里添加非常多的ForeignKey。示例如下所示:

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)

# 如果有40張表,則每一個都要建立外鍵關系
class Coupon(models.Model):
    """
    id      title          food_id    fruit_id
    1       面包九五折         1         null
    2       香蕉滿10元減5元    null       2
    """
    title = models.CharField(max_length=32)
    food = models.ForeignKey(to="Food")
    fruit = models.ForeignKey(to="Fruit")

 

  這樣做很傻,會造成代碼重復和字段浪費。有一種優化的方案是:用兩個字段去定位對象不用去創建多個外鍵關系

# 方法二:用兩個字段去定位對象不用去創建多個外鍵關系
class Coupon(models.Model):
    """
    id      title          table_id      object_id(對應表對應對象的ID)
    1       面包九五折          1             1
    2       香蕉滿10元減5元     2             2
    """
    title = models.CharField(max_length=32)
    table = models.ForeignKey(to="Table")    # 與table表建立外鍵關系
    object_id = models.IntegerField()        # 由object_id定位到表中的某一個對象,但沒有建立外鍵關系


class Table(models.Model):
    """
    id      app_name       table_name
    1       demo            food
    2       demo            fruit
    """
    app_name = models.CharField(max_length=32)
    table_name = models.CharField(max_length=32)

  最好的方式是,只有當你需要對某個對象或模型進行評論時,才創建pricepolicy與那個模型的關系。示例如下所示:

# 方法三:基於ContentTypes創建表結構
class Coupon(models.Model):
    title = models.CharField(max_length=32)   # 優惠券名稱
    # 第一步:與ContentType表綁定外鍵關系
    content_type = models.ForeignKey(to=ContentType, on_delete=None)
    # 第二步:建立對象id
    object_id = models.IntegerField()
    # 第三步:content_type和object_id綁定外鍵關系
    content_object = GenericForeignKey("content_type", "object_id")

  學位課程、專題課程、價格策略基於django contenttypes創建表結構如下所示:

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


class DegreeCourse(models.Model):
    """學位課程"""
    name = models.CharField(max_length=128, unique=True)
    course_img = models.CharField(max_length=255, verbose_name="縮略圖")
    brief = models.TextField(verbose_name="學位課程簡介", )


class Course(models.Model):
    """專題課程"""
    name = models.CharField(max_length=128, unique=True)
    course_img = models.CharField(max_length=255)

    # 不會在數據庫生成列,只用於幫助你進行查詢
    policy_list = GenericRelation("PricePolicy")


class PricePolicy(models.Model):
    """價格策略表"""
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)  # 關聯course or degree_course
    object_id = models.PositiveIntegerField()   # 正整數PositiveInteger

    # GenericForeignKey不會在數據庫生成列,只用於幫助你進行添加和查詢
    content_object = GenericForeignKey('content_type', 'object_id')   # 將兩個字段放在這個對象中

    # 周期
    valid_period_choices = (
        (1, '1天'),
        (3, '3天'),
        (7, '1周'), (14, '2周'),
        (30, '1個月'),
        (60, '2個月'),
        (90, '3個月'),
        (180, '6個月'), (210, '12個月'),
        (540, '18個月'), (720, '24個月'),
    )
    # 價格
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

(1)GenericForeignKey

  Django ContentType提供了一種GenericForeignKey的類型,通過這種類型可以指定content_object。

  GenericForeignKey不會在數據庫生成列,只用於幫助你進行添加查詢

(2)GenericRelation

  GenericRelation不會在數據庫生成列,只用於幫助你進行查詢

(3)pricepolicy里有三個重要字段

  • content_type: 內容類型,代表了模型的名字(比如Course,DegreeCourse)

  • object_id: 傳入對象的id

  • content_object: 傳入的實例化對象,其包含兩個屬性content_type和object_id。

2、視圖操作

(1)在價格策略表(pricepolicy)中添加數據

from django.shortcuts import render, HttpResponse
from app01 import models
from django.contrib.contenttypes.models import ContentType

def test(request):
  # 方法一: models.PricePolicy.objects.create( valid_period=7, price=6.6, content_type=ContentType.objects.get(model="course"), object_id=1 )   # 方法二: models.PricePolicy.objects.create( valid_period=14, price=9.9, content_object=models.Course.objects.get(id=1) # 'content_type', 'object_id' ) return HttpResponse("...")

  訪問http://127.0.0.1:8000/test/ 后,查看價格策略表保存的數據:

  

(2)根據某個價格策略對象,找到其對應的表和數據

  這里以查看管理課程名稱為例:

from django.shortcuts import render, HttpResponse
from app01 import models
from django.contrib.contenttypes.models import ContentType

def test(request):
    price = models.PricePolicy.objects.get(id=2)
    print(price.content_object.name)   # 21天入門python  即自動幫忙找到對應的對象

    return HttpResponse("...")

(3)找到某個課程關聯的所有價格策略

  注意這里需要利用到GenericRelation。

from django.shortcuts import render, HttpResponse
from app01 import models
from django.contrib.contenttypes.models import ContentType

def test(request):
    obj = models.Course.objects.get(id=1)
    print(obj.policy_list.all())   # <QuerySet [<PricePolicy: PricePolicy object (1)>, <PricePolicy: PricePolicy object (2)>]>

    return HttpResponse("...")

  查詢結果是一個QuerySet對象,如果想讓查詢結果更加清楚:

from django.shortcuts import render, HttpResponse
from app01 import models
from django.contrib.contenttypes.models import ContentType

def test(request):

    obj = models.Course.objects.get(id=1)
    for item in obj.policy_list.all():
        print(item.id, item.valid_period, item.price)
        """
        1 7 6.6
        2 14 9.9
        """
    return HttpResponse("...")

四、總結ContentType

  如果一張表與N張表動態地要創建Foreign Key關系,如果創建 Foreign key 將生成很多列,這樣很多都是空的,造成嚴重浪費空間。只要是一張表要和多張表建立外鍵關系的情況,都可以考慮使用django的ContentType組件來幫助實現,以簡化表結構的設計。

  ContentType組件的作用:可以通過兩個字段(GenericForeignKey, GenericRelation),在保證列數不變的情況下,讓一張表和N張表做Foreign Key關系。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM