0

I have the model

class Owner(models.Model):
    name = models.CharField(max_length=10)

class PhoneNumber(models.Model):
    isActive = model.BooleanField(default=False)
    owner = model.ForeignKey('Owner')

I want to enforce that only one PhoneNumber is active so if the user, when creating or editing a 'not active' PhoneNumber, accidentally sets it to 'active' and there is another 'active' PhoneNumber already the form should not submit and a clear error will get displayed to change the 'active' field to False and error text "Please deactivate the old active PhoneNumber before assigning a new active PhoneNumber"

How do I do this? In which validation method in which class do I check this easily ?

Thank you

mp3por
  • 1,796
  • 3
  • 23
  • 35

4 Answers4

5
class Owner(models.Model):
    name = models.CharField(max_length=10)

class PhoneNumber(models.Model):
    isActive = models.NullBooleanField(default=None, unique=True)
    owner = model.ForeignKey('Owner')

    def save(self, *args, **kwargs):
        if self.isActive is False:
            self.isActive = None
        super(PhoneNumber, self).save(*args, **kwargs)
Daniyal Syed
  • 543
  • 4
  • 22
0

Often validation like this can be done automatically by defining unique_together in the model's Meta class, but here that will not work as you could possibly have many inactive phone numbers for each user. So you will need custom validation. One way to do that will be in the clean method of the model itself, which is called when the form is validated.

def clean(self):
    if self.isActive:
        active = PhoneNumber.objects.filter(isActive=True, owner=self.owner)
        if self.pk:
            active = active.exclude(pk=self.pk)
        if active.exists():
            raise ValidationError("An active phone number already exists for this user")
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
0

There is not one way to do it. But I can suggest doing it in the model itself and override save method like the following:

class PhoneNumber(models.Model):
    isActive = model.BooleanField(default=False)
    owner = models.ForeignKey('Owner')
    phone_number = odels.CharField(max_length=50)

     class Meta:
        unique_together = ('user', 'phone_number',)

     def save(self, *args, **kwargs):
        if self.id: # to ensure that the object is being updated and is not a new one
             if not self.isActive:
                  check_active_uniquniess = self._meta.model.objects.filter(user = self.user).exclude(id=self.id).count()
                  if check_active_uniquniess:
                         raise ValidationError("Please deactivate the old active PhoneNumber before assigning a new active PhoneNumber")
        super(PhoneNumber, self).save(*args, **kwargs)

I can also suggest to override the clean method in ModelForm.

Ahmed Hosny
  • 1,162
  • 10
  • 21
0
class Owner(models.Model):
    name = models.CharField(max_length=10)

class PhoneNumber(models.Model):
    phone_number = model.CharField(max_length=255)
    owner = model.ForeignKey('Owner')

class ActivePhoneNumber(models.Model):

    active_phone_number = model.ForeignKey(PhoneNumber)
    owner = model.OneToOneField(Owner)

PhoneNumber can hold multiple numbers per owner, where ActivePhoneNumber only one number per owner. This will enforce the one active number per user rule in the model level.

You will still need to provide a form to replace active number. Something like:

owner=self.request.user
new_number = PhoneNumber(owner=owner,phone_number="foobar")
# or new_number = owner.phone_number_set.get(phone_number="foobar")
new_number.save()
owner.active_phone_number.delete()
new_active = ActivePhoneNumber(owner=owner,active_phone_number=new_number)
new_active.save()

Note: If one PhoneNumber can be used by only one owner, then set:

active_phone_number = model.OneToOneField(PhoneNumber)
Aviah Laor
  • 3,620
  • 2
  • 22
  • 27