ModelFormSet ... Django Filter Filter ... Different from Formset Request Set Limit

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 }} <!-- included for debugging --> 

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 
+6
source share
2 answers

As far as I can tell, you actually assembled all the parts except one. Here is the last link.

You said you were reading a dynamic form post that includes an override of the __init__ method in a subclass of forms.Form that you don't have. But nothing prevents you from having it, and that you can override your options.

Even if modelformset_factory does not require an explicit Form class (it builds one of the model if it is not specified), it can take one. Use the Form keyword argument:

 PupilModelFormset = modelformset_factory(Pupil, form=PupilForm) 

Obviously, this requires defining the PupilForm class. I get the impression that you already know how to do this, but it should be something like:

 from django import forms class PupilForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(PupilForm, self).__init__(*args, **kwargs) self.fields['teaching_group'].choices = ______ # code to generate choices here class Meta: model = Pupil 

The last problem that may arise is that modelformset_factory just accepts the class, which means that the constructor will be called without arguments. If you need to send an argument dynamically, the way to do this is to create a metaclass that generates the form class itself and call that metaclass in your modelformset_factory call.

+4
source

You can accomplish this by setting the field forms of the form in the init form factor and overwriting self.fields ['field_name']. This worked fine for me, but I needed more logic in my opinion after initializing the set of forms. Here is what works for me in Django 1.6.5:

 from django.forms.models import modelformset_factory user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5) my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices) # and now for the magical for loop and override each desired fields choices for choice_form in my_formset: choice_form.fields['model'].choices = user_choices 

I could not find the answer for this, but I tried it and it works in Django 1.6.5. I figured this out because the shapes and loops seem to blend so well :)

+1
source

Source: https://habr.com/ru/post/897039/


All Articles