Skip to content

Commit

Permalink
Merge 3d5e525 into d5d738f
Browse files Browse the repository at this point in the history
  • Loading branch information
treyhunner committed Apr 12, 2013
2 parents d5d738f + 3d5e525 commit 1b46b39
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 86 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.pyc
dist/
*.egg-info/
build/
MANIFEST
.coverage
.tox/
htmlcov/
7 changes: 0 additions & 7 deletions .hgignore

This file was deleted.

31 changes: 31 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
language: python

python:
- "2.6"
- "2.7"

env:
- DJANGO=Django==1.2.7 SOUTH=1
- DJANGO=Django==1.3.7 SOUTH=1
- DJANGO=Django==1.4.5 SOUTH=1
- DJANGO=Django==1.5.1 SOUTH=1
- DJANGO=https://github.com/django/django/tarball/master SOUTH=1
- DJANGO=Django==1.3.7 SOUTH=0

install:
- pip install coverage $DJANGO --use-mirrors
- sh -c "if [ '$SOUTH' = '1' ]; then pip install South==0.7.6; fi"
- sh -c "if [ '$COVERALLS' != '0' ]; then pip install coveralls; fi"

script: coverage run -a --branch --include="simple_history/*" --omit="simple_history/tests/*" setup.py test

matrix:
include:
- python: 2.5
env: DJANGO=Django==1.2.7 SOUTH=1 COVERALLS=0
- python: 2.5
env: DJANGO=Django==1.3.7 SOUTH=1 COVERALLS=0
- python: 2.5
env: DJANGO=Django==1.4.5 SOUTH=1 COVERALLS=0

after_success: coveralls
77 changes: 0 additions & 77 deletions README

This file was deleted.

87 changes: 87 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
==================
django-simple-history
==================

.. image:: https://secure.travis-ci.org/treyhunner/django-simple-history.png?branch=master
:target: http://travis-ci.org/treyhunner/django-simple-history
.. image:: https://coveralls.io/repos/treyhunner/django-simple-history/badge.png?branch=master
:target: https://coveralls.io/r/treyhunner/django-simple-history

django-simple-history is a tool to store state of DB objects on every create/update/delete. It has been tested to work in django 1.X (including 1.2.3 as of 10/25/2010).

Install
-------
Download the tar.gz, extract it and run the following inside the directory:

.. code-block:: bash
$ python setup.py install
Basic usage
-----------
Using this package is _really_ simple; you just have to import HistoricalRecords and create an instance of it on every model you want to historically track.

On your models you need to include the following line at the top:

.. code-block:: python
from simple_history.models import HistoricalRecords
Then in your model class, include the following line:

.. code-block:: python
history = HistoricalRecords()
Then from either the model class or from an instance, you can access history.all() which will give you either every history item of the class, or every history item of the specific instance.

Example
-------
Models:

.. code-block:: python
class Poll(models.Model):
question = models.CharField(max_length = 200)
pub_date = models.DateTimeField('date published')
history = HistoricalRecords()
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
votes = models.IntegerField()
history = HistoricalRecords()
Usage:

.. code-block:: pycon
>>> from poll.models import Poll, Choice
>>> Poll.objects.all()
[]
>>> import datetime
>>> p = Poll(question="what's up?", pub_date=datetime.datetime.now())
>>> p.save()
>>> p
<Poll: Poll object>
>>> p.history.all()
[<HistoricalPoll: Poll object as of 2010-10-25 18:03:29.855689>]
>>> p.pub_date = datetime.datetime(2007,4,1,0,0)
>>> p.save()
>>> p.history.all()
[<HistoricalPoll: Poll object as of 2010-10-25 18:04:13.814128>, <HistoricalPoll: Poll object as of 2010-10-25 18:03:29.855689>]
>>> p.choice_set.create(choice='Not Much', votes=0)
<Choice: Choice object>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: Choice object>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)
>>> c.poll
<Poll: Poll object>
>>> c.history.all()
[<HistoricalChoice: Choice object as of 2010-10-25 18:05:30.160595>]
>>> Choice.history
<simple_history.manager.HistoryManager object at 0x1cc4290>
>>> Choice.history.all()
[<HistoricalChoice: Choice object as of 2010-10-25 18:05:30.160595>, <HistoricalChoice: Choice object as of 2010-10-25 18:05:12.183340>, <HistoricalChoice: Choice object as of 2010-10-25 18:04:59.047351>]
35 changes: 35 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python
import sys
from os.path import abspath, dirname

from django.conf import settings


sys.path.insert(0, abspath(dirname(__file__)))


if not settings.configured:
settings.configure(
INSTALLED_APPS=(
'django.contrib.contenttypes',
'django.contrib.auth',
'simple_history',
'simple_history.tests'
),
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
},
)


def main():
from django.test.simple import DjangoTestSuiteRunner
failures = DjangoTestSuiteRunner(
verbosity=1, interactive=True, failfast=False).run_tests(['tests'])
sys.exit(failures)


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions runtests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
coverage erase
tox
coverage html --include=simple_history/* --omit=simple_history/tests/*
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from distutils.core import setup
from setuptools import setup
import os

# compile the list of packages available, because distutils doesn't have an easy way to do this
Expand Down Expand Up @@ -38,4 +38,6 @@
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
],
)
tests_require=["Django>=1.2"],
test_suite='runtests.main',
)
Empty file.
17 changes: 17 additions & 0 deletions simple_history/tests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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()


class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
votes = models.IntegerField()

history = HistoricalRecords()
98 changes: 98 additions & 0 deletions simple_history/tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from datetime import datetime, timedelta
from django.test import TestCase

from .models import Poll


today = datetime(2021, 1, 1, 10, 0)
tomorrow = today + timedelta(days=1)


class HistoricalRecordsTest(TestCase):

def assertDatetimesEqual(self, time1, time2):
self.assertAlmostEqual(time1, time2, delta=timedelta(seconds=2))

def assertRecordValues(self, record, values_dict):
for key, value in values_dict.items():
self.assertEqual(getattr(record, key), value)

def test_create(self):
p = Poll(question="what's up?", pub_date=today)
p.save()
history = p.history.all()
record, = history
self.assertRecordValues(record, {
'question': "what's up?",
'pub_date': today,
'id': p.id,
'history_type': "+"
})
self.assertDatetimesEqual(record.history_date, datetime.now())

def test_update(self):
Poll.objects.create(question="what's up?", pub_date=today)
p = Poll.objects.get()
p.pub_date = tomorrow
p.save()
history = p.history.all()
update_record, create_record = history
self.assertRecordValues(create_record, {
'question': "what's up?",
'pub_date': today,
'id': p.id,
'history_type': "+"
})
self.assertRecordValues(update_record, {
'question': "what's up?",
'pub_date': tomorrow,
'id': p.id,
'history_type': "~"
})

def test_delete(self):
p = Poll.objects.create(question="what's up?", pub_date=today)
poll_id = p.id
p.delete()
history = Poll.history.all()
delete_record, create_record = history
self.assertRecordValues(create_record, {
'question': "what's up?",
'pub_date': today,
'id': poll_id,
'history_type': "+"
})
self.assertRecordValues(delete_record, {
'question': "what's up?",
'pub_date': today,
'id': poll_id,
'history_type': "-"
})


class HistoryManagerTest(TestCase):
def test_most_recent(self):
poll = Poll.objects.create(question="what's up?", pub_date=today)
poll.question = "how's it going?"
poll.save()
poll.question = "why?"
poll.save()
poll.question = "how?"
most_recent = poll.history.most_recent()
self.assertEqual(most_recent.__class__, Poll)
self.assertEqual(most_recent.question, "why?")

def test_as_of(self):
poll = Poll.objects.create(question="what's up?", pub_date=today)
poll.question = "how's it going?"
poll.save()
poll.question = "why?"
poll.save()
poll.question = "how?"
most_recent = poll.history.most_recent()
self.assertEqual(most_recent.question, "why?")
times = [r.history_date for r in poll.history.all()]
question_as_of = lambda time: poll.history.as_of(time).question
self.assertEqual(question_as_of(times[0]), "why?")
self.assertEqual(question_as_of(times[1]), "how's it going?")
self.assertEqual(question_as_of(times[2]), "what's up?")
Loading

0 comments on commit 1b46b39

Please sign in to comment.