Models and mixins for recording changes in Django models
Project description
Compatibility
Python3 is currently not supported.
Dependencies
Installation
pip install django-record
Rationale
Often there are situations where you want to record your properties of your models and where you want to track their changes. Although that recording process can be implemented by handcrafted, ad-hoc signals or overriding save() methods of your models, it’s not a generic way, and it’ll eventually mess up your code base.
django-record automatically creates an snapshot-like extra record when an audited Django model instance has been changed either directly or indirectly, without messing up your code base.
RecordModel will detect any changes in recording_fields of recording_model at it’s post_save time or auditing_relatives’s post_save time and create an new record for it.
You can access records via record manager records in your recorded model instance. You can also access recorded model’s instance via recording, which is in effect ForeignKey. from your records.
More conveniently, just mixin RecordedModelMixin to your model and provide recording_fields and auditing_relatives as RecordModel to record specific model.
Mixins
django-record provides mixins which auto-registers RecordModel for your mixed in model to help your model instance recording.
- RecordedModelMixin
- Attributes
recording_fields (list): A List consists of either to-be-recoreded field names or tuples of a property name and it’s field instance to be saved in database.
auditing_relatives (list): A List of audited relatives. An extra record will be created on every save() calls of these relative instances that indirectly affects the recording instance, along with recording on direct save() calls from recording_model instances.
RecordMeta (class): A class storing meta information for recording. Set audit_all_relatives to audit all relatives of your recorded model’s instance. Note that auditing all relatives can cause a performance issue in large scale database.
Example
from django.db import models from django.db.models import Sum from django_record.models import TimeStampedModel from django_record.mixins import RecordedModelMixin class Article(RecordedModelMixin, TimeStampedModel): author = models.ForeignKey(User, related_name='articles') title = models.CharField(max_length=100) @property def total_comment_count(self): return self.comments.count() @property def total_score(self): return 0 if not self.votes.exists() else \ int(self.votes.aggregate(Sum('score'))['score__sum']) @property def full_name_of_author(self): return self.author.username recording_fields = [ 'title', ('total_comment_count', models.IntegerField()), ('total_score', models.IntegerField()), ('full_name_of_author', models.CharField(max_length=100)) ] class RecordMeta: audit_all_relatives = True class Comment(RecordedModelMixin, TimeStampedModel): article = models.ForeignKey(Article, related_name='comments') text = models.TextField() @property def title_of_article_with_prefix(self): return 'title: ' + self.article.title recording_fields = [ 'text', ('title_of_article_with_prefix', models.CharField(max_length=200)) ] class RecordMeta: audit_all_relatives = True class Vote(models.Model): article = models.ForeignKey(Article, related_name='votes') score = models.IntegerField()
Models
django-record provides models for recording model instances, including RecordModel and TimeStampedModel.
- RecordModel
- Attributes
recording_model (class): A model class to be recorded. An extra record will be created on every changed save() calls of it’s instance or audited relative’s save() calls.
recording_fields (list): A List consists of either to-be-recoreded field names or tuples of a property name and it’s field instance to be saved in database.
auditing_relatives (list): A List of audited relatives. An extra record will be created on every save() calls of these relative instances that indirectly affects the recording instance, along with recording on direct save() calls from recording_model instances.
RecordMeta (class): A class storing meta information for recording. Set audit_all_relatives to audit all relatives of your recorded model’s instance. Note that auditing all relatives can cause a performance issue in large scale database.
Example
from django.db import models from django.db.models import Sum from django_record.models import TimeStampedModel from django_record.models import RecordModel # Models class Article(TimeStampedModel): author = models.ForeignKey(User, related_name='articles') title = models.CharField(max_length=100) @property def total_comment_count(self): return self.comments.count() @property def total_score(self): return 0 if not self.votes.exists() else \ int(self.votes.aggregate(Sum('score'))['score__sum']) @property def full_name_of_author(self): return self.author.username class Comment(TimeStampedModel): article = models.ForeignKey(Article, related_name='comments') text = models.TextField() @property def title_of_article_with_prefix(self): return 'title: ' + self.article.title class Vote(models.Model): article = models.ForeignKey(Article, related_name='votes') score = models.IntegerField() # Record Model class ArticleRecord(RecordModel): recording_model = Article recording_fields = [ 'title', ('full_name_of_author', models.CharField(max_length=50)), ('total_comment_count', models.IntegerField()), ('total_score', models.IntegerField()) ] class RecordMeta: auditing_all_relatives = True class CommentRecord(RecordModel): recording_model = Comment recording_fields = [ 'text', ('title_of_article_with_prefix', models.CharField(max_length=200)) ] class RecordMeta: audit_all_relatives = True
Note
Recursive auditing is not currently supported. Indirect effect only those from direct relatives will be detected and recorded.
Only primitive types are supported for properties. You must offer appropriate django field for them.
RecordModel is also a subclass of TimeStampedModel, so make sure that you don’t record either ‘created’ or ‘modified’ fields.
Usage
from django.db import models
from django.db.models import Sum
from django_record.models import TimeStampedModel
from django_record.models import RecordModel
# Models
class Article(RecordedModelMixin, TimeStampedModel):
author = models.ForeignKey(User, related_name='articles')
title = models.CharField(max_length=100)
@property
def total_comment_count(self):
return self.comments.count()
@property
def total_score(self):
return 0 if not self.votes.exists() else \
int(self.votes.aggregate(Sum('score'))['score__sum'])
@property
def full_name_of_author(self):
return self.author.username
recording_fields = [
'title',
('full_name_of_author', models.CharField(max_length=50)),
('total_comment_count', models.IntegerField()),
('total_score', models.IntegerField()),
]
auditing_relatives = [
'user', 'comments', 'votes'
]
class Comment(RecordedModelMixin, TimeStampedModel):
article = models.ForeignKey(Article, related_name='comments')
text = models.TextField()
@property
def title_of_article_with_prefix(self):
return 'title: ' + self.article.title
recording_fields = [
'article',
('title_of_article_with_prefix', models.CharField(100)),
]
auditing_relatives = [
'article',
]
class Vote(models.Model):
article = models.ForeignKey(Article, related_name='votes')
score = models.IntegerField()
>>> a = Article.objects.first()
>>> v = a.votes.first()
>>>
>>> v.score = 999
>>> v.save() # recorder creates a new article record, updating 'total_score'.
>>>
>>> r = a.records.latest()
>>> a.total_score == r.total_score
>>> True
...
>>> count_before = a.total_comment_count
>>>
>>> Comment.objects.create(article=a, text='text of comment') # recorder creates first record for created comment and
>>> # a new record for existing article, updating 'total_comment_count'.
>>> r = a.records.latest()
>>> r.total_comment_count == count_before + 1
>>> True
...
>>> records_before_yesterday = d.records.filter(created__lte=yesterday) # you can filter records by created time.
>>> records_of_today = d.records.filter(created__gte=today)
...
Changes
06.13.2015
Following shortcut properties added to recording_model. All of properties below are ordinary django querysets.
records_in_hour: Records created in last recent hour.
records_in_day: Records created today.
records_in_week: Records created in this week.
records_in_month: Records created in this month.
records_in_year: Records created in this year.
Following shortcut properties providing resampled records has been added to recording_model. All of properties below are ordinary django querysets.
resampled_records_in_hour: Records created in last recent hour, resampled with minutes.
resampled_records_in_day: Records created today, resampled with hours.
resampled_records_in_week: Records created in this week, resamped with days.
resampled_records_in_month: Records created in this month, resampled with days.
resamped_records_in_year: Records created in this year, resampled with months.
05.18.2015
RecordedModelMixin added.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.