0

I have a model Model. This model have multiple attributes, three of them are: domaintld, subdomain, url - OneToOneFields.

I'm trying to allow one and only one from these fields to be non empty.

My approach works but I'm curious if it's there a better way to do this (I use PostgreSQL).

def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    self.clean()
    super(Model, self).save(force_insert, force_update, using, update_fields)

def clean(self):
    super(Model, self).clean()

    if not(((bool(self.url) ^ bool(self.domaintld)) ^ bool(self.subdomain)) and not(self.url and self.domaintld and self.subdomain)):
        raise exceptions.ValidationError("One and only one field can be used: url,domaintld,subdomain")
Milano
  • 18,048
  • 37
  • 153
  • 353

1 Answers1

0

Assuming that you have something like this in your models.py:

class DomainTLD(models.Model):
    # attributes and methods

class Subdomain(models.Model):
    # attributes and methods

class URL(models.Model):
    # attributes and methods

class MyModel(models.Model):
    domaintld = models.OneToOneField(DomainTLD)
    subdomain = models.OneToOneField(Subdomain)
    url = models.OneToOneField(URL)
    # other attributes and methods

I'd suggest using django contenttypes and refactoring like this:

class MyModel(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

and scrap the save and clean method. Now MyModel can have a relation with any model, be it DomainTLD, Subdomain or URL - content_type points to the model, object_id holds the object ID and content_object, which won't become a field in the database, will be used to retrieve the object.

This is much cleaner way and you can get rid of that obscure clean method. You can further enhance it and limit the choices for content_type only to those you wish. This SO answer explains that.

cezar
  • 11,616
  • 6
  • 48
  • 84