Как аннотировать тип Manager().from_queryset()?

В Django у меня есть пользовательские QuerySet и Manager:

from django.db import models

class CustomQuerySet(models.QuerySet):
    def live(self):
        return self.filter(is_draft=False)

class CustomManager(models.Manager):
    def publish(self, instance: "MyModel"):
        instance.is_draft = False
        instance.save()

В моей модели я хочу использовать оба варианта, поэтому я использую метод from_queryset:

class MyModel(models.Model):
    objects: CustomManager = CustomManager().from_queryset(CustomQuerySet)()

    is_draft = models.BooleanField(blank=True, default=True)

Поскольку я аннотировал objects как CustomManager, Пайланс (через vscode) логично кричит мне, что MyModel.objects.live() неверно, из-за Cannot access attribute "live" for class "CustomManager" Attribute "live" is unknown.

Удаление аннотации типа приводит к аналогичной жалобе: Cannot access attribute "live" for class "BaseManager[MyModel]" Attribute "live" is unknown.

Как аннотировать objects в MyModel, чтобы Pylance знал, что в objects также доступны CustomQuerySet методы, а не только CustomManager методы?

Кажется, нет особого смысла размещать publish(..) на уровне менеджера, поскольку можно захотеть опубликовать всевозможные QuerySets.

Обычно вы определяете это на QuerySet с помощью:

class CustomQuerySet(models.QuerySet):
    def live(self) -> CustomQuerySet['MyModel']:
        return self.filter(is_draft=False)

    def publish(self):
        return self.update(is_draft=False)

и затем вы вводите CustomQuerySet с помощью:

class MyModel(models.Model):
    objects: QuerySet['MyModel'] = CustomQuerySet.as_manager()

Затем вы можете опубликовать набор элементов с помощью QuerySet, например:

MyModel.objects.filter(pk=1).publish()

CustomManager.from_queryset(CustomQuerySet) создает во время выполнения новый подкласс CustomManager, который копирует набор атрибутов из CustomQuerySet. Таким образом, «правильным» решением будет определение протокола, который перечисляет атрибуты, скопированные частным Manager._get_queryset_methods, используемым from_queryset.

Поскольку это, скорее всего, потребует много работы (и будет хрупким, так как зависит от частных деталей реализации, которые могут измениться), предложенный вами более широкий быстрый и грязный метод притворяться, что объект является экземпляром и CustomManager, и CustomQuerySet, может быть достаточным для ваших нужд.

class ManagerQuerySet(CustomManager, CustomQuerySet):
    pass


class MyModel(models.Model):
    objects: ManagerQuerySet = CustomManager().from_queryset(CustomQuerySet)()

    is_draft = models.BooleanField(blank=True, default=True)

(Действительно, from_queryset, похоже, выполняет своего рода «структурное» наследование, которое может быть неполным, но достаточно хорошим, чтобы позволить белую ложь, что новый класс является номинальным подклассом CustomQuerySet.)

Вернуться на верх