Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to create a unique id over 2 fields?

Here is my model:

class GroupedModels(models.Model):
    other_model_one = models.ForeignKey('app.other_model')
    other_model_two = models.ForeignKey('app.other_model')

Essentially, what I want is is for other_model to be unique in this table. That means that if there is a record where other_model_one id is 123, I should not allow another record to be created with other_model_two id as 123. I can override clean I guess but I was wondering if django has something built in.

I am using version 2.2.5 with PSQL.

Edit: This is not a unqiue together situation. If I add a record with other_model_one_id=1 and other other_model_two_id=2, I should not be able to add another record with other_model_one_id=2 and other other_model_two_id=1

like image 291
Pittfall Avatar asked Oct 23 '19 13:10

Pittfall


2 Answers

I explain several options here, maybe one of them or a combination can be useful for you.

Overriding save

Your constraint is a business rule, you can override save method to keep data consistent:


class GroupedModels(models.Model): 
    # ...
    def clean(self):
        if (self.other_model_one.pk == self.other_model_two.pk):
            raise ValidationError({'other_model_one':'Some message'}) 
        if (self.other_model_one.pk < self.other_model_two.pk):
            #switching models
            self.other_model_one, self.other_model_two = self.other_model_two, self.other_model_one
    # ...
    def save(self, *args, **kwargs):
        self.clean()
        super(GroupedModels, self).save(*args, **kwargs)

Change design

I put a sample easy to understand. Let's suppose this scenario:

class BasketballMatch(models.Model):
    local = models.ForeignKey('app.team')
    visitor = models.ForeignKey('app.team')

Now, you want to avoid a team playing a match with itself also team A only can play with team B for once (almost your rules). You can redesign your models as:

class BasketballMatch(models.Model):
    HOME = 'H'
    GUEST = 'G'
    ROLES = [
        (HOME, 'Home'),
        (GUEST, 'Guest'),
    ]
    match_id = models.IntegerField()
    role = models.CharField(max_length=1, choices=ROLES)
    player = models.ForeignKey('app.other_model')

    class Meta:
      unique_together = [ ( 'match_id', 'role', ) ,
                          ( 'match_id', 'player',) , ]

ManyToManyField.symmetrical

This look like a symetrical issue, django can handle it for you. Instead of create GroupedModels model, just make a ManyToManyField field with itself on OtherModel:

from django.db import models
class OtherModel(models.Model):
    ...
    grouped_models = models.ManyToManyField("self")

This is what django has as built in for these scenarios.

like image 106
dani herrera Avatar answered Nov 12 '22 15:11

dani herrera


It's not a very satisfying answer, but unfortunately the truth is there is no way to do what you're describing with a simple built-in feature.

What you described with clean would work, but you have to be careful to manually call it as I think it's only automatically called when using ModelForm. You might be able to create a complex database constraint but that would live outside of Django and you'd have to handle database exceptions (which can be difficult in Django when in the middle of a transaction).

Maybe there's a better way to structure the data?

like image 30
Tim Tisdall Avatar answered Nov 12 '22 14:11

Tim Tisdall