django內置組件——ContentTypes

1、什麼是Django ContentTypes?

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

  進一步瞭解ContentTypes能夠直接查閱如下這兩個連接:數據庫

2、Django ContentTypes作了什麼?

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

# 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組件:session

from django.contrib.contenttypes.models import ContentType

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

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屬性)。 ui

  django_content_type並不僅是記錄屬性這麼簡單.了contenttypes是對model的一次封裝,所以能夠經過contenttypes動態的訪問model類型,而不須要每次import具體的model類型。this

一、ContentType實例提供的接口

  • ContentType.model_class() 

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

  • ContentType.get_object_for_this_type() 

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

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

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

 3、Django ContentTypes框架使用場景

一、設計模型(建立表結構)

  假設咱們建立以下模型,裏面包含學位課程、專題課程、價格策略。

  價格策略既能夠是專題課程的價格策略,也能夠是學位課程的價格策略。須要在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。

二、視圖操做

(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("...")

4、總結ContentType

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

  ContentType組件的做用:能夠經過兩個字段(GenericForeignKey, GenericRelation),在保證列數不變的狀況下,讓一張表和N張表作Foreign Key關係。

相關文章
相關標籤/搜索