J'ai les modèles suivants:
class Bill(models.Model):
date = models.DateTimeField(_("Date of bill"),null=True,blank=True)
class Item(models.Model):
name = models.CharField(_("Name"),max_length=100)
price = models.FloatField(_("Price"))
quantity = models.IntegerField(_("Quantity"))
bill = models.ForeignKey("Bill",verbose_name=_("Bill"),
related_name="billitem")
Je sais que c'est possible:
from Django.forms.models import inlineformset_factory
inlineformset_factory(Bill, Item)
puis traitez-le via la vue standard.
Maintenant, je me demandais s'il y avait un moyen d'y parvenir (ce qui signifie: utiliser une ligne pour ajouter/modifier des articles appartenant à une facture) en utilisant vues basées sur la classe (pas pour l'interface d'administration).
Les points clés sont:
FormSet
s généré dans forms.py
en utilisant inlineformset_factory
:
BookImageFormSet = inlineformset_factory(BookForm, BookImage, extra=2)
BookPageFormSet = inlineformset_factory(BookForm, BookPage, extra=5)
a renvoyé les FormSet
s dans une classe CreateView
dans views.py
:
def get_context_data(self, **kwargs):
context = super(BookCreateView, self).get_context_data(**kwargs)
if self.request.POST:
context['bookimage_form'] = BookImageFormSet(self.request.POST)
context['bookpage_form'] = BookPageFormSet(self.request.POST)
else:
context['bookimage_form'] = BookImageFormSet()
context['bookpage_form'] = BookPageFormSet()
return context
Utilisé form_valid
pour enregistrer le formulaire et le jeu de formulaires:
def form_valid(self, form):
context = self.get_context_data()
bookimage_form = context['bookimage_formset']
bookpage_form = context['bookpage_formset']
if bookimage_form.is_valid() and bookpage_form.is_valid():
self.object = form.save()
bookimage_form.instance = self.object
bookimage_form.save()
bookpage_form.instance = self.object
bookpage_form.save()
return HttpResponseRedirect('thanks/')
else:
return self.render_to_response(self.get_context_data(form=form))
Je viens d'ajouter ma propre version après avoir vérifié certains de ces CBV prédéfinis. J'avais spécifiquement besoin d'un contrôle sur multiple formsets -> one parent
Dans une seule vue avec chacune des fonctions de sauvegarde individuelles.
J'ai essentiellement bourré la liaison de données FormSet dans une fonction get_named_formsets
Qui est appelée par get_context_data
Et form_valid
.
Là, je vérifie si tous les jeux de formulaires sont valides, et je recherche également une méthode qui remplace une ancienne formset.save()
simple par jeu de formulaires pour une sauvegarde personnalisée.
Le modèle rend les jeux de formulaires via
{% with named_formsets.my_specific_formset as formset %}
{{ formset }}
{{ formset.management_form }}
{% endwith %}
Je pense que j'utiliserai ce système régulièrement.
class MyView(UpdateView): # FormView, CreateView, etc
def get_context_data(self, **kwargs):
ctx = super(MyView, self).get_context_data(**kwargs)
ctx['named_formsets'] = self.get_named_formsets()
return ctx
def get_named_formsets(self):
return {
'followup': FollowUpFormSet(self.request.POST or None, prefix='followup'),
'action': ActionFormSet(self.request.POST or None, prefix='action'),
}
def form_valid(self, form):
named_formsets = self.get_named_formsets()
if not all((x.is_valid() for x in named_formsets.values())):
return self.render_to_response(self.get_context_data(form=form))
self.object = form.save()
# for every formset, attempt to find a specific formset save function
# otherwise, just save.
for name, formset in named_formsets.items():
formset_save_func = getattr(self, 'formset_{0}_valid'.format(name), None)
if formset_save_func is not None:
formset_save_func(formset)
else:
formset.save()
return http.HttpResponseRedirect('')
def formset_followup_valid(self, formset):
"""
Hook for custom formset saving.. useful if you have multiple formsets
"""
followups = formset.save(commit=False) # self.save_formset(formset, contact)
for followup in followups:
followup.who = self.request.user
followup.contact = self.object
followup.save()
Vous devriez essayer Django-extra-views . Recherchez CreateWithInlinesView
et UpdateWithInlinesView
.
Le code dans la réponse de Jordan n'a pas fonctionné pour moi. J'ai posté ma propre question à ce sujet, que je crois avoir résolu maintenant. Le premier argument de inlineformset_factory doit être Book, pas BookForm.
J'ai dû apporter une autre modification à la vue de Jordan et de Speq get_context_data()
afin d'avoir formset.non_form_errors
existe dans le contexte du modèle.
...
if self.request.POST:
context['fs'] = MyInlineFS(self.request.POST, instance=self.object)
context['fs'].full_clean() # <-- new
else:
context['fs'] = MyInlineFS(instance=self.object)
return context
J'ai redéfini le code source générique de la 1.3-beta-1:
Le code n'est absolument pas prêt pour l'édition de la liste ou il y a de la magie noire ici. Mais je pense que cela peut être mis en œuvre rapidement.
Si vous regardez le module Django.view.generic.edit (qui prend en charge l'édition détaillée des objets) comment il utilise le module Django.view.generic.detail.
Je pense qu'un module Django.view.generic.list_edit peut être implémenté en utilisant Django.view.generic.list et une partie de Django.view.generic.edit.
J'ai apporté quelques modifications à la solution d'origine pour laisser formset.is_valid () fonctionner:
if self.request.POST:
context['fs'] = MyInlineFS(self.request.POST, instance=self.object)
else:
context['fs'] = MyInlineFS(instance=self.object)