Skip to main content

Models and mixins for recording changes in Django models

Project description

*************
django-record
*************

``django-record`` automatically creates an extra record when an audited
Django model instance has been changed either directly or indirectly.

``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. Also, you are able to access audited model instance via ``recording`` in
your records, which is in effect ``ForeignKey``.

More conveniently, just mixin ``RecordedModelMixin`` to your model and provide
``recording_fields`` and ``auditing_relatives`` as ``RecordModel`` to record
specific model.


Author
======
* `Jun Soo Ha <kuc2477@gmail.com>`_


Compatibility
=============
* Python3 is currently not supported.


Dependencies
============
* ``django-record`` supports `django <https://github.com/django/django>`_ (>= 1.7) or later.
* Requires `faker <https://github.com/joke2k/faker>`_ for tests.


Installation
============
``pip install django-record``


Mixins
------
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**
.. code-block:: python

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))
]

auditing_relatives = ['author']


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
------
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**
.. code-block:: python

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(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


class Vote(models.Model):
article = models.ForeignKey(Article, related_name='votes')
score = models.IntegerField()


# Record Models

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())
]

auditing_relatives = ['user', 'comments', 'votes']

# Uncomment this meta class if you want to audit
# all relative instances to monitor their indirect
# effects on our ``recording_model``.
"""
class RecordMeta:
audit_all_relatives = True
"""
# Note that setting this attribute as True can cause
# performance issue in large scale database.


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



Usage
=====
.. code-block:: python

>>> 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()
>>> assert(a.total_score == r.total_score)

...

>>> count_before = a.total_comment_count
>>> Comment.objects.create(article=a, text='text of comment') # recorder creates first record for created comment and
>>> r = a.records.latest() # a new record for existing article, updating 'total_comment_count'.
>>> 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)

...


Note
====
* **Only primitive types are supported for properties** and **you must
offer appropriate field** for them when you put a tuple of a property
name and it's field in 'recording_fields' for expected recording.

* ``RecordModel`` is also a subclass of ``TimeStampedModel``, so **make sure that
you don't record either 'created' or 'modified' fields.**


=======
Changes
=======

2015-05-17
==========
* Migrated entire recorder implemetation related methods to
``RecordModel`` iteself, rather than putting them on ``__init__``
method of ``RecordModelMetaClass``.

* ``indirect_effect_recorder`` now works as expected, recording
effects both for *related* and *reverse related* instances

* Tests for ``indirect_effect_recorder`` added and all passed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-record-0.1.5.tar.gz (7.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page