django-simple-history icon indicating copy to clipboard operation
django-simple-history copied to clipboard

How to specify table name format in base model depending on child model's overriden table name ?

Open avinashs2401 opened this issue 1 year ago • 9 comments

Hi, I have a Base model like this, which many models would inherit from,

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    last_updated_date = models.DateTimeField(auto_now=True)
    history = HistoricalRecords(inherit=True)

I have many child models inheriting from this BaseModel and I have overriden the db table names to be with underscores. Eg.

class ChildModel(BaseModel):
   title = models.CharField(max_length=255, blank=False, null=False)
   class Meta:
       db_table = "child_model"

I want History table to be created as appname_historical_child_model. But right now its getting created as appname_historicalchildmodel

Any suggestions on how to achieve this?

avinashs2401 avatar May 04 '23 06:05 avinashs2401

@avinashs2401 If you manage it in the base model only, you can override the HistoricalRecords class and specify a custom attribute DEFAULT_MODEL_NAME_PREFIX. Otherwise you can mention the history field specifically each model with table_name attribute.

singhravi1 avatar May 05 '23 17:05 singhravi1

@avinashs2401 As per my understanding, there is no way to specify custom table name for history models of inherited child models. You can only change the history table name where HistoricalRecords instance is created (i.e. in the Base Class).

Overriding db_table option of Meta will only update table name for the model itself ( this is done by django: Meta db_table option). For the corresponding, history model db table name will be derived from the history model name.

muneeb706 avatar Jun 22 '23 14:06 muneeb706

@muneeb706 You could override the HistoricalRecords's create_history_model method and add a logic using the model name being generated, to change the attrs["Meta"].db_table as per your requirements

singhravi1 avatar Jun 22 '23 17:06 singhravi1

@singhravi1

Do you mean, something like this ?

from django.db import models
from simple_history.models import HistoricalRecords


class CustomHistoricalRecords(HistoricalRecords):
    def create_history_model(self, model, inherited):
        history_model = super().create_history_model(model, inherited)
        history_model._meta.db_table = "historical_" + model._meta.db_table
        return history_model

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    last_updated_date = models.DateTimeField(auto_now=True)
    history = CustomHistoricalRecords(inherit=True)
  
class ChildModel(BaseModel):
    title = models.CharField(max_length=255, blank=False, null=False)
    
    class Meta:
       db_table = "child_model"

muneeb706 avatar Jun 23 '23 05:06 muneeb706

@singhravi1

Do you mean, something like this ?

from django.db import models
from simple_history.models import HistoricalRecords


class CustomHistoricalRecords(HistoricalRecords):
    def create_history_model(self, model, inherited):
        history_model = super().create_history_model(model, inherited)
        history_model._meta.db_table = "historical_" + model._meta.db_table
        return history_model

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    last_updated_date = models.DateTimeField(auto_now=True)
    history = CustomHistoricalRecords(inherit=True)
  
class ChildModel(BaseModel):
    title = models.CharField(max_length=255, blank=False, null=False)
    
    class Meta:
       db_table = "child_model"

Please, note, the meta class db_table attribute will not be updated.

muneeb706 avatar Jun 23 '23 06:06 muneeb706

@singhravi1 Do you mean, something like this ?

from django.db import models
from simple_history.models import HistoricalRecords


class CustomHistoricalRecords(HistoricalRecords):
    def create_history_model(self, model, inherited):
        history_model = super().create_history_model(model, inherited)
        history_model._meta.db_table = "historical_" + model._meta.db_table
        return history_model

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    last_updated_date = models.DateTimeField(auto_now=True)
    history = CustomHistoricalRecords(inherit=True)
  
class ChildModel(BaseModel):
    title = models.CharField(max_length=255, blank=False, null=False)
    
    class Meta:
       db_table = "child_model"

Please, note, the meta class db_table attribute will not be updated.

I don't think we can access attrs attribute as it is defined inside the create_history_model method.

muneeb706 avatar Jun 23 '23 06:06 muneeb706

@muneeb706 I'm thinking of something like the following,

class CustomHistoricalRecords(HistoricalRecords):
    def create_history_model(self, model, inherited):
        """
        Creates a historical model to associate with the model provided.
        """
        if not self.table_name:
            self.table_name = f"{model._meta.app_label}_{re.sub(r'(?<!^)(?=[A-Z])', '_', self.get_history_model_name(model)).lower()}"
        return super().create_history_model(model, inherited)

Or maybe you directly refer to the child model's db_table Meta option you've specified using model._meta.db_table.

I've not tested this but please confirm if it works.

singhravi1 avatar Jun 23 '23 09:06 singhravi1

@singhravi1 If you update self.table_name then the db_table will be updated. However, in your code snippet, you have added the condition. if not self.table_name which will be True only when the table_name is not set in the base model HistoricalRecords instance.

However, you can make it work by updating self.table_name property as in HistoricalRecords.create_history_model method attrs["Meta"].db_table = self.table_name. This may not be the best way to do it, as according to current implementation self.table_name is supposed to point to value that is set when HistoricalRecords instance is created (which is in base class in this scenario).

muneeb706 avatar Jun 24 '23 02:06 muneeb706

Hey @muneeb706 , The check can be changed based on the logic that mentioning table_name with inherit=True would not make sense. Not the best way but till the time this feature is included in the package, users such as the author can extend it to have what they require. The following updated code should work for the problem in hand,

class CustomHistoricalRecords(HistoricalRecords):
    def create_history_model(self, model, inherited):
        if self.inherit:
            self.table_name = f"{model._meta.app_label}_{re.sub(r'(?<!^)(?=[A-Z])', '_', self.get_history_model_name(model)).lower()}"
        return super().create_history_model(model, inherited)

singhravi1 avatar Jun 24 '23 18:06 singhravi1