Django ModelMultipleChoiceField update queryset within constructor fails on POST
I've been pulling from various questions on StackOverflow to try to figure out how to work with ModelMultipleChoiceFields within a form. I almost have a working form that allows users to select languages to translate an article to. I created a form that takes a SourceArticle
as the first constructor argument and uses it to specify the queryset for the languages
field of my form.
class AddTargetLanguagesForm(forms.Form):
def __init__(self, article=None, *args, **kwargs):
super(AddTargetLanguagesForm, self).__init__(*args, **kwargs)
self.fields['languages'].queryset = Language.objects.exclude(
Q(id = article.language.id) |
Q(id__in=[o.id for o in article.get_target_languages()]) |
Q(code="templates"))
languages = forms.ModelMultipleChoiceField(_("Languages"))
Note that my AddTargetLanguagesForm
is not based on a ModelForm
, because it is not directly related to any of my model objects.
When I render the form for the first time, it correctly provides me with languages that (a) aren't the source language, (b) aren't already selected, and (c) aren't the special "templates" language. However, when I try to post my form, I get the following error:
AttributeError: 'QueryDict' object has no attribute 'language'
I assume that this is related to how forms work in Django, but I'm pretty new. Rather than accepting a SourceArticle
as the first parameter in my constructor, a QueryDict
is placed instead. I assume that this contains the POST params from the request. How do I need to modify my code to allow it to capture the selected languages?
Here is a copy of my view, if it helps you see how I'm using the form.
@login_required
def add_target_languages(request, aid, template_name="wt_articles/add_target_languages.html"):
"""
Adds one or more target language translations to a source article.
"""
content_dict = {}
# Fetch the article
no_match = False
sa_set = SourceArticle.objects.filter(id=aid)
if len(sa_set) < 1:
no_match = True
content_dict['no_match'] = no_match
else:
article = sa_set[0]
content_dict['article'] = article
if request.method == "POST":
target_language_form = AddTargetLanguagesForm(request.POST)
if target_language_form.is_valid():
languages = target_language_form.cleaned_data['languages']
article.add_target_languages(languages)
return HttpResponseRedirect('/articles/list')
else:
target_language_form = AddTargetLanguagesForm(article)
content_dict['target_language_form'开发者_JS百科] = target_language_form
return render_to_response(template_name, content_dict,
context_instance=RequestContext(request))
This line is your problem:
target_language_form = AddTargetLanguagesForm(request.POST)
That's the standard way of instantiating a form from a POST, but the trouble is that you've rewritten the method signature of AddTargetLanguagesForm.__init__
:
def __init__(self, article=None, *args, **kwargs):
so that the first positional argument (after the automatic self
), is article
. You could change the instantiation, but I prefer to do this:
def __init__(self, *args, **kwargs):
article = kwargs.pop('article', None)
super(AddTargetLanguagesForm, self).__init__(*args, **kwargs)
if article is not None:
...etc...
精彩评论