Django übergibt benutzerdefinierte Formularparameter an Formset

Lesezeit: 10 Minuten

Django ubergibt benutzerdefinierte Formularparameter an Formset
Paolo Bergantino

Dies wurde in Django 1.9 mit behoben form_kwargs.

Ich habe ein Django-Formular, das so 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 rufe dieses Formular so auf:

form = ServiceForm(affiliate=request.affiliate)

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

Mein Problem ist, dass ich dieses einzelne Formular jetzt in ein Formset umwandeln möchte. Was ich nicht herausfinden kann, ist, wie ich die Affiliate-Informationen beim Erstellen des Formsets an die einzelnen Formulare übergeben kann. Laut den Dokumenten, um daraus ein Formset zu machen, muss ich so etwas tun:

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

Und dann muss ich es so erstellen:

formset = ServiceFormSet()

Wie kann ich nun affiliate=request.affiliate auf diese Weise an die einzelnen Formulare übergeben?

1643798170 388 Django ubergibt benutzerdefinierte Formularparameter an Formset
Karl Meier

ich würde … benutzen 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 wirkt sich in keiner Weise auf ServiceForm aus (z. B. indem es schwierig wird, Unterklassen zu bilden).

  • Es funktioniert nicht für mich. Ich erhalte den Fehler: AttributeError: ‘_curriedFormSet’ object has no attribute ‘get’

    – Paolo Bergantino

    9. März 09 um 0:35 Uhr

  • Ich kann diesen Fehler nicht duplizieren. Es ist auch seltsam, weil ein Formset normalerweise kein ‘get’-Attribut hat, also scheint es, dass Sie etwas Seltsames in Ihrem Code machen. (Außerdem habe ich die Antwort mit einer Möglichkeit aktualisiert, Kuriositäten wie ‘_curriedFormSet’ zu beseitigen).

    – Karl Meier

    9. März 09 um 12:50 Uhr

  • Ich überarbeite dies, weil ich Ihre Lösung zum Laufen bringen möchte. Ich kann das Formset gut deklarieren, aber wenn ich versuche, es zu drucken, indem ich {{ formset }} mache, erhalte ich den Fehler “hat kein Attribut ‘get'”. Es passiert mit beiden Lösungen, die Sie bereitgestellt haben. Wenn ich das Formset durchlaufe und die Formulare als {{ form }} drucke, erhalte ich den Fehler erneut. Wenn ich zum Beispiel eine Schleife mache und als {{ form.as_table }} drucke, erhalte ich leere Formulartabellen, dh. es werden keine Felder gedruckt. Irgendwelche Ideen?

    – Paolo Bergantino

    19. April 09 um 7:01 Uhr

  • Wenn der Kommentarthread hier keinen Sinn ergibt, liegt das daran, dass ich gerade die Antwort bearbeitet habe, um Python zu verwenden functools.partial statt Djangos django.utils.functional.curry. Sie tun dasselbe, außer dass functools.partial gibt anstelle einer regulären Python-Funktion einen eindeutigen aufrufbaren Typ zurück, und die partial type wird nicht als Instanzmethode gebunden, was das Problem, das dieser Kommentarthread weitgehend dem Debuggen gewidmet war, sauber löst.

    – Karl Meier

    12. Februar 14 um 23:24 Uhr

  • Es ist ein Hack!! Verwenden Sie die Antwort von Roach, die dem offiziellen Doc-Weg folgt

    – BigAir

    29. Juni 18 um 2:29 Uhr

Django ubergibt benutzerdefinierte Formularparameter an Formset
sergi0

Offizieller Dokumentenweg

Django 2.0:

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

https://docs.djangoproject.com/en/2.0/topics/forms/formsets/#passing-custom-parameters-to-formset-forms

  • Dies sollte jetzt der richtige Weg sein. Die akzeptierte Antwort funktioniert und ist nett, aber ein Hack

    – Junchao Gu

    3. November 17 um 9:36 Uhr

  • definitiv die beste Antwort und der richtige Weg, es zu tun.

    – janiw14

    8. September 19 um 15:05 Uhr

  • Funktioniert auch in Django 1.11 docs.djangoproject.com/en/1.11/topics/forms/formsets/…

    – Ruola

    24. Juni 20 um 11:30 Uhr


1643798170 175 Django ubergibt benutzerdefinierte Formularparameter an Formset
Matthäus Marshall

Ich würde die Form-Klasse dynamisch in einer Funktion aufbauen, damit sie per 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 Abfrageset im Optionsfeld nicht neu schreiben. Der Nachteil ist, dass die Unterklassenbildung etwas unkonventionell ist. (Jede Unterklasse muss auf ähnliche 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)
    ...

  • Danke – das hat funktioniert. Ich halte mich damit zurück, dies als akzeptiert zu markieren, weil ich irgendwie hoffe, dass es eine sauberere Option gibt, da es sich auf diese Weise definitiv komisch anfühlt.

    – Paolo Bergantino

    8. März 09 um 4:36 Uhr

  • Als akzeptiert markieren, da dies anscheinend der beste Weg ist. Fühlt sich komisch an, tut aber seinen Zweck. 🙂 Danke schön.

    – Paolo Bergantino

    8. März 09 um 7:17 Uhr

  • Carl Meyer hat, glaube ich, die sauberere Art, nach der Sie gesucht haben.

    – Jarret Hardie

    8. März 09 um 18:15 Uhr

  • Ich verwende diese Methode mit Django ModelForms.

    – chefsmart

    20. Oktober 09 um 16:37 Uhr

  • Ich mag diese Lösung, aber ich bin mir nicht sicher, wie ich sie in einer Ansicht wie einem Formset verwenden soll. Haben Sie gute Beispiele dafür, wie Sie dies in einer Ansicht verwenden können? Irgendwelche Vorschläge werden geschätzt.

    – Jo J

    9. Juni 10 um 4:42

Folgendes hat bei mir funktioniert, Django 1.7:

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, ich habe lange genug gebraucht, um es herauszufinden;)

Ich mag die Schließungslösung, weil sie “sauberer” und pythonischer ist (also +1 an mmarshall answer), 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 Hinweis darauf ist, dass die Django-Entwickler es möglicherweise nicht so sehr mögen.

Also erstellen Sie Ihr Formset im Grunde gleich, 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 so 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 Vorstellung vermitteln. Es ist etwas komplexer, den Callback zu einer Objektmethode wie dieser zu machen, gibt Ihnen aber etwas mehr Flexibilität im Gegensatz zu einem einfachen Funktions-Callback.

  • Danke für die Antwort. Ich verwende gerade die Lösung von mmarshall und da Sie zustimmen, dass sie eher pythonisch ist (etwas, das ich nicht wissen würde, da dies mein erstes Python-Projekt ist), bleibe ich wohl dabei. Es ist auf jeden Fall gut, über den Rückruf Bescheid zu wissen. Danke noch einmal.

    – Paolo Bergantino

    8. März 09 um 7:17 Uhr

  • Danke schön. Dieser Weg funktioniert hervorragend mit modelformset_factory. Ich konnte die anderen Möglichkeiten, mit Modelformsets zu arbeiten, nicht richtig verstehen, aber dieser Weg war sehr einfach.

    – Spitze

    7. März 10 um 19:16 Uhr

  • Das Curry-Funktional schafft im Wesentlichen einen Verschluss, nicht wahr? Warum sagen Sie, dass die Lösung von @mmarshall eher pythonisch ist? Bzw danke für deine Antwort. Ich mag diesen Ansatz.

    – Josch

    23. August 12 um 20:03 Uhr

1643798171 213 Django ubergibt benutzerdefinierte Formularparameter an Formset
Peter Mortensen

Ich wollte dies als Kommentar zu Carl Meyers Antwort platzieren, aber da dafür Punkte erforderlich sind, habe ich es einfach hier platziert. Ich habe 2 Stunden gebraucht, um es herauszufinden, also hoffe ich, dass es jemandem helfen wird.

Ein Hinweis zur Verwendung der inlineformset_factory.

Ich habe diese Lösung selbst verwendet und sie hat perfekt funktioniert, bis ich sie mit der inlineformset_factory ausprobiert habe. Ich habe Django 1.0.2 ausgeführt und eine seltsame KeyError-Ausnahme erhalten. Ich habe auf den neuesten Trunk aufgerüstet und es hat direkt funktioniert.

Ich kann es jetzt ähnlich verwenden:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))

  • Danke für die Antwort. Ich verwende gerade die Lösung von mmarshall und da Sie zustimmen, dass sie eher pythonisch ist (etwas, das ich nicht wissen würde, da dies mein erstes Python-Projekt ist), bleibe ich wohl dabei. Es ist auf jeden Fall gut, über den Rückruf Bescheid zu wissen. Danke noch einmal.

    – Paolo Bergantino

    8. März 09 um 7:17 Uhr

  • Danke schön. Dieser Weg funktioniert hervorragend mit modelformset_factory. Ich konnte die anderen Möglichkeiten, mit Modelformsets zu arbeiten, nicht richtig verstehen, aber dieser Weg war sehr einfach.

    – Spitze

    7. März 10 um 19:16 Uhr

  • Das Curry-Funktional schafft im Wesentlichen einen Verschluss, nicht wahr? Warum sagen Sie, dass die Lösung von @mmarshall eher pythonisch ist? Bzw danke für deine Antwort. Ich mag diesen Ansatz.

    – Josch

    23. August 12 um 20:03 Uhr

Ab Commit e091c18f50266097f648efc7cac2503968e9d217 am Dienstag, 14. August, 23:44:46 2012 +0200 funktioniert die akzeptierte Lösung nicht mehr.

Die aktuelle Version der Funktion django.forms.models.modelform_factory() verwendet eine “Typkonstruktionstechnik”, ruft die type()-Funktion auf dem übergebenen Formular auf, um den Metaklassentyp zu erhalten, und verwendet dann das Ergebnis, um ein Klassenobjekt davon zu erstellen on the fly eingeben::

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

Das bedeutet sogar ein curryed oder partial Objekt, das anstelle eines Formulars übergeben wird, “bewirkt sozusagen, dass die Ente dich beißt”: Es ruft eine Funktion mit den Konstruktionsparametern von a auf ModelFormClass Objekt, das die Fehlermeldung zurückgibt::

function() argument 1 must be code, not str

Um dies zu umgehen, habe ich eine Generatorfunktion geschrieben, die eine Schließung verwendet, um eine Unterklasse einer beliebigen Klasse zurückzugeben, die als erster Parameter angegeben ist und dann aufruft super.__init__ nach updateVerändern der Kwargs mit denen, die beim Aufruf der Generatorfunktion geliefert werden:

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://stackoverflow.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 rufen Sie in Ihrem Code die Formularfabrik auf als:

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

Vorbehalte:

  • Dies wurde zumindest vorerst nur sehr wenig getestet
  • Angegebene Parameter könnten kollidieren und diejenigen überschreiben, die von irgendeinem Code verwendet werden, der das vom Konstruktor zurückgegebene Objekt verwendet

  • Danke, scheint in Django 1.10.1 im Gegensatz zu einigen anderen Lösungen hier sehr gut zu funktionieren.

    – fpghost

    14. September 16 um 18:38 Uhr

  • @fpghost Denken Sie daran, dass Sie es zumindest bis 1.9 (ich bin aus mehreren Gründen immer noch nicht auf 1.10) aktualisieren können, wenn Sie nur das QuerySet ändern müssen, auf dem das Formular aufgebaut ist gab MyFormSet zurück, indem es sein Attribut .queryset änderte, bevor es verwendet wurde. Weniger flexibel als diese Methode, aber viel einfacher zu lesen/zu verstehen.

    – RobM

    16. September 16 um 19:14 Uhr

  • @RobM – Ich erhalte eine Fehlermeldung NameError: Name ‘self’ ist nicht definiert

    – Schaan

    12. November 20 um 5:51 Uhr

  • @shaan auf welcher Linie? Der Aufruf von super() (den Sie jetzt, da Sie Python 3 verwenden, einfach als super().__init__(*args, **kwargs) schreiben können) oder der Aufruf von inlineformset_factory? Wenn es sich um einen Call to Factory handelt, müssen Sie self.request.user durch die Variable ersetzen, die den Benutzer in Ihrem Code enthält. Sie verwenden wahrscheinlich keine klassenbasierte Ansicht, also haben Sie nicht self, sondern request als Parameter: in diesem Fall ist es request.user

    – RobM

    18. November 20 um 2:34 Uhr

.

740110cookie-checkDjango übergibt benutzerdefinierte Formularparameter an Formset

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy