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

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

问题描述

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

我尝试使用

保存"后,(所有内容均已选中)

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

我使用 QuerySet.union()是错误的方法,还是给定解决方案

正如@ tom-carrick指出的那样,看来是 QuerySet.union() QuerySet >无法过滤.我想这是文档的以下摘录所隐含的:

此外,仅 LIMIT OFFSET COUNT(*) 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():对于我在范围(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()(请参阅 tom-卡里克的答案.

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

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

  class ArticleAdmin(admin.ModelAdmin)类:def formfield_for_manytomany(self,db_field,request,** kwargs):如果db_field.name =='publications':queryset_union = Publication.objects.all().union(Publication.objects.all())kwargs ['queryset'] = Publication.objects.filter(id__in = queryset_union)返回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()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆