Search code examples
pythondjangodjango-modelsbooleandjango-admin

Django Admin - One to Many - How to make sure only one children has a boolean field selected


In django, I have the following models:

class System(models.Model):
    name = models.CharField(max_length=200)
    """ ... many other fields, not useful for here ..."""
    # Would it make more sense to have the primary instance here ?  
        

class Instance(models.Model):
    name = models.CharField(max_length=200)
    url = models.UrlField(max_length=200)
    system = models.ForeignKey(System, on_delete=models.PROTECT)
    is_production = models.BooleanField()

This data is managed using the admin. What I want is that when an instance of the system is marked as is_production, all other instances, for that system have their is_production field updated to False.

Also, I am interested in how to best setup the admin for this case. I, will be using inlines for the edition/creation of instances.

However, I am not sure about how to make sure each system can only have one instance in production.

  • Should I use a dropdown on the System to select the production instance and filter using formfield_for_foreignkey?
  • Use an admin action, something like: Mark as production ?
  • Use signals after a save ?
  • is there any other way I have not thought about ?

Solution

  • You asked multiple questions but I'll focus on what I interpreted as the main one:

    What I want is that when an instance of the system is marked as is_production, all other instances, for that system have their is_production field updated to False.

    How about overriding the Instance model's save method?

    class Instance(models.Model):
        name = models.CharField(max_length=200)
        url = models.URLField(max_length=200)
        system = models.ForeignKey(System, on_delete=models.PROTECT)
        is_production = models.BooleanField()
    
        def save(self, *args, **kwargs):
            if self.is_production:
                self.system.instance_set.exclude(id=self.id).update(is_production=False)
            super().save(*args, **kwargs)
    

    This ensures that whenever an Instance instance with is_production=True is saved, all other Instance instances that are linked to the related System object will have their is_production values updated to False.

    Depending on how you go about changing the Instance instances' is_production values, this might or might not be suitable for what you want to do. See e. g. this thread discussing how using the .update() method doesn't lead to the save() method being called: Django .update doesn't call override save? (also described in the Django docs, referred to in the linked thread)