Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude fields #274

Merged
merged 13 commits into from
Jun 12, 2017
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Authors
- Grzegorz Bialy
- Hamish Downer
- James Pulec
- Joao Junior(@joaojunior)
- Joao Pedro Francese
- jofusa
- John Whitlock
Expand Down
26 changes: 26 additions & 0 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,32 @@ You can use the ``table_name`` parameter with both ``HistoricalRecords()`` or

register(Question, table_name='polls_question_history')

Choosing fields to not be stored
--------------------------------

It is possible to use the parameter ``excluded_fields`` to choose which fields
will be stored on every create/update/delete.

For example, if you have the model:

.. code-block:: python

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

And you don't want to store the changes for the field ``pub_date``, it is necessary to update the model to:

.. code-block:: python

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

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

By default, django-simple-history stores the changes for all fields in the model.

Change Reason
-------------

Expand Down
24 changes: 18 additions & 6 deletions simple_history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ class HistoricalRecords(object):
thread = threading.local()

def __init__(self, verbose_name=None, bases=(models.Model,),
user_related_name='+', table_name=None, inherit=False):
user_related_name='+', table_name=None, inherit=False,
excluded_fields=None):
self.user_set_verbose_name = verbose_name
self.user_related_name = user_related_name
self.table_name = table_name
self.inherit = inherit
if excluded_fields is None:
excluded_fields = []
self.excluded_fields = excluded_fields
try:
if isinstance(bases, six.string_types):
raise TypeError
Expand Down Expand Up @@ -134,13 +138,20 @@ def create_history_model(self, model):
return python_2_unicode_compatible(
type(str(name), self.bases, attrs))

def fields_included(self, model):
fields = []
for field in model._meta.fields:
if field.name not in self.excluded_fields:
fields.append(field)
return fields

def copy_fields(self, model):
"""
Creates copies of the model's original fields, returning
a dictionary mapping field name to copied field object.
"""
fields = {}
for field in model._meta.fields:
for field in self.fields_included(model):
field = copy.copy(field)
try:
field.remote_field = copy.copy(field.remote_field)
Expand Down Expand Up @@ -212,7 +223,7 @@ def get_instance(self):
('~', _('Changed')),
('-', _('Deleted')),
)),
'history_object': HistoricalObjectDescriptor(model),
'history_object': HistoricalObjectDescriptor(model, self.fields_included(model)),
'instance': property(get_instance),
'instance_type': model,
'revert_url': revert_url,
Expand Down Expand Up @@ -252,7 +263,7 @@ def create_historical_record(self, instance, history_type):
history_change_reason = getattr(instance, 'changeReason', None)
manager = getattr(instance, self.manager_name)
attrs = {}
for field in instance._meta.fields:
for field in self.fields_included(instance):
attrs[field.attname] = getattr(instance, field.attname)
manager.create(history_date=history_date, history_type=history_type,
history_user=history_user,
Expand Down Expand Up @@ -308,10 +319,11 @@ def convert_auto_field(field):


class HistoricalObjectDescriptor(object):
def __init__(self, model):
def __init__(self, model, fields_included):
self.model = model
self.fields_included = fields_included

def __get__(self, instance, owner):
values = (getattr(instance, f.attname)
for f in self.model._meta.fields)
for f in self.fields_included)
return self.model(*values)
7 changes: 7 additions & 0 deletions simple_history/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ class Poll(models.Model):
history = HistoricalRecords()


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

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


class Temperature(models.Model):
location = models.CharField(max_length=200)
temperature = models.IntegerField()
Expand Down
15 changes: 12 additions & 3 deletions simple_history/tests/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
Country, Document, Employee, ExternalModel1,
ExternalModel3, FileModel, HistoricalChoice,
HistoricalCustomFKError, HistoricalPoll, HistoricalState,
Library, MultiOneToOne, Person, Poll, PollInfo, Province,
Restaurant, SelfFK, Series, SeriesWork, State,
Temperature, UnicodeVerboseName, WaterLevel)
Library, MultiOneToOne, Person, Poll, PollInfo,
PollWithExcludeFields, Province, Restaurant, SelfFK,
Series, SeriesWork, State, Temperature,
UnicodeVerboseName, WaterLevel)

try:
from django.apps import apps
Expand Down Expand Up @@ -334,6 +335,14 @@ def test_foreignkey_primarykey(self):
poll_info = PollInfo(poll=poll)
poll_info.save()

def test_model_with_excluded_fields(self):
p = PollWithExcludeFields(question="what's up?", pub_date=today)
p.save()
history = PollWithExcludeFields.history.all()[0]
all_fields_names = [f.name for f in history._meta.fields]
self.assertIn('question', all_fields_names)
self.assertNotIn('pub_date', all_fields_names)


class CreateHistoryModelTests(unittest.TestCase):

Expand Down