GenericForeignKey в Grappelli admin: фильтр content_type для отображения только соответствующих моделей - PullRequest
1 голос
/ 10 мая 2019

С помощью связанных поисков я могу легко получить доступ ко всем моделям, для которых у меня есть общий внешний ключ.Очевидно, это не то, что я хочу сделать.Я хочу ограничить это только подмножеством моделей, которые у меня есть - в частности, все наследовать от абстрактной модели Registry.

Мои модели выглядят так:

class Registry(models.Model):
    """A base registry class."""

    number = models.BigAutoField(primary_key=True)
    when = models.DateField(default=timezone.now)
    title = models.CharField(
        max_length=1024, default='', blank=True, null=True)

    class Meta:
        """The meta class."""

        abstract = True

    […]


class Revision(models.Model):
    """A revision model."""

    when = models.DateTimeField(default=timezone.now)
    identification = models.BigIntegerField()
    content_type = models.ForeignKey(
        ContentType, on_delete=models.CASCADE, related_name='+')
    object_id = models.PositiveIntegerField()
    parent = GenericForeignKey('content_type', 'object_id')

    […]


class Document(Registry):

    […]

class Drawing(Registry):

    […]

Так что каждый Registry производный экземпляр может иметь много разных ревизий.

И соответствующий администратор:

class RevisionAdmin(admin.ModelAdmin):
    """Revision administration definition."""

    fieldsets = [
        ('Revision', {
            'fields': [
                'when',
                'identification',
            ]
        }),
        ('Registry', {
            'classes': ('grp-collapse grp-open',),
            'fields': ('content_type', 'object_id', )
        }),
    ]

1 Ответ

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

Вы можете использовать limit_choices_to [Django-doc] .Так как вы хотите ограничить выбор потомками , нам нужно написать некоторую дополнительную логику, чтобы сначала вычислить их:

Мы можем, например, сначала вычислить все подклассы с помощью thisfunction :

def get_descendants(klass):
    gen = { klass }
    desc = set()
    while gen:
        gen = { skls for kls in gen for skls in kls.__subclasses__() }
        desc.update(gen)
    return desc

Теперь мы можем определить вызываемый объект для получения первичных ключей ContentType s, которые являются подклассами класса, в данном случае Registry:

from django.db.models import Q
from django.contrib.contenttypes.models import ContentType

def filter_qs():
    if not hasattr(filter_qs_registry, '_q'):
        models = get_descendants(<b>Registry</b>)
        pks = [v.pk for v in ContentType.objects.get_for_models(*models).values()]
        filter_qs_registry._q = Q(pk__in=pks)
    return filter_qs_registry._q

В ForeignKey к ContentType мы можем использовать поле limited_choices_to:

class Revision(models.Model):
    """A revision model."""
    when = models.DateTimeField(default=timezone.now)
    identification = models.BigIntegerField()
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        <b>limit_choices_to=filter_qs_registry</b>,
        related_name='+'
    )
    object_id = models.PositiveIntegerField()
    parent = GenericForeignKey('content_type', 'object_id')

Переменное число восхождений

Мы можем обобщить количество восхождений, обобщив, например, функцию get_descendants:

def get_descendants(*klass):
    gen = { *klass }
    desc = set()
    while gen:
        gen = { skls for kls in gen for skls in kls.__subclasses__() }
        desc.update(gen)
    return desc

Далее мы можем просто вызвать его с помощью:

from django.db.models import Q
from django.contrib.contenttypes.models import ContentType

def filter_qs():
    if not hasattr(filter_qs_registry, '_q'):
        models = get_descendants(<b>Registry, OtherAbstractModel</b>)
        pks = [v.pk for v in ContentType.objects.get_for_models(*models).values()]
        filter_qs_registry._q = Q(pk__in=pks)
    return filter_qs_registry._q
...