如何获取Django模型的所有反向关系类集合?
给定:
from django.db import models
class Food(models.Model):
"""Food, by name."""
name = models.CharField(max_length=25)
class Cat(models.Model):
"""A cat eats one type of food"""
food = models.ForeignKey(Food)
class Cow(models.Model):
"""A cow eats one type of food"""
food = models.ForeignKey(Food)
class Human(models.Model):
"""A human may eat lots of types of food"""
food = models.ManyToManyField(Food)
如果只有一个叫做Food的类,怎么才能找到所有与它有“反向关系”的类呢?也就是说,给定Food这个类,怎么能找到Cat、Cow和Human这几个类。
我觉得这是可能的,因为Food有三个“反向关系”:Food.cat_set、Food.cow_set和Food.human_set。
谢谢大家的帮助!
2 个回答
14
经过一些对源代码的研究,我发现了:
django/db/models/options.py:
def get_all_related_objects(self, local_only=False):
def get_all_related_many_to_many_objects(self, local_only=False)
然后,假设你在上面的模型上使用这些函数,你可能会得到:
>>> Food._meta.get_all_related_objects()
[<RelatedObject: app_label:cow related to food>,
<RelatedObject: app_label:cat related to food>,]
>>> Food._meta.get_all_related_many_to_many_objects()
[<RelatedObject: app_label:human related to food>,]
# and, per django/db/models/related.py
# you can retrieve the model with
>>> Food._meta.get_all_related_objects()[0].model
<class 'app_label.models.Cow'>
注意:我听说 Model._meta 是“不稳定”的,可能在 Django 1.0 之后不应该依赖它。
谢谢你的阅读。:)
7
要么
A) 使用多表继承,创建一个“Eater”基础类,让猫、牛和人都从这个类继承。
B) 使用通用关系,这样食物就可以和任何其他模型关联。
这两种方法都有详细的文档说明,并且是官方支持的功能,最好还是按照这些来做,这样可以保持代码的整洁,避免使用变通的方法,并且确保将来仍然会得到支持。
-- 编辑(也就是“如何提高声望”)
那么,这里有一个针对这种情况的解决方案。
假设你真的想为猫、牛和人创建独立的模型。在实际应用中,你应该问自己,为什么一个“类别”字段不能解决问题。
通过通用关系更容易找到“真实”的类,所以这里是B的实现。我们不能在“人”、“猫”或“牛”中直接添加“食物”字段,否则会遇到同样的问题。因此,我们将创建一个中介模型“FoodConsumer”。如果我们不想让一个实例有多于一种食物,我们还需要编写额外的验证测试。
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Food(models.Model):
"""Food, by name."""
name = models.CharField(max_length=25)
# ConsumedFood has a foreign key to Food, and a "eaten_by" generic relation
class ConsumedFood(models.Model):
food = models.ForeignKey(Food, related_name="eaters")
content_type = models.ForeignKey(ContentType, null=True)
object_id = models.PositiveIntegerField(null=True)
eaten_by = generic.GenericForeignKey('content_type', 'object_id')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
address = models.CharField(max_length=100)
city = models.CharField(max_length=50)
foods = generic.GenericRelation(ConsumedFood)
class Cat(models.Model):
name = models.CharField(max_length=50)
foods = generic.GenericRelation(ConsumedFood)
class Cow(models.Model):
farmer = models.ForeignKey(Person)
foods = generic.GenericRelation(ConsumedFood)
现在,为了演示这一点,我们来写一个可工作的doctest:
"""
>>> from models import *
Create some food records
>>> weed = Food(name="weed")
>>> weed.save()
>>> burger = Food(name="burger")
>>> burger.save()
>>> pet_food = Food(name="Pet food")
>>> pet_food.save()
John the farmer likes burgers
>>> john = Person(first_name="John", last_name="Farmer", birth_date="1960-10-12")
>>> john.save()
>>> john.foods.create(food=burger)
<ConsumedFood: ConsumedFood object>
Wilma the cow eats weed
>>> wilma = Cow(farmer=john)
>>> wilma.save()
>>> wilma.foods.create(food=weed)
<ConsumedFood: ConsumedFood object>
Felix the cat likes pet food
>>> felix = Cat(name="felix")
>>> felix.save()
>>> pet_food.eaters.create(eaten_by=felix)
<ConsumedFood: ConsumedFood object>
What food john likes again ?
>>> john.foods.all()[0].food.name
u'burger'
Who's getting pet food ?
>>> living_thing = pet_food.eaters.all()[0].eaten_by
>>> isinstance(living_thing,Cow)
False
>>> isinstance(living_thing,Cat)
True
John's farm is in fire ! He looses his cow.
>>> wilma.delete()
John is a lot poorer right now
>>> john.foods.clear()
>>> john.foods.create(food=pet_food)
<ConsumedFood: ConsumedFood object>
Who's eating pet food now ?
>>> for consumed_food in pet_food.eaters.all():
... consumed_food.eaten_by
<Cat: Cat object>
<Person: Person object>
Get the second pet food eater
>>> living_thing = pet_food.eaters.all()[1].eaten_by
Try to find if it's a person and reveal his name
>>> if isinstance(living_thing,Person): living_thing.first_name
u'John'
"""