Formulaires imbriqués

a marqué ce sujet comme résolu.

Bonjour à tous,

Dans mon application Django (1.9) je dispose de deux classes. "Workout" qui représente un entraînement physique, et "Exercise", qui représente un exercice qui peut faire partie d'un entraînement. Ces deux classes sont liées par une classe intermédiaire, "WorkoutExercise", qui contient quelques paramètres supplémentaires.

Ce que je souhaite faire c'est, lorsque j'affiche un formulaire de création de Workout, je puisse préciser autant de WorkoutExercises que je désire, un peu comme dans l'administration de Django.

Après avoir parcouru la doc, je ne trouve pas vraiment mon bonheur. J'ai vu les formset, mais je n'arrive pas à trouver un exemple concret pour savoir comment ça fonctionne. Est-ce que quelqu'un pourrait m'aider ?

Voici mon models.py :

 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from django.db import models


class Exercise(models.Model):
    EQUIPMENT_NONE = 0
    EQUIPMENT_PULLUP_BAR = 1
    EQUIPMENT_WALL = 2
    EQUIPMENT_RUN = 3
    EQUIPMENTS = (
        (EQUIPMENT_NONE, 'None'),
        (EQUIPMENT_PULLUP_BAR, 'Pullup bar'),
        (EQUIPMENT_WALL, 'Wall'),
        (EQUIPMENT_RUN, 'Run')
    )
    UNIT_METERS = 0
    UNIT_REPS = 1
    UNITS = (
        (UNIT_METERS, 'm'),
        (UNIT_REPS, 'repetitions')
    )
    VIDEO_YOUTUBE = 0
    VIDEO_TYPES = (
        (VIDEO_YOUTUBE, 'YouTube'),
    )

    name = models.CharField(max_length=255)
    points = models.FloatField()
    video_link = models.CharField(max_length=255)
    video_type = models.SmallIntegerField(choices=VIDEO_TYPES, default=VIDEO_YOUTUBE)
    equipment = models.IntegerField(choices=EQUIPMENTS, default=EQUIPMENT_NONE)
    unit = models.SmallIntegerField(choices=UNITS, default=UNIT_REPS)

    def __str__(self):
        return self.name


class Workout(models.Model):
    name = models.CharField(max_length=255)
    creation_date = models.DateTimeField(auto_now_add=True)
    update_date = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    @property
    def points(self):
        total = 0
        for workout_exercise in self.workoutexercise_set.all():
            total += workout_exercise.exercise.points * workout_exercise.quantity

        return round(total)


class WorkoutExercise(models.Model):
    workout = models.ForeignKey(Workout, on_delete=models.CASCADE)
    exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    position = models.IntegerField()

Merci d'avance ! :)

Il me semble qu'il te faudrait utiliser un InlineFormset, et comme je ne suis pas le plus à l'aise sur ce point, je pense que tu trouveras plus d'information sur cet article de Charles Leifer: Django's inlineformset_factory and you.

Petits conseils carrément hors-sujet:

  1. Au lieu de définir des constantes directements dans tes modèles, ce qui rend le tout moins lisible, je te conseille d'utiliser la librairie Django-Choices

  2. Afin de pouvoir accéder plus facilement aux exercices d'un workout, ou aux workouts liés à un exercice, tu pourrais définir une relation N-N entre ces deux modèles.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Nouvelle définition du modèle Workout
class Workout(models.Model):
    name = models.CharField(max_length=255)
    creation_date = models.DateTimeField(auto_now_add=True)
    update_date = models.DateTimeField(auto_now=True)
    exercices = models.ManyToManyField(Exercise, related_name='workouts', through='WorkoutExercise')

# On requête tous les exercices d'un Workout
my_workout.exercices.all()
my_workout.exercices.filter(points=3)

# Inversement, on requête tous les workouts d'un exercice.
my_exercise.workouts.all()
my_exercise.workouts.filter(name='workout_name')
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