djangomodelformformview

FormView not saving data in Django


I am trying to allow users to save details of a workout for a specific exercise through submitting a form. My ExerciseDetailView displays the form how I'd like it to:

class ExerciseDetailView(DetailView):
    model = Exercise
    template_name = 'workouts/types.html'

    def get_context_data(self, **kwargs):
        context = super(ExerciseDetailView, self).get_context_data(**kwargs)
        context['form'] = WorkoutModelForm
        return context

But my problem is with saving the inputted data in the database. I have tried making both a FormView and a CreateView but am clearly missing something:

class ExerciseFormView(FormView):
    form_class = WorkoutModelForm
    success_url = 'workouts:exercise_detail'

    def form_valid(self, form):
        form.save()
        return super(ExerciseFormView, self).form_valid(form)

Here is my referenced WorkoutModelForm:

class WorkoutModelForm(forms.ModelForm):
    class Meta:
        model = Workout
        fields = ['weight', 'reps']

My template:

<form action="{% url 'workouts:workout' exercise.id %}" method="post">
    {% csrf_token %}
    {{ form }}
    <button type="submit">Save</button>
</form>

Urls:

path('exercise/<int:pk>/detail/', ExerciseDetailView.as_view(), name='exercise_detail'),
path('exercise/<int:pk>/detail/', ExerciseFormView.as_view(), name='workout'),

And for context here is my Workout model which contains a get_absolute_url method:

class Workout(models.Model):
    weight = models.FloatField(default=0)
    reps = models.PositiveIntegerField(default=0)
    created = models.DateField(auto_now_add=True)
    updated = models.DateField(auto_now=True)
    exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, default=None)

    def get_absolute_url(self):
        return reverse('exercise_detail', args=[str(self.pk)])

I am not receiving any errors, but when I submit the form my url remains the same, as I hoped, however the page just appears blank and the objects are not recorded. Can anybody please help me see what the problem is?


Solution

  • The problem is not your view, the Django logic will never trigger this view, the URLs are perfectly overlapping, so that means that for a URL, it will always trigger the first view (here the ExerciseDetailView), you should make the paths non-overlapping, for example with:

    path('exercise/<int:pk>/detail/', ExerciseDetailView.as_view(), name='exercise_detail'),
    path('exercise/<int:pk>/workout/', ExerciseFormView.as_view(), name='workout'),

    Triggering the logic will however not be sufficient, since it will not link the Workout to the necessary exercise, you can alter the logic to:

    from django.urls import reverse
    
    class ExerciseFormView(CreateView):
        form_class = WorkoutModelForm
    
        def form_valid(self, form):
            form.instance.exercise_id = self.kwargs['pk']
            return super().form_valid(form)
    
        def get_success_url(self):
            return reverse('workouts:exercise_detail', kwargs={'pk': self.kwargs['pk']})