Как аннотировать тип 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.)