Modifier plusieurs objets depuis un seul formulaire

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je suis sous Django 1.9 et ai un modèle de la forme suivante :

1
2
3
4
class Progression(models.Model):
    project = models.OneToOneField(Project)
    value = models.PositiveSmallIntegerField(default=0)
    proposition = models.PositiveSmallIntegerField(null=True, blank=True)

Quand un membre met à jour une progression, la valeur spécifiée est stockée dans l'attribut proposition. Alors, l'instance apparaît dans la liste des progressions à valider. Quand une progression est validée, on a :

1
2
progression.value = progression.proposition
progression.proposition = None

Ce que je souhaiterais, c'est avoir un formulaire sous forme de tableau, avec toutes les progressions à valider :

Projet Valeur courante Proposition Valider

Mais j'ignore comment m'y prendre. Pour l'instant, je suis parti avec des form sets :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# forms.py

class ProgressionValidationForm(forms.Form):
    validated = forms.BooleanField(label='')

# views.py

def validate(request):
    # ...
    progressions = models.Progression.objects.filter(proposition__isnull=False)
    context['progressions'] = zip(progressions, django.forms.formset_factory(
        forms.ProgressionValidationForm,
        extra=len(progressions)
    )())
    # ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<form action="{% url 'projects:validate_progressions' %}" method="post">
  {% csrf_token %}
  <table border="1">
    <tr class="table-header">
      <th>Projet</th>
      <th>Progression courante</th>
      <th>Proposition</th>
      <th>Valider</th>
    </tr>
  {% for progression, form in progressions %}
    <tr>
      <td>{{ progression.project.name }}</td>
      <td>{{ progression.percentage }} %</td>
      <td>{{ progression.proposition }} %</td>
      <td>{{ form.as_p }}</td>
    </tr>
  {% endfor %}
  </table>

  <input type="submit" value="Envoyer"/>
</form>

Mais j'ignore ensuite comment faire la correspondance au moment de la réception, d'autant plus que la vue appelée (validate_progressions) n'est pas celle ayant généré le form set.

Merci.

Edit : j'ai aussi essayé de créer simplement mes checkboxes à la main :

1
2
3
4
5
6
7
8
  {% for progression in progressions %}
    <tr>
      <td>{{ progression.project.name }}</td>
      <td>{{ progression.percentage }} %</td>
      <td>{{ progression.proposition }} %</td>
      <td><input type="checkbox" name="{{ progression.id }}"/></td>
    </tr>
  {% endfor %}

Mais j'ignore un peu comment récupérer les données proprement par la suite, vu que je n'ai pas de formulaire Django associé.

+0 -0

C'est une piste que j'ai envisagée également, mais dans la mesure où ma checkbox ne correspond à aucun attribut de mon modèle, je ne vois pas tellement comment faire. A moins que je fasse un truc du genre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# forms.py

class ProgressionValidationForm(forms.Form):
    validated = forms.BooleanField(label='')

ProgressionFormSet = modelformset_factory(
    Progression,
    form=ProgressionValidationForm,
    queryset=Progression.objects.filter(proposition__isnull=False)
)
+0 -0

Ça me semble être une piste.

L'autre solution est effectivement d'utiliser des FormSet en excluant les champs inutiles et en ne laissant que les pk.

+0 -0

Du coup, il fallait bien passer par un ModelFormSet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# forms.py

class ProgressionValidationForm(forms.Form):
    validated = forms.BooleanField(required=False, label='')

# views.py

def progression_formset():
    return django.forms.modelformset_factory(
        models.Progression,
        form=forms.ProgressionValidationForm,
        extra=0
    )

def validate(request):
    progressions = models.Progression.objects.filter(proposition__isnull=False)
    formset = progression_formset()(queryset=progressions)

    context = {
        'progressions': zip(progressions, formset),
        'progression_formset': formset,
    }

    return render(request, 'projects/validate.html', context)

def validate_progression(progression):
    progression.percentage = progression.proposition
    progression.proposition = None
    progression.save()

@require_http_methods(['POST'])
def validate_progressions(request):
    formset = progression_formset()(request.POST)

    if formset.is_valid():
        for progression in formset.save(commit=False):
            validate_progression(progression)

    return redirect('projects:validate')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form action="{% url 'projects:validate_progressions' %}" method="post">
  {% csrf_token %}
  {{ progression_formset.management_form }}

  <table border="1">
    <tr class="table-header">
      <th>Projet</th>
      <th>Progression courante</th>
      <th>Proposition</th>
      <th>Valider</th>
    </tr>
  {% for progression, form in progressions %}
    <tr>
      <td><a href="{% url 'projects:preview' progression.project.id progression.project.slug %}">{{ progression.project.name }}</a></td>
      <td>{{ progression.percentage }} %</td>
      <td>{{ progression.proposition }} %</td>
      <td>{{ form.as_p }}</td>
    </tr>
  {% endfor %}
  </table>

  <input type="submit" value="Envoyer"/>
</form>
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte