Hi everybody!
What is the best or possible way to implement the interaction of the main model (for example, Customer) and one or a related set of date-related attributes in django models (for example, the official names of Customer, which may change over time), but their history must be preserved?
Here’s what I’ve come up with so far. The class code is probably redundant, but this is done specifically to demonstrate the problem.
Main class:
class Customer(models.Model):
name = models.CharField(max_length=128,
verbose_name='Customer system name')
# A key for relation with current value of secondary class
# I have doubts about the correctness of adding this field,
# but so far I have not come up with anything better.
legal_names = models.ForeignKey('CustomerNames', on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name='Legal customer names')
Auxiliary (secondary) class:
class CustomerNames(models.Model):
# A key for relation with the main model, specifying a list of names
customer = models.ForeignKey(Customer, on_delete=models.CASCADE,
related_name='customer_names',
verbose_name='Customer')
short_name = models.CharField(max_length=256,
verbose_name='Short name')
full_name = models.CharField(max_length=1024,
verbose_name='Full name')
# An attribute for determining the current value
# if we do not use a foreign key in the main class
is_current = models.BooleanField(default=True,
verbose_name='Is current value')
# Dates of relevance of the names
date_begin = models.DateField(verbose_name='Relevance begin date for names')
date_end = models.DateField(verbose_name='Relevance end date for names')
Accordingly, there is also a problem with the implementation of processing this model in the form. As an option for a foreign key in the main inlineformset model, but somehow it looks crooked.
Has anyone ever faced a similar problem and what was the solution?
Hello there!
I believe that I would do in this case, is to have the Customer
class as being the current state, where everytime the information changes, the Customer
model changes as well, and the other where the history is kept is “immutable” where you only read from it, and use it as reference where you need it.
One implementation could look like, I’ve simplified the example to be working, you may tweak it as you wish:
class CustomerBase(models.Model):
short_name = models.CharField()
full_name = models.CharField()
class Meta:
abstract = True
class Customer(CustomerBase):
system_name = models.CharField()
class CustomerHistory(CustomerBase):
customer = models.ForeignKey(to=Customer, on_delete=models.CASCADE, related_name="history")
created_at = models.DateTimeField(auto_now_add=True)
# Where you use them, such on invoices, or some other app
# ------------------
class Invoice(models.Model):
# other fields
customer = models.ForeignKey(to=CustomerHistory) # <-- Use the history here
# So, when you're querying invoices, you have the customer history instance
# that has the same fields views.py for example
for invoice in Invoice.objects.all():
print(invoice.customer.short_name, invoice.customer.full_name)
# And when you update the instance, such on a form, you would point the
# update, to the real Customer model, and create a new instance
# of the CustomerHistory
customer = Customer.objects.get(pk=1)
customer.full_name = "Richard Campbell"
customer.short_name = "Richard"
CustomerHistory.objects.create(customer=customer, full_name=customer.full_name, short_name=customer.short_name)
# And when you need to create a invoice, for example, or any other model
# that would point to the CustomerHistory, you can do something like
customer = Customer.objects.get(pk=1)
Invoice.objects.create(customer=customer.history.order_by("created_at").last())
Let me know if this helps, cheers!
1 Like
Thank you for idea!
I was also given the idea to use django_simple_history. I’ll try both options and choose the most suitable one.
The problem is that in my case, the Customer entity has two independent groups of atributes whose change history needs to be stored - official names and tax statuses - and I would like to separate their storage. Need to think.
If you absolutely must ensure that you are tracking all changes to these models, then the only way to do this properly is by adding triggers to the database to save those updates to some type of “history” or “tracking” table. (See Audit trigger 91plus - PostgreSQL wiki among other sources for some ideas.)
(The problem is that there is no way to guarantee within Django that all database operations will be performed through the ORM, leading to potential “gaps” within that history data.)
There is no task to record all changes to the record. In fact, I need a structure in which you can manually enter the data and the date period in which they are relevant, and refer to it in the main record. In some specific ORMs (for example, in the Russian 1C system for creating an ERP) there is such a “periodic register”.
Django Simple History partially solves the problem of saving a modified field (or a group of them), but it is too hand-tied by automatically managing the dates of relevance.
Thanks everyone, I will experiment further.