Общие вопросы¶
Массовое создание и обновление наборов запросов¶
django-simple-history
функционирует, сохраняя историю с помощью сигнала post_save
каждый раз, когда сохраняется объект с историей. Однако для некоторых массовых операций, таких как bulk_create, bulk_update и queryset updates, сигналы не посылаются, и история не сохраняется автоматически. Однако django-simple-history
предоставляет служебные функции для обхода этой проблемы.
Bulk Создание модели с историей¶
Начиная с версии django-simple-history
2.2.0, мы можем использовать служебную функцию bulk_create_with_history
для массового создания объектов с сохранением их истории:
>>> from simple_history.utils import bulk_create_with_history
>>> from simple_history.tests.models import Poll
>>> from django.utils.timezone import now
>>>
>>> data = [Poll(id=x, question='Question ' + str(x), pub_date=now()) for x in range(1000)]
>>> objs = bulk_create_with_history(data, Poll, batch_size=500)
>>> Poll.objects.count()
1000
>>> Poll.history.count()
1000
Если вы хотите указать причину изменения или пользователя истории для каждой записи при массовом создании, вы можете добавить _change_reason or _history_user на каждом экземпляре:
>>> for poll in data:
poll._change_reason = 'reason'
poll._history_user = my_user
>>> objs = bulk_create_with_history(data, Poll, batch_size=500)
>>> Poll.history.get(id=data[0].id).history_change_reason
'reason'
Вы также можете указать пользователя по умолчанию или причину изменения по умолчанию, ответственную за изменение (_history_user and _change_reason имеет приоритет).
>>> user = User.objects.create_user("tester", "tester@example.com")
>>> objs = bulk_create_with_history(data, Poll, batch_size=500, default_user=user)
>>> Poll.history.get(id=data[0].id).history_user == user
True
Массовое обновление модели с историей (новое)¶
Массовое обновление было введено в Django 2.2. Мы можем использовать функцию bulk_update_with_history
для массового обновления объектов с помощью функции Django bulk_update
, сохраняя при этом историю объектов:
>>> from simple_history.utils import bulk_create_with_history
>>> from simple_history.tests.models import Poll
>>> from django.utils.timezone import now
>>>
>>> data = [Poll(id=x, question='Question ' + str(x), pub_date=now()) for x in range(1000)]
>>> objs = bulk_create_with_history(data, Poll, batch_size=500)
>>> for obj in objs: obj.question = 'Duplicate Questions'
>>> bulk_update_with_history(objs, Poll, ['question'], batch_size=500)
>>> Poll.objects.first().question
'Duplicate Question'
Обновление QuerySet с помощью истории (обновлено в Django 2.2)¶
В отличие от bulk_create
, queryset updates выполняет SQL-запрос на обновление кверисета и никогда не возвращает фактические обновленные объекты (которые были бы необходимы для вставок в историческую таблицу). Таким образом, мы говорим, что обновления queryset не сохранят историю (поскольку не посылается сигнал post_save
). Как говорится в документации Django:
If you want to update a bunch of records for a model that has a custom
``save()`` method, loop over them and call ``save()``, like this:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
Примечание: Django 2.2 теперь позволяет bulk_update
. Сигналы pre_save
или post_save
по-прежнему не посылаются.
Отслеживание пользовательских пользователей¶
fields.E300
:ERRORS: custom_user.HistoricalCustomUser.history_user: (fields.E300) Field defines a relation with model 'custom_user.CustomUser', which is either not installed, or is abstract.
Используйте
register()
для отслеживания изменений в пользовательской модели пользователя вместо того, чтобы устанавливатьHistoricalRecords
непосредственно на модели.Причина этого в том, что, к сожалению,
HistoricalRecords
не может быть установлен непосредственно на подмененную пользовательскую модель из-за внешнего ключа user для отслеживания пользователя, вносящего изменения.
Использование django-webtest с Middleware¶
При использовании django-webtest для тестирования вашего Django проекта с промежуточным программным обеспечением django-simple-history, вы можете столкнуться с ошибкой, подобной следующей:
django.db.utils.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`test_env`.`core_historicaladdress`, CONSTRAINT `core_historicaladdress_history_user_id_0f2bed02_fk_user_user_id` FOREIGN KEY (`history_user_id`) REFERENCES `user_user` (`id`))')
Эта ошибка возникает из-за того, что django-webtest
устанавливает DEBUG_PROPAGATE_EXCEPTIONS
в true, не позволяя промежуточному ПО очистить запрос. Чтобы решить эту проблему, добавьте следующий код в любой метод clean_environment
или tearDown
, который вы используете:
from simple_history.middleware import HistoricalRecords
if hasattr(HistoricalRecords.thread, 'request'):
del HistoricalRecords.thread.request
Использование выражений F()¶
Выражения F()
, как описано здесь, не работают на моделях, имеющих историю. Простая история вставляет новую запись в историческую таблицу для любой обновляемой модели. Однако выражения F()
работают только для обновлений. Таким образом, когда выражение F()
используется в модели с исторической таблицей, историческая модель пытается вставить запись с помощью выражения F()
и вызывает ошибку ValueError
.
Зарезервированные имена полей¶
Для каждой базовой модели, история которой отслеживается с помощью django-simple-history
, создается соответствующая историческая модель. Таким образом, если мы имеем:
class BaseModel(models.Model):
history = HistoricalRecords()
Также создается модель Django под названием HistoricalBaseModel
со всеми полями из BaseModel
, плюс несколько дополнительных полей и методов, которые есть во всех исторических моделях.
Поскольку эти поля и методы присутствуют во всех исторических моделях, любые имена полей и методов в базовой модели, которые не совпадают с этими именами, не будут присутствовать в исторической модели (и, следовательно, не будут отслеживаться). Ниже приведены зарезервированные исторические имена полей и методов:
history_id
history_date
history_change_reason
history_type
history_object
history_user
history_user_id
instance
instance_type
next_record
prev_record
revert_url
__str__
Так что если у нас есть:
class BaseModel(models.Model):
instance = models.CharField(max_length=255)
history = HistoricalRecords()
поле instance
фактически не будет отслеживаться в таблице истории, поскольку оно находится в зарезервированном наборе терминов.
Наследование нескольких таблиц¶
django-simple-history
поддерживает отслеживание истории на моделях, использующих многотабличное наследование, таких как:
class ParentModel(models.Model):
parent_field = models.CharField(max_length=255)
history = HistoricalRecords()
class ChildModel(ParentModel):
child_field = models.CharField(max_length=255)
history = HistoricalRecords()
Несколько замечаний:
В дочерней модели экземпляр
HistoricalRecords
не наследуется от родительской модели. Это означает, что вы можете выбрать отслеживание изменений только в родительской модели, только в дочерней модели или в обеих.Таблица истории дочерней модели содержит все поля из дочерней модели, а также все поля из родительской модели.
Обновление дочернего экземпляра обновляет только таблицу истории дочернего экземпляра, но не таблицу истории родительского экземпляра.
Использование с django-modeltranslation¶
Если у вас установлен django-modeltranslation
, вам необходимо использовать метод register()
для моделирования перевода, как описано here.
Указание на модель¶
Иногда вам нужно указать на модель исторических записей. Примером могут служить общие представления Django или сериализаторы Django REST framework. Вы можете попасть туда через менеджер HistoricalRecords, который вы определили в своей модели. Согласно нашему примеру:
class PollHistoryListView(ListView): # or PollHistorySerializer(ModelSerializer):
class Meta:
model = Poll.history.model
# ...
Работа с конвейерами BitBucket¶
При использовании BitBucket Pipelines для тестирования вашего Django проекта с промежуточным ПО django-simple-history вы столкнетесь с ошибкой, связанной с отсутствием миграций, относящихся к исторической модели User из приложения auth. Это происходит потому, что файл миграции не хранится ни в вашем проекте, ни в django-simple-history. Чтобы обойти эту ошибку, вам нужно добавить `python manage.py makemigrations auth`
шаг в ваш YML файл перед запуском тестов.