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

Несколько полей в один столбец БД

Мы пытаемся ускорить наше приложение с 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'
4b9b3361

Ответ 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(...)

Я признаю, что я не тестировал его, поэтому он, вероятно, не работает как есть. Но я считаю, что этот подход звучит.