How can I get a set of all classes with inverse relationships for a model in Django?

Given:

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) 

As possible, given only the Food class, get a set of all classes to which it has an “inverse relationship”. That is, given the class Food , how you can get the classes Cat , Cow and Man .

I think this is possible because Food has three “backward relationships”: Food.cat_set, Food.cow_set and Food.human_set.

Help rate and thank you!

+4
source share
2 answers

Or

A) Use multiple table inheritance and create a base class "Eater" that inherits Cat, Cow and Human.

B) Use Generic Relation , where Food can be associated with any other model.

These are well-documented and officially supported features, you should stick with them to keep your own code clean, avoid workarounds, and be sure that it will continue to be supported in the future.

- EDIT (Aka "how to be a reputation whore")

So, here is the recipe for this particular case.

Suppose you absolutely want separate models for Cat, Cow and Human. In a real application, you want to ask yourself why the category field will not do the job.

It’s easier to get to the “real” class through general relationships, so here is an implementation for B. We cannot have this “food” field in Person, Cat or Cow, or we will face the same problem. Therefore, we will create an intermediate model "FoodConsumer". We will need to write additional validation checks if we do not want more than one product per instance.

 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) 

Now, to demonstrate this, simply write this working 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 getting pet food ? >>> living_thing = pet_food.eaters.all()[0].eaten_by >>> isinstance(living_thing,Cow) False >>> isinstance(living_thing,Cat) True John 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 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 a person and reveal his name >>> if isinstance(living_thing,Person): living_thing.first_name u'John' """ 
+7
source

Some digging in the source code showed:

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) 

And, using these functions on the models above, you hypothetically get:

 >>> 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'> 

Note. I heard that Model._meta is "intermittent" and maybe you should not rely on the world of post Django-1.0.

Thank you for reading.:)

+14
source

All Articles