Поле модели Django для абстрактного базового класса
Я искал вокруг для ответа на этот (возможно простой) вопрос, но большинство решений, которые я вижу, кажутся слишком сложными и трудными для понимания.
У меня есть модель "Post", которая является абстрактным базовым классом. Модели "Анонс" и "Событие" наследуются от Почты.
Сейчас я храню связанные списки событий и объявлений в других моделях. Например, у меня есть поля "removed_events" и "deleted_announcements" в другой модели.
Однако в моем проекте "removed_events" и "deleted_announcements" обрабатываются точно так же. Нет необходимости дистанцироваться между "удаленным событием" и "удаленным объявлением". Другими словами, будет достаточно отслеживания полей "removed_posts".
Я не знаю, как (или, возможно, не могу) создать поле "removed_posts", поскольку Post является абстрактным. Тем не менее, прямо сейчас я чувствую, что повторяюсь в коде (и мне приходится делать много беспорядка - некоторые проверки, чтобы выяснить, является ли сообщение, на которое я смотрю, событие или объявление и добавить его к соответствующему удаленное поле).
Какой лучший вариант здесь? Я мог бы делать сообщения не абстрактными, но сами объекты Post никогда не должны создаваться, и я не думаю, что могу применить это на не абстрактном объекте.
Мое понимание баз данных слабое, но у меня создается впечатление, что создание не-абстрактного сообщения будет затруднять базу данных из-за объединений. Это большая сделка?
Наконец, в других моделях есть другие поля, где я хотел бы конденсировать вещи, которые составляют event_list и объявление_записью в post_list, но эти поля необходимо устранить. Я мог бы фильтровать post_list на основе типа post, но вызов filter() будет медленнее, чем возможность прямого доступа к спискам событий и объявлений отдельно, не так ли? Любые предложения здесь?
Спасибо за тонну за это.
Ответы
Ответ 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
и с помощью менеджеров конкретных классов, вы получите все, что захотите.