2011-09-10 11 views
6

Capisco che sia possibile override the default queryset 'used' by the modelformset. Questo limita solo gli oggetti per i quali viene creato un modulo.Filtro Django ModelForm Imposta le scelte di campo ... diverso dalla limitazione della queryset di Formset

Ho anche trovato una domanda Stack Overflow su filtering ForeignKey choices in a Django ModelForm, ma non un ModelForm Set e circa limiting available choices in a Django formset, ma non un modello FormSet. Ho incluso la mia versione di questo codice qui sotto.

Quello che voglio fare è rendere un ModelFormSet, per una classe scolastica ('teachinggroup' o 'theclass' per evitare lo scontro con la parola chiave 'class') con un campo limitato da un queryset. Questo è per il modulo di modifica della classe di un insegnante, per essere in grado di riassegnare gli studenti a una classe diversa, ma limitato a classi all'interno della stessa coorte.

mio models.py

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'] 

mio 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()) 

Come potete vedere, sto limitando le scelte al classes_by_year set di query, che dista solo classi che appartengono alla stessa gruppo di anni. Questo queryset viene visualizzato correttamente, come puoi vedere nella pagina di rendering qui sotto, ma non ha alcun effetto sul campo del modulo.

mio modello

{% 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 --> 

Il rendering della pagina con tutti i gruppi insegnamento (classi) visibile nel widget di selezione, ma il tag nella parte inferiore della pagina rende come: [<TeachingGroup: Y8 82Ma2>, <TeachingGroup: Y8 82Ma3>], mostrando con precisione solo le due classi in anno 8.

si noti che ho letto anche attraverso il post di James Bennett So you want a dynamic form come raccomandato da How can I limit the available choices for a foreign key field in a django modelformset?, ma che comporta la modifica del metodo __init__ in forms.py, eppure l'unico modo che conosco come creare un ModelFormSet è con modelformset_factory, che doesn comportare la definizione di qualsiasi classe in forms.py.

Ulteriori informazioni su Luke Sneeringer, ecco la mia nuova entrata in forms.py. Dopo aver letto Why do I get an object is not iterable error? ho capito che alcuni dei miei problemi derivavano dal dare una tupla al metodo field.choices, quando si aspettava un dizionario. Ho usato l'approccio .queryset invece, e funziona bene:

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 

risposta

4

come meglio posso dire, hai effettivamente messo insieme tutti i pezzi tranne uno. Ecco il link finale.

Hai detto di leggere il modulo di posta dinamico, che implica l'override del metodo __init__ in una sottoclasse forms.Form, che non hai. Ma niente ti impedisce di averne uno ed è qui che puoi ignorare le tue scelte.

Anche se modelformset_factory non richiede una classe esplicita Form (ne costruisce uno dal modello se non ne viene fornito nessuno), può averne uno. Usa la parola chiave form argomento:

PupilModelFormset = modelformset_factory(Pupil, form=PupilForm) 

Ovviamente, questo richiede la definizione della classe PupilForm.Ho l'impressione che già sapete come fare questo, ma dovrebbe essere qualcosa di simile:

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 

L'ultimo problema si potrebbe avere è che una modelformset_factory prende solo la classe, il che significa che il costruttore sarà chiamato con senza argomenti. Se è necessario inviare un argomento in modo dinamico, il modo per farlo è creare un metaclasse che generi il modulo classe e chiamare tale metaclass nella chiamata modelformset_factory.

+0

Grazie per l'antipasto dettagliato, ma sto ammettendo la sconfitta nell'interpretarlo. Se provo letteralmente con '__init __ (self)' e '__init __()', ottengo 'TypeError: __init __() ottenuto un argomento di parole chiave inattese 'auto_id'', che è generato dalla fabbrica del formset. Se provo con '(self, * args, ** kwargs)' e '__init __ (* args, ** kwargs)' (sto solo pugnalando al buio), ottengo DoesNotExist, e la forma sembra non essere vincolata . Se provo con 'super (PupilForm, self.instance, self)' Ricevo l'oggetto 'AttributeError:' PupilForm 'non ha attributo' instance''. – nimasmi

+0

Oh, non avevo capito che c'erano argomenti. La tua seconda ipotesi era giusta. Lo guarderò in modo più dettagliato e ti ricontatterò con una modifica. –

+0

Per quanto riguarda gli argomenti, a seguito del [suddetto articolo] (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/) ho provato anche: 'def __init __ (self, pupil, * args, ** kwargs): ' ma ottenuto' __init __() richiede almeno 2 argomenti senza parole chiave (1 dato) ' – nimasmi

1

È possibile eseguire questa operazione impostando le scelte di campo di un modulo in un formset nei moduli init e sovrascrivendo self.fields ['field_name']. Choices. Questo ha funzionato bene per me, ma avevo bisogno di più logica nella mia vista dopo che il formset è stato inizializzato. Ecco ciò che funziona per 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 

non ero in grado di trovare la risposta per questo, ma provato fuori e funziona in Django 1.6.5. L'ho capito da quando forms e loop sembrano andare così bene insieme :)

Problemi correlati