In Django, how to create a model that stores proposed changes to another model?

42 Views Asked by At

I'm using Python 3.9 and Django 3.2. I have the following model

class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType, blank=False)
    addresses = models.ManyToManyField(Address, through='CoopAddressTags')
    enabled = models.BooleanField(default=True, null=False)
    phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
    email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
    web_site = models.TextField()
    description = models.TextField(null=True)
    approved = models.BooleanField(default=False, null=True)

We would like to set up a situation where someone could propose a change to a row in the db that would be reviewed before being saved, so I've created this struture

class CoopChange(Coop):
    """
    """
    created_at = models.DateTimeField(null=False, default=datetime.now)

The problem is that when I create a migration, the table that is created simply points back to the original model, instead of storing all the fields

        Table "public.directory_coopchange"
   Column    |           Type           | Modifiers 
-------------+--------------------------+-----------
 coop_ptr_id | integer                  | not null
 created_at  | timestamp with time zone | not null

This is non-ideal because the original table would contain both finalized entries and those suggested for changes. Is there a way to create an entity that stores proposed changes that mirrors the structure of the original entity?

1

There are 1 best solutions below

3
Artisan On

There are a few of ways you could tackle this

  1. Change Coop to an abstract base class: https://docs.djangoproject.com/en/4.0/topics/db/models/#abstract-base-classes

Then you can inherit two classes from it CoopProposed and CoopApplied and you can work out what logic happens to create each

  1. Just use the one class Coop and add a property called approved defaulted to False and then set it to True when you approve the changes. This might not be what you want as it will overwrite any previous changes and you might want to keep a history of changes.

  2. Add a property proposed as a JSONField (check the docs if the DB you are using supports it) on the Coop class. This can store a whole dictionary with the keys and values of the proposed changes. When approved, the field can be read and applied to the model's properties in a function or something like that.

class Coop():
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType, blank=False)
    ...
    proposed = models.JSONField("Proposed Changes", null=True)

    def apply_proposed_changes(self):
       proposed = self.proposed
       self.name = proposed.get('name')
       for type in proposed.get('types'):
           self.types.add(CoopType.objects.get(name=type))
       ...
    

In any case if you're trying to inherit all the properties from Coop you need to understand how Django model inheritance work and all the caveats before you get bitten later down the track: https://docs.djangoproject.com/en/4.0/topics/db/models/#model-inheritance