Прокси-модель Django и ForeignKey

Как сделать entry.category примером экземпляра CategoryProxy? Подробнее см. В коде:

class Category(models.Model): pass

class Entry(models.Model):
    category = models.ForeignKey(Category)

class EntryProxy(Entry):
    class Meta:
        proxy = True

class CategoryProxy(Category):
    class Meta:
        proxy = True

entry = EntryProxy.objects.get(pk=1)
entry.category # !!! I want CategoryProxy instance here

Отключение от категории к категорииProxy тоже нормально, но я не очень хорошо знаком с внутренними компонентами ORM, чтобы правильно скопировать внутреннее состояние...

ИЗМЕНИТЬ. Причина: я добавил метод CategoryProxy и хочу использовать его:

EntryProxy.objects.get(pk=1).category.method_at_category_proxy()

ИЗМЕНИТЬ 2. В настоящее время я реализовал это следующим образом:

EntryProxy._meta.get_field_by_name('category')[0].rel.to = CategoryProxy

но выглядит ужасно...

Ответы

Ответ 1

Чтобы перейти от класса модели к прокси-классу без попадания в базу данных:

class EntryProxy(Entry):
    @property
    def category(self):
        new_inst = EntryProxy()
        new_inst.__dict__ = super(EntryProxy, self).category.__dict__
        return new_inst

edit: фрагмент выше, похоже, не работает на django 1.4.

Так как django 1.4, я беру все поля значений вручную следующим образом:

class EntryProxy(Entry):
    @property
    def category(self):
        category = super(EntryProxy, self).category
        new_inst = EntryProxy()
        for attr in [f.attname for f in category.__class__._meta.fields] + ['_state']:
            setattr(new_inst, attr, getattr(category, attr))
        return new_inst

Чтобы переключиться с набора запросов на дочерний прокси-класс без попадания в базу данных:

class CategoryProxy(Category):
    @property
    def entry_set(self):
        qs = super(CategoryProxy, self).entry_set
        qs.model = EntryProxy
        return qs

Ответ 3

У этого вопроса уже есть принятый ответ, но он хотел опубликовать его для всех, кто может найти его.

Вы можете исправить модель во время выполнения с новым полем, чтобы отношения работали должным образом. Полный пример можно увидеть здесь - https://gist.github.com/carymrobbins/8721082

from django.db.models.fields.related import ReverseSingleRelatedObjectDescriptor

def override_model_field(model, field, field_name, column_name):
    """Force override a field in a Django Model.
    Usage: override_model_field(
        MyModel, models.ForeignKey(OtherModel), 'other', 'other_id')
    :type model: django.db.models.base.ModelBase
    :type field: django.db.models.fields.Field
    :type field_name: basestring
    :type column_name: basestring
    """
    field.name = field_name
    field.attname = column_name
    for i, f in enumerate(model._meta.fields):
        if f.name == field_name:
            model._meta.fields[i] = field
            break
    else:
        raise TypeError('Model {!r} does not have a field {!r}.'
                        .format(model, field_name))
    model.add_to_class(field_name,
                       ReverseSingleRelatedObjectDescriptor(field))

Ответ 4

Ответ Джозефа Спироса на один из моих вопросов может помочь вам:

Наследование и перформанс Django

Я не уверен, как это будет работать с прокси-моделями.

Ответ 7

Определите свойство category в EntryProxy, которое ищет CategoryProxy по его id:

class EntryProxy(Entry):
    @property
    def category(self):
        cid = super(EntryProxy, self).category.id
        return CategoryProxy.objects.get(id=cid)

    class Meta:
        proxy = True

Ответ 8

Адаптация Бернда Петерсона слегка ответит, мы имеем:

class EntryProxy(Entry):
    @property
    def category(self):
        return CategoryProxy.objects.get(id=self.category_id)

Это должно быть более экономичным с базой данных. Для дополнительных улучшений вы можете установить частный атрибут (self._category) при первом вызове метода, а затем вернуть это все последующие времена.