Поле модели Джанго для абстрактного базового класса - PullRequest
14 голосов
/ 14 апреля 2011

Я искал вопрос переполнения стека для ответа на этот (возможно, простой) вопрос, но большинство решений, которые я вижу, кажутся слишком сложными и трудными для понимания.

У меня есть модель "Post", котораяабстрактный базовый классМодели «Объявление» и «Событие» наследуются от Post.

Прямо сейчас я веду связанные списки событий и объявлений в других моделях.Например, в другой модели у меня есть поля «remove_events» и «remove_announcements».

Однако в моем проекте «remove_events» и «remove_announcements» обрабатываются точно так же.Нет необходимости проводить различие между «удаленным событием» и «удаленным объявлением».Другими словами, было бы достаточно отслеживания поля «Удаленные_посты».

Я не знаю, как (или, возможно, не могу) создать поле «удаленные_позиции», так как Post является абстрактным.Тем не менее, сейчас я чувствую, что повторяю себя в коде (и мне приходится делать много беспорядка - некоторые проверки, чтобы выяснить, является ли публикация, на которую я смотрю, событием или объявлением, и добавить ее к соответствующемуудаленное поле).

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

Мое понимание баз данных слабое, но у меня сложилось впечатление, что создание неабстрактного Post усложнит работу базы данных из-за объединений.Это большое дело?

Наконец, в других моделях есть другие поля, в которых я хотел бы сжать вещи, которые составляют список событий и список объявлений, в список постов, но эти поля необходимо устранить.Я мог бы отфильтровать post_list по типу записи, но вызов filter () будет медленнее, чем возможность прямого доступа к спискам событий и объявлений отдельно, не так ли?Любые предложения здесь?

Спасибо огромное за чтение этого.

Ответы [ 2 ]

13 голосов
/ 14 апреля 2011

В Django существует два вида подклассов моделей - абстрактные базовые классы; и наследование нескольких таблиц.

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

Например:

class Address(models.Model):
    street = ...
    city = ...

    class Meta:
        abstract = True


class Employee(Address):
    name = ...

class Employer(Address):
    employees = ...
    company_name = ...

Это надуманный пример, но, как вы можете видеть, Employee не является Address и не является Employer. Они оба содержат поля, относящиеся к адресу. В этом примере только две таблицы; Employee и Employer - и оба они содержат все поля адреса. Адрес работодателя нельзя сравнивать с адресом сотрудника на уровне базы данных - адрес не имеет собственного ключа.

Теперь, с наследованием нескольких таблиц (удалите реферат = True из адреса), у адреса есть таблица для себя. Это приведет к 3 отдельным таблицам; Address, Employer и Employee. И у работодателя, и у сотрудника будет уникальный внешний ключ (OneToOneField) для адреса.

Теперь вы можете обращаться к адресу, не беспокоясь о его типе.

for address in Address.objects.all():
    try:
        print address.employer
    except Employer.DoesNotExist: # must have been an employee
        print address.employee

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

class FakeAddresses(models.Model):
    address = models.ForeignKey(Address)
    note = ...

Наследование нескольких таблиц - это то, что вам нужно, если вам нужно работать с объектами типа Post, не беспокоясь о том, что это за тип Post. При доступе к любому из полей Post из подкласса будет происходить дополнительная нагрузка; но накладные расходы будут минимальными. Это уникальное индексное соединение, которое должно быть невероятно быстрым.

Просто убедитесь, что если вам нужен доступ к Post, вы используете select_related в наборе запросов.

Events.objects.select_related(depth=1)

Это позволит избежать дополнительных запросов для извлечения родительских данных, но приведет к возникновению соединения. Поэтому используйте только select related, если вам нужна почта.

Две последние заметки; если сообщение может быть как объявлением, так и событием, то вам нужно сделать традиционную вещь и связать сообщение с помощью ForeignKey. В этом случае подклассы работать не будут.

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

Родовые отношения по сути хранят данные следующим образом:

class GenericRelation(models.Model):
    model = ...
    model_key = ...


DeletedPosts(models.Model):
    post = models.ForeignKey(GenericRelation)

Присоединение в SQL будет намного сложнее (вам поможет django), но оно также будет менее производительным, чем простое объединение OneToOne. Вам следует идти по этому пути только в том случае, если присоединения OneToOne серьезно снижают производительность вашего приложения, что, вероятно, маловероятно.

2 голосов
/ 14 апреля 2011

Общие отношения и внешние ключи - ваш друг на пути к успеху. Определите промежуточную модель, в которой одна сторона является общей, а другая сторона получит связанный список полиморфных моделей. Это немного сложнее, чем стандартная модель соединения m2m, в которой общая сторона имеет два столбца, один для ContentType (фактически FK), а другой - для PK фактического экземпляра связанной модели. Вы также можете ограничить связывание моделей с помощью стандартных параметров FK. Вы привыкнете к этому быстро.

(теперь, когда у меня есть настоящая клавиатура для записи, вот пример:)

class Post(models.Model):
    class Meta: abstract = True
    CONCRETE_CLASSES = ('announcement', 'event',)
    removed_from = generic.GenericRelation('OwnerRemovedPost',
        content_type_field='content_type',
        object_id_field='post_id',
    )

class Announcement(Post): pass

class Event(Post): pass

class Owner(models.Model):

    # non-polymorphic m2m
    added_events = models.ManyToManyField(Event, null=True)

    # polymorphic m2m-like property
    def removed_posts(self):
        # can't use ManyToManyField with through.
        # can't return a QuerySet b/c it would be a union.
        return [i.post for i in self.removed_post_items.all()]

    def removed_events(self):
        # using Post's GenericRelation
        return Event.objects.filter(removed_from__owner=self)


class OwnerRemovedPost(models.Model):
    content_type = models.ForeignKey(ContentType,
        limit_choices_to={'name__in': Post.CONCRETE_CLASSES},
    )
    post_id = models.PositiveIntegerField()
    post = generic.GenericForeignKey('content_type', 'post_id')
    owner = models.ForeignKey(Owner, related_name='removed_post_items')

    class Meta:
        unique_together = (('content_type', 'post_id'),)  # to fake FK constraint

Вы не можете фильтровать связанные коллекции, как классические многие-ко-многим, но с правильными методами в Owner и умным использованием менеджеров конкретных классов вы получаете все, что хотите.

...