Issac Gable Issac Gable - 2 months ago 12
Python Question

Django: Using get_form_kwarg to pass url pramater into form __init__ for ModelChoiceField selection filiter

I am building a FAQ app.

Model flow Topic -> Section -> Article.

Article has a FK to Section which has a FK to Topic.

In my create article from I want to take in the Topic_Pk so when the user selects a Section the choice selection is limited to just the Sections attached under the Topic.

I am using get_from_kwarg to pass the Topic_Pk from the url to

__init__
in the form. I keep getting a
TypeError __init__() got an unexpected keyword argument 'topic_pk'
. I do not want to pop the data or set topic_pk=None in the
__init__
parameters as this would invalidate the whole point.

What is it I am missing to allow me to use this variable?

Url:

url(r'^ironfaq/(?P<topic_pk>\d+)/article/create$', ArticleCreateView.as_view()),


View:

class ArticleCreateView(CreateView):
model = Article
form_class = CreateArticleForm
template_name = "faq/form_create.html"
success_url = "/ironfaq"

def get_form_kwargs(self):
kwargs = super(ArticleCreateView,self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs


Form:

class CreateArticleForm(forms.ModelForm):

section = forms.ModelChoiceField(queryset=Section.objects.none())

def __init__(self, *args, **kwargs):
super(CreateArticleForm, self).__init__(*args, **kwargs)
self.fields['section'].queryset = Section.objects.filter(topic_pk=self.kwargs['topic_pk'])

class Meta:
model = Article
widgets = {
'answer': forms.Textarea(attrs={'data-provide': 'markdown', 'data-iconlibrary': 'fa'}),
}
fields = ('title','section','answer')


Model:

class Article(Audit):
title = models.CharField(max_length=255)
sort = models.SmallIntegerField()
slug = models.SlugField()
section = models.ForeignKey(Section,on_delete=models.CASCADE)
answer = models.TextField()
vote_up = models.IntegerField()
vote_down = models.IntegerField()
view_count = models.IntegerField(default=0)

class Meta:
verbose_name_plural = "articles"

def __str__(self):
return self.title

def total_votes(self):
return self.vote_up + self.vote_down

def percent_yes(self):
return (float(self.vote_up) / self.total_votes()) * 100

def get_absolute_url(self):
return ('faq-article-detail',(), {'topic__slug': self.section.topic.slug,
'section__slug': self.section.slug, 'slug': self.slug})

Answer

For your current __init__ signature, you must pop topic_pk from kwargs before you call super(), otherwise you'll get the TypeError.

In your question, you say that popping the value would 'invalidate the whole point', but I think you're mistaken. You can still use the topic_pk value after calling super().

class CreateArticleForm(forms.ModelForm):

    section = forms.ModelChoiceField(queryset=Section.objects.none())

    def __init__(self, *args, **kwargs):
        topic_pk = kwargs.pop('topic_pk')
        super(CreateArticleForm, self).__init__(*args, **kwargs)
        self.fields['section'].queryset = Section.objects.filter(topic_pk=topic_pk)

Another approach would be to use topic_pk as a named argument. Note that this changes the signature of the __init__ method, so it might break other code (for example if you had CreateArticleForm(request.POST) somewhere else).

    def __init__(self, topic_pk=None, *args, **kwargs):
        super(CreateArticleForm, self).__init__(*args, **kwargs)
        self.fields['section'].queryset = Section.objects.filter(topic_pk=topic_pk)