Django.fun

IntegrityError, NOT NULL constraint failed: contest_contestant.contest_id while updating record

I Have two models Contest and Contestant, users can start a contest, and other users can register as a contestant under any contest…

models.py
class Contest(models.Model):
    contest_title = models.CharField(max_length=30)
    contest_post = models.CharField(max_length=100)
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

    

class Contestant(models.Model):
    contest = models.ForeignKey(Contest, on_delete=models.CASCADE, related_name='participating_contest')
    contestant_name = models.CharField(max_length=10, blank=True, null=True)
    votes = models.IntegerField(default=0)
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

Fans of any contestant can cast an unlimited amount of vote for their favourite contestant, anytime a fan votes i want to increment the Contestant.votes with the amount of votes being casted, i have tried using FBV but it is too complicated for my level of django, i am now using CBV, but i am getting an IntegrityError saying IntegrityError at /contests/contestant/2/ NOT NULL constraint failed: contest_contestant.contest_id

this error doesn’t make sense to me because contest_id has already being assigned to contestant upon creation, and i can even confirm it from the database

view.py

class ContestantCreateView(CreateView):
    model = Contestant
    fields = ['contestant_name']

    def form_valid(self, form):
        form.instance.contest_id = self.kwargs.get('pk')
        return super().form_valid(form)

the voting logic is in side contestant detailView, which is being triggered by a POST request from a form

views.py

class ContestantDetail(DetailView, FormMixin):
    model = Contestant
    context_object_name = 'contestants'
    template_name = 'contest/contestant_detail.html'
    form_class = VoteForm

    def get_success_url(self):
        return reverse('contest:contestant-detail', kwargs={'pk': self.object.pk})

    def get_context_data(self, *args, **kwargs):
        context = super(ContestantDetail, self).get_context_data(*args, **kwargs)
        context['vote_contestant'] = Contestant.objects.get(pk=self.kwargs.get('pk'))
        return context

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        self.object = self.get_object()

        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, *args, **kwargs):
        contestant = Contestant.objects.get(pk=self.kwargs['pk'])
        vote_count = form.cleaned_data['votes']
        contestant.votes += vote_count
        form.save()
        messages.success(self.request, f'You have successfully casted your vote.')
        return super().form_valid(form)



forms.py

class VoteForm(forms.ModelForm):
    class Meta:
        model = Contestant
        fields = ['votes']

html form template

<form action="" method="post">
                {% csrf_token %}
                {% include 'contest/bs4_form.html' with form=form %}

                <button type="submit">Vote Now</button>
            </form>

below is my urls.py

    path('contestant/<int:pk>/new/', views.ContestantCreateView.as_view(), name='new-contestant'),
    path('contestant/<int:pk>/edit/', views.ContestantUpdateView.as_view(), name='edit-contestant'),
    path('contestant/<int:pk>/', views.ContestantDetail.as_view(), name='contestant-detail'),

Answers: 1

Answered by Willem Van Onsem, Sept. 14, 2021, 10:53 a.m.

By making use of form.save() you are saving the form, since you did not pass an instance to the form, that means that form.save() will try to create a new record. You thus should omit form.save() from the form_valid(…) method:

from django.db.models import F

def form_valid(self, form, *args, **kwargs):
    contestant = Contestant.objects.get(pk=self.kwargs['pk'])
    vote_count = form.cleaned_data['votes']
    contestant.votes = F('votes') + vote_count
    contestant.save()
    # no form.save()
    messages.success(self.request, f'You have successfully casted your vote.')
    return super().form_valid(form)