web-dev-qa-db-fra.com

django vues basées sur une classe avec formulaire en ligne ou formulaire)

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).

55
Hixi

Les points clés sont:

  1. FormSets généré dans forms.py en utilisant inlineformset_factory:

    BookImageFormSet = inlineformset_factory(BookForm, BookImage, extra=2)
    BookPageFormSet = inlineformset_factory(BookForm, BookPage, extra=5)
    
  2. a renvoyé les FormSets 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
    
  3. 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))
    
62
Jordan Reiter

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()
14

Vous devriez essayer Django-extra-views . Recherchez CreateWithInlinesView et UpdateWithInlinesView.

9
Udi

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.

1
knite

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
1
pztrick

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.

1
VGE

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)
1
Speq