实例化内联表单集时指定查询集

Posted

技术标签:

【中文标题】实例化内联表单集时指定查询集【英文标题】:specify queryset when instantiating an inlineformset 【发布时间】:2011-05-07 15:44:34 【问题描述】:

我在 views.py 中有以下内容来生成一个页面,该页面显示用户“资格”的内联表单集

from django.db.models import User
from models import UserQualification

def edit_quals(request):
    QualsFormSet = inlineformset_factory(User, UserQualification, fields=('qualification', 'qualification_year', 'qualification_source'))
    if request.method == 'POST':
        formset = QualsFormSet(request.POST, request.FILES, instance = request.user)
        if formset.is_valid():
            formset.save()

            #send user somewhere
            return HttpResponseRedirect(request.user.get_profile_url())
    else:
        formset = QualsFormSet(instance = request.user)

    return render_to_response("edit_quals.html", 
        "formset": formset,
    , context_instance=RequestContext(request))

这很好用,但是,我想创建一个formset,它只包含某些“UserQualification”对象(即,只有那些被标记为某种类型的对象),所以当用户访问这个页面时,它们只是编辑其资格的子集。这个参考似乎是我想要的:http://docs.djangoproject.com/en/1.1/topics/forms/modelforms/#changing-the-queryset。它处理 modelformset_factory,但是 inlineformset_factory 是基于先验的,所以我认为同样的事情应该可以工作:

formset = QualsFormSet(instance = request.user, queryset=UserQualification.objects.filter(type__startswith='xyz'))

但是进入这个页面只会给出一个 TypeError: init() got an unexpected keyword argument 'queryset'。该参考文献中列出了两种方法,我都遇到了问题。

【问题讨论】:

【参考方案1】:

我在这里为这个问题的一个稍微简单的版本提供了一个答案: Limit Django's inlineformset_factory to only create new objects

对于您尝试执行的操作,InlineFormSet 类将如下所示:

class BaseInlineFilteredFormSet(BaseInlineFormSet):
    def get_queryset(self):
        ## See get_queryset method of django.forms.models.BaseModelFormSet
        if not hasattr(self, '_queryset'):
            self._queryset = self.queryset.filter(type__startswith='xyz'))
            if not self._queryset.ordered:
                self._queryset = self._queryset.order_by(self.model._meta.pk.name)                
        return self._queryset

【讨论】:

【参考方案2】:

我相信在这种情况下,您需要在调用 inlineformset_factory 时使用 form 参数,并将您使用 ad-hoc __init__ 方法制作的自定义表单传递给它。 如果你需要它是参数化的,我前段时间在这里找到了救生线

    FormSetName.form = staticmethod(curry(FormName, customparam=chosenparam))

其中,如上所述,FormSetName 是用指向 FormName 的 form 参数定义的。

顺便说一句,很想感谢提出上述解决方案的人,很想留下一个暖心的答复,但声誉太低了。

编辑以更好地解释我的解决方案:

这将是自定义的 form 类(在这种情况下,选择一个人作为 relative,但仅在一部分人中,那些由 relatives() 方法):

class RelativeSelectForm(forms.ModelForm):
class Meta:
    model = Person
    fields = ('relative',)

def __init__(self, *args, **kwargs):
    try:
        profile = kwargs.pop('profile')
    except KeyError:
        super(RelativeSelectForm, self).__init__(*args, **kwargs)
        return
    super(RelativeSelectForm, self).__init__(*args, **kwargs)
    self.fields['relative'].queryset = profile.relatives().order_by('name')

它会像这样在你的 vies.py 中使用:

RelativesFormSet = inlineformset_factory(Person, Person, form=RelativeSelectForm, can_delete=True, extra=1)
RelativesFormSet.form = staticmethod(curry(RelativeSelectForm, profile=request.user.profile))

这样每次您实例化一个表单集时,它会自动获取填充了正确对象的 profile 参数,并且 relative 选择框的查询集只显示他有权访问的人到。 希望我让自己更清楚一点:)

【讨论】:

如果我理解你的想法,我应该创建一个自定义 formset,而不是像你建议的那样创建自定义 form。因为在表单集初始化中我可以定义一个唯一的查询集。如果这与您所指的想法相同,则这是我包含的参考文献中列出的第二种方法,但这似乎对我不起作用。我和那个参考之间的唯一区别是我使用的是 inlineformset,而不仅仅是 modelformset。所以我仍然不明白我做错了什么?哦,我不认为我需要参数,但谢谢你的想法。 我的意思是,与其手动选择要使用的每个字段,不如调用 FormSetName = inlineformset_factory(FirstModel, SecondModel, form=CustomForm, can_delete=True, extra=1) - 你看,通过一个 Form 类作为用于实例化每个表单的参数。 哦,我明白你的意思了。不幸的是,我认为我们在谈论不同的事情:我在那个表单集中选择的记录有问题,而不是选择的字段。即,用户 A 使用此表单集打开页面,而不是看到他的所有资格(比如说总共 5 个),他只看到其中一些(可能是前 3 个)。他看到的那些资格领域不是问题。我希望我澄清了这一点? 我编辑了我的解决方案,因为我相信我所理解的就是您所需要的 :)

以上是关于实例化内联表单集时指定查询集的主要内容,如果未能解决你的问题,请参考以下文章

没有实例的 Django Formset

具有静态(内联)方法的从未实例化类的基类

内联静态自动的初始化程序“sizeof(T)”...是不是需要实例化?

程序集和类的类实例化(“新”)问题[重复]

为实例化的表单对象捕获 onClose 事件

我可以在运行时加载 .NET 程序集并实例化只知道名称的类型吗?