Как аннотировать тип 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(..)
на уровне менеджера, поскольку можно захотеть опубликовать всевозможные QuerySet
s.
Обычно вы определяете это на 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
.)