webentwicklung-frage-antwort-db.com.de

Erstellen einer Django-Formularklasse mit einer dynamischen Anzahl von Feldern

Ich arbeite an so etwas wie einem Online-Shop. Ich mache ein Formular, in dem der Kunde einen Artikel kauft, und sie kann auswählen, wie viele dieser Artikel sie kaufen möchte. Bei jedem Artikel, den sie kauft, muss sie jedoch die Farbe auswählen. Es gibt also eine nicht konstante Anzahl von Feldern: Wenn der Kunde 3 Artikel kauft, sollte er 3 <select>-Boxen für die Auswahl einer Farbe erhalten, wenn er 7 Artikel kauft, sollte er 7 solcher <select>-Boxen erhalten.

Ich werde die HTML-Formularfelder mit JavaScript anzeigen und verschwinden lassen. Aber wie gehe ich damit in meinem Django-Formkurs um? Ich sehe, dass Formularfelder Klassenattribute sind, und ich weiß nicht, wie ich damit umgehen soll, dass eine Formularinstanz 3 Farbfelder und einige 7 haben sollte.

Irgendeine Ahnung?

46
Ram Rachum

Jacob Kaplan-Moss hat eine umfassende Beschreibung dynamischer Formularfelder: http://jacobian.org/writing/dynamic-form-generation/

Im Wesentlichen fügen Sie während der Instantiierung dem self.fields-Wörterbuch des Formulars weitere Elemente hinzu.

66
GDorn

Hier ist eine weitere Option: Wie wäre es mit einem formset ? Da Ihre Felder alle gleich sind, werden Formulare genau dafür verwendet.

Der Django-Administrator verwendet FormSets + ein wenig Javascript, um Inlines beliebiger Länge hinzuzufügen.

class ColorForm(forms.Form):
    color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))

ColorFormSet = formset_factory(ColorForm, extra=0) 
# we'll dynamically create the elements, no need for any forms

def myview(request):
    if request.method == "POST":
        formset = ColorFormSet(request.POST)
        for form in formset.forms:
            print "You've picked {0}".format(form.cleaned_data['color'])
    else:
        formset = ColorFormSet()
    return render(request, 'template', {'formset': formset}))

JavaScript

    <script>
        $(function() {
            // this is on click event just to demo.
            // You would probably run this at page load or quantity change.
            $("#generate_forms").click(function() {
                // update total form count
                quantity = $("[name=quantity]").val();
                $("[name=form-TOTAL_FORMS]").val(quantity);  

                // copy the template and replace prefixes with the correct index
                for (i=0;i<quantity;i++) {
                    // Note: Must use global replace here
                    html = $("#form_template").clone().html().replace(/__prefix_/g', i);
                    $("#forms").append(html);
                };
            })
        })
    </script>

Vorlage

    <form method="post">
        {{ formset.management_form }}
        <div style="display:none;" id="form_template">
            {{ formset.empty_form.as_p }}
        </div><!-- stores empty form for javascript -->
        <div id="forms"></div><!-- where the generated forms go -->
    </form>
    <input type="text" name="quantity" value="6" />
    <input type="submit" id="generate_forms" value="Generate Forms" />
30

du kannst es gerne machen

 def __init __ (self, n, * args, ** kwargs): 
 super (your_form, self) .__ init__ (* args, ** kwargs) 
 für i im Bereich (0, n): 
 self.fields ["Feldname% d"% i] = forms.CharField () 

und wenn Sie eine Formularinstanz erstellen, tun Sie dies einfach

 forms = Ihre_Form (n) 

es ist nur die Grundidee, Sie können den Code beliebig ändern. : D

16
owenwater

So würde ich es machen:

  1. Erstellen Sie eine "leere" Klasse, die von froms.Form erbt, wie folgt:

    class ItemsForm(forms.Form):
        pass
    
  2. Erstellen Sie ein Wörterbuch mit Formularobjekten, bei denen es sich um die eigentlichen Formulare handelt, deren Zusammensetzung vom Kontext abhängig ist (z. B. können Sie sie aus einem externen Modul importieren). Zum Beispiel:

    new_fields = {
        'milk'  : forms.IntegerField(),
        'butter': forms.IntegerField(),
        'honey' : forms.IntegerField(),
        'eggs'  : forms.IntegerField()}
    
  3. In Ansichten können Sie die native "type" - Funktion von Python verwenden, um dynamisch eine Formularklasse mit variabler Anzahl von Feldern zu generieren.

    DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
    
  4. Übergeben Sie den Inhalt an das Formular und rendern Sie ihn in der Vorlage:

    Form = DynamicItemsForm(content)
    context['my_form'] = Form
    return render(request, "demo/dynamic.html", context)
    

Der "Inhalt" ist ein Wörterbuch von Feldwerten (z. B. sogar request.POST würde das tun) . Sie können mein gesamtes Beispiel hier sehen.

2
OZ13

Ein anderer Ansatz: Anstatt den normalen Feldinitialisierungsfluss zu unterbrechen, können Sie Felder mit einem Mixin überschreiben und ein OrderedDict von dynamischen Feldern in generate_dynamic_fields zurückgeben, die bei jeder Menge hinzugefügt werden.

from collections import OrderedDict

class DynamicFormMixin:
    _fields: OrderedDict = None

    @property
    def fields(self):
      return self._fields

    @fields.setter
    def fields(self, value):
        self._fields = value
        self._fields.update(self.generate_dynamic_fields())

    def generate_dynamic_fields(self):
        return OrderedDict()

Ein einfaches Beispiel:

class ExampleForm(DynamicFormMixin, forms.Form):
    instance = None

    def __init__(self, instance = None, data=None, files=None, auto_id='id_%s', prefix=None, initial=None,
                 error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None,
                 use_required_attribute=None, renderer=None):
        self.instance = instance
        super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
                         use_required_attribute, renderer)

    def generate_dynamic_fields(self):
        dynamic_fields = OrderedDict()
        instance = self.instance
        dynamic_fields["dynamic_choices"] = forms.ChoiceField(label=_("Number of choices"),
                                                              choices=[(str(x), str(x)) for x in range(1, instance.number_of_choices + 1)],
                                                              initial=instance.initial_choice)
        return dynamic_fields
0
bob