shadow
shadow

Reputation: 311

Make field of submodel unique in django

I want to make the name of a submodel unique but I can't think of a way to do it. Imagine I have the following model architecture:

class Animal(models.Model):
   name = field.CharField(...)
   class Meta:
      abstract = False

class Panther(Animal):
   class Meta:
     ...

class Tiger(Animal):
   class Meta:
    ....

Now, what I want is that within the scope of the subclasses, the name of should be unique. So let's say I have a Tiger called JackTheTiger and a Panther called JackyThePanther, then no other Tiger with this name should allowed to be created and no other Panther with the name JackyThePanther should be allowed to be created.

But I want to be able to create a Tiger which is called JackyThePanther and a panther which is also called JackyThePanther. So the uniqueness should only be applied within the scope of the submodel.

I tried 2 ways to achieve what I want, both are not optimal:

  1. I create a name field for each submodel and make it unique. But then I can't query all animals and serialize the name. It also seems like bad architecture to me
  2. I make Animal abstract. But this is no option for me since I need the database table for animal (3. I also thought about a custom validator but I don't think it's possible because I could only validate the instances of the Supermodel)

Is there another way to achieve what I intend? Help is very much appreciated, thanks in advance

Upvotes: 1

Views: 188

Answers (2)

koko
koko

Reputation: 397

Maybe its your solution

class Animal(models.Model):
    TYPE = (
        (1, 'Tyger'),
        (2, 'Panter')
    )

    name = models.CharField(max_length=64)
    type_of_animal = models.IntegerField(choices=TYPE)

    class Meta:
        unique_together = [['name','type_of_animal']]

In this case you can create JackyThePanther for 'Tyger' and 'Panter'. You can expand logic, for example add 'Elephant' JackyThePanther

>>> animals = Animal.objects.all()
>>> animals = Animal.objects.filter(name='JackyThePanther')
>>> animals
<QuerySet [<Animal: Animal object (1)>, <Animal: Animal object (2)>]>

Upvotes: 0

kungphu
kungphu

Reputation: 4849

Model inheritance in Django is rarely the best solution, and I don't think I'd turn to it in your case. You're already seeing the pitfalls it can come with in situations that aren't perfectly suited to it.

Meta.unique_together is a model option that enables what you seem to be looking for, though you'll have to change the approach you're using:

class Species(models.Model):
   name = models.CharField(...)

class Animal(models.Model):
   name = models.CharField(...)
   species = models.ForeignKey(Species, on_delete=models.CASCADE)
   
   class Meta:
      unique_together = [['name', 'species']]

In this case, you'd likely maintain a data migration or fixture for species, as your current architecture has them predefined in code.

Note that this uses unique_together, which is currently valid, but the linked documentation mentions that UniqueConstrant is likely more future-proof. I'm sticking with the former as a demonstration here since it's the one I've used.

Upvotes: 1

Related Questions