******
Django的contenttype表中存放发的是app名称和模型的对应关系 contentType使用方式 - 导入模块 from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation - 定义models class PythonBasic(models.Model): course_name = models.CharField(max_length=32) class Oop(models.Model): course_name = models.CharField(max_length=32) class Coupon(models.Model): coupon_name = models.CharField(max_length=32) # 关联到contenttype外键,用来存放对应的表的id(在contenttype表中的id) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 关联对应的商品的id object_id = models.PositiveIntegerField() # 关联的对象商品 content_object = GenericForeignKey("content_type", "object_id") - 使用 class CourseView(APIView): def get(self, request): pb = ContentType.objects.get(app_label="myapp", model="pythonbasic") # model_class()获取对应的表类 object_id = pb.model_class().objects.get(pk=3) # 其中的关系我们可以使用content_object进行关联,它相当于一个管理器 # 我们将对应的对象给他,他会自动给字段建立关系 # 但是其实我对于它的应用了解还不是很多,如果需要请看django文档 Coupon.objects.create(coupon_name="python通关优惠券", content_object=object_id) return HttpResponse("ok") 总结: 关于它的使用时机,其实就是如果一个模型类与多个类之间都存在外键关系的时候。比如订单表与书籍表以及衣服表,都会产生订单,但是万一还有其他类的商品也要关联订单的时候,我们可以使用,contentType进行关联。其实就是一个万能的中间表,可以关联任何的其他表。
当一张表作为多个表的FK,就可以使用content_type表;例如上面的优惠券表,被食物和衣服当作FK,数据库表一旦建立就难以更改,如果以后需要增加电器等表并把优惠券表作为FK表,这时就不能做到在优惠券表增加列字段electr_id,因为表只能增加行记录而不能增加列字段(虽然扩张表字段也可以,但是一个是浪费空间,第二个就是麻烦难道没有一种商品类型你就加一个字段,累死了),因此就可以使用content_type表来将表与表中的对象进行关联,从而做到不增加列字段的情况下增加FK关系。
在使用content_type表时,需要在FK表中增加content_type作为FK字段,并增加GenericForeignKey便于优惠券表记录的建立以及单个优惠券对象对应的其他商品的查找。在优惠券表关联的“一”的关系表中增加GenericRelation字段便于查找关联的优惠券记录的查找
def get_order_serializer(queryset): class OrderItemSerializer(StandardModelSerializer): object_id = serializers.PrimaryKeyRelatedField(queryset=queryset) price = serializers.DecimalField(max_digits=10, decimal_places=2) class Meta: model = OrderItem fields = ("price", "object_id") class OrderCSerializer(StandardModelSerializer): item = OrderItemSerializer(many=True) class Meta: model = Order fields = ("item",) return OrderCSerializer, OrderItemSerializer class OrderItemSerializer(StandardModelSerializer): name = serializers.CharField(source="content_type.name") class Meta: model = OrderItem fields = ('price', "name") class OrderSerializer(StandardModelSerializer): item = OrderItemSerializer(many=True) # 与嵌套的serializer中的model表中的related_name关联 class Meta: model = Order fields = ("amount", "item")
class OrderViewSet(base_view.BaseGenericViewSet, CreateModelMixin, ListModelMixin): queryset = Order.objects.all() serializer_class = OrderSerializer filter_backends = (StandardOrderingFilter, StandardFilterBackend, StandardSearchFilter) search_fields = ("amount", "id") filter_fields = ("amount", "id") ordering = "id" def create(self, request: Request, model, app_label): content_type = ContentType.objects.get(model=model, app_label=app_label) prd_model = content_type.model_class() order_ser, order_item_ser = get_order_serializer(prd_model._default_manager.all()) serializer = order_ser(data=request.data) serializer.is_valid() data = serializer.validated_data order = Order.objects.create(amount=0) total_amount = 0 for post_item in data["item"]: content_object = post_item["object_id"] price = content_object.get_price if price != post_item["price"]: raise Invalid total_amount += int(price) OrderItem.objects.create(content_object=content_object, price=price, order=order) order.amount = total_amount order.save() ser = self.get_serializer(order) headers = self.get_success_headers(ser.data) return Response(data=ser.data, headers=headers, status=status.HTTP_201_CREATED)
class Order(models.Model): amount = models.DecimalField(max_digits=10, decimal_places=2, default=-1) class OrderItem(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING) object_id = models.IntegerField() content_object = GenericForeignKey() price = models.DecimalField(max_digits=10, decimal_places=2) order = models.ForeignKey(db_constraint=False, db_column="order_id", db_index=True, on_delete=models.DO_NOTHING, related_name="item", to="Order", to_field="id") # 设置的related_name是很关键的,因为在serializer传入数据的时候,Orde表会根据它进行查找,如果名称不对会发生错误 class Product(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=10, decimal_places=2) @property def get_price(self): return self.price class Meta: db_table = "product" verbose_name_plural = verbose_name = "product" def __str__(self): return "{}".format(self.name)
1 contenttypes框架¶ 2 Django包含一个contenttypes应用程序,可以跟踪Django驱动的项目中安装的所有模型,为您的模型提供高级通用界面。 3 4 概述¶ 5 contenttypes应用程序的核心是ContentType生活在的 模型 django.contrib.contenttypes.models.ContentType。ContentType表示和存储有关项目中安装的模型的信息的实例, 以及安装ContentType新模型时自动创建的新实例 。 6 7 ContentType具有返回它们所代表的模型类以及从这些模型查询对象的方法的实例。ContentType 还有一个自定义管理器,它添加了使用ContentType和获取ContentType 特定模型实例的方法。 8 9 模型之间的关系 ContentType也可用于启用某个模型的实例与已安装的任何模型的实例之间的“通用”关系。 10 11 安装contenttypes框架¶ 12 contenttypes框架包含在由其INSTALLED_APPS创建的默认 列表中,但如果您已将其删除,或者您手动设置了 列表,则可以通过添加到您的设置来启用它 。django-admin startprojectINSTALLED_APPS'django.contrib.contenttypes'INSTALLED_APPS 13 14 安装contenttypes框架通常是个好主意; Django的其他几个捆绑应用程序需要它: 15 16 管理应用程序使用它来记录通过管理界面添加或更改的每个对象的历史记录。 17 Django 使用它将用户权限绑定到特定模型。authentication framework 18 该ContentType模型¶ 19 类ContentType¶ 20 每个实例ContentType 都有两个字段,它们一起唯一地描述了已安装的模型: 21 22 app_label¶ (这个是你app的名称) 23 模型所属应用程序的名称。这取自app_label模型的属性,仅包括应用程序的Python导入路径的 最后部分; django.contrib.contenttypes,例如,成为一个 app_label的contenttypes。 24 25 model¶ (这个是你模型的表名称,全小写) 26 模型类的名称。 27 28 此外,还提供以下房产: 29 30 name¶ 31 人类可读的内容类型名称。这取自verbose_name 模型的 属性。 32 33 让我们看一个例子来看看它是如何工作的。如果您已安装该contenttypes应用程序,然后添加 到您的 设置并运行以进行安装,则该模型将安装到您的数据库中。除此之外,还将使用以下值创建一个新实例 :the sites applicationINSTALLED_APPSmanage.py migratedjango.contrib.sites.models.SiteContentType 34 35 app_label 将被设置为'sites'(Python路径的最后一部分django.contrib.sites)。 36 model 将被设置为'site'。 37 ContentType实例上的方法¶ 38 每个ContentType实例都有一些方法,允许您从ContentType实例获取 它所代表的模型,或者从该模型中检索对象: 39
content_type = Content_type.objects.get(app_label="XXX", model="xxx") (下面用的ContConentType=content_type.model_class()就是这样查找出来的)
还有一个办法可以拿到模型多有的对象 content_type.model_class._default_manager.all() (红色部分其实就是objects模型管理器对象)
40 ContConentType.get_object_for_this_type(** kwargs)¶ (uuid="ssfdsfa")其实就是传入对应的字段与get相同 41 为 表示的模型获取一组有效的查找参数ContentType,并 在该模型上执行,返回相应的对象。a get() lookup 42 43 ContentType.model_class()¶ # 可以拿到对应的模型 他很重要 44 返回此ContentType实例表示的模型类 。 45 46 例如,我们可以仰望 ContentType的 User模型: 47 48 >>> from django.contrib.contenttypes.models import ContentType 49 >>> user_type = ContentType.objects.get(app_label='auth', model='user') 50 >>> user_type 51 <ContentType: user> 52 然后使用它来查询特定的 User,或者访问User模型类: 53 54 >>> user_type.model_class() 55 <class 'django.contrib.auth.models.User'> 56 >>> user_type.get_object_for_this_type(username='Guido') 57 <User: Guido> 58 一起, get_object_for_this_type() 并model_class()启用两个非常重要的用例: 59 60 使用这些方法,您可以编写对任何已安装模型执行查询的高级通用代码 - 您可以在运行时将一个app_label并 model传入 ContentType查找,然后使用模型类或从中检索对象。 61 您可以将另一个模型与 ContentType将其实例绑定到特定模型类的方法相关联,并使用这些方法来访问这些模型类。 62 Django的几个捆绑应用程序使用后一种技术。例如, 在Django的身份验证框架中使用带有外键的 模型; 这可以 代表“可以添加博客条目”或“可以删除新闻故事”等概念。the permissions systemPermissionContentTypePermission 63 64 该ContentTypeManager¶ 65 类ContentTypeManager¶ 66 ContentType还有一个自定义管理器,ContentTypeManager它添加了以下方法: 67 68 clear_cache()¶ 69 清除用于ContentType跟踪其已创建ContentType实例的模型 的内部缓存 。您可能不需要自己调用此方法; Django会在需要时自动调用它。 70 71 get_for_id(id)¶ 72 ContentType按ID 查找。由于此方法使用相同的共享缓存 get_for_model(),因此优先使用此方法 ContentType.objects.get(pk=id) 73 74 get_for_model(model,for_concrete_model = True)¶ (传入模型名) 75 获取模型类或模型的实例,并返回ContentType表示该模型的 实例。for_concrete_model=False允许获取ContentType代理模型。 76 77 get_for_models(* models,for_concrete_models = True)¶ 78 获取可变数量的模型类,并返回将模型类映射到ContentType表示它们的实例的字典 。for_concrete_models=False允许获取 ContentType代理模型。 79 80 get_by_natural_key(app_label,model)¶ 81 返回ContentType 由给定应用程序标签和模型名称唯一标识的实例。此方法的主要目的是允许 在反序列化期间ContentType通过自然键引用对象。 82 83 get_for_model()当您知道需要使用a ContentType但不想在获取模型的元数据以执行手动查找时遇到麻烦,该方法特别有用 : 84 85 >>> from django.contrib.auth.models import User 86 >>> ContentType.objects.get_for_model(User) 87 <ContentType: user> 88 通用关系¶ 89 从您自己的模型中添加外键,以 ContentType允许模型有效地将自身绑定到另一个模型类,如Permission上面模型的示例 所示。但是可以更进一步,用于 ContentType在模型之间实现真正通用(有时称为“多态”)关系。 90 91 一个简单的例子是标记系统,它可能如下所示: 92 93 from django.contrib.contenttypes.fields import GenericForeignKey 94 from django.contrib.contenttypes.models import ContentType 95 from django.db import models 96 97 class TaggedItem(models.Model): 98 tag = models.SlugField() 99 content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 100 object_id = models.PositiveIntegerField() 101 content_object = GenericForeignKey('content_type', 'object_id') 102 103 def __str__(self): 104 return self.tag 105 法线ForeignKey只能“指向”另一个模型,这意味着如果TaggedItem模型使用了 ForeignKey它,则必须选择一个且只有一个模型来存储标签。contenttypes应用程序提供了一个特殊的字段类型(GenericForeignKey),它可以解决这个问题,并允许关系与任何模型: 106 107 类GenericForeignKey¶ 108 设置一个有三个部分 GenericForeignKey: 109 110 给你的模型ForeignKey 来ContentType。该字段的通常名称是“content_type”。 111 为您的模型提供一个字段,该字段可以存储您要与之相关的模型中的主键值。对于大多数模型,这意味着一个 PositiveIntegerField。该字段的通常名称是“object_id”。 112 给你的模型a GenericForeignKey,并传递上述两个字段的名称。如果这些字段名为“content_type”和“object_id”,则可以省略 - 这些是GenericForeignKey将要查找的默认字段名称 。 113 for_concrete_model¶ 114 如果False,该字段将能够引用代理模型。默认是True。这反映了这个for_concrete_model论点 get_for_model()。 115 116 主键类型兼容性 117 118 “object_id”字段不必与相关模型上的主键字段具有相同的类型,但它们的主键值必须通过其get_db_prep_value()方法可强制化为与“object_id”字段相同的类型 。 119 120 例如,如果要允许与具有任一IntegerField或 CharField主键字段的模型的泛型关系 ,则可以使用CharField模型上的“object_id”字段,因为整数可以通过强制转换为字符串get_db_prep_value()。 121 122 为了获得最大的灵活性,您可以使用 TextField没有定义最大长度的a,但是这可能会导致严重的性能损失,具体取决于您的数据库后端。 123 124 对于哪种字段类型最好,没有一种通用的解决方案。您应该评估您希望指向的模型,并确定哪种解决方案对您的用例最有效。 125 126 序列化ContentType对象的引用 127 128 如果fixtures从实现泛型关系的模型中序列化数据(例如,生成时 ),则应该使用自然键来唯一标识相关ContentType 对象。请参阅自然键和 更多信息。dumpdata --natural-foreign 129 130 这将启用类似于用于正常的API的API ForeignKey; 每个TaggedItem都有一个content_object返回与其相关的对象的字段,您也可以在创建时使用该字段或使用它TaggedItem: 131 132 >>> from django.contrib.auth.models import User 133 >>> guido = User.objects.get(username='Guido') 134 >>> t = TaggedItem(content_object=guido, tag='bdfl') 135 >>> t.save() 136 >>> t.content_object 137 <User: Guido> 138 如果删除了相关对象,则content_type和object_id字段仍将设置为其原始值并GenericForeignKey返回 None: 139 140 >>> guido.delete() 141 >>> t.content_object # returns None 142 由于方式GenericForeignKey 实现,你不能直接使用的过滤器等领域(filter() 以及exclude()通过数据库API,例如)。由于 GenericForeignKey不正常的领域对象,这些例子不工作: 143 144 # This will fail 145 >>> TaggedItem.objects.filter(content_object=guido) 146 # This will also fail 147 >>> TaggedItem.objects.get(content_object=guido) 148 同样,GenericForeignKeys没有出现在ModelForms中。 149 150 反向泛型关系¶ 151 类GenericRelation¶ 152 related_query_name¶ 153 默认情况下,相关对象与该对象的关系不存在。设置related_query_name创建从相关对象回到此关系的关系。这允许从相关对象查询和过滤。 154 155 如果您知道最常使用的模型,还可以添加“反向”通用关系以启用其他API。例如: 156 157 from django.contrib.contenttypes.fields import GenericRelation 158 from django.db import models 159 160 class Bookmark(models.Model): 161 url = models.URLField() 162 tags = GenericRelation(TaggedItem) 163 Bookmark每个实例都有一个tags属性,可以用来检索它们的关联TaggedItems: 164 165 >>> b = Bookmark(url='https://www.djangoproject.com/') 166 >>> b.save() 167 >>> t1 = TaggedItem(content_object=b, tag='django') 168 >>> t1.save() 169 >>> t2 = TaggedItem(content_object=b, tag='python') 170 >>> t2.save() 171 >>> b.tags.all() 172 <QuerySet [<TaggedItem: django>, <TaggedItem: python>]> 173 GenericRelation使用 related_query_nameset 定义允许从相关对象查询: 174 175 tags = GenericRelation(TaggedItem, related_query_name='bookmark') 176 这使得过滤,排序等查询操作上的Bookmark 来自TaggedItem: 177 178 >>> # Get all tags belonging to bookmarks containing `django` in the url 179 >>> TaggedItem.objects.filter(bookmark__url__contains='django') 180 <QuerySet [<TaggedItem: django>, <TaggedItem: python>]> 181 当然,如果您不添加related_query_name,您可以手动执行相同类型的查找: 182 183 >>> bookmarks = Bookmark.objects.filter(url__contains='django') 184 >>> bookmark_type = ContentType.objects.get_for_model(Bookmark) 185 >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks) 186 <QuerySet [<TaggedItem: django>, <TaggedItem: python>]> 187 就像GenericForeignKey 接受content-type和object-ID字段的名称一样,也是如此 GenericRelation; 如果具有通用外键的模型对这些字段使用非默认名称,则必须在设置字段时传递字段的名称 GenericRelation。例如,如果TaggedItem上面提到的模型使用了命名的字段content_type_fk并 object_primary_key创建了它的通用外键,那么 GenericRelation需要像这样定义返回它: 188 189 tags = GenericRelation( 190 TaggedItem, 191 content_type_field='content_type_fk', 192 object_id_field='object_primary_key', 193 ) 194 另请注意,如果删除具有a的对象,则 GenericRelation任何GenericForeignKey 指向该对象的对象也将被删除。在上面的示例中,这意味着如果Bookmark删除了某个对象,TaggedItem则会同时删除指向该对象的任何对象。 195 196 不同ForeignKey, GenericForeignKey不接受on_delete自定义此行为的参数; 如果需要,您可以通过不使用来避免级联删除 GenericRelation,并且可以通过pre_delete 信号提供替代行为。 197 198 通用关系和聚合¶ 199 Django的数据库聚合API适用于 GenericRelation。例如,您可以找出所有书签的标签数量: 200 201 >>> Bookmark.objects.aggregate(Count('tags')) 202 {'tags__count': 3} 203 表格中的通用关系¶ 204 该django.contrib.contenttypes.forms模块提供: 205 206 BaseGenericInlineFormSet 207 一个formset工厂,generic_inlineformset_factory()用于 GenericForeignKey。 208 类BaseGenericInlineFormSet¶ 209 generic_inlineformset_factory(model,form = ModelForm,formset = BaseGenericInlineFormSet,ct_field =“content_type”,fk_field =“object_id”,fields = None,exclude = None,extra = 3,can_order = False,can_delete = True,max_num = None,formfield_callback = None,validate_max = False,for_concrete_model = True,min_num = None,validate_min = False)¶ 210 返回一个GenericInlineFormSet使用 modelformset_factory()。 211 212 您必须提供ct_field与fk_field他们是否是不同的默认值,content_type并object_id分别。其它参数类似于那些记录 modelformset_factory()和 inlineformset_factory()。 213 214 该for_concrete_model参数对应 for_concrete_model 的参数GenericForeignKey。 215 216 管理中的通用关系¶ 217 该django.contrib.contenttypes.admin模块提供 GenericTabularInline和 GenericStackedInline(子类 GenericInlineModelAdmin) 218 219 这些类和函数支持在表单和管理员中使用通用关系。有关更多信息,请参阅模型formset和 管理文档。 220 221 类GenericInlineModelAdmin¶ 222 在GenericInlineModelAdmin 类继承自的所有属性 InlineModelAdmin类。但是,它增加了一些用于处理泛型关系的东西: 223 224 ct_field¶ 225 ContentType模型上的外键字段的名称 。默认为content_type。 226 227 ct_fk_field¶ 228 表示相关对象ID的整数字段的名称。默认为object_id。 229 230 类GenericTabularInline¶ 231 类GenericStackedInline¶ 232 GenericInlineModelAdmin分别具有堆叠和表格布局的子类。