I understand that it is possible to override the default query set used by 'modelformset . It simply limits the objects for which the form is created.
I also found a stack overflow question about filtering ForeignKey selections in Django ModelForm , but not ModelForm Set and about limiting the available options in the Django set , but not Model FormSet. I have included my version of this code below.
I want to make a ModelFormSet model for a school class ("teaching group" or "class" to avoid colliding with the keyword "class") with one field limited by a set of queries. This is for the teacher’s class editing form, in order to be able to reassign students to another class, but be limited to classes within the same cohort.
My .py models
class YearGroup(models.Model): intake_year = models.IntegerField(unique=True) year_group = models.IntegerField(unique=True, default=7) def __unicode__(self): return u'%s (%s intake)' % (self.year_group, self.intake_year) class Meta: ordering = ['year_group'] class TeachingGroup(models.Model): year = models.ForeignKey(YearGroup) teachers = models.ManyToManyField(Teacher) name = models.CharField(max_length=10) targetlevel = models.IntegerField() def __unicode__(self): return u'Y%s %s' % (self.year.year_group, self.name) class Meta: ordering = ['year', 'name']
My views.py
def edit_pupils(request, teachinggroup): theclass = TeachingGroup.objects.get(name__iexact = teachinggroup) pupils = theclass.pupil_set.all() PupilModelFormSet = modelformset_factory(Pupil) classes_by_year = theclass.year.teachinggroup_set.all() choices = [t for t in classes_by_year] # choices = [t.name for t in classes_by_year] #### I also tried this if request.method == 'POST': formset = PupilModelFormSet(request.POST,queryset=pupils) if formset.is_valid(): formset.save() return redirect(display_class_list, teachinggroup = teachinggroup) else: formset = PupilModelFormSet(queryset=pupils) for form in formset: for field in form: if 'Teaching group' == field.label: field.choices = choices return render_to_response('reassign_pupils.html', locals())
As you can see, I am restricting the choice of the class class request class_by_year, which is only classes belonging to a group of the same year. This request is executed correctly, as you can see on the page below, but it does not affect the form field.
My template
{% for form in formset %} <tr> {% for field in form.visible_fields %} <td> {# Include the hidden fields in the form #} {% if forloop.first %} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} {% endif %} <p><span class="bigtable">{{ field }}</span> {% if field.errors %} <p><div class="alert-message error"> {{field.errors|striptags}}</p> </div> {% endif %} </td> {% endfor %} </tr> {% endfor %} </table> <input type="submit" value="Submit changes"></p> </form> {{ choices }}
The page is displayed with all the training groups (classes) visible in the selection element, but the tag at the bottom of the page is displayed as: [<TeachingGroup: Y8 82Ma2>, <TeachingGroup: Y8 82Ma3>] , which accurately displays only two classes in the 8th year.
Note that I also read James Bennett's message. So you need a dynamic form , as recommended. How to limit the available options for a foreign key field in a django model file? but this has to do with modifying the __init__ method in forms.py, and yet the only way I know how to create a ModelFormSet is modelformset_factory, which does not include the definition of any classes in forms.py.
Next, to help Luke Sninger , here is my new form.py entry. After reading, Why am I getting an object, is not an iterable error? I realized that some of my problems arose from providing a tuple to the field.choices method when it was expecting a dictionary. Instead, I used the .queryset approach and it works fine:
class PupilForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(PupilForm, self).__init__(*args, **kwargs) thepupil = self.instance classes_by_year = thepupil.teaching_group.year.teachinggroup_set.all() self.fields['teaching_group'].queryset = classes_by_year class Meta: model = Pupil