Несколько полей в один столбец БД
Мы пытаемся ускорить наше приложение с prefetch_related
. Он может следовать за отношениями GenericForeignKey
, и он может пойти глубже с __
, но, к сожалению, он не сработает, если соответствующая модель не имеет такого поля.
Вот пример структуры модели
class ModelA(models.Model):
event_object = models.ForeignKey(SomeModelA)
class ModelB(models.Model):
event = models.ForeignKey(SomeModelB)
class ModelC(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
Итак, экземпляр ModelC
может указывать либо на ModelA
, либо ModelB
. И я могу использовать такой запрос для предварительной выборки как моделей A, так и B: ModelC.objects.all().prefetch_related('content_object')
К сожалению, мне также нужно выбрать объект события (SomeModelA
или SomeModelB
)
Если я попытаюсь запустить
ModelC.objects.all().prefetch_related('content_object', 'content_object__event_object')
Он будет работать, если у меня есть только теги ModelC
, которые указывают на ModelA
, но в противном случае он будет терпеть неудачу, потому что ModelB
не имеет поля event_object
и вместо него имеет event
.
Эти модели используются во многих местах кода, поэтому не рекомендуется переименовывать поле. Поэтому я задаюсь вопросом, есть ли способ создать псевдоним для поля/столбца.
Я пытался сделать вот так:
class ModelB(models.Model):
event = models.ForeignKey(SomeModelB)
event_object = models.ForeignKey(SomeModelB, db_column='event_id', related_name='+')
чтобы сделать два поля, которые указывают на один и тот же столбец в таблице DB. Однако это не работает, поскольку он разбивает метод save
. Django создает SQL-запрос UPDATE
, где один столбец помещается дважды и получает DatabaseError
Есть ли способ создать такой псевдоним? Или, может быть, есть еще одно решение сделать prefetch_related
не выдавать исключение?
Обновление. В методе save
имеется параметр update_fields
, который можно использовать для исключения этого поля. Однако он был введен в 1.5, и мы используем 1.4. Поэтому я продолжаю искать ответ.
Обновление # 2: @shx2 попросил меня предоставить трассировку. Существует 2 возможных трассировки.
1st - когда атрибут отсутствует для первого объекта:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
data = list(self[:REPR_OUTPUT_SIZE + 1])
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
len(self)
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
self._prefetch_related_objects()
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1664, in prefetch_related_objects
(attr, first_obj.__class__.__name__, lookup))
AttributeError: Cannot find 'event_object' on ModelB object, 'content_object__event_object' is an invalid parameter to prefetch_related()
И если параметры prefetch_related действительны для первого объекта, я получаю второй трассировку:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
data = list(self[:REPR_OUTPUT_SIZE + 1])
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
len(self)
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
self._prefetch_related_objects()
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1680, in prefetch_related_objects
obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr)
File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1803, in prefetch_one_level
qs = getattr(obj, attname).all()
AttributeError: 'ModelB' object has no attribute 'event_object'
Ответы
Ответ 1
Это похоже на ошибку или недосмотр в django. В качестве обходного пути вы можете попробовать определить пользовательский менеджер, который выполняет двухэтапную предварительную выборку.
from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
class PrefetchWorkaroundManager(models.Manager):
def get_queryset(self):
q = super(PrefetchWorkaroundManager, self).get_queryset()
content_typeA = ContentType.objects.get_for_model(ModelA)
content_typeB = ContentType.objects.get_for_model(ModelB)
return q.filter(content_type__pk = content_typeA.id).prefetch_related('content_object', 'content_object__event_object') | \
q.filter(content_type__pk = content_typeB.id).prefetch_related('content_object', 'content_object__event')
class ModelC(models.Model):
...
objects_prefetched = PrefetchWorkaroundManager()
Каждый вызывающий абонент, который хочет выполнить предварительную выборку, должен получить доступ к ModelC.objects_prefetched
вместо ModelC.objects
:
ModelC.objects_prefetched.filter(...)
Я признаю, что я не тестировал его, поэтому он, вероятно, не работает как есть. Но я считаю, что этот подход звучит.