Подтвердить что ты не робот

Поле модели Django для абстрактного базового класса

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

У меня есть модель "Post", которая является абстрактным базовым классом. Модели "Анонс" и "Событие" наследуются от Почты.

Сейчас я храню связанные списки событий и объявлений в других моделях. Например, у меня есть поля "removed_events" и "deleted_announcements" в другой модели.

Однако в моем проекте "removed_events" и "deleted_announcements" обрабатываются точно так же. Нет необходимости дистанцироваться между "удаленным событием" и "удаленным объявлением". Другими словами, будет достаточно отслеживания полей "removed_posts".

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

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

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

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

Спасибо за тонну за это.

4b9b3361

Ответ 1

Существует два типа подклассов модели в 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 - и оба они содержат все поля Address. Адрес работодателя нельзя сравнивать с адресом сотрудника на уровне базы данных - адрес не имеет собственного ключа.

Теперь, с наследованием на несколько таблиц (удалите абстрактный = True из адреса), адрес имеет таблицу для себя. Это приведет к появлению 3 отдельных таблиц; Address, Employer и Employee. И 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, если вам нужна почта.

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

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

Общие отношения по существу хранят такие данные:

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


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

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

Ответ 2

Общие отношения и внешние ключи - ваш друг на вашем пути к успеху. Определите промежуточную модель, где одна сторона является общей, тогда другая сторона получит связанный список полиморфных моделей. Это немного сложнее стандартной модели m2m join, поскольку общая сторона имеет два столбца: один для 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 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 и с помощью менеджеров конкретных классов, вы получите все, что захотите.