Как отфильтровать однозначное родовое отношение с Django? - PullRequest
1 голос
/ 09 мая 2019

У меня модерационная модель:

class ItemModeration(models.Model):

    class Meta:
        indexes = [
            models.Index(fields=['object_id', 'content_type']),
        ]
        unique_together = ('content_type', 'object_id')

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    item = GenericForeignKey('content_type', 'object_id')

    published = models.BooleanField(default=False)
    ...

Дескриптор для прикрепления объекта модерации на лету:

class ItemModerationDescriptor(object):
    def __init__(self, **default_kwargs):
        self.default_kwargs = default_kwargs

    def __get__(self, instance, owner):
        ctype = ContentType.objects.get_for_model(instance.__class__)
        try:
            moderation = ItemModeration.objects.get(content_type__pk=ctype.id,
                                                    object_id=instance.pk)
        except ItemModeration.DoesNotExist:
            moderation = ItemModeration(item=instance,**self.default_kwargs)
            moderation.save()
        return moderation

И модель, которую я хочу модерировать:

class Product(models.Model):
    user = models.ForeignKey(
        User,
        null=True,
        on_delete=models.SET_NULL)
    created = models.DateTimeField(
        auto_now_add=True,
        blank=True, null=True,
    )
    modified = models.DateTimeField(
        auto_now=True,
        blank=True, null=True,
    )
    name = models.CharField(
        max_length=PRODUCT_NAME_MAX_LENGTH,
        blank=True, null=True,
    )
    moderation = ItemModerationDescriptor()

Теперь я легко вижу состояние «опубликовано» продукта:

p=Product(name='my super product')
p.save()
print(p.moderation.published)
-> False

Родовое отношение полезно, потому что я буду в состоянии искать объекты, чтобы модерировать независимо от типа: это могут быть продукты, изображения, комментарии.

 to_moderate_qs = ItemModeration.objects.filter(published=False)

Теперь, как я могу получить отфильтрованный список опубликованных продуктов? Я хотел бы сделать что-то вроде этого

published_products_qs = Product.objects.filter(moderation__published=True, name__icontains='sony')

Но, конечно, он не будет работать, поскольку атрибут moderation не является полем модели Django.

Как я могу сделать это эффективно? Я подумываю о подходящем JOIN, но не могу понять, как это сделать с помощью django без использования необработанного SQL.

Ответы [ 2 ]

1 голос
/ 09 мая 2019

У Django есть отличный встроенный ответ для этого: GenericRelation .Вместо вашего дескриптора, просто определите общее отношение в вашей модели Product и используйте его как обычное связанное поле:

from django.contrib.contenttypes.fields import GenericRelation

class Product(models.Model):
    ...
    moderation = GenericRelation(ItemModeration)

Затем обработайте создание, как вы это обычно делаете со связанной моделью, и фильтрация должна работатьименно так, как вы предусмотрели.Чтобы работать как ваша текущая система, вам нужно было бы подключить метод save для создания связанного объекта ItemModeration при создании нового Product, но это ничем не отличается от других связанных моделей django.Если вы действительно хотите сохранить класс дескриптора, вы, очевидно, можете использовать вторичное поле модели для GenericRelation.

. Вы также можете добавить related_query_name, чтобы разрешить фильтрацию объектов ItemModeration только на основеProduct тип содержимого.

ПРЕДУПРЕЖДЕНИЕ если вы делаете , обратите внимание на GenericRelation, что он имеет фиксированное каскадное удаление.Поэтому, если вы не хотите, чтобы объект ItemModeration был удален при удалении Product, будьте осторожны, добавив хук pre_delete или эквивалентный!

Обновление

Я случайно проигнорировал OneToOne аспект вопроса, потому что GenericForeignKey является отношением один-ко-многим, но аналогичные функциональные возможности могут быть реализованы посредством разумного использования QuerySets.Это правда, у вас нет доступа к product.moderation как к одному объекту.Но, например, следующий запрос перебирает фильтрованный список продуктов и извлекает их имя, имя пользователя и дату публикации соответствующего ModerationItem:

Product.objects.filter(...).values_list(
    'name', 'user__username', 'moderation__published'
)
0 голосов
/ 09 мая 2019

Вам придется использовать content_type для запроса таблицы по конкретному типу модели.

как это:

product_type = ContentType.objects.get_for_model(Product)
unpublished_products = ItemModeration.objects.filter(content_type__pk=product_type.id, published=False)

Для более подробной информации по теме проверьте contenttypes doc

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...