UniqueConstraint and clean() method in forms.py

In my models.py file I defined the following class:

class Space(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    node = models.ForeignKey(Node, on_delete=models.CASCADE, related_name='spaces', related_query_name='space')
    nav_link = models.ForeignKey(NavLink, on_delete=models.CASCADE, related_name='space_nav_links', related_query_name='space_nav_link')
   ...

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['node', 'nav_link'], name='unique_space')
        ]

I learned that Model constraints are “applied” at the time the model is attempting to be saved and that if I want to ensure that this doesn’t throw an IntegrityError I have to create a custom clean() method in forms.py

class SpaceForm(ModelForm):
    class Meta:
        model = models.Space
        fields = (
            'nav_link',
        )

    def clean(self):
        cleaned_data = super().clean()
        nav_link_id = cleaned_data.get('nav_link')

        if models.Space.objects.filter(node_id=self.user.node_id, nav_link_id=nav_link_id)).exists():
            raise ValidationError(message='Space already exists. Please enter a unique space', code='invalid')

In a CreateView this works, however, in an UpdateView it raises the exception.

I thought adding `id’ to the filter would solve the issue:

if models.Space.objects.filter(~Q(id=id) & Q(node_id=self.user.node_id) & Q(nav_link_id=nav_link_id)).exists():
    raise ValidationError(message='Space already exists. Please enter a unique space', code='invalid')

The issue here is, in case of a CreateView there is no id and in case of an UpdateView the form doesn’t contain the id.

My question is, is there a way to solve this issue?

I think the issue here is that there could be an instance of Space where the node_id of the instance being edited is not the node_id in self.user. (How is the user object being assigned to the form?)

Your test should properly check the instance of the model represented by that form. e.g.:
if Space.objects.filter(node_id=self.instance.node_id, nav_link_id=nav_link_id).exists():

def form_valid(self, form):
        form.instance.node_id = self.request.user.node_id
        return super().form_valid(form)

I think I solved the issue:

if models.Space.objects.filter(~Q(id=self.instance.id) & Q(node_id=self.user.node_id) & Q(nav_link_id=nav_link_id)).exists():
    raise ValidationError(message='Space already exists. Please enter a unique space', code='invalid')

The problem was that the instance in the form and the instance in the queryset don’t recognize that they’re the same instance. By adding this ~Q(id=self.instance.id) to the filter method the problem was solved.

1 Like