例如,我有以下模型:
class Person(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
class SNS(models.Model):
name = models.CharField(max_length=255)
# for display in raw_id_fields
def __str__(self):
return self.name
class PersonSNS(models.Model):
person = models.ForeignKey('Person', related_name='sns')
sns = models.ForeignKey('SNS')
url = models.CharField(max_length=255)
在admin.py
class PersonSNSInline(admin.StackedInline):
model = PersonSNS
fields = ('sns', 'url')
raw_id_fields = ('sns',)
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
inlines = [PersonSNSInline]
def get_queryset(self, request):
return super(PersonAdmin, self).get_queryset(request).prefetch_related('sns')
但是,django-debug-toolbar
像下面这样显示执行的SQL
:
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` IN (1, 2, 3, 5, 6)
和
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` = 1
Duplicated 5 times.
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` = 5
Duplicated 5 times.
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` = 6
Duplicated 5 times.
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` = 3
Duplicated 5 times.
SELECT `sns`.`id`, `sns`.`name` FROM `sns` WHERE `sns`.`id` = 2
Duplicated 5 times.
我想知道为什么PersonSNSInline
尽管我已经预取了相关数据,仍然仍然一个一个地查询数据库。
答案 0 :(得分:0)
目前不支持
该框架甚至不使用相关的模型管理器来检索内联表单集的查询集。
即使您尝试执行自己的自定义内联和内联表单集,基类的工作方式也非常不利于对象缓存,并且预取/选择相关内容也不会按您预期的那样工作。
qs = queryset.filter(**{self.fk.name: self.instance})
为了能够真正创建支持这一点的自定义内联,框架需要:
ge_instance_by_index
方法中的索引提取实例,以便我们可以实现管理员在当前基类的顶部执行我们想做的事情。如果这样做,那么我们将能够覆盖 ge_instance_by_index 以在支持预取的内联上返回 list(self.get_queryset())[i]
而不是 self.get_queryset()[i]
BaseInlineFormSet
构造函数中删除查询集准备逻辑。例如,如果查询集是用如下方法准备的,我们可以覆盖它以保持相关的管理器查询集完整:
def prepare_queryset(queryset):
if queryset is None:
queryset = self.model._default_manager
if self.instance.pk is not None:
qs = queryset.filter(**{self.fk.name: self.instance})
else:
qs = queryset.none()
return qs
会变得只是
def prepare_queryset(queryset):
return queryset
证明如果上述问题得到修复,我们可以获得可用的自定义解决方案的黑客方法是:
class RelatedManagerInlineFormSet(BaseInlineFormSet):
"""
-------hack around issue 2-------
"""
def get_queryset(self):
return list(self.queryset)
@property
def queryset(self):
return self._related_manager_queryset
@queryset.setter
def queryset(self, v):
#keeps the property readonly
pass
def __init__(self,*args, **kwargs) -> None:
self._related_manager_queryset=kwargs['queryset']
kwargs['queryset']=None
super().__init__(*args, **kwargs)
class RelatedModelManagerInline(BaseInlineFormSet):
"""Relate manager inline mixin to be used
"""
formset=RelatedManagerInlineFormSet
def get_formset(self, request: HttpRequest, obj=None, *args, **kwargs):
"""
-------hack around issue 3-------
"""
if obj is not None:
self.obj=obj
return super().get_formset(request, obj=obj, *args, **kwargs)
def get_queryset(self, request: HttpRequest) -> list:
"""
Returns a list for related manager's queryset, although dangerous
-------returning a list is a hack around issue 1-------
add 'related_name' property to the class that extends this
Args:
request (HttpRequest): the request being performed
Returns:
list: the list returned from related queryset
"""
return list(getattr(self.obj, self.related_name).all())
请不要使用。以上只是表明如果问题得到解决,我们可以有一个自定义的内联支持预取
我个人认为预取支持应该是一个内置功能,因为广泛的讨论证明社区希望它作为默认行为,因此我在这里创建了一张票: https://code.djangoproject.com/ticket/32587