如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?
How to use Django QuerySet.union() in ModelAdmin.formfield_for_manytomany()?
不确定我在这里做错了什么:
我尝试在 Django 2.2.10 中使用 QuerySet.union(),在 ModelAdmin.formfield_for_manytomany()
中组合两个查询集(对于同一模型)。但是,当保存表单时,将选择整个查询集,而不管实际选择是什么。
请考虑下面基于标准 Django 的最小示例 Article/Publication example。
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)
publications
字段的初始 queryset
使用 formfield_for_manytomany
过滤,如 docs.
中所述
请注意:此示例中的实际查询没有任何意义,它只是 returns 一切,但这并不重要:关键是 QuerySet.union()
弄乱了选择。如果您删除 union()
.
它可以正常工作
这是我在管理中添加新 Article
时发生的情况, 没有选择任何出版物:
“保存”之前(未选择)
“保存”后(全部选中)
无论我做什么,每次保存表单时都会自动选择所有选项。
考虑到 QuerySet.union()
返回的查询集上的 restrictions,我是否以错误的方式使用 QuerySet.union()
,或者这是预期的行为?
问题似乎是 .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)
正如@tom-carrick 指出的那样,QuerySet.union()
返回的 QuerySet
似乎无法过滤。我想这是从 documentation 的以下摘录中暗示的:
In addition, only LIMIT
, OFFSET
, COUNT(*)
, ORDER BY
, and specifying columns (i.e. slicing, count()
, order_by()
, and values()
/values_list()
) are allowed on the resulting QuerySet
.
如果您使用的是 Django 3.0,在 QuerySet.union()
的结果上调用 filter()
将引发异常并显示一条非常明确的消息:
django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.
但是,如果您使用的是 Django 2.2,则不会引发异常:在这种情况下,它只是 returns 完整的查询集,而不考虑过滤器参数。这是一个小测试来说明(在 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))
所以,当我们使用 QuerySet.union()
限制 ModelAdmin
中的查询集时,一定会发生这种情况:选择小部件按预期工作,但是当表单被验证时,filter()
在 QuerySet.union()
的输出上调用(请参阅 source 了解 ModelMultipleChoiceField
),并且总是 returns 完整的查询集,而不管实际的子选择。
根据实际用例,可能有使用 union()
的方法,如 .
中所述
但是,在这种情况下,至少有一种方法可以解决 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)
同样,这个人为示例中的实际查询没有任何意义,但这在这里并不重要。
就数据库访问而言,这可能不是最有效的解决方案。
不确定我在这里做错了什么:
我尝试在 Django 2.2.10 中使用 QuerySet.union(),在 ModelAdmin.formfield_for_manytomany()
中组合两个查询集(对于同一模型)。但是,当保存表单时,将选择整个查询集,而不管实际选择是什么。
请考虑下面基于标准 Django 的最小示例 Article/Publication example。
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)
publications
字段的初始 queryset
使用 formfield_for_manytomany
过滤,如 docs.
请注意:此示例中的实际查询没有任何意义,它只是 returns 一切,但这并不重要:关键是 QuerySet.union()
弄乱了选择。如果您删除 union()
.
这是我在管理中添加新 Article
时发生的情况, 没有选择任何出版物:
“保存”之前(未选择)
“保存”后(全部选中)
无论我做什么,每次保存表单时都会自动选择所有选项。
考虑到 QuerySet.union()
返回的查询集上的 restrictions,我是否以错误的方式使用 QuerySet.union()
,或者这是预期的行为?
问题似乎是 .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)
正如@tom-carrick 指出的那样,QuerySet.union()
返回的 QuerySet
似乎无法过滤。我想这是从 documentation 的以下摘录中暗示的:
In addition, only
LIMIT
,OFFSET
,COUNT(*)
,ORDER BY
, and specifying columns (i.e. slicing,count()
,order_by()
, andvalues()
/values_list()
) are allowed on the resultingQuerySet
.
如果您使用的是 Django 3.0,在 QuerySet.union()
的结果上调用 filter()
将引发异常并显示一条非常明确的消息:
django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.
但是,如果您使用的是 Django 2.2,则不会引发异常:在这种情况下,它只是 returns 完整的查询集,而不考虑过滤器参数。这是一个小测试来说明(在 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))
所以,当我们使用 QuerySet.union()
限制 ModelAdmin
中的查询集时,一定会发生这种情况:选择小部件按预期工作,但是当表单被验证时,filter()
在 QuerySet.union()
的输出上调用(请参阅 source 了解 ModelMultipleChoiceField
),并且总是 returns 完整的查询集,而不管实际的子选择。
根据实际用例,可能有使用 union()
的方法,如
但是,在这种情况下,至少有一种方法可以解决 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)
同样,这个人为示例中的实际查询没有任何意义,但这在这里并不重要。
就数据库访问而言,这可能不是最有效的解决方案。