如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?

How to use Django QuerySet.union() in ModelAdmin.formfield_for_manytomany()?(如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?)

本文介绍了如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

不知道我在这里做错了什么:

我尝试使用

保存"之后(全选)

无论我做什么,每次保存表单时都会自动选择所有选项.

考虑到 QuerySet.union(),还是这是预期的行为/models/querysets/#union" rel="nofollow noreferrer">限制 QuerySet.union()返回的查询集?

解决方案

正如@tom-carrick 所指出的,似乎 QuerySet.union()QuerySet> 无法过滤.我想文档的以下摘录暗示了这一点:

<块引用>

另外,只有LIMITOFFSETCOUNT(*)ORDER BY,以及指定列(即切片、count()order_by()values()/values_list())在生成的 QuerySet 上允许.

如果您使用的是 Django 3.0,则在 QuerySet.union() 的结果上调用 filter() 将引发异常并显示非常清晰的消息:

django.db.utils.NotSupportedError: 不支持在 union() 之后调用 QuerySet.filter().

但是,如果您使用的是 Django 2.2,则不会引发异常:在这种情况下,它只会返回完整的查询集,而不管过滤器参数如何.这里有一个小测试来说明这一点(在 Django 2.2 中):

# 使用 Django 2.2.10类 PublicationTests(TestCase):def test_union_filter(self):对于范围内的 i (2):Publication.objects.create()queryset_union = Publication.objects.filter(id=1).union(Publication.objects.filter(id=2))self.assertEqual(2, len(queryset_union))对于 queryset_union.all() 中的 obj: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 中的查询集时,一定会发生这种情况:选择小部件按预期工作,但是当验证表单,在 QuerySet.union() 的输出上调用 filter()(参见 source for the ModelMultipleChoiceField),并且总是返回完整的查询集,无论实际的子选择如何.

根据实际用例,可能有使用 union() 的方法,如 tom-卡里克的回答.

但是,在这种情况下,至少有一种方法可以解决 QuerySet.union() 施加的限制,那就是从 queryset-union 创建一个新的查询集:

这是原始示例中 ArticleAdmin 的修改版本:

class ArticleAdmin(admin.ModelAdmin):def formfield_for_manytomany(self, db_field, request, **kwargs):如果 db_field.name == '出版物':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)

同样,这个人为示例中的实际查询没有意义,但这在这里并不重要.

就数据库访问而言,这可能不是最有效的解决方案.

Not sure what I am doing wrong here:

I tried to use QuerySet.union(), in Django 2.2.10, to combine two querysets (for the same model) inside ModelAdmin.formfield_for_manytomany(). However, when the form is saved, the entire queryset is selected, regardless of the actual selection made.

Please consider the minimal example below, based on the standard 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)

The initial queryset for the publications field is filtered using formfield_for_manytomany, as described in the docs.

PLEASE NOTE: The actual query in this example makes no sense, it just returns everything, but that's not important: the point is that QuerySet.union() messes up the selection. It works normally if you remove the union().

Here's what happens when I add a new Article in the admin, without selecting any publications:

Before "Save" (nothing selected)

After "Save" (everything is selected)

No matter what I do, all options are automatically selected every time the form is saved.

Am I using QuerySet.union() the wrong way, or is this expected behavior, given the restrictions on querysets returned by QuerySet.union()?

解决方案

As @tom-carrick pointed out, it appears that a QuerySet returned by QuerySet.union() cannot be filtered. I suppose this is implied by the following excerpt from the 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.

If you're using Django 3.0, calling filter() on the result of QuerySet.union() will raise an exception with a pretty clear message:

django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.

However, no exception is raised if you're using Django 2.2: In that case it just returns the complete queryset, regardless of the filter arguments. Here's a little test to illustrate that (in 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))

So, this must be what happens when we use QuerySet.union() to restrict a queryset in the ModelAdmin: The selection widget works as expected, but when the form is validated, filter() is called on the output of QuerySet.union() (see source for the ModelMultipleChoiceField), and that always returns the complete queryset, regardless of the actual subselection.

Depending on the actual use case, there may be ways around using union(), as explained in tom-carrick's answer.

However, there is at least one way to work around the restrictions imposed by QuerySet.union() in this situation, and that is to create a new queryset from the queryset-union:

Here's a modified version of the ArticleAdmin from the original example:

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)

Again, the actual query in this contrived example makes no sense, but that is not important here.

This might not be the most efficient solution in terms of database access.

这篇关于如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:如何在 ModelAdmin.formfield_for_manytomany() 中使用 Django QuerySet.union()?

基础教程推荐