From c1cca4525dc218e759b9a43d15a417dbd845d76c Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Thu, 11 Apr 2013 19:18:13 -0700 Subject: [PATCH 1/5] Add test files --- runtests.py | 34 ++++++++++++++++ setup.py | 6 ++- simple_history/tests/__init__.py | 0 simple_history/tests/models.py | 17 ++++++++ simple_history/tests/tests.py | 68 ++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100755 runtests.py create mode 100644 simple_history/tests/__init__.py create mode 100644 simple_history/tests/models.py create mode 100644 simple_history/tests/tests.py diff --git a/runtests.py b/runtests.py new file mode 100755 index 000000000..bfe6ad99a --- /dev/null +++ b/runtests.py @@ -0,0 +1,34 @@ +#!/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', + '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() diff --git a/setup.py b/setup.py index 25fc16118..949a63758 100644 --- a/setup.py +++ b/setup.py @@ -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 @@ -36,4 +36,6 @@ "Development Status :: 5 - Production/Stable", "Framework :: Django", ], - ) + tests_require=["Django>=1.2"], + test_suite='runtests.main', +) diff --git a/simple_history/tests/__init__.py b/simple_history/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py new file mode 100644 index 000000000..59e61c142 --- /dev/null +++ b/simple_history/tests/models.py @@ -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() diff --git a/simple_history/tests/tests.py b/simple_history/tests/tests.py new file mode 100644 index 000000000..ad50e9fb7 --- /dev/null +++ b/simple_history/tests/tests.py @@ -0,0 +1,68 @@ +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': "+" + }) + + 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) + p.delete() + history = p.history.all() + delete_record, create_record = history + self.assertRecordValues(create_record, { + 'question': "what's up?", + 'pub_date': today, + 'id': p.id, + 'history_type': "+" + }) + self.assertRecordValues(delete_record, { + 'question': "what's up?", + 'pub_date': today, + 'id': p.id, + 'history_type': "-" + }) From 6c21a0273453e2aa607d490718bc865442084972 Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Thu, 11 Apr 2013 19:18:49 -0700 Subject: [PATCH 2/5] Add travis and tox files --- .travis.yml | 30 ++++++++++++++++++++++++++++ runtests.sh | 4 ++++ tox.ini | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 .travis.yml create mode 100755 runtests.sh create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..812d7af9a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: python + +python: + - "2.5" + - "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 $DJANGO --use-mirrors + - pip install coverage coveralls --use-mirrors + - sh -c "if [ '$SOUTH' = '1' ]; then pip install South==0.7.6; fi" + +script: coverage run -a --branch --include="simple_history/*" --omit="simple_history/tests/*" setup.py test + +matrix: + exclude: + - python: 2.5 + env: DJANGO=Django==1.5.1 SOUTH=1 + - python: 2.5 + env: DJANGO=https://github.com/django/django/tarball/master SOUTH=1 + +after_success: coveralls diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 000000000..4edc36033 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,4 @@ +#!/bin/sh +coverage erase +tox +coverage html --include=simple_history/* --omit=simple_history/tests/* diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..82a5e9329 --- /dev/null +++ b/tox.ini @@ -0,0 +1,57 @@ +[tox] +envlist=py25-1.2,py25-1.3,py26-1.4,py26,py26-trunk,py27,py27-trunk,py27-1.5-nosouth + +[testenv] +deps= + django==1.5.1 + South==0.7.6 + coverage==3.6 +commands=coverage run -a --branch setup.py test + +[testenv:py25-1.2] +basepython=python2.5 +deps= + django==1.2.7 + South==0.7.6 + coverage==3.6 + +[testenv:py25-1.3] +basepython=python2.5 +deps= + django==1.3.7 + South==0.7.6 + coverage==3.6 + +[testenv:py26-1.4] +basepython=python2.6 +deps= + django==1.4.5 + South==0.7.6 + coverage==3.6 + +[testenv:py26] +basepython=python2.6 +deps= + django==1.5.1 + South==0.7.6 + coverage==3.6 + +[testenv:py26-trunk] +basepython=python2.6 +deps= + https://github.com/django/django/tarball/master + South==0.7.6 + coverage==3.6 + +[testenv:py27-trunk] +basepython=python2.7 +deps= + https://github.com/django/django/tarball/master + South==0.7.6 + coverage==3.6 + +[testenv:py27-1.5-nosouth] +basepython=python2.7 +deps= + django==1.5.0 + coverage==3.6 From 046fe72b1700d07ce2b44ca93d1a783677c4adab Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Thu, 11 Apr 2013 19:27:14 -0700 Subject: [PATCH 3/5] Rename README file to README.rst --- README => README.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.rst (100%) diff --git a/README b/README.rst similarity index 100% rename from README rename to README.rst From 1c012e2f17faa1c1c26c4b3ac86e189ac829bb22 Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Thu, 11 Apr 2013 19:28:24 -0700 Subject: [PATCH 4/5] Add travis and coveralls badges to README --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 3a12cfbf2..05bf1035c 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,12 @@ +================== +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 == From 9c01171690ec654d31556110001b9592669ee9e7 Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Thu, 11 Apr 2013 19:34:30 -0700 Subject: [PATCH 5/5] Improve reStructuredText in README --- README.rst | 125 +++++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/README.rst b/README.rst index 05bf1035c..d45c679b3 100644 --- a/README.rst +++ b/README.rst @@ -9,78 +9,79 @@ 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 == +Install +------- Download the tar.gz, extract it and run the following inside the directory: - python setup.py install -== Basic usage == +.. 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: - from simple_history.models import HistoricalRecords -Then in your model class, include the following line: - 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. +.. code-block:: python -== Example == -class Poll(models.Model): - question = models.CharField(max_length = 200) - pub_date = models.DateTimeField('date published') + from simple_history.models import HistoricalRecords - history = HistoricalRecords() +Then in your model class, include the following line: -class Choice(models.Model): - poll = models.ForeignKey(Poll) - choice = models.CharField(max_length=200) - votes = models.IntegerField() +.. code-block:: python history = HistoricalRecords() - - -$ ./manage.py shell -In [2]: from poll.models import Poll, Choice - -In [3]: Poll.objects.all() -Out[3]: [] - -In [4]: import datetime - -In [5]: p = Poll(question="what's up?", pub_date=datetime.datetime.now()) -In [6]: p.save() - -In [7]: p -Out[7]: - -In [9]: p.history.all() -Out[9]: [] - -In [10]: p.pub_date = datetime.datetime(2007,4,1,0,0) - -In [11]: p.save() - -In [13]: p.history.all() -Out[13]: [, ] - -In [14]: p.choice_set.create(choice='Not Much', votes=0) -Out[14]: - -In [15]: p.choice_set.create(choice='The sky', votes=0) -Out[15]: - -In [16]: c = p.choice_set.create(choice='Just hacking again', votes=0) - -In [17]: c.poll -Out[17]: - -In [19]: c.history.all() -Out[19]: [] - -In [20]: Choice.history -Out[20]: - -In [21]: Choice.history.all() -Out[21]: [, , ] +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 + + >>> p.history.all() + [] + >>> p.pub_date = datetime.datetime(2007,4,1,0,0) + >>> p.save() + >>> p.history.all() + [, ] + >>> p.choice_set.create(choice='Not Much', votes=0) + + >>> p.choice_set.create(choice='The sky', votes=0) + + >>> c = p.choice_set.create(choice='Just hacking again', votes=0) + >>> c.poll + + >>> c.history.all() + [] + >>> Choice.history + + >>> Choice.history.all() + [, , ]