Можете ли вы получить доступ к подклассовой модели из модели суперкласса в Django ORM? - PullRequest
3 голосов
/ 27 июня 2009

Допустим, у меня настроено наследование модели, как описано ниже.

class ArticleBase(models.Model):
    title = models.CharField()
    author = models.CharField()

class Review(ArticleBase):
    rating = models.IntegerField()

class News(ArticleBase):
    source = models.CharField()

Если мне нужен список всех статей, независимо от их типа (в данном случае и «Обзоры», и «Новости»), упорядоченных по заголовкам, я могу выполнить запрос к ArticleBase. Есть ли простой способ, если у меня есть запись ArticleBase, чтобы определить, относится ли она к обзору или записи новостей, не запрашивая обе модели, чтобы узнать, у кого есть внешний ключ записи, на которой я нахожусь?

Ответы [ 4 ]

1 голос
/ 02 июля 2009

Я предполагаю, что все экземпляры ArticleBase являются экземплярами подклассов ArticleBase.

Одним из решений является сохранение имени подкласса в ArticleBase и некоторых методов, которые возвращают объект подкласса или подкласса на основе этой информации. Поскольку наследование нескольких таблиц определяет свойство родительского экземпляра для доступа к дочернему экземпляру, все это довольно просто.

from django.db import models

class ArticleBase(models.Model):
    title = models.CharField()
    author = models.CharField()
    # Store the actual class name.
    class_name = models.CharField()

    # Define save to make sure class_name is set.
    def save(self, *args, **kwargs):
        self.class_name = self.__class__.__name__
        super(ArticleBase, self).save(*args, **kwargs)

    # Multi-table inheritance defines an attribute to fetch the child
    # from a parent instance given the lower case subclass name.
    def get_child(self):
        return getattr(self, self.class_name.lower())

    # If indeed you really need the class.
    def get_child_class(self):
        return self.get_child().__class__

    # Check the type against a subclass name or a subclass.
    # For instance, 'if article.child_is(News):'
    # or 'if article.child_is("News"):'.
    def child_is(self, cls):
        if isinstance(cls, basestring):
            return cls.lower() == self.class_name.lower()
        else:
            return self.get_child_class()  == cls

class Review(ArticleBase):
    rating = models.IntegerField()

class News(ArticleBase):
    source = models.CharField()

Это ни в коем случае не единственный способ сделать это. Это, однако, довольно простое и прямолинейное решение. Отличное приложение contrib contenttypes и универсальный модуль, который его использует, предлагают множество способов сделать это, ну, в общем.

Может быть полезно иметь следующее в ArticleBase:

def __unicode__(self)
    return self.get_child().__unicode__()

В этом случае следует помнить, что неудача в определении __unicode__ в подклассах или вызов __unicode__ в экземпляре ArticleBase (который не был разделен на подклассы) приведет к бесконечной рекурсии. Таким образом, приведенное ниже предостережение о проверке работоспособности (например, предотвращение только такой реализации ArticleBase напрямую).

Отказ от ответственности:

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

1 голос
/ 27 июня 2009

Вам никогда не нужно знать подкласс ArticleBase. Два подкласса - если вы спроектировали это правильно - имеют одинаковые методы и могут использоваться взаимозаменяемо.

В настоящее время ваши два подкласса не являются тривиально полиморфными. У каждого из них есть уникальные атрибуты.

В 80% случаев они не имеют ничего общего друг с другом, и это прекрасно работает.

Фраза «все статьи независимо от типа» - это вводящий в заблуждение способ описать, что вы делаете. Это звучит проще, чем есть на самом деле.

То, что вы просите, - это «союз новостей и обзоров». Я не уверен, что вы будете делать с этим союзом - возможно, внесите их в указатель. Чтобы страница индекса работала просто, необходимо делегировать детали форматирования каждому подклассу. Они должны генерировать удобную для HTML сводку из метода, который реализуют оба подкласса. Это резюме - то, на что будет опираться ваш шаблон.

class Review(ArticleBase):
    rating = models.IntegerField()
    def summary( self ):
        return '<span class="title">%s</span><span class="author">%s</span><span class="rating">%s</span>' %  ( self.title, self.author, self.rating )

Аналогичный summary метод требуется для новостей.

Затем вы можете объединить эти два непересекающихся типа в один индекс.

0 голосов
/ 01 марта 2012

https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance

Вам не нужно явно запрашивать базу данных, вы можете сделать

article_base_instance.review

это либо возвратит экземпляр Review, либо вызовет исключение. То же самое для остальных подклассов.

Если вы используете MTI, это ваш единственный вариант. :)

0 голосов
/ 28 июня 2009

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

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

...