djv*_*jvg 3 python django django-admin django-queryset
不确定我在这里做错了什么:
我尝试在 Django 2.2.10 中使用QuerySet.union( ) 在ModelAdmin.formfield_for_manytomany(). 但是,在保存表单时,会选择整个查询集,而不管实际做出的选择如何。
请考虑下面基于标准 Django文章/出版物示例的最小示例。
from django.db import models
from django.contrib import admin
class Publication(models.Model):
pass
class Article(models.Model):
publications = models.ManyToManyField(to=Publication, blank=True)
class ArticleAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'publications':
# the following query makes no sense, but it shows an attempt to
# combine two separate QuerySets using QuerySet.union()
kwargs['queryset'] = Publication.objects.all().union(
Publication.objects.all())
return super().formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(Publication)
admin.site.register(Article, ArticleAdmin)
Run Code Online (Sandbox Code Playgroud)
初始queryset的publications电场是利用过滤formfield_for_manytomany,如在所描述的文档。
请注意:本示例中的实际查询没有意义,它只是返回所有内容,但这并不重要:关键是会QuerySet.union()弄乱选择。如果删除union().
这是当我Article在管理员中添加一个新的,而不选择任何出版物时会发生什么:
在“保存”之前(未选择任何内容)
“保存”后(选择所有内容)
无论我做什么,每次保存表单时都会自动选择所有选项。
QuerySet.union()鉴于对?返回的查询集的限制,我是否使用了错误的方式,或者这是预期的行为QuerySet.union()?
正如@tom-carrick 指出的那样,似乎无法过滤QuerySet返回的 by QuerySet.union()。我想这是从文档中的以下摘录所暗示的:
此外,只有
LIMIT,OFFSET,COUNT(*),ORDER BY,和指定列(即切片,count(),order_by(),和values()/values_list())被允许对所得到的QuerySet。
如果您使用的是 Django 3.0,则调用filter()的结果QuerySet.union()将引发异常并带有非常明确的消息:
django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.
Run Code Online (Sandbox Code Playgroud)
但是,如果您使用的是 Django 2.2,则不会引发异常:在这种情况下,它只返回完整的查询集,而不管过滤器参数如何。这里有一个小测试来说明这一点(在 Django 2.2 中):
# using Django 2.2.10
class PublicationTests(TestCase):
def test_union_filter(self):
for i in range(2):
Publication.objects.create()
queryset_union = Publication.objects.filter(id=1).union(
Publication.objects.filter(id=2))
self.assertEqual(2, len(queryset_union))
for obj in queryset_union.all():
self.assertIn(obj, queryset_union.filter(id=1))
self.assertIn(obj, queryset_union.filter())
self.assertIn(obj, queryset_union.filter(id=0))
Run Code Online (Sandbox Code Playgroud)
所以,这一定是当我们使用会发生什么QuerySet.union()限制在一个查询集ModelAdmin:如预期部件工作的选择,但是当表单验证,filter()被称为上的输出QuerySet.union()(见来源的ModelMultipleChoiceField),并且总是返回完整查询集,不管实际的子选择。
根据实际用例,可能有一些方法可以使用union(),如tom-carrick's answer 中所述。
但是,至少有一种方法可以解决QuerySet.union()这种情况下施加的限制,那就是从查询集联合创建一个新的查询集:
这是ArticleAdmin原始示例中的修改版本:
class ArticleAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'publications':
queryset_union = Publication.objects.all().union(
Publication.objects.all())
kwargs['queryset'] = Publication.objects.filter(id__in=queryset_union)
return super().formfield_for_manytomany(db_field, request, **kwargs)
Run Code Online (Sandbox Code Playgroud)
同样,这个人为示例中的实际查询没有意义,但这在这里并不重要。
就数据库访问而言,这可能不是最有效的解决方案。
问题似乎确实是.union(),虽然我不知道为什么。这似乎是一个错误,或者至少是时髦的行为。
由于您没有指定您的实际用例,因此很难知道,但是对于您提供的示例,您可以改用OR运算符,这将适用于:
class ArticleAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'publications':
# the following query makes no sense, but it shows an attempt to
# combine two separate QuerySets using QuerySet.union()
kwargs['queryset'] = (
Publication.objects.filter(id__lt=3)
| Publication.objects.filter(id__gt=2)
)
return super().formfield_for_manytomany(db_field, request, **kwargs)
Run Code Online (Sandbox Code Playgroud)