Historical Model Customizations

Пользовательский history_id

По умолчанию историческая таблица модели будет использовать AutoField для history_id (первичный ключ исторической таблицы). Однако вы можете указать другой тип поля для history_id, передав другое поле в параметр history_id_field.

В приведенном ниже примере вместо UUIDField используется AutoField:

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    history = HistoricalRecords(
        history_id_field=models.UUIDField(default=uuid.uuid4)
    )

Поскольку использование UUIDField для history_id является распространенным случаем, существует параметр SIMPLE_HISTORY_HISTORY_ID_USE_UUID, который будет устанавливать все history_id в UUID. Установите это с помощью следующей строки в вашем settings.py файле:

SIMPLE_HISTORY_HISTORY_ID_USE_UUID = True

Эта настройка может быть отменена с помощью параметра history_id_field на основе каждой модели.

Вы можете использовать параметр history_id_field как с HistoricalRecords(), так и с register(), чтобы изменить это поведение.

Примечание: независимо от того, какой тип поля вы укажете в качестве поля history_id, это поле будет автоматически устанавливать primary_key=True и editable=False.

Пользовательский history_date

Вы можете установить пользовательский атрибут history_date для исторической записи, определив свойство _history_date в вашей модели. Это полезно, если вы хотите добавить в модель версии, которые произошли до текущей версии модели, например, при пакетном импорте исторических данных. Содержимое свойства _history_date должно быть datetime-объектом, но установка значения свойства на DateTimeField, которое уже определено в модели, тоже будет работать.

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    changed_by = models.ForeignKey('auth.User')
    history = HistoricalRecords()
    __history_date = None

    @property
    def _history_date(self):
        return self.__history_date

    @_history_date.setter
    def _history_date(self, value):
        self.__history_date = value
from datetime import datetime
from models import Poll

my_poll = Poll(question="what's up?")
my_poll._history_date = datetime.now()
my_poll.save()

Имя пользовательской таблицы истории

По умолчанию имя таблицы для исторических моделей соответствует соглашению Django и просто добавляет historical перед именем модели. Например, если имя вашего приложения polls, а имя модели Question, то имя таблицы будет polls_historicalquestion.

Вы можете использовать параметр table_name как с HistoricalRecords(), так и с register(), чтобы изменить это поведение.

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    history = HistoricalRecords(table_name='polls_question_history')
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

register(Question, table_name='polls_question_history')

Пользовательское название модели

По умолчанию историческая модель называется „Historical“ + имя модели. Например, исторические записи для Choice называются HistoricalChoice. Пользователи могут указать пользовательское имя модели через конструктор на HistoricalRecords. Обычно это делается для того, чтобы избежать конфликта имен, если пользователь уже определил модель с именем „Historical“ + имя модели.

Эта функция предоставляет возможность переопределить имя модели по умолчанию, используемое для сгенерированной модели истории.

Чтобы настроить модели истории на использование другого имени для класса модели истории, используйте опцию custom_model_name. Значение этой опции может быть строкой или вызываемым. Простая строка заменяет имя по умолчанию „Historical“ + имя модели на заданную строку. Ниже показан наиболее простой пример использования простой строки:

class ModelNameExample(models.Model):
    history = HistoricalRecords(
        custom_model_name='SimpleHistoricalModelNameExample'
    )

Если вы используете базовый класс для своих моделей и хотите применить изменение имени для исторической модели для всех моделей, использующих базовый класс, то можно использовать вызываемый класс. В вызываемую переменную передается имя модели, для которой будет создана историческая модель. В качестве примера использования механизма callable ниже приводится изменение префикса по умолчанию Historical на Audit:

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords(custom_model_name=lambda x:f'Audit{x}')

class Opinion(models.Model):
    opinion = models.CharField(max_length=2000)

register(Opinion, custom_model_name=lambda x:f'Audit{x}')

В результате имена классов истории будут AuditPoll и AuditOpinion. Если приложение, в котором определены модели, является yoda, то соответствующие имена таблиц истории будут yoda_auditpoll и yoda_auditopinion.

ВАЖНО: Установка custom_model_name в lambda x:f“{x}“ не допускается.

Если они одинаковы, будет выдана ошибка и не будет создана модель истории.

TextField as history_change_reason

Объект HistoricalRecords можно настроить так, чтобы он принимал поле модели TextField для сохранения истории_изменений_причины либо через настройки, либо через конструктор модели. Обычно это используется для поддержки больших историй изменений модели для поддержки функций, подобных журналу изменений.

SIMPLE_HISTORY_HISTORY_CHANGE_REASON_USE_TEXT_FIELD=True

или

class TextFieldExample(models.Model):
    greeting = models.CharField(max_length=100)
    history = HistoricalRecords(
        history_change_reason_field=models.TextField(null=True)
    )

Изменить базовый класс моделей HistoricalRecord

Чтобы изменить автоматически генерируемый базовый класс модели HistoricalRecord с models.Model, передайте в списке абстрактный класс на bases.

class RoutableModel(models.Model):
    class Meta:
        abstract = True


class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    changed_by = models.ForeignKey('auth.User')
    history = HistoricalRecords(bases=[RoutableModel])

Исключенные поля

Можно использовать параметр excluded_fields, чтобы выбрать, какие поля будут сохраняться при каждом создании/обновлении/удалении.

Например, если у вас есть модель:

class PollWithExcludeFields(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

И вы не хотите хранить изменения для поля pub_date, необходимо обновить модель до:

class PollWithExcludeFields(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    history = HistoricalRecords(excluded_fields=['pub_date'])

По умолчанию django-simple-history хранит изменения для всех полей в модели.

Добавление дополнительных полей в исторические модели

Иногда полезно иметь возможность добавлять в исторические модели дополнительные поля, которые не существуют в исходной модели. Это возможно путем комбинирования функциональности bases с сигналом pre_create_historical_record.

# in models.py
class IPAddressHistoricalModel(models.Model):
    """
    Abstract model for history models tracking the IP address.
    """
    ip_address = models.GenericIPAddressField(_('IP address'))

    class Meta:
        abstract = True


class PollWithExtraFields(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    history = HistoricalRecords(bases=[IPAddressHistoricalModel,])
# define your signal handler/callback anywhere outside of models.py
def add_history_ip_address(sender, **kwargs):
    history_instance = kwargs['history_instance']
    # thread.request for use only when the simple_history middleware is on and enabled
    history_instance.ip_address = HistoricalRecords.thread.request.META['REMOTE_ADDR']
# in apps.py
class TestsConfig(AppConfig):
    def ready(self):
        from simple_history.tests.models \
            import HistoricalPollWithExtraFields

        pre_create_historical_record.connect(
            add_history_ip_address,
            sender=HistoricalPollWithExtraFields
        )

Более подробную информацию о сигналах в django-simple-history можно найти в Сигналы.

Причина изменения

Причина изменения - это сообщение, объясняющее, почему было произведено изменение в экземпляре. Оно хранится в поле history_change_reason и его значение по умолчанию None.

По умолчанию, django-simple-history получает причину изменения в поле _change_reason экземпляра. Также, есть возможность передавать _change_reason явно. Для этого после сохранения или удаления в экземпляре необходимо вызвать функцию utils.update_change_reason. Первым аргументом этой функции является экземпляр, а вторым - сообщение, представляющее причину изменения.

Например, для модели:

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords()

Вы можете создать экземпляр с неявной причиной изменения.

poll = Poll(question='Question 1')
poll._change_reason = 'Add a question'
poll.save()

Или вы можете передать причину изменения в явном виде:

from simple_history.utils import update_change_reason

poll = Poll(question='Question 1')
poll.save()
update_change_reason(poll, 'Add a question')

Удаление исторической записи

В некоторых обстоятельствах вы можете захотеть удалить все исторические записи при удалении основной записи. Этого можно добиться, установив значение cascade_delete_history=True.

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords(cascade_delete_history=True)

Разрешить наследование отслеживания

По умолчанию отслеживание истории добавляется только для модели, которая передана в register() или имеет дескриптор HistoricalRecords. Передав inherit=True любому из способов регистрации, вы можете изменить это поведение так, что любая дочерняя модель, наследующая от нее, также будет иметь историческое отслеживание. Однако будьте осторожны, в случаях, когда модель может быть отслежена более одного раза, будет выдано предупреждение MultipleRegistrationsError.

from django.contrib.auth.models import User
from django.db import models
from simple_history import register
from simple_history.models import HistoricalRecords

# register() example
register(User, inherit=True)

# HistoricalRecords example
class Poll(models.Model):
    history = HistoricalRecords(inherit=True)

Оба значения User и Poll в приведенном примере приведут к тому, что любая модель, наследующая от них, также будет иметь историческое отслеживание.

Модель истории в различных приложениях

По умолчанию app_label для модели истории совпадает с базовой моделью. В некоторых обстоятельствах вы можете захотеть, чтобы модели истории принадлежали другому приложению. Это позволит создать модели истории в базе данных, отличной от базовой модели, используя функцию маршрутизации базы данных на основе метки app_label. Чтобы настроить модели истории в другом приложении, добавьте следующее в инстанцию HistoricalRecords или вызов записи: app="SomeAppName".

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords(app="SomeAppName")

class Opinion(models.Model):
    opinion = models.CharField(max_length=2000)

register(Opinion, app="SomeAppName")

FileField как CharField

По умолчанию FileField в базовой модели становится TextField в модели истории. Это исторический выбор, который django-simple-history сохраняет для обратной совместимости; правильнее будет, если FileField будет преобразовано в CharField. Чтобы принять новое поведение, установите следующую строку в вашем файле settings.py:

SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD = True
Вернуться на верх