How to handle UniqueConstraint failure in Django Meta Class?

Question:

I have created a UniqueConstraint Meta Class for my model, and it works when I test it by intentionally creating a duplicate but I am getting an error page instead of being redirected back to the original page. Where do I insert the code to go back to the page that I was just on?

I assume the issue is in the view, but I have no idea what code to put in there and the Django docs contain nothing alluding to this problem.

Model:

class StudentScheduledClass(models.Model):
    student = models.ForeignKey("users.User", on_delete=models.CASCADE, db_column="Student")
    scheduled_class = models.ForeignKey("ScheduledClass", on_delete=models.CASCADE, db_column="ScheduledClass")
    grade = models.FloatField(db_column="Grade", blank=True, null=True)

    class Meta:
        managed = True
        db_table = "StudentScheduledClass"
        verbose_name_plural = "StudentsScheduledClasses"
        constraints = [
            models.UniqueConstraint(fields=['student', 'scheduled_class'], name='student scheduled class restraint')
        ]

View:

class StudentScheduledClassCreateView(LoginRequiredMixin, CreateView):
    model = StudentScheduledClass
    context_object_name = "student_scheduled_class"
    fields = ["student"]

    def form_valid(self, form):
        scheduled_class = self.kwargs["scheduled_class"]
        form.instance.scheduled_class = ScheduledClass(scheduled_class)
        return super().form_valid(form)

    def get_success_url(self):
        scheduled_class = self.kwargs["scheduled_class"]
        return reverse("scheduled-class-detail", args={scheduled_class})

I’d like to just go back to the original page with an error message, instead I get this integrity error:

IntegrityError at /classes/student_scheduled_class_create/1/
UNIQUE constraint failed: StudentScheduledClass.Student, StudentScheduledClass.ScheduledClass
Request Method: POST
Request URL:    http://localhost:8000/classes/student_scheduled_class_create/1/
Django Version: 2.2.2
Exception Type: IntegrityError
Exception Value:    
UNIQUE constraint failed: StudentScheduledClass.Student, StudentScheduledClass.ScheduledClass
Asked By: Roostercrab

||

Answers:

Your form only requires the field student so it’s valid because you haven’t given its instance a value for scheduled_class when it gets validated.

You should initialise your form with an instance for which scheduled_class is already set. You can do that in get_form_kwargs() or, since that method passes self.object as instance, you can do that even simpler in the post():

def post(self, request, *args, **kwargs):
    self.object = StudentScheduledClass(scheduled_class=kwargs['scheduled_class'])
    return super().post(request, *args, **kwargs)

Alternatively, although a bit less clean (I like to think that once form_valid() is called everything should be ok):

def form_valid(self, form):
    scheduled_class = self.kwargs["scheduled_class"]
    form.instance.scheduled_class = ScheduledClass(scheduled_class)
    try:
        return super().form_valid(form)
    except IntegrityError:
        return self.form_invalid(form)

The problem with the last one is that the form won’t have any errors to show, so you’d probably want to add a Django message to the self.request before returning (assuming you display any message in your template).

Answered By: dirkgroten

This is the code I ended up using:

View:

class StudentScheduledClassCreateView(LoginRequiredMixin, CreateView):
    model = StudentScheduledClass
    context_object_name = "student_scheduled_class"
    fields = ["student"]

    def form_valid(self, form):
        try:
            scheduled_class = self.kwargs["scheduled_class"]
            form.instance.scheduled_class = ScheduledClass(scheduled_class)
            return super().form_valid(form)
        except IntegrityError as error:
            scheduled_class = self.kwargs["scheduled_class"]
            return self.form_invalid(form)

    def get_success_url(self):
        scheduled_class = self.kwargs["scheduled_class"]
        return reverse("scheduled-class-detail", args={scheduled_class})

Model stayed the same:

class StudentScheduledClass(models.Model):
    student = models.ForeignKey("users.User", on_delete=models.CASCADE, db_column="Student")
    scheduled_class = models.ForeignKey("ScheduledClass", on_delete=models.CASCADE, db_column="ScheduledClass")
    grade = models.FloatField(db_column="Grade", blank=True, null=True)

    class Meta:
        managed = True
        db_table = "StudentScheduledClass"
        verbose_name_plural = "StudentsScheduledClasses"
        constraints = [
            models.UniqueConstraint(fields=['student', 'scheduled_class'], name='student scheduled class restraint')
        ]
Answered By: Roostercrab

If anyone is getting atomic block issues

(TransactionManagementError at….
An error occurred in the current transaction. You can’t execute queries until the end of the ‘atomic’ block)

due to the form.save() generating an IntegrityError from the models.UniqueConstraint with this error handling you can with transaction.atomic():

Like this:

    def form_valid(self, form, *args, **kwargs):
        try:
            with transaction.atomic():
                return super().form_valid(form, *args, **kwargs)
        except IntegrityError as ex :
            form.add_error(None, 'Error! %s '% ex)
            return super().form_invalid(form)
        except Exception as ex:
            messages.error(
                self.request,
                "Error creating Training Procedure. Error: {}".format(
                    ex
                ),
            )
            # self.object = None
            return super().form_invalid(form)

This answer also illustrates error handling with form.add_error as well as messages.error

Answered By: Chad
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.