python - fields - jquery formset plugin




Django übergibt benutzerdefinierte Formularparameter an Formset (8)

Dies wurde in Django 1.9 mit form_kwargs .

Ich habe eine Django-Form, die folgendermaßen aussieht:

class ServiceForm(forms.Form):
    option = forms.ModelChoiceField(queryset=ServiceOption.objects.none())
    rate = forms.DecimalField(widget=custom_widgets.SmallField())
    units = forms.IntegerField(min_value=1, widget=custom_widgets.SmallField())

    def __init__(self, *args, **kwargs):
        affiliate = kwargs.pop('affiliate')
        super(ServiceForm, self).__init__(*args, **kwargs)
        self.fields["option"].queryset = ServiceOption.objects.filter(affiliate=affiliate)

Ich nenne diese Form etwa so:

form = ServiceForm(affiliate=request.affiliate)

Wo request.affiliate ist der angemeldete Benutzer. Dies funktioniert wie vorgesehen.

Mein Problem ist, dass ich nun diese einzelne Form in ein Formset umwandeln möchte. Was ich nicht herausfinden kann ist, wie ich die Affiliate-Informationen an die einzelnen Formulare weitergeben kann, wenn ich das Formset erstelle. Laut den Unterlagen, um daraus ein Formularsatz zu machen, muss ich so etwas tun:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)

Und dann muss ich es so erstellen:

formset = ServiceFormSet()

Nun, wie kann ich affiliate = request.affiliate auf diese Weise an die einzelnen Formulare weitergeben?


Ab commit e091c18f50266097f648efc7cac2503968e9d217 am Di Aug 14 23:44:46 2012 +0200 kann die akzeptierte Lösung nicht mehr funktionieren.

Die aktuelle Version der Funktion django.forms.models.modelform_factory () verwendet eine "type construction technique", ruft die type () -Funktion des übergebenen Formulars auf, um den Metaklassentyp zu erhalten, und verwendet dann das Ergebnis, um ein Klassenobjekt daraus zu konstruieren tippen Sie auf die Fliege ::

# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)

Das bedeutet, dass sogar ein curry oder ein ModelFormClass , das anstelle eines Formulars übergeben wird, dazu führt, dass die Ente Sie beißt: Es ruft eine Funktion mit den Konstruktionsparametern eines ModelFormClass Objekts auf und gibt die Fehlermeldung zurück:

function() argument 1 must be code, not str

Um dies zu super.__init__ schrieb ich eine Generatorfunktion, die eine Closure verwendet, um eine Unterklasse einer Klasse als ersten Parameter zurückzugeben, die dann super.__init__ nachdem die Kwargs mit den beim Aufruf der Generatorfunktion super.__init__ update :

def class_gen_with_kwarg(cls, **additionalkwargs):
  """class generator for subclasses with additional 'stored' parameters (in a closure)
     This is required to use a formset_factory with a form that need additional 
     initialization parameters (see http://.com/questions/622982/django-passing-custom-form-parameters-to-formset)
  """
  class ClassWithKwargs(cls):
      def __init__(self, *args, **kwargs):
          kwargs.update(additionalkwargs)
          super(ClassWithKwargs, self).__init__(*args, **kwargs)
  return ClassWithKwargs

Dann nennen Sie in Ihrem Code die Formular-Factory als ::

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))

Einschränkungen:

  • Dies wurde sehr wenig getestet, zumindest für den Moment
  • Die angegebenen Parameter könnten kollidieren und diejenigen überschreiben, die von dem Code verwendet werden, der das vom Konstruktor zurückgegebene Objekt verwendet

Basierend auf dieser Antwort fand ich eine klarere Lösung:

class ServiceForm(forms.Form):
    option = forms.ModelChoiceField(
            queryset=ServiceOption.objects.filter(affiliate=self.affiliate))
    rate = forms.DecimalField(widget=custom_widgets.SmallField())
    units = forms.IntegerField(min_value=1, 
            widget=custom_widgets.SmallField())

    @staticmethod
    def make_service_form(affiliate):
        self.affiliate = affiliate
        return ServiceForm

Und renne es im Blick wie

formset_factory(form=ServiceForm.make_service_form(affiliate))

Das hat bei Django 1.7 funktioniert:

from django.utils.functional import curry    

lols = {'lols':'lols'}
formset = modelformset_factory(MyModel, form=myForm, extra=0)
formset.form = staticmethod(curry(MyForm, lols=lols))
return formset

#form.py
class MyForm(forms.ModelForm):

    def __init__(self, lols, *args, **kwargs):

Hoffe es hilft jemandem, nahm mich lange genug um es herauszufinden;)


Django 1.9:

ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})

form_kwargs


Ich habe einige Zeit damit verbracht, dieses Problem zu lösen, bevor ich diesen Beitrag gesehen habe.

Die Lösung, die ich gefunden habe, war die Verschlusslösung (und es ist eine Lösung, die ich zuvor mit Django-Modellformen verwendet habe).

Ich habe die curry () - Methode wie oben beschrieben ausprobiert, aber ich konnte es einfach nicht mit Django 1.0 arbeiten lassen, so dass ich am Ende auf die Methode zum Schließen zurückkehrte.

Die Closure-Methode ist sehr ordentlich und die einzige leichte Besonderheit ist, dass die Klassendefinition innerhalb der View oder einer anderen Funktion verschachtelt ist. Ich denke, die Tatsache, dass das für mich seltsam aussieht, ist ein Hang-up von meiner vorherigen Programmiererfahrung und ich denke, jemand mit einem Hintergrund in dynamischeren Sprachen würde nicht ein Auge zupfen!


Ich mag die Verschlusslösung für "sauberer" und mehr Pythonic (also +1 zu mmarshall Antwort), aber Django-Formulare haben auch einen Rückrufmechanismus, den Sie zum Filtern von Abfragesätzen in Formsets verwenden können.

Es ist auch nicht dokumentiert, was meiner Meinung nach ein Indikator dafür ist, dass die Django-Entwickler es nicht so mögen.

Sie erstellen also im Grunde dasselbe Formset, fügen aber den Callback hinzu:

ServiceFormSet = forms.formsets.formset_factory(
    ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)

Dies erstellt eine Instanz einer Klasse, die folgendermaßen aussieht:

class Callback(object):
    def __init__(self, field_name, aff):
        self._field_name = field_name
        self._aff = aff
    def cb(self, field, **kwargs):
        nf = field.formfield(**kwargs)
        if field.name == self._field_name:  # this is 'options' field
            nf.queryset = ServiceOption.objects.filter(affiliate=self._aff)
        return nf

Dies sollte Ihnen die allgemeine Idee geben. Es ist etwas komplizierter, den Callback zu einer Objektmethode wie diesem zu machen, bietet Ihnen aber etwas mehr Flexibilität als einen einfachen Funktionsrückruf.


Ich würde functools.partial und functools.wraps :

from functools import partial, wraps
from django.forms.formsets import formset_factory

ServiceFormSet = formset_factory(wraps(ServiceForm)(partial(ServiceForm, affiliate=request.affiliate)), extra=3)

Ich denke, dies ist der sauberste Ansatz und beeinträchtigt ServiceForm in keiner Weise (zB durch Unterlassen der Unterklasse).


Ich würde die Formularklasse dynamisch in einer Funktion aufbauen, so dass sie über closure Zugriff auf den Affiliate hat:

def make_service_form(affiliate):
    class ServiceForm(forms.Form):
        option = forms.ModelChoiceField(
                queryset=ServiceOption.objects.filter(affiliate=affiliate))
        rate = forms.DecimalField(widget=custom_widgets.SmallField())
        units = forms.IntegerField(min_value=1, 
                widget=custom_widgets.SmallField())
    return ServiceForm

Als Bonus müssen Sie das Abfrage-Set im Optionsfeld nicht neu schreiben. Der Nachteil ist, dass Subclassing ein wenig funky ist. (Jede Unterklasse muss in ähnlicher Weise erstellt werden.)

bearbeiten:

Als Antwort auf einen Kommentar können Sie diese Funktion an jeder Stelle aufrufen, an der Sie den Klassennamen verwenden würden:

def view(request):
    affiliate = get_object_or_404(id=request.GET.get('id'))
    formset_cls = formset_factory(make_service_form(affiliate))
    formset = formset_cls(request.POST)
    ...




django-forms