Как получить набор всех классов с обратными отношениями для модели в Django? - PullRequest
4 голосов
/ 11 ноября 2008

Дано:

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 есть три "обратных отношения": Food.cat_set , Food.cow_set и Food.human_set .

Помощь приветствуется и спасибо!

Ответы [ 2 ]

14 голосов
/ 11 ноября 2008

Некоторые раскопки в исходном коде показали:

Джанго / дб / модели / 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 голосов
/ 11 ноября 2008

Либо

A) Используйте наследование нескольких таблиц и создайте базовый класс "Eater", от которого наследуются Кошка, Корова и Человек.

B) Используйте родовое отношение , где Пища может быть связана с любой другой моделью.

Это хорошо документированные и официально поддерживаемые функции, вам лучше придерживаться их, чтобы поддерживать собственный код в чистоте, избегать обходных путей и быть уверенным, что он будет поддерживаться в будущем.

- РЕДАКТИРОВАТЬ (А.К.А. "Как быть шлюхой с репутацией")

Итак, вот рецепт для этого конкретного случая.

Предположим, вам абсолютно необходимы отдельные модели для кошек, коров и людей. В реальном приложении вы хотите спросить себя, почему поле «категория» не сработает.

Проще добраться до «реального» класса через родовые отношения, поэтому здесь есть реализация для B. У нас не может быть этого поля «еда» в Person, Cat или Cow, или мы столкнемся с тем же проблемы. Поэтому мы создадим посредническую модель «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'

"""
...