Je travaille sur quelque chose comme un magasin en ligne. Je crée un formulaire dans lequel la cliente achète un article et elle peut choisir le nombre d'articles qu'elle souhaite acheter. Mais, sur chaque article qu'elle achète, elle doit choisir sa couleur. Il y a donc un nombre non constant de champs: si le client achète 3 articles, il doit obtenir 3 cases <select>
pour choisir une couleur; s'il en achète 7, il doit en obtenir 7 telles <select>
.
Je ferai apparaître et disparaître les champs du formulaire HTML à l'aide de JavaScript. Mais comment puis-je gérer cela dans ma classe de formulaires Django? Je vois que les champs de formulaire sont des attributs de classe, alors je ne sais pas comment gérer le fait qu’une instance de formulaire devrait avoir 3 champs de couleur et d’autres 7.
Un indice?
Jacob Kaplan-Moss a beaucoup écrit sur les champs de formulaire dynamiques: http://jacobian.org/writing/dynamic-form-generation/
Essentiellement, vous ajoutez d'autres éléments au dictionnaire self.fields
du formulaire lors de l'instanciation.
Voici une autre option: que diriez-vous d’un formset ? Puisque vos champs sont tous identiques, c’est précisément à quoi servent les formulaires.
L’administrateur Django utilise FormSet
s + un peu de javascript pour ajouter des inlines de longueur arbitraire.
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}))
<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>
<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" />
tu peux le faire comme
def __init __ (self, n, * arguments, ** kwargs): super (your_form, self) .__ init __ (* arguments, ** kwargs) pour i dans la plage (0, n): self.fields ["nom de champ% d"% i] = forms.CharField ()
et lorsque vous créez une instance de formulaire, vous ne faites que
forms = your_form (n)
c'est juste l'idée de base, vous pouvez changer le code comme bon vous semble. :RÉ
La façon dont je le ferais est la suivante:
Créez une classe "vide" qui hérite de froms.Form
, comme ceci:
class ItemsForm(forms.Form):
pass
Construisez un dictionnaire des objets de formulaires, qui sont les formulaires réels, dont la composition dépend du contexte (vous pouvez par exemple les importer à partir d'un module externe). Par exemple:
new_fields = {
'milk' : forms.IntegerField(),
'butter': forms.IntegerField(),
'honey' : forms.IntegerField(),
'eggs' : forms.IntegerField()}
Dans les vues, vous pouvez utiliser la fonction "type" native de python pour générer dynamiquement une classe Form avec un nombre variable de champs.
DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
Passez le contenu au formulaire et rendez-le dans le modèle:
Form = DynamicItemsForm(content)
context['my_form'] = Form
return render(request, "demo/dynamic.html", context)
Le "contenu" est un dictionnaire de valeurs de champs (par exemple, même request.POST suffirait) . Vous pouvez voir tout mon exemple expliqué ici .
Une autre approche: Plutôt que d'interrompre le flux normal d'initialisation de champ, nous pouvons remplacer les champs par un mixin, renvoyer un OrderedDict de champs dynamiques dans generate_dynamic_fields, qui sera ajouté à chaque fois qu'il est défini.
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()
Un exemple simple:
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