Django:让 ModelChoiceField 在运行时评估查询集

Posted

技术标签:

【中文标题】Django:让 ModelChoiceField 在运行时评估查询集【英文标题】:Django: make ModelChoiceField evaluate queryset at run-time 【发布时间】:2011-07-18 02:34:21 【问题描述】:

根据登录的用户(一种特定于对象的权限),我已覆盖模型的默认管理器,以便仅显示允许的项目:

class User_manager(models.Manager):
    def get_query_set(self):
        """ Filter results according to logged user """
        #Compose a filter dictionary with current user (stored in a middleware method)
        user_filter = middleware.get_user_filter() 
        return super(User_manager, self).get_query_set().filter(**user_filter)

class Foo(models.Model):
    objects = User_manager()
    ...

这样,每当我使用Foo.objects 时,都会检索当前用户并对默认查询集应用过滤器,以便仅显示允许的记录。

然后,我有一个带有 Foo 的 ForeignKey 的模型:

class Bar(models.Model):
    foo = models.ForeignKey(Foo)

class BarForm(form.ModelForm):
    class Meta:
        model = Bar

当我编写 BarForm 时,我希望只看到过滤器 Foo 实例,但未应用过滤器。我认为这是因为查询集是在 Django 启动时评估和缓存的,当时没有用户登录,也没有应用过滤器。

有没有一种方法可以让 Django 在运行时评估 ModelChoice 查询集,而不必在表单定义中明确说明? (尽管存在所有性能问题...)

编辑 我找到了查询集的评估位置(django\db\models\fields\related.py: 887):

def formfield(self, **kwargs):
    db = kwargs.pop('using', None)
    defaults = 
        'form_class': forms.ModelChoiceField,
        'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
        'to_field_name': self.rel.field_name,
    
    defaults.update(kwargs)
    return super(ForeignKey, self).formfield(**defaults)

有什么提示吗?

【问题讨论】:

ModelChoice 的查询集已经很懒惰了,可能您正在某处评估查询集,显示一些代码(管理器 + 表单实现)会有所帮助:) 我已经添加了代码。在自定义管理器中放置一个断点,我看到 get_queryest' 仅在 Django 启动时评估(当消息 'Validating models...` 显示在 'runserver' 中时) 【参考方案1】:

恰好遇到了这个问题——需要使用组中的用户对象填充选择表单,但 fun_vit 的答案不正确(至少对于 django 1.5)

首先,您不想覆盖 field['somefield'].choices 对象——它是一个 ModelChoiceIterator 对象,而不是一个查询集。其次,django.forms.BaseForm 中的注释警告您不要覆盖 base_fields:

    # The base_fields class attribute is the *class-wide* definition of
    # fields. Because a particular *instance* of the class might want to
    # alter self.fields, we create self.fields here by copying base_fields.
    # Instances should always modify self.fields; they should not modify
    # self.base_fields.

这对我有用(django 1.5):

class MyForm(ModelForm):
    users = ModelMultipleChoiceField(queryset=User.objects.none())

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args,**kwargs)

        site = Site.objects.get_current()
        self.fields['users'].queryset = site.user_group.user_set.all()

    class Meta:
        model = MyModel

【讨论】:

注意,你也可以通过创建一个新的字段对象来替换整个 self.fields['users'] 对象。【参考方案2】:

我使用 init 的自定义形式:

class BT_Form(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BT_Form, self).__init__(*args, **kwargs)

        #prepare new values
        cities = [(u'',u'------')] #default value
        cities.extend([
            (
                c.pk, 
                c.__unicode__()
            ) for c in City.objects.filter(enabled=True).all()
        ])

        self.fields['fly_from_city'].choices = cities #renew values

【讨论】:

【参考方案3】:

没办法:我不得不重写查询集定义(在启动时评估)

【讨论】:

以上是关于Django:让 ModelChoiceField 在运行时评估查询集的主要内容,如果未能解决你的问题,请参考以下文章

Django ModelChoiceField 的问题

Modelchoicefield 查询集混淆

Django:按用户过滤 ModelChoiceField

表单 ModelChoiceField 查询集 + 额外选择字段 django 表单

Django - ModelChoiceField 查询集如何工作?

Django 表单不使用 ModelChoiceField 保存 - ForeignKey