django UniqueConstraint violation_error_message message not handled

Question:

I have this model:

class DataMapping(models.Model):
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    location = models.ForeignKey(Location, null=True, blank=True, on_delete=models.CASCADE)
    department = models.ForeignKey(LocationDepartment, null=True, blank=True, on_delete=models.CASCADE)
    catagory = models.ForeignKey(CompanyCategory, on_delete=models.CASCADE)
    reason = models.ForeignKey(ReasonForProcessing, on_delete=models.CASCADE)

        class Meta:
            constraints = [
                UniqueConstraint(
                    name='unique_data_map', 
                    fields=['company', 'location', 'department', 'catagory', 'reason'],
                    condition=Q(location__isnull=False),
                    violation_error_message="This data map has already been added.",
                ),
                UniqueConstraint(
                    name='unique_data_map_both_none_dep_none', 
                    fields=['company', 'location', 'catagory', 'reason'],
                    condition=Q(department__isnull=True),
                    violation_error_message="This data map has already been added."
                ),
                UniqueConstraint(
                    name='unique_data_map_both_none', 
                    fields=['company', 'catagory', 'reason'],
                    condition=Q(location__isnull=True) & Q(department__isnull=True),
                    violation_error_message="This data map has already been added."
                )
    ]

with a basic modelform

class DataMapForm(forms.ModelForm):
    class Meta:
        model = DataMapping
        fields = (
            'location',
            'department',
            'catagory',
            'reason',
    )
    widgets = {
        'company': forms.HiddenInput()
    }

and a view of:

def data_mapping_new(request):
    company = get_current_logged_in_company(request)

    if request.method == "POST":
        form = DataMapForm(request.POST or None)
        if form.is_valid():
            catagory = form.save(commit=False)
            catagory.company = company
            if form.is_valid():
                form.save()

I have a similar setup on a different model that works fine, but this one just raises an error django.db.utils.IntegrityError: duplicate key value violates unique constraint "unique_data_map_both_none"

Should it not check the constraint at the first if form.is_valid():

What am I missing here?

EDIT:
I used a similar pattern for a model somewhere else and it works fine there, with the only difference there being that the condition is on a char field and not a foreign key as in this model. condition=Q(email__isnull=False) & Q(email__gt=''). Maybe my issue is in how I set up the condition?

Answers:

The issue is with the form.save(commit=False) line, which does not include all the fields necessary to satisfy the unique constraints specified in the model’s Meta class. When you call form.save(commit=False), you are creating a new instance of the DataMapping model that has not yet been saved to the database. Then you are assigning the company field to the current company, but you are not assigning values to the other fields that are part of the unique constraint.

Try to add those fields before calling form.save() like so:

def data_mapping_new(request):
    company = get_current_logged_in_company(request)

    if request.method == "POST":
        form = DataMapForm(request.POST or None)
        if form.is_valid():
            data_mapping = form.save(commit=False)
            data_mapping.company = company
            data_mapping.location = form.cleaned_data['location']
            data_mapping.department = form.cleaned_data['department']
            data_mapping.catagory = form.cleaned_data['catagory']
            data_mapping.reason = form.cleaned_data['reason']

            
            data_mapping.save()

    # ...

The above solution should work but additionally I’d recommend you to see Using request.POST or None antipattern made by Sir Willem Van Onsem.

Answered By: Sunderam Dubey

Ok, so after a lot of "when in doubt cout", I noticed that model forms only check constraints that include all the fields that the form includes. In my case the model included

'location',
'department',
'catagory',
'reason',

but the constraint included company. I did not add company here as it is not something the user sets in the form, but it is a hidden field. Point is that the constraint did not trigger as the constraint includes company. Adding company to the fields list in the model form resolved my problem.

Answered By: Christo Labuschagne