10 votes

Comment soumettre un formulaire et un jeu de formulaires en même temps ?

J'essaie de rendre un formulaire et un jeu de formulaires en même temps. Le jeu de formulaires fonctionne bien (je pense), mais le formulaire ne se valide pas (comme s'il n'y avait pas de données affichées).

J'ai essayé de jouer avec le bouton, mais sa fonction principale de soumission passe par le js.

les formulaires fonctionnent indépendamment les uns des autres, mais pas lorsqu'ils sont soumis ensemble. Il semble donc que le problème se situe au niveau des vues. Voici le code :

views.py

from django.shortcuts import render, render_to_response
from django.http import HttpResponseRedirect
from forms import LessonForm, AddMaterialForm
from models import Lesson, SUBJECT_OPTIONS, Materials, MATERIAL_TYPES
from django.forms.formsets import formset_factory

def Create_Lesson(request):
    AddMaterials=formset_factory(AddMaterialForm, extra=9)
    if request.method == "POST": # If the form has been submitted...
        lesson = LessonForm(request.POST, prefix="lesson") # A form bound to the POST data
        formset = AddMaterials(request.POST, request.FILES) # A form bound to the POST data
        if lesson.is_valid() and formset.is_valid(): # All validation rules pass
            lesson = lesson.save(commit=False)
            lesson.creator = request.user
            lesson.save()
            for form in formset:
                form = form.save(commit=False)
                form.lesson = lesson.pk
                form.save()
            return render(request, 'index.html',)
    else:
        lesson= LessonForm(prefix='lesson') # An unbound form
        formset = AddMaterials()
    return render(request, 'create_lesson/create.html', {
    'form': lesson,'formset':formset
})

.html

    <form id="create_lesson_form" method="post" action="">
    <h2>1: Create Your Lesson</h2>

        {{ form.non_field_errors }}
        <label for="subject"><span>Subject</span></label>
        {{form.subject}}
        {{ form.subject.errors }}
        <label for="title"><span>Title</span></label>
        <input type="text" id="title" name="name" placeholder="Give it a name"/>
        {{ form.name.errors }}
        <label class="error" for="title" id="title_error">You must choose a title!</label>            
        <label for="subtitle"><span>Subtitle</span></label>
        <input type="text" id="subtitle" name="subtitle" placeholder="Type your subtitle here"/>
        {{ form.subtitle.errors }}
        <label class="error" for="subtitle" id="subtitle_error">are you sure you want to leave subtititle blank?</label>
        <label for="description"><span>Description</span></label>
        <textarea id="description" name= "description" cols="42" rows="5" placeholder="why is it important? this can be a longer description"'></textarea>
        {{ form.description.errors }}
        <label class="error" for="description" id="description_error">are you sure you want to leave the description blank?</label>
        <label for="success" id="Goals_title"><span>Goals</span></label>
        <textarea id="success" name="success" cols="42" rows="5" placeholder="explain what sucess might look like for someone doing this lesson...what does mastery look like?" '></textarea>
        {{ form.success.errors }}
        <label class="error" for="success" id="success_error">are you sure you want to leave the goals blank?</label>
    {{ form.directions.errors }}
        <label class="error" for="directions" id="directions_error">are you sure you do not want to include dierections?</label>
    <label for="directions" id="Directions_title"><span>Directions</span></label>
        <textarea id="directions" name="directions" cols="42" rows="5" placeholder="you can add simple directions here" '></textarea><br>
    </form>

    <form id="add_elements_form" method="post" action="">
    {% csrf_token %}
    {{ formset.as_p}}
    <button type='submit' id='finish'>Finish Editing Lesson</button>
    </form>

10voto

catherine Points 9634

Cela soumettra le formulaire et le jeu de formulaires en même temps.

//When your uploading files or images don't forget to put "multipart/form-data" 
//   in your form. 
//To connect formset in your form, don't forget to put the model in the formset 
//   for instance.
//In this you can add many lines as you want or delete it.

forms.py

class LessonForm(forms.ModelForm):
    class Meta:
        model = Lesson

MaterialsFormset = inlineformset_factory(Lesson, Materials, 
    fields=('field_name', 'field_name'), can_delete=True)

vues.py

def view_name(request):
    form = LessonForm()
    formset = MaterialsFormset(instance=Lesson())
    if request.method == 'POST':
        form = LessonForm(request.POST)
        if form.is_valid():
            lesson = form.save()
            formset = MaterialsFormset(request.POST, request.FILES,
                instance=lesson)
            if formset.is_valid():
                formset.save()
                return render(request, 'index.html',)
    return render(request, "page.html", {
        'form': form, 'formset': formset
    })

html

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    {{ formset.as_p }}
    <input type="submit" value="Save"/>
</form>

1voto

Brandon Points 11873

Vous n'avez besoin que d'une seule balise de formulaire. Si vous souhaitez recevoir toutes les données en même temps, vous devez envelopper tous les champs dans une seule balise de formulaire.

1voto

Plup Points 128

Maintenant que django 4 est sorti, il est possible de faire la même chose dans le formulaire lui-même en utilisant la fonction Modèles réutilisables . Je préfère cette solution car il est alors plus simple de réutiliser des formulaires complexes sans toucher aux vues.

Pour mémoire, voici comment je procède

Les 2 modèles connexes :

# models.py
from django.db import models

class Recipe(models.Model):
    name = models.CharField(max_length=100)
    def __str__(self):
        return self.name

class Ingredient(models.Model):
    name = models.CharField(max_length=100)
    quantity = models.FloatField()
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="ingredients")
    def __str__(self):
        return self.name

Les formes sont liées entre elles :

# forms.py
from django import forms
from .models import Recipe, Ingredient

class IngredientForm(forms.ModelForm):
    class Meta:
        model = Ingredient
        exclude = ('recipe',)

IngredientFormSet = forms.inlineformset_factory(Recipe, Ingredient, form=IngredientForm)

class RecipeForm(forms.ModelForm):
    class Meta:
        model = Recipe
        fields = '__all__'

    template_name = 'recipe_form.html'

    def __init__(self, *args, **kwargs):
        """Initialize the formset with its fields."""
        self.formset = IngredientFormSet(*args, **kwargs)
        super().__init__(*args, **kwargs)

    def get_context(self):
        """Add the formset to the context for rendering."""
        context = super().get_context()
        context['formset'] = self.formset
        return context

    def save(self, commit=True):
        """Bind both models together."""
        instance = super().save(commit=False)
        self.formset.instance = instance
        if self.formset.is_valid():
            instance.save()
            self.formset.save()
        return instance

Le modèle du formulaire :

<!-- templates/recipe_form.html -->
<p>Recipe: {{ form.name }}</p> <!-- calling "form" creates a rendering recursion -->
<p>Ingredients:</p>
{{ formset.management_form }}
<ul>
{% for elt in formset %}
  <li>{{ elt }}</li>
{% endfor %}
</ul>

Et la vue qui l'utilise :

# views.py
from django.views.generic import TemplateView
from .forms import RecipeForm

class RecipeView(TemplateView):
    template_name = 'recipe.html'
    def get_context_data(self):
        context = super().get_context_data()
        context['form'] = RecipeForm()
        return context

    def post(self, *args, **kwargs):
        form = RecipeForm(self.request.POST)
        if form.is_valid():
            form.save()
        else:
            raise Exception('Something bad happened!')
        return self.get(*args, **kwargs)

Avec un modèle très basique :

<!-- templates/recipe.html -->
<form action="." method="post">
    {% csrf_token %}
    {{ form }}
    <button type="submit">Submit</button>
</form>

Et enfin, vous êtes prêt à partir :

# tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Recipe

class TestFormSet(TestCase):
    def test_new_recipe(self):
        data = {
            "name": "quiche",
            "ingredients-TOTAL_FORMS": 3,
            "ingredients-INITIAL_FORMS": 0,
            "ingredients-0-name": 'bacon bits',
            "ingredients-0-quantity": 200,
            "ingredients-1-name": 'eggs',
            "ingredients-1-quantity": 4,
            "ingredients-2-name": 'cream',
            "ingredients-2-quantity": 150,
        }
        r = self.client.post(reverse('recipe'), data=data)
        self.assertEqual(Recipe.objects.first().ingredients.count(),3)

$ python manage.py test
OK

J'espère que cela sera utile à quelqu'un.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X