On the current project I’m working on, users can create a subject and subsequent topics within each subject. A topic has to be unique within each subject, but not unique across the app. So Math as a subject can have only one Theory topic, but English can have a Theory topic as well.
I have a TopicForm (instance of ModelForm) that has a hidden field called subject, which stores a reference to a Subject object. The hidden field is a ModelChoiceField, which is basically how forms render a ForeignKey field from a model. I needed to pass a Subject object from the previous view to the TopicCreateView and save it with the TopicForm. In retrospect, the solution was quite easy, but I took a rather circuitous path to get to it, so documenting it for future use is probably a good idea.
In my forms.py, the subject field is first hidden. Normally, to hide a form field, I add the
widget = forms.HiddenInput
in as an argument, but with the ModelChoiceField, that just broke the form. I had to add
queryset=Subject.objects.all()
and that fixed it. I need to figure out why.
class TopicForm(ModelForm):
subject = forms.ModelChoiceField(label="", queryset=Subject.objects.all(), widget=forms.HiddenInput)
class Meta:
model = Topic
fields = ['title', 'subject']
After that I needed to set the value of the subject field, which I did in the form valid method on the TopicCreateView.
def form_valid(self, form):
obj = form.save(commit=False)
current_user = get_user(self.request)
obj.subject = Subject.objects.get(id=self.kwargs['subject'], submitter_id=current_user.id)
obj.save()
return HttpResponseRedirect(self.get_success_url())
Now that we have the subject set on the form, when we try and save the new topic, we need to make sure it doesn’t exist within that Subject. I do that in the form’s clean method.
def clean(self):
cleaned_data = self.cleaned_data
title = cleaned_data.get('title')
subject = cleaned_data.get('subject')
if Topic.objects.filter(title=title, subject_id=subject.id).exists():
raise ValidationError(u'Topic already exists!')
Finally, in my template, I raise an error if the user tries to input the same topic, letting them know that the topic already exists.
{% raw %}
{% for field in form.visible_fields %}
{% if form.errors %}
<div class="form-group has-error">
<label class="control-label" for="inputError">Topic already exists!</label>
{% else %}
<div class="form-group">
{% endif %}
<label for="{{ field.html_name }}" class="col-lg-1 control-label">{{ field.label }}</label>
<div class="col-lg-4">
{{ field }}
</div>
</div>
{% endfor %}
{% endraw %}
And that solved the problem.