Reputation: 95
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
Upvotes: 2
Views: 1374
Reputation: 40
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
Upvotes: 0
Reputation: 95
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')
]
Upvotes: 0
Reputation: 20702
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).
Upvotes: 2