pax_global_header 0000666 0000000 0000000 00000000064 12236676246 0014530 g ustar 00root root 0000000 0000000 52 comment=34b88d0be6e1de3ca7054c7f377d5c343aa258d2
django-reversion-release-1.8/ 0000775 0000000 0000000 00000000000 12236676246 0016272 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/.gitignore 0000664 0000000 0000000 00000000173 12236676246 0020263 0 ustar 00root root 0000000 0000000 .DS_Store
*.db
.project
.pydevproject
.settings
*.pyc
*.pyo
dist
build
MANIFEST
src/django_reversion.egg-info/
docs/_build
django-reversion-release-1.8/.travis.yml 0000664 0000000 0000000 00000000437 12236676246 0020407 0 ustar 00root root 0000000 0000000 language: python
python:
- 2.7
- 3.3
env:
- DJANGO=django==1.5.2
- DJANGO=https://github.com/django/django/tarball/stable/1.6.x
install:
- pip install $DJANGO
- python setup.py -q install
script: python src/test_project/manage.py test reversion
notifications:
email: false
django-reversion-release-1.8/CHANGELOG.rst 0000664 0000000 0000000 00000010442 12236676246 0020314 0 ustar 00root root 0000000 0000000 django-reversion changelog
==========================
1.8.0
-----
- Django 1.6 compatibility (@niwibe & @meshy).
- Using bulk_create to speed up revision creation.
- Including docs in source distribution (@pquentin & @fladi).
- Spanish translation (@alexander-ae).
- Fixing edge-case bugs in revision middleware (@pricem & @oppianmatt).
1.7.1 - 26/06/2013
------------------
- Bugfixes when using a custom User model.
- Minor bugfixes.
1.7 - 27/02/2013
----------------
- Django 1.5 compatibility.
- Experimantal Python 3.3 compatibility!
1.6.6 - 12/02/2013
------------------
- Removing version checking code. It's more trouble than it's worth.
- Dutch translation improvements.
1.6.5 - 12/12/2012
------------------
- Support for Django 1.4.3.
1.6.4 - 28/10/2012
------------------
- Support for Django 1.4.2.
1.6.3 - 05/09/2012
------------------
- Fixing issue with reverting models with unique constraints in the admin.
- Enforcing permissions in admin views.
1.6.2 - 31/07/2012
------------------
- Batch saving option in createinitialrevisions.
- Suppressing warning for Django 1.4.1.
1.6.1 - 20/06/2012
------------------
- Swedish translation.
- Fixing formating for PyPi readme and license.
- Minor features and bugfixes.
1.6 - 27/03/2012
----------------
- Django 1.4 compatibility.
1.5.2 - 27/03/2012
------------------
- Multi-db support.
- Brazillian Portuguese translation.
- New manage_manually revision mode.
1.5.1 - 20/10/2011
------------------
- Polish translation.
- Minor bug fixes.
1.5 - 04/09/2011
----------------
- Added in simplified low level API methods, and deprecated old low level API methods.
- Added in support for multiple revision managers running in the same project.
- Added in significant speedups for models with integer primary keys.
- Added in cleanup improvements to patch generation helpers.
- Minor bug fixes.
1.4 - 27/04/2011
----------------
- Added in a version flag for add / change / delete annotations.
- Added experimental deleterevisions management command.
- Added a --comment option to createinitialrevisions management command.
- Django 1.3 compatibility.
1.3.3 - 05/03/2011
------------------
- Improved resilience of revert() to database integrity errors.
- Added in Czech translation.
- Added ability to only save revisions if there is no change.
- Fixed long-running bug with file fields in inline related admin models.
- Easier debugging for createinitialrevisions command.
- Improved compatibility with Oracle database backend.
- Fixed error in MySQL tests.
- Greatly improved performance of get_deleted() Version manager method.
- Fixed an edge-case UnicodeError.
1.3.2 - 22/10/2010
------------------
- Added Polish translation.
- Added French translation.
- Improved resilience of unit tests.
- Improved scaleability of Version.object.get_deleted() method.
- Improved scaleability of createinitialrevisions command.
- Removed post_syncdb hook.
- Added new createinitialrevisions management command.
- Fixed DoesNotExistError with OneToOneFields and follow.
1.3.1 - 31/05/2010
------------------
This release is compatible with Django 1.2.1.
- Django 1.2.1 admin compatibility.
1.2.1 - 03/03/2010
------------------
This release is compatible with Django 1.1.1.
- The django syncdb command will now automatically populate any
version-controlled models with an initial revision. This ensures existing
projects that integrate Reversion won't get caught out.
- Reversion now works with SQLite for tables over 999 rows.
- Added Hebrew translation.
1.2 - 12/10/2009
----------------
This release is compatible with Django 1.1.
- Django 1.1 admin compatibility.
1.1.2 - 23/07/2009
------------------
This release is compatible with Django 1.0.4.
- Doc tests.
- German translation update.
- Better compatibility with the Django trunk.
- The ability to specify a serialization format used by the ReversionAdmin
class when models are auto-registered.
- Reduction in the number of database queries performed by the Reversion
- admin interface.
1.1.1 - 25/03/2010
------------------
This release is compatible with Django 1.0.2.
- German and Italian translations.
- Helper functions for generating diffs.
- Improved handling of one-to-many relationships in the admin.
django-reversion-release-1.8/LICENSE 0000664 0000000 0000000 00000002764 12236676246 0017310 0 ustar 00root root 0000000 0000000 Copyright (c) 2009, David Hall.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of David Hall nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
django-reversion-release-1.8/MANIFEST.in 0000664 0000000 0000000 00000000313 12236676246 0020025 0 ustar 00root root 0000000 0000000 include src/reversion/templates/reversion/*.html
include src/reversion/locale/*/LC_MESSAGES/django.*
include LICENSE
include README.rst
include CHANGELOG.rst
include MANIFEST.in
recursive-include docs *
django-reversion-release-1.8/README.rst 0000664 0000000 0000000 00000004431 12236676246 0017763 0 ustar 00root root 0000000 0000000 django-reversion
================
**django-reversion** is an extension to the Django web framework that provides
comprehensive version control facilities.
Features
--------
- Roll back to any point in a model's history - an unlimited undo facility!
- Recover deleted models - never lose data again!
- Admin integration for maximum usability.
- Group related changes into revisions that can be rolled back in a single
transaction.
- Automatically save a new version whenever your model changes using Django's
flexible signalling framework.
- Automate your revision management with easy-to-use middleware.
**django-reversion** can be easily added to your existing Django project with an
absolute minimum of code changes.
Documentation
-------------
Please read the `Getting Started `_
guide for more information.
Download instructions, bug reporting and links to full documentation can be
found at the `main project website `_.
You can keep up to date with the latest announcements by joining the
`django-reversion discussion group `_.
Upgrading
---------
If you're upgrading your existing installation of django-reversion, please check
the `Schema Migrations `_
documentation for information on any database changes and how to upgrade. If you're using
South to manage database migrations in your project, then upgrading is as easy as running
a few django management commands.
It's always worth checking the `CHANGELOG `_
before upgrading too, just in case you get caught off-guard by a minor upgrade to the library.
More information
----------------
The django-reversion project was developed by Dave Hall. You can get the code
from the `django-reversion project site `_.
Dave Hall is a freelance web developer, based in Cambridge, UK. You can usually
find him on the Internet in a number of different places:
- `Website `_
- `Twitter `_
- `Google Profile `_
django-reversion-release-1.8/docs/ 0000775 0000000 0000000 00000000000 12236676246 0017222 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/docs/Makefile 0000664 0000000 0000000 00000012744 12236676246 0020672 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-reversion.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-reversion.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/django-reversion"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-reversion"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
django-reversion-release-1.8/docs/admin.rst 0000664 0000000 0000000 00000005033 12236676246 0021045 0 ustar 00root root 0000000 0000000 .. _admin:
Admin integration
=================
django-reversion can be used to add a powerful rollback and recovery facility to your admin site. To enable this, simply register your models with a subclass of ``reversion.VersionAdmin``.
::
import reversion
class YourModelAdmin(reversion.VersionAdmin):
pass
admin.site.register(YourModel, YourModelAdmin)
You can also use ``reversion.VersionAdmin`` as a mixin with another specialized admin class.
::
class YourModelAdmin(reversion.VersionAdmin, YourBaseModelAdmin):
pass
If you're using an existing third party app, then you can add patch django-reversion into its admin class by using the ``reversion.helpers.patch_admin()`` method. For example, to add version control to the built-in User model:
::
from reversion.helpers import patch_admin
patch_admin(User)
Admin customizations
--------------------
It's possible to customize the way django-reversion integrates with your admin site by specifying options on the subclass of ``reversion.VersionAdmin`` as follows:
::
class YourModelAdmin(reversion.VersionAdmin):
option_name = option_value
The available admin options are:
* **history_latest_first:** Whether to display the available versions in reverse chronological order on the revert and recover views (default ``False``)
* **ignore_duplicate_revisions:** Whether to ignore duplicate revisions when storing version data (default ``False``)
* **recover_form_template:** The name of the template to use when rendering the recover form (default ``'reversion/recover_form.html'``)
* **reversion_format:** The name of a serialization format to use when storing version data (default ``'json'``)
* **revision_form_template:** The name of the template to use when rendering the revert form (default ``'reversion/revision_form.html'``)
* **recover_list_template:** The name of the template to use when rendering the recover list view (default ``'reversion/recover_list.html'``)
Customizing admin templates
---------------------------
In addition to specifying custom templates using the options above, you can also place specially named templates on your template root to override the default templates on a per-model or per-app basis.
For example, to override the recover_list template for the user model, the auth app, or all registered models, you could create a template with one of the following names:
* ``'reversion/auth/user/recover_list.html'``
* ``'reversion/auth/recover_list.html'``
* ``'reversion/recover_list.html'``
django-reversion-release-1.8/docs/api.rst 0000664 0000000 0000000 00000026013 12236676246 0020527 0 ustar 00root root 0000000 0000000 .. _api:
Low-level API
=============
You can use django-reversion's API to build powerful version-controlled views outside of the built-in admin site.
**Please note:** The django-reversion API underwent a number of changes for the 1.5 release. The old API is now deprecated, and was removed in django-reversion 1.7. Documentation for the old API can be found on the :ref:`deprecated low-level API ` page.
Registering models with django-reversion
----------------------------------------
If you're already using the :ref:`admin integration ` for a model, then there's no need to register it. However, if you want to register a model without using the admin integration, then you need to use the ``reversion.register()`` method.
::
import reversion
reversion.register(YourModel)
**Warning:** If you’re using django-reversion in an management command, and are using the automatic ``VersionAdmin`` registration method, then you’ll need to import the relevant ``admin.py`` file at the top of your management command file.
**Warning:** When Django starts up, some python scripts get loaded twice, which can cause 'already registered' errors to be thrown. If you place your calls to ``reversion.register()`` in the ``models.py`` file, immediately after the model definition, this problem will go away.
Creating revisions
------------------
A revision represents one or more changes made to your models, grouped together as a single unit. You create a revision by marking up a section of code to represent a revision. Whenever you call ``save()`` on a model within the scope of a revision, it will be added to that revision.
**Note:** If you call ``save()`` outside of the scope of a revision, a revision is NOT created. This means that you are in control of when to create revisions.
There are several ways to create revisions, as explained below. Although there is nothing stopping you from mixing and matching these approaches, it is recommended that you pick one of the methods and stick with it throughout your project.
RevisionMiddleware
^^^^^^^^^^^^^^^^^^
The simplest way to create revisions is to use ``reversion.middleware.RevisionMiddleware``. This will automatically wrap every request in a revision, ensuring that all changes to your models will be added to their version history.
To enable the revision middleware, simply add it to your ``MIDDLEWARE_CLASSES`` setting as follows::
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'reversion.middleware.RevisionMiddleware',
# Other middleware goes here...
)
Please note that ``RevisionMiddleware`` should go after ``TransactionMiddleware``. It is highly recommended that you use ``TransactionMiddleware`` in conjunction with ``RevisionMiddleware`` to ensure data integrity.
reversion.create_revision() decorator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you need more control over revision management, you can decorate any function with the ``reversion.create_revision()`` decorator. Any changes to your models that occur during this function will be grouped together into a revision.
::
@reversion.create_revision()
def you_view_func(request):
your_model.save()
reversion.create_revision() context manager
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For Python 2.5 and above, you can also use a context manager to mark up a block of code. Once the block terminates, any changes made to your models will be grouped together into a revision.
::
with reversion.create_revision():
your_model.save()
Version meta data
-----------------
It is possible to attach a comment and a user reference to an active revision using the following method::
with reversion.create_revision():
your_model.save()
reversion.set_user(user)
reversion.set_comment("Comment text...")
If you use ``RevisionMiddleware``, then the user will automatically be added to the revision from the incoming request.
Custom meta data
^^^^^^^^^^^^^^^^
You can attach custom meta data to a revision by creating a separate django model to hold the additional fields. For example::
from reversion.models import Revision
class VersionRating(models.Model):
revision = models.OneToOneField(Revision) # This is required
rating = models.PositiveIntegerField()
You can then attach this meta class to a revision using the following method::
reversion.add_meta(VersionRating, rating=5)
Reverting to previous revisions
-------------------------------
To revert a model to a previous version, use the following method::
your_model = YourModel.objects.get(pk=1)
# Build a list of all previous versions, latest versions first:
version_list = reversion.get_for_object(your_model)
# Build a list of all previous versions, latest versions first, duplicates removed:
version_list = reversion.get_unique_for_object(your_model)
# Find the most recent version for a given date:
version = reversion.get_for_date(your_model, datetime.datetime(2008, 7, 10))
# Access the model data stored within the version:
version_data = version.field_dict
# Revert all objects in this revision:
version.revision.revert()
# Revert all objects in this revision, deleting related objects that have been created since the revision:
version.revision.revert(delete=True)
# Just revert this object, leaving the rest of the revision unchanged:
version.revert()
Recovering Deleted Objects
--------------------------
To recover a deleted object, use the following method::
# Built a list of all deleted objects, latest deletions first.
deleted_list = reversion.get_deleted(YourModel)
# Access a specific deleted object.
delete_version = deleted_list.get(id=5)
# Recover all objects in this revision:
deleted_version.revision.revert()
# Just recover this object, leaving the rest of the revision unchanged:
deleted_version.revert()
Transaction Management
----------------------
django-reversion does not manage database transactions for you, as this is something that needs to be configured separately for the entire application. However, it is important that any revisions you create are themselves wrapped in a database transaction.
The easiest (and recommended) way to do this is by using the ``TransactionMiddleware`` supplied by Django. As noted above, this should go before the ``RevisionMiddleware``, if used.
If you want finer-grained control, then you should use the ``transaction.create_on_success`` decorator to wrap any functions where you will be creating revisions.
Advanced model registration
---------------------------
Following foreign key relationships
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally, when you save a model it will only save the primary key of any ForeignKey or ManyToMany fields. If you also wish to include the data of the foreign key in your revisions, pass a list of relationship names to the ``reversion.register()`` method.
::
reversion.register(YourModel, follow=["your_foreign_key_field"])
**Please note:** If you use the follow parameter, you must also ensure that the related model has been registered with django-reversion.
In addition to ForeignKey and ManyToMany relationships, you can also specify related names of one-to-many relationships in the follow clause. For example, given the following database models::
class Person(models.Model):
pass
class Pet(models.Model):
person = models.ForeignKey(Person)
reversion.register(Person, follow=["pet_set"])
reversion.register(Pet)
Now whenever you save a revision containing a ``Person``, all related ``Pet`` instances will be automatically saved to the same revision.
Multi-table inheritance
^^^^^^^^^^^^^^^^^^^^^^^
By default, django-reversion will not save data in any parent classes of a model that uses multi-table inheritance. If you wish to also add parent models to your revision, you must explicitly add them to the follow clause when you register the model.
For example::
class Place(models.Model):
pass
class Restaurant(Place):
pass
reversion.register(Place)
reversion.register(Restaurant, follow=["place_ptr"])
Saving a subset of fields
^^^^^^^^^^^^^^^^^^^^^^^^^
If you only want a subset of fields to be saved to a revision, you can specify a ``fields`` or ``exclude`` argument to the ``reversion.register()`` method.
::
reversion.register(YourModel, fields=["pk", "foo", "bar"])
reversion.register(YourModel, exclude=["foo"])
**Please note:** If you are not careful, then it is possible to specify a combination of fields that will make the model impossible to recover. As such, approach this option with caution.
Custom serialization format
^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, django-reversion will serialize model data using the ``'json'`` serialization format. You can override this on a per-model basis using the format argument to the register method.
::
reversion.register(YourModel, format="yaml")
**Please note:** The named serializer must serialize model data to a utf-8 encoded character string. Please verify that your serializer is compatible before using it with django-reversion.
Really advanced registration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It's possible to customize almost every aspect of model registration by registering your model with a subclass of ``reversion.VersionAdapter``. Behind the scenes, ``reversion.register()`` does this anyway, but you can explicitly provide your own VersionAdapter if you need to perform really advanced customization.
::
class MyVersionAdapter(reversion.VersionAdapter):
pass # Please see the reversion source code for available methods to override.
reversion.register(MyModel, adapter_cls=MyVersionAdapter)
Automatic Registration by the Admin Interface
---------------------------------------------
As mentioned at the start of this page, the admin interface will automatically register any models that use the ``VersionAdmin`` class. The admin interface will automatically follow any InlineAdmin relationships, as well as any parent links for models that use multi-table inheritance.
For example::
# models.py
class Place(models.Model):
pass
class Restaurant(Place):
pass
class Meal(models.Model):
restaurant = models.ForeignKey(Restaurant)
# admin.py
class MealInlineAdmin(admin.StackedInline):
model = Meal
class RestaurantAdmin(VersionAdmin):
inlines = MealInlineAdmin,
admin.site.register(Restaurant, RestaurantAdmin)
Since ``Restaurant`` has been registered with a subclass of ``VersionAdmin``, the following registration calls will be made automatically::
reversion.register(Place)
reversion.register(Restaurant, follow=("place_ptr", "meal_set"))
reversion.register(Meal)
It is only necessary to manually register these models if you wish to override the default registration parameters. In most cases, however, the defaults will suit just fine.
django-reversion-release-1.8/docs/commands.rst 0000664 0000000 0000000 00000001153 12236676246 0021555 0 ustar 00root root 0000000 0000000 .. _commands:
Management commands
===================
django-reversion comes with a number of additional django-admin.py management commands, detailed below.
createinitialrevisions
----------------------
This command is used to create a single, base revision for all registered models in your project. It should be run after installing django-reversion. If your project contains a lot of version-controlled data, then this might take a while to complete.
::
django-admin.py createinitialrevisions
django-admin.py createinitialrevisions someapp
django-admin.py createinitialrevisions someapp.SomeModel
django-reversion-release-1.8/docs/conf.py 0000664 0000000 0000000 00000017441 12236676246 0020530 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# django-reversion documentation build configuration file, created by
# sphinx-quickstart on Thu Aug 29 09:17:37 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'django-reversion'
copyright = '2013, Dave Hall'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.8'
# The full version, including alpha/beta/rc tags.
release = '1.8.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'django-reversiondoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'django-reversion.tex', 'django-reversion Documentation',
'Dave Hall', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'django-reversion', 'django-reversion Documentation',
['Dave Hall'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'django-reversion', 'django-reversion Documentation',
'Dave Hall', 'django-reversion', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
django-reversion-release-1.8/docs/deprecated-api.rst 0000664 0000000 0000000 00000024765 12236676246 0022641 0 ustar 00root root 0000000 0000000 :orphan:
.. _deprecated-api:
Deprecated Low Level API
========================
For most projects, simply activating the admin integration will satisfy all your version-control needs. However, django-reversion comes with a lower-level API that allows you to manage versions within your own code.
Registering Models for Version Control
--------------------------------------
If you wish to use version control with a Django model, you must first register it with the version control machinery. If you have already registered the model with a subclass of ``VersionAdmin``, then this will have been done automatically. If not, then you must manually register the model as follows::
import reversion
reversion.register(YourModel)
A good place to do this is underneath the model definition, within your ``models.py`` file.
**Warning:** If you're using django-reversion in an management command, and are using the automatic ``VersionAdmin`` registration method, then you'll need to import the relevant admin.py file at the top of your management command file.
**Warning:** When Django starts up, some python scripts get loaded twice, which can cause 'already registered' errors to be thrown. If you place your calls to ``reversion.register`` in the ``models.py`` file, immediately after the model definition, this problem will go away.
Creating Revisions
------------------
A revision represents one or more changes made to your models, grouped together as a single unit. You create a revision by marking up a section of code to represent a revision. Whenever you call ``save()`` on a model within the scope of a revision, it will be added to that revision.
There are several ways to create revisions, as explained below. Although there is nothing stopping you from mixing and matching these approaches, it is recommended that you pick one of the methods and stick with it throughout your project.
Revision Middleware
^^^^^^^^^^^^^^^^^^^
Perhaps the simplest way to create revisions is to use ``reversion.middleware.RevisionMiddleware``. This will automatically wrap every request in a revision, ensuring that all changes to your models will be added to their version history.
To enable the revision middleware, simply add it to your ``MIDDLEWARE_CLASSES`` setting as follows::
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'reversion.middleware.RevisionMiddleware',
# Other middleware goes here...
)
Please note that ``RevisionMiddleware`` should go after ``TransactionMiddleware``. It is highly recommended that you use ``TransactionMiddleware`` in conjunction with ``RevisionMiddleware`` to ensure data integrity.
create_on_success Decorator
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you need more control over revision management, you can decorate any function with the ``revision.create_on_success`` decorator. Any changes to your models that occur during this function will be grouped together into a revision.
::
from reversion import revision
@revision.create_on_success
def your_view_func(request):
"""Changes to models will be saved in a revision."""
your_model.save()
Revision Context Manager
^^^^^^^^^^^^^^^^^^^^^^^^
For Python 2.5 and above, you can also use a context manager to mark up a block of code. Once the block terminates, any changes made to your models will be grouped together into a revision.
::
from __future__ import with_statement
import reversion
with reversion.revision:
# Make changes to your models here.
your_model.save()
Version meta data
-----------------
It is possible to attach a comment and a user reference to an active revision using the following method::
@revision.create_on_success
def your_view(request):
your_model.save()
# Set the revision meta data.
revision.user = me
revision.comment = "Doing some changes..."
If you use ``RevisionMiddleware``, then the user will automatically be added to the revision from the incoming request.
Custom meta data
^^^^^^^^^^^^^^^^
You can attach entirely custom meta data to a revision by creating a separate Django model to hold the additional fields. For example::
class VersionRating(models.Model):
revision = models.ForeignKey("reversion.Revision") # This is required
rating = models.PositiveIntegerField()
You can then attach this meta class to a revision using the following method::
revision.add_meta(VersionRating, rating=5)
Reverting to previous revisions
-------------------------------
To revert a model to a previous version, use the following method::
import datetime
from reversion.models import Version
from yoursite.models import YourModel
your_model = YourModel.objects.get(pk=1)
# Build a list of all previous versions, in order of creation:
version_list = Version.objects.get_for_object(your_model)
# Find the most recent version for a given date:
version = Version.objects.get_for_date(your_model, datetime.datetime(2008, 7, 10))
# Access the model data stored within the version:
version_data = version.field_dict
# Revert all objects in this revision:
version.revision.revert()
# Just revert this object, leaving the rest of the revision unchanged:
version.revert()
Recovering Deleted Objects
--------------------------
To recover a deleted object, use the following method::
from reversion.models import Version
from yoursite.models import YourModel
# Built a list of all deleted objects.
deleted_list = Version.objects.get_deleted(YourModel)
# Find the last version of an object before it was deleted:
deleted_version = Version.objects.get_deleted_object(YourModel, object_id=1)
# Recover all objects in this revision:
deleted_version.revision.revert()
# Just recover this object, leaving the rest of the revision unchanged:
deleted_version.revert()
Transaction Management
----------------------
Reversion does not manage database transactions for you, as this is something that needs to be configured separately for the entire application. However, it is important that any revisions you create are themselves wrapped in a database transaction.
The easiest (and recommended) way to do this is by using the ``TransactionMiddleware`` supplied by Django. As noted above, this should go before the ``RevisionMiddleware``, if used.
If you want finer-grained control, then you should use the ``transaction.create_on_success`` decorator to wrap any functions where you will be creating revisions.
Advanced Model Registration
---------------------------
It is possible to customize how a model's data is saved to a revision by passing additional parameters to the ``reversion.register`` method. These are explained below.
Following foreign key relationships
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally, when you save a model it will only save the primary key of any ``ForeignKey`` or ``ManyToMany`` fields. If you also wish to include the data of the foreign key in your revisions, pass a list of relationship names to the ``reversion.register`` method.
::
reversion.register(YourModel, follow=["your_foreign_key_field"])
**Please note:** If you use the follow parameter, you must also ensure that the related model has been registered with django-reversion.
In addition to ``ForeignKey`` and ``ManyToMany`` relationships, you can also specify related names of one-to-many relationships in the follow clause. For example, given the following database models::
class Person(models.Model):
pass
class Pet(models.Model):
person = models.ForeignKey(Person)
reversion.register(Person, follow=["pet_set"])
reversion.register(Pet)
Now whenever you save a revision containing a ``Person``, all related ``Pet`` instances will be automatically saved to the same revision.
Multi-table inheritance
^^^^^^^^^^^^^^^^^^^^^^^
By default, django-reversion will not save data in any parent classes of a model that uses multi-table inheritance. If you wish to also add parent models to your revision, you must explicitly add them to the follow clause when you register the model.
For example::
class Place(models.Model):
pass
class Restaurant(Place):
pass
reversion.register(Place)
reversion.register(Restaurant, follow=["place_ptr"])
Saving a subset of fields
-------------------------
If you only want a subset of fields to be saved to a revision, you can specify a fields argument to the register method.
::
reversion.register(YourModel, fields=["pk", "foo", "bar"])
**Please note:** If you are not careful, then it is possible to specify a combination of fields that will make the model impossible to recover. As such, approach this option with caution.
Custom serialization format
^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, django-reversion will serialize model data using the 'json' serialization format. You can override this on a per-model basis using the format argument to the register method.
::
reversion.register(YourModel, format="yaml")
**Please note:** The named serializer must serialize model data to a utf-8 encoded character string. Please verify that your serializer is compatible before using it with django-reversion.
Automatic Registration by the Admin Interface
---------------------------------------------
As mentioned at the start of this page, the admin interface will automatically register any models that use the ``VersionAdmin`` class. The admin interface will automatically follow any ``InlineAdmin`` relationships, as well as any parent links for models that use multi-table inheritance.
For example::
# models.py
class Place(models.Model):
pass
class Restaurant(Place):
pass
class Meal(models.Model):
restaurant = models.ForeignKey(Restaurant)
# admin.py
class MealInlineAdmin(admin.StackedInline):
model = Meal
class RestaurantAdmin(VersionAdmin):
inline = MealInlineAdmin,
admin.site.register(Restaurant, RestaurantAdmin)
Since ``Restaurant`` has been registered with a subclass of ``VersionAdmin``, the following registration calls will be made automatically::
reversion.register(Place)
reversion.register(Restaurant, follow=["place_ptr", "meal_set"])
reversion.register(Meal)
As such, it is only necessary to manually register these models if you wish to override the default registration parameters. In most cases, however, the defaults will suit just fine.
django-reversion-release-1.8/docs/diffs.rst 0000664 0000000 0000000 00000004451 12236676246 0021053 0 ustar 00root root 0000000 0000000 .. _diffs:
Generating Diffs
================
A common problem when dealing with version-controlled text is generating diffs to highlight changes between different versions.
django-reversion comes with a number of helper functions that make generating diffs easy. They all rely on the `google-diff-match-patch `_ library, so make sure you have this installed before trying to use the functions.
Low-Level API
-------------
It is possible to generate two types of diff using the diff helper functions. For the purpose of these examples, it is assumed that you have created a model called ``Page``, which contains a text field called ``content``.
First of all, you need to use the :ref:`low level API ` to retrieve the versions you want to compare.
::
from reversion.helpers import generate_patch
# Get the page object to generate diffs for.
page = Page.objects.all()[0]
# Get the two versions to compare.
available_versions = Version.objects.get_for_object(page)
old_version = available_versions[0]
new_version = available_versions[1]
Now, in order to generate a text patch::
from reversion.helpers import generate_patch
patch = generate_patch(old_version, new_version, "content")
Or, to generate a pretty HTML patch::
from reversion.helpers import generate_patch_html
patch_html = generate_patch_html(old_version, new_version, "content")
Because text diffs can often be fragmented and hard to read, an optional ``cleanup`` parameter may be passed to generate friendlier diffs.
::
patch_html = generate_patch_html(old_version, new_version, "content", cleanup="semantic")
patch_html = generate_patch_html(old_version, new_version, "content", cleanup="efficiency")
Of the two cleanup styles, the one that generally produces the best result is 'semantic'.
Admin Integration
-----------------
The admin integration for django-reversion does not currently support diff generation. This is a deliberate design decision, as it would make the framework a lot more heavyweight, as well as carrying the risk of confusing non-technical end users.
While future versions may support a more advanced admin class, for the time being it is left up to your own imagination for ways in which to integrate diffs with your project.
django-reversion-release-1.8/docs/django-versions.rst 0000664 0000000 0000000 00000003162 12236676246 0023066 0 ustar 00root root 0000000 0000000 .. _django-versions:
Compatible Django Versions
==========================
django-reversion is an actively-maintained project, and aims to stay compatible with the latest version of Django. Unfortunately, this means that the latest release of django-reversion might not work with older versions of Django.
If you are using anything other than the latest release of Django, it is important that you check the table below to ensure that your django-reversion download will be compatible.
============== =================
Django version Reversion release
============== =================
1.6 1.8
1.5.1 1.7.1
1.5 1.7
1.4.5 1.6.6
1.4.4 1.6.6
1.4.3 1.6.5
1.4.2 1.6.4
1.4.1 1.6.3
1.4 1.6.1
1.3.6 1.5.7
1.3.5 1.5.6
1.3.4 1.5.5
1.3.3 1.5.4
1.3.2 1.5.3
1.3.1 1.5.2
1.3 1.5
1.2.5 1.3.3
1.2.4 1.3.3
1.2.3 1.3.2
1.2 1.3
1.1.1 1.2.1
1.1 1.2
1.0.4 1.1.2
1.0.3 1.1.2
1.0.2 1.1.1
============== =================
Getting the code
----------------
All django-reversion releases are available from the `project downloads area `_. You can also use Git to checkout tags from the `public git repository `_.
There are a number of alternative methods you can use when installing django-reversion. Please check the :ref:`installation methods ` page for more information.
django-reversion-release-1.8/docs/how-it-works.rst 0000664 0000000 0000000 00000005536 12236676246 0022337 0 ustar 00root root 0000000 0000000 .. _how-it-works:
How it works
============
Saving Revisions
----------------
Enabling version control for a model is achieved using the ``reversion.register`` method. This registers the version control machinery with the ``post_save`` signal for that model, allowing new changes to the model to be caught.
::
import reversion
reversion.register(YourModel)
Any models that use subclasses of ``VersionAdmin`` in the admin interface will be automatically registered with django-reversion. As such, it is only necessary to manually register these models if you wish to override the default registration settings.
Whenever you save changes to a model, it is serialized using the Django serialization framework into a JSON string. This is saved to the database as a ``reversion.models.Version`` model. Each ``Version`` model is linked to a model instance using a ``GenericForeignKey``.
Foreign keys and many-to-many relationships are normally saved as their primary keys only. However, the ``reversion.register`` method takes an optional follow clause allowing these relationships to be automatically added to revisions. Please see :ref:`Low Level API ` for more information.
Reverting Versions
------------------
Reverting a version is simply a matter of loading the appropriate ``Version`` model from the database, deserializing the model data, and re-saving the old data.
There are a number of utility methods present on the ``Version`` object manager to assist this process. Please see :ref:`Low Level API ` for more information.
Revision Management
-------------------
Related changes to models are grouped together in revisions. This allows for atomic rollback from one revision to another. You can automate revision management using either ``reversion.middleware.RevisionMiddleware``, or the ``reversion.revision.create_on_success decorator``.
For more information on creating revisions, please see :ref:`Low Level API `.
Admin Integration
-----------------
Full admin integration is achieved using the ``reversion.admin.VersionAdmin`` class. This will create a new revision whenever a model is edited using the admin interface. Any models registered for version control, including inline models, will be included in this revision.
The ``object_history`` view is extended to make each ``LogEntry`` a link that can be used to revert the model back to the most recent version at the time the ``LogEntry`` was created.
Choosing to revert a model will display the standard model change form. The fields in this form are populated using the data contained in the revision corresponding to the chosen ``LogEntry``. Saving this form will result in a new revision being created containing the new model data.
For most projects, simply registering a model with a subclass of ``VersionAdmin`` is enough to satisfy all its version-control needs.
django-reversion-release-1.8/docs/index.rst 0000664 0000000 0000000 00000004762 12236676246 0021074 0 ustar 00root root 0000000 0000000 .. _index:
django-reversion documentation
==============================
Getting started with django-reversion
-------------------------------------
To install django-reversion, follow these steps:
1. Install with pip: ``pip install django-reversion``.
2. Add ``'reversion'`` to ``INSTALLED_APPS``.
3. Run ``manage.py syncdb``.
The latest release (1.8) of django-reversion is designed to work with Django 1.6. If you have installed anything other than the latest version of Django, please check the :ref:`compatible Django versions ` page before installing django-reversion.
There are a number of alternative methods you can use when installing django-reversion. Please check the :ref:`installation methods ` page for more information.
Admin integration
-----------------
django-reversion can be used to add a powerful rollback and recovery facility to your admin site. To enable this, simply register your models with a subclass of ``reversion.VersionAdmin``::
import reversion
class YourModelAdmin(reversion.VersionAdmin):
pass
admin.site.register(YourModel, YourModelAdmin)
Whenever you register a model with the ``VersionAdmin`` class, be sure to run the ``./manage.py createinitialrevisions`` command to populate the version database with an initial set of model data. Depending on the number of rows in your database, this command could take a while to execute.
For more information about admin integration, please read the :ref:`admin integration ` documentation.
Automatic versioning
--------------------
To store a new revision for every save() in your views, the simplest way is to add those two classes to `MIDDLEWARE_CLASSES`::
MIDDLEWARE_CLASSES = (
# Your middlewares...
'django.middleware.transaction.TransactionMiddleware',
'reversion.middleware.RevisionMiddleware'
)
The first one makes sure data is only saved if the request completed succesfully, ensuring data integrity. The second one automatically commits a new revision. Order is important.
Low Level API
-------------
You can use django-reversion's API to build powerful version-controlled views. For more information, please read the :ref:`low level API ` documentation.
More information
----------------
Installation
^^^^^^^^^^^^
.. toctree::
:maxdepth: 1
installation
django-versions
migrations
admin
Further reading
^^^^^^^^^^^^^^^
.. toctree::
:maxdepth: 1
api
commands
signals
how-it-works
diffs
django-reversion-release-1.8/docs/installation.rst 0000664 0000000 0000000 00000002304 12236676246 0022454 0 ustar 00root root 0000000 0000000 .. _installation:
Installation methods
====================
**Note:** It is recommended that you always use the latest release of django-reversion with the latest release of Django. If you are using an older version of Django, then please check out the :ref:`Compatible Django Versions ` page for more information.
pip
---
You can install django-reversion into your system, or virtual environment, by running the following command in a terminal::
$ pip install django-reversion
easy_install
------------
The popular easy_install utility can be used to install the latest django-reversion release from the Python Package Index. Simply run the following command in a terminal::
$ sudo easy_install django-reversion
Git
---
Using Git to install django-reversion provides an easy way of upgrading your installation at a later date. Simply clone the `public git repository `_ and symlink the ``src/reversion`` directory into your ``PYTHONPATH``::
$ git clone git://github.com/etianen/django-reversion.git
$ cd django-reversion.git
$ git checkout release-1.8
$ ln -s src/reversion /your/pythonpath/location/reversion django-reversion-release-1.8/docs/make.bat 0000664 0000000 0000000 00000011774 12236676246 0020641 0 ustar 00root root 0000000 0000000 @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^` where ^ is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-reversion.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-reversion.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
django-reversion-release-1.8/docs/migrations.rst 0000664 0000000 0000000 00000002347 12236676246 0022136 0 ustar 00root root 0000000 0000000 .. _migrations:
Schema migrations
=================
This page describes the schema migrations that have taken place over the lifetime of django-reversion, along with a how-to guide for updating your schema using `South `_.
django-reversion 1.5
--------------------
The current working version adds in significant speedups for models with integer primary keys.
In order to apply this migration using south, simply run::
./manage.py migrate reversion
If you have a large amount of existing version data, then this command might take a little while to run while the database tables are updated.
django-reversion 1.4
--------------------
This version added a much-requested 'type' field to Version models, allows statistic to be gathered about the number of additions, changes and deletions that have been applied to a model.
In order to apply this migration, it is first necessary to install South.
1. Add 'south' to your ``INSTALLED_APPS`` setting.
2. Run ``./manage.py syncdb``
You then need to run the following two commands to complete the migration::
./manage.py migrate reversion 0001 --fake
./manage.py migrate reversion
django-reversion 1.3.3
----------------------
No migration needed.
django-reversion-release-1.8/docs/signals.rst 0000664 0000000 0000000 00000003713 12236676246 0021420 0 ustar 00root root 0000000 0000000 .. _signals:
Signals sent by django-reversion
================================
django-reversion provides a number of custom signals that can be used to tie-in additional functionality to the version creation mechanism.
**Important:** Don't connect to the pre_save or post_save signals of the Version or Revision models directly, use the signals outlined below instead. The pre_save and post_save signals are longer sent by the Version or Revision models since django-reversion 1.7.
reversion.pre_revision_commit
-----------------------------
This signal is triggered just before a revision is saved to the database. It receives the following keyword arguments:
* **instances** - A list of the model instances in the revision.
* **revision** - The unsaved Revision model.
* **versions** - The unsaved Version models in the revision.
reversion.post_revision_commit
------------------------------
This signal is triggered just after a revision is saved to the database. It receives the following keyword arguments:
* **instances** - A list of the model instances in the revision.
* **revision** - The saved Revision model.
* **versions** - The saved Version models in the revision.
Connecting to signals
---------------------
The signals listed above are sent only once *per revision*, rather than once *per model in the revision*. In practice, this means that you should connect to the signals without specifying a `sender`, as below::
def on_revision_commit(**kwargs):
pass # Your signal handler code here.
reversion.post_revision_commit.connect(on_revision_commit)
To execute code only when a revision has been saved for a particular Model, you should inspect the contents of the `instances` parameter, as below::
def on_revision_commit(instances, **kwargs):
for instance in instances:
if isinstance(instance, MyModel):
pass # Your signal handler code here.
reversion.post_revision_commit.connect(on_revision_commit)
django-reversion-release-1.8/setup.py 0000664 0000000 0000000 00000003310 12236676246 0020001 0 ustar 00root root 0000000 0000000 import sys
sys.path.insert(0, 'src/reversion')
from distutils.core import setup
from version import __version__
# Load in babel support, if available.
try:
from babel.messages import frontend as babel
cmdclass = {"compile_catalog": babel.compile_catalog,
"extract_messages": babel.extract_messages,
"init_catalog": babel.init_catalog,
"update_catalog": babel.update_catalog,}
except ImportError:
cmdclass = {}
setup(name="django-reversion",
version='.'.join(str(x) for x in __version__),
license="BSD",
description="An extension to the Django web framework that provides comprehensive version control facilities",
author="Dave Hall",
author_email="dave@etianen.com",
url="http://github.com/etianen/django-reversion",
zip_safe=False,
packages=["reversion", "reversion.management", "reversion.management.commands", "reversion.migrations"],
package_dir={"": "src"},
package_data = {"reversion": ["locale/*/LC_MESSAGES/django.*", "templates/reversion/*.html"]},
cmdclass = cmdclass,
classifiers=["Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
"Framework :: Django",])
django-reversion-release-1.8/src/ 0000775 0000000 0000000 00000000000 12236676246 0017061 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/src/reversion/ 0000775 0000000 0000000 00000000000 12236676246 0021075 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/src/reversion/__init__.py 0000664 0000000 0000000 00000003302 12236676246 0023204 0 ustar 00root root 0000000 0000000 """
Transactional version control for Django models.
Developed by Dave Hall.
"""
from __future__ import unicode_literals
from reversion.revisions import default_revision_manager, revision_context_manager, VersionAdapter
from reversion.admin import VersionAdmin
from reversion.models import pre_revision_commit, post_revision_commit
from reversion.version import __version__
VERSION = __version__
# Legacy revision reference.
revision = default_revision_manager # TODO: Deprecate eventually.
# Easy registration methods.
register = default_revision_manager.register
is_registered = default_revision_manager.is_registered
unregister = default_revision_manager.unregister
get_adapter = default_revision_manager.get_adapter
get_registered_models = default_revision_manager.get_registered_models
# Context management.
create_revision = revision_context_manager.create_revision
# Revision meta data.
get_db = revision_context_manager.get_db
set_db = revision_context_manager.set_db
get_user = revision_context_manager.get_user
set_user = revision_context_manager.set_user
get_comment = revision_context_manager.get_comment
set_comment = revision_context_manager.set_comment
add_meta = revision_context_manager.add_meta
get_ignore_duplicates = revision_context_manager.get_ignore_duplicates
set_ignore_duplicates = revision_context_manager.set_ignore_duplicates
# Low level API.
get_for_object_reference = default_revision_manager.get_for_object_reference
get_for_object = default_revision_manager.get_for_object
get_unique_for_object = default_revision_manager.get_unique_for_object
get_for_date = default_revision_manager.get_for_date
get_deleted = default_revision_manager.get_deleted
django-reversion-release-1.8/src/reversion/admin.py 0000664 0000000 0000000 00000061212 12236676246 0022541 0 ustar 00root root 0000000 0000000 """Admin extensions for django-reversion."""
from __future__ import unicode_literals
from functools import partial
from django import template
from django.db import models, transaction, connection
from django.conf.urls import patterns, url
from django.contrib import admin
from django.contrib.admin import helpers, options
from django.contrib.admin.util import unquote, quote
from django.contrib.contenttypes.generic import GenericInlineModelAdmin, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.forms.formsets import all_valid
from django.forms.models import model_to_dict
from django.http import HttpResponseRedirect
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, render_to_response
from django.utils.html import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
from django.utils.encoding import force_text
from django.utils.formats import localize
from reversion.models import Revision, Version, has_int_pk
from reversion.revisions import default_revision_manager, RegistrationError
class VersionAdmin(admin.ModelAdmin):
"""Abstract admin class for handling version controlled models."""
object_history_template = "reversion/object_history.html"
change_list_template = "reversion/change_list.html"
revision_form_template = None
recover_list_template = None
recover_form_template = None
# The revision manager instance used to manage revisions.
revision_manager = default_revision_manager
# The serialization format to use when registering models with reversion.
reversion_format = "json"
# Whether to ignore duplicate revision data.
ignore_duplicate_revisions = False
# If True, then the default ordering of object_history and recover lists will be reversed.
history_latest_first = False
def _autoregister(self, model, follow=None):
"""Registers a model with reversion, if required."""
if model._meta.proxy:
raise RegistrationError("Proxy models cannot be used with django-reversion, register the parent class instead")
if not self.revision_manager.is_registered(model):
follow = follow or []
for parent_cls, field in model._meta.parents.items():
follow.append(field.name)
self._autoregister(parent_cls)
self.revision_manager.register(model, follow=follow, format=self.reversion_format)
@property
def revision_context_manager(self):
"""The revision context manager for this VersionAdmin."""
return self.revision_manager._revision_context_manager
def _introspect_inline_admin(self, inline):
"""Introspects the given inline admin, returning a tuple of (inline_model, follow_field)."""
inline_model = None
follow_field = None
if issubclass(inline, GenericInlineModelAdmin):
inline_model = inline.model
ct_field = inline.ct_field
ct_fk_field = inline.ct_fk_field
for field in self.model._meta.virtual_fields:
if isinstance(field, GenericRelation) and field.rel.to == inline_model and field.object_id_field_name == ct_fk_field and field.content_type_field_name == ct_field:
follow_field = field.name
elif issubclass(inline, options.InlineModelAdmin):
inline_model = inline.model
fk_name = inline.fk_name
if not fk_name:
for field in inline_model._meta.fields:
if isinstance(field, (models.ForeignKey, models.OneToOneField)) and issubclass(self.model, field.rel.to):
fk_name = field.name
if not inline_model._meta.get_field(fk_name).rel.is_hidden():
accessor = inline_model._meta.get_field(fk_name).related.get_accessor_name()
follow_field = accessor
return inline_model, follow_field
def __init__(self, *args, **kwargs):
"""Initializes the VersionAdmin"""
super(VersionAdmin, self).__init__(*args, **kwargs)
# Automatically register models if required.
if not self.revision_manager.is_registered(self.model):
inline_fields = []
for inline in self.inlines:
inline_model, follow_field = self._introspect_inline_admin(inline)
if inline_model:
self._autoregister(inline_model)
if follow_field:
inline_fields.append(follow_field)
self._autoregister(self.model, inline_fields)
# Wrap own methods in manual revision management.
self.add_view = self.revision_context_manager.create_revision(manage_manually=True)(self.add_view)
self.change_view = self.revision_context_manager.create_revision(manage_manually=True)(self.change_view)
self.recover_view = self.revision_context_manager.create_revision(manage_manually=True)(self.recover_view)
self.revision_view = self.revision_context_manager.create_revision(manage_manually=True)(self.revision_view)
self.changelist_view = self.revision_context_manager.create_revision(manage_manually=True)(self.changelist_view)
def _get_template_list(self, template_name):
opts = self.model._meta
return (
"reversion/%s/%s/%s" % (opts.app_label, opts.object_name.lower(), template_name),
"reversion/%s/%s" % (opts.app_label, template_name),
"reversion/%s" % template_name,
)
def get_urls(self):
"""Returns the additional urls used by the Reversion admin."""
urls = super(VersionAdmin, self).get_urls()
admin_site = self.admin_site
opts = self.model._meta
info = opts.app_label, opts.module_name,
reversion_urls = patterns("",
url("^recover/$", admin_site.admin_view(self.recoverlist_view), name='%s_%s_recoverlist' % info),
url("^recover/([^/]+)/$", admin_site.admin_view(self.recover_view), name='%s_%s_recover' % info),
url("^([^/]+)/history/([^/]+)/$", admin_site.admin_view(self.revision_view), name='%s_%s_revision' % info),)
return reversion_urls + urls
def get_revision_instances(self, request, object):
"""Returns all the instances to be used in the object's revision."""
return [object]
def get_revision_data(self, request, object):
"""Returns all the revision data to be used in the object's revision."""
return dict(
(o, self.revision_manager.get_adapter(o.__class__).get_version_data(o))
for o in self.get_revision_instances(request, object)
)
def log_addition(self, request, object):
"""Sets the version meta information."""
super(VersionAdmin, self).log_addition(request, object)
self.revision_manager.save_revision(
self.get_revision_data(request, object),
user = request.user,
comment = _("Initial version."),
ignore_duplicates = self.ignore_duplicate_revisions,
db = self.revision_context_manager.get_db(),
)
def log_change(self, request, object, message):
"""Sets the version meta information."""
super(VersionAdmin, self).log_change(request, object, message)
self.revision_manager.save_revision(
self.get_revision_data(request, object),
user = request.user,
comment = message,
ignore_duplicates = self.ignore_duplicate_revisions,
db = self.revision_context_manager.get_db(),
)
def _order_version_queryset(self, queryset):
"""Applies the correct ordering to the given version queryset."""
if self.history_latest_first:
return queryset.order_by("-pk")
return queryset.order_by("pk")
def recoverlist_view(self, request, extra_context=None):
"""Displays a deleted model to allow recovery."""
# check if user has change or add permissions for model
if not self.has_change_permission(request) and not self.has_add_permission(request):
raise PermissionDenied
model = self.model
opts = model._meta
deleted = self._order_version_queryset(self.revision_manager.get_deleted(self.model))
context = {
"opts": opts,
"app_label": opts.app_label,
"module_name": capfirst(opts.verbose_name),
"title": _("Recover deleted %(name)s") % {"name": force_text(opts.verbose_name_plural)},
"deleted": deleted,
"changelist_url": reverse("%s:%s_%s_changelist" % (self.admin_site.name, opts.app_label, opts.module_name)),
}
extra_context = extra_context or {}
context.update(extra_context)
return render_to_response(self.recover_list_template or self._get_template_list("recover_list.html"),
context, template.RequestContext(request))
def get_revision_form_data(self, request, obj, version):
"""
Returns a dictionary of data to set in the admin form in order to revert
to the given revision.
"""
return version.field_dict
def get_related_versions(self, obj, version, FormSet):
"""Retreives all the related Version objects for the given FormSet."""
object_id = obj.pk
# Get the fk name.
try:
fk_name = FormSet.fk.name
except AttributeError:
# This is a GenericInlineFormset, or similar.
fk_name = FormSet.ct_fk_field.name
# Look up the revision data.
revision_versions = version.revision.version_set.all()
related_versions = dict([(related_version.object_id, related_version)
for related_version in revision_versions
if ContentType.objects.get_for_id(related_version.content_type_id).model_class() == FormSet.model
and force_text(related_version.field_dict[fk_name]) == force_text(object_id)])
return related_versions
def _hack_inline_formset_initial(self, inline, FormSet, formset, obj, version, revert, recover):
"""Hacks the given formset to contain the correct initial data."""
# if the FK this inline formset represents is not being followed, don't process data for it.
# see https://github.com/etianen/django-reversion/issues/222
_, follow_field = self._introspect_inline_admin(inline.__class__)
if follow_field not in self.revision_manager.get_adapter(self.model).follow:
return
# Now we hack it to push in the data from the revision!
initial = []
related_versions = self.get_related_versions(obj, version, FormSet)
formset.related_versions = related_versions
for related_obj in formset.queryset:
if force_text(related_obj.pk) in related_versions:
initial.append(related_versions.pop(force_text(related_obj.pk)).field_dict)
else:
initial_data = model_to_dict(related_obj)
initial_data["DELETE"] = True
initial.append(initial_data)
for related_version in related_versions.values():
initial_row = related_version.field_dict
pk_name = ContentType.objects.get_for_id(related_version.content_type_id).model_class()._meta.pk.name
del initial_row[pk_name]
initial.append(initial_row)
# Reconstruct the forms with the new revision data.
formset.initial = initial
formset.forms = [formset._construct_form(n) for n in range(len(initial))]
# Hack the formset to force a save of everything.
def get_changed_data(form):
return [field.name for field in form.fields]
for form in formset.forms:
form.has_changed = lambda: True
form._get_changed_data = partial(get_changed_data, form=form)
def total_form_count_hack(count):
return lambda: count
formset.total_form_count = total_form_count_hack(len(initial))
def render_revision_form(self, request, obj, version, context, revert=False, recover=False):
"""Renders the object revision form."""
model = self.model
opts = model._meta
object_id = obj.pk
# Generate the model form.
ModelForm = self.get_form(request, obj)
formsets = []
if request.method == "POST":
# This section is copied directly from the model admin change view
# method. Maybe one day there will be a hook for doing this better.
form = ModelForm(request.POST, request.FILES, instance=obj, initial=self.get_revision_form_data(request, obj, version))
if form.is_valid():
form_validated = True
new_object = self.save_form(request, form, change=True)
# HACK: If the value of a file field is None, remove the file from the model.
for field in new_object._meta.fields:
if isinstance(field, models.FileField) and field.name in form.cleaned_data and form.cleaned_data[field.name] is None:
setattr(new_object, field.name, None)
else:
form_validated = False
new_object = obj
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request, new_object),
self.get_inline_instances(request)):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(request.POST, request.FILES,
instance=new_object, prefix=prefix,
queryset=inline.queryset(request))
self._hack_inline_formset_initial(inline, FormSet, formset, obj, version, revert, recover)
# Add this hacked formset to the form.
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=True)
form.save_m2m()
for formset in formsets:
# HACK: If the value of a file field is None, remove the file from the model.
related_objects = formset.save(commit=False)
for related_obj, related_form in zip(related_objects, formset.saved_forms):
for field in related_obj._meta.fields:
if isinstance(field, models.FileField) and field.name in related_form.cleaned_data and related_form.cleaned_data[field.name] is None:
setattr(related_obj, field.name, None)
related_obj.save()
formset.save_m2m()
change_message = _("Reverted to previous version, saved on %(datetime)s") % {"datetime": localize(version.revision.date_created)}
self.log_change(request, new_object, change_message)
self.message_user(request, _(u'The %(model)s "%(name)s" was reverted successfully. You may edit it again below.') % {"model": force_text(opts.verbose_name), "name": force_text(obj)})
# Redirect to the model change form.
if revert:
return HttpResponseRedirect("../../")
elif recover:
return HttpResponseRedirect("../../%s/" % quote(object_id))
else:
assert False
else:
# This is a mutated version of the code in the standard model admin
# change_view. Once again, a hook for this kind of functionality
# would be nice. Unfortunately, it results in doubling the number
# of queries required to construct the formets.
form = ModelForm(instance=obj, initial=self.get_revision_form_data(request, obj, version))
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request, obj), self.get_inline_instances(request)):
# This code is standard for creating the formset.
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=obj, prefix=prefix,
queryset=inline.queryset(request))
self._hack_inline_formset_initial(inline, FormSet, formset, obj, version, revert, recover)
# Add this hacked formset to the form.
formsets.append(formset)
# Generate admin form helper.
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
self.prepopulated_fields, self.get_readonly_fields(request, obj),
model_admin=self)
media = self.media + adminForm.media
# Generate formset helpers.
inline_admin_formsets = []
for inline, formset in zip(self.get_inline_instances(request), formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
readonly = list(inline.get_readonly_fields(request, obj))
prepopulated = inline.get_prepopulated_fields(request, obj)
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
fieldsets, prepopulated, readonly, model_admin=self)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
# Generate the context.
context.update({"adminform": adminForm,
"object_id": object_id,
"original": obj,
"is_popup": False,
"media": mark_safe(media),
"inline_admin_formsets": inline_admin_formsets,
"errors": helpers.AdminErrorList(form, formsets),
"app_label": opts.app_label,
"add": False,
"change": True,
"revert": revert,
"recover": recover,
"has_add_permission": self.has_add_permission(request),
"has_change_permission": self.has_change_permission(request, obj),
"has_delete_permission": self.has_delete_permission(request, obj),
"has_file_field": True,
"has_absolute_url": False,
"form_url": mark_safe(request.path),
"opts": opts,
"content_type_id": ContentType.objects.get_for_model(self.model).id,
"save_as": False,
"save_on_top": self.save_on_top,
"changelist_url": reverse("%s:%s_%s_changelist" % (self.admin_site.name, opts.app_label, opts.module_name)),
"change_url": reverse("%s:%s_%s_change" % (self.admin_site.name, opts.app_label, opts.module_name), args=(quote(obj.pk),)),
"history_url": reverse("%s:%s_%s_history" % (self.admin_site.name, opts.app_label, opts.module_name), args=(quote(obj.pk),)),
"recoverlist_url": reverse("%s:%s_%s_recoverlist" % (self.admin_site.name, opts.app_label, opts.module_name))})
# Render the form.
if revert:
form_template = self.revision_form_template or self._get_template_list("revision_form.html")
elif recover:
form_template = self.recover_form_template or self._get_template_list("recover_form.html")
else:
assert False
return render_to_response(form_template, context, template.RequestContext(request))
@transaction.commit_on_success
def recover_view(self, request, version_id, extra_context=None):
"""Displays a form that can recover a deleted model."""
# check if user has change or add permissions for model
if not self.has_change_permission(request) and not self.has_add_permission(request):
raise PermissionDenied
version = get_object_or_404(Version, pk=version_id)
obj = version.object_version.object
context = {"title": _("Recover %(name)s") % {"name": version.object_repr},}
context.update(extra_context or {})
return self.render_revision_form(request, obj, version, context, recover=True)
@transaction.commit_on_success
def revision_view(self, request, object_id, version_id, extra_context=None):
"""Displays the contents of the given revision."""
# check if user has change or add permissions for model
if not self.has_change_permission(request):
raise PermissionDenied
object_id = unquote(object_id) # Underscores in primary key get quoted to "_5F"
obj = get_object_or_404(self.model, pk=object_id)
version = get_object_or_404(Version, pk=version_id, object_id=force_text(obj.pk))
# Generate the context.
context = {"title": _("Revert %(name)s") % {"name": force_text(self.model._meta.verbose_name)},}
context.update(extra_context or {})
return self.render_revision_form(request, obj, version, context, revert=True)
def changelist_view(self, request, extra_context=None):
"""Renders the change view."""
opts = self.model._meta
context = {"recoverlist_url": reverse("%s:%s_%s_recoverlist" % (self.admin_site.name, opts.app_label, opts.module_name)),
"add_url": reverse("%s:%s_%s_add" % (self.admin_site.name, opts.app_label, opts.module_name)),}
context.update(extra_context or {})
return super(VersionAdmin, self).changelist_view(request, context)
def history_view(self, request, object_id, extra_context=None):
"""Renders the history view."""
# check if user has change or add permissions for model
if not self.has_change_permission(request):
raise PermissionDenied
object_id = unquote(object_id) # Underscores in primary key get quoted to "_5F"
opts = self.model._meta
action_list = [
{
"revision": version.revision,
"url": reverse("%s:%s_%s_revision" % (self.admin_site.name, opts.app_label, opts.module_name), args=(quote(version.object_id), version.id)),
}
for version
in self._order_version_queryset(self.revision_manager.get_for_object_reference(
self.model,
object_id,
).select_related("revision__user"))
]
# Compile the context.
context = {"action_list": action_list}
context.update(extra_context or {})
return super(VersionAdmin, self).history_view(request, object_id, context)
class VersionMetaAdmin(VersionAdmin):
"""
An enhanced VersionAdmin that annotates the given object with information about
the last version that was saved.
"""
def queryset(self, request):
"""Returns the annotated queryset."""
content_type = ContentType.objects.get_for_model(self.model)
pk = self.model._meta.pk
if has_int_pk(self.model):
version_table_field = "object_id_int"
else:
version_table_field = "object_id"
return super(VersionMetaAdmin, self).queryset(request).extra(
select = {
"date_modified": """
SELECT MAX(%(revision_table)s.date_created)
FROM %(version_table)s
JOIN %(revision_table)s ON %(revision_table)s.id = %(version_table)s.revision_id
WHERE %(version_table)s.content_type_id = %%s AND %(version_table)s.%(version_table_field)s = %(table)s.%(pk)s
""" % {
"revision_table": connection.ops.quote_name(Revision._meta.db_table),
"version_table": connection.ops.quote_name(Version._meta.db_table),
"table": connection.ops.quote_name(self.model._meta.db_table),
"pk": connection.ops.quote_name(pk.db_column or pk.attname),
"version_table_field": connection.ops.quote_name(version_table_field),
}
},
select_params = (content_type.id,),
)
def get_date_modified(self, obj):
"""Displays the last modified date of the given object, typically for use in a change list."""
return localize(obj.date_modified)
get_date_modified.short_description = "date modified"
django-reversion-release-1.8/src/reversion/helpers.py 0000664 0000000 0000000 00000005766 12236676246 0023127 0 ustar 00root root 0000000 0000000 """A number of useful helper functions to automate common tasks."""
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.admin.sites import NotRegistered
from django.utils.encoding import force_text
from reversion.admin import VersionAdmin
def patch_admin(model, admin_site=None):
"""
Enables version control with full admin integration for a model that has
already been registered with the django admin site.
This is excellent for adding version control to existing Django contrib
applications.
"""
admin_site = admin_site or admin.site
try:
ModelAdmin = admin_site._registry[model].__class__
except KeyError:
raise NotRegistered("The model {model} has not been registered with the admin site.".format(
model = model,
))
# Unregister existing admin class.
admin_site.unregister(model)
# Register patched admin class.
class PatchedModelAdmin(VersionAdmin, ModelAdmin):
pass
admin_site.register(model, PatchedModelAdmin)
# Patch generation methods, only available if the google-diff-match-patch
# library is installed.
#
# http://code.google.com/p/google-diff-match-patch/
try:
from diff_match_patch import diff_match_patch
except ImportError:
pass
else:
dmp = diff_match_patch()
def generate_diffs(old_version, new_version, field_name, cleanup):
"""Generates a diff array for the named field between the two versions."""
# Extract the text from the versions.
old_text = old_version.field_dict[field_name] or ""
new_text = new_version.field_dict[field_name] or ""
# Generate the patch.
diffs = dmp.diff_main(force_text(old_text), force_text(new_text))
if cleanup == "semantic":
dmp.diff_cleanupSemantic(diffs)
elif cleanup == "efficiency":
dmp.diff_cleanupEfficiency(diffs)
elif cleanup is None:
pass
else:
raise ValueError("cleanup parameter should be one of 'semantic', 'efficiency' or None.")
return diffs
def generate_patch(old_version, new_version, field_name, cleanup=None):
"""
Generates a text patch of the named field between the two versions.
The cleanup parameter can be None, "semantic" or "efficiency" to clean up the diff
for greater human readibility.
"""
diffs = generate_diffs(old_version, new_version, field_name, cleanup)
patch = dmp.patch_make(diffs)
return dmp.patch_toText(patch)
def generate_patch_html(old_version, new_version, field_name, cleanup=None):
"""
Generates a pretty html version of the differences between the named
field in two versions.
The cleanup parameter can be None, "semantic" or "efficiency" to clean up the diff
for greater human readibility.
"""
diffs = generate_diffs(old_version, new_version, field_name, cleanup)
return dmp.diff_prettyHtml(diffs)
django-reversion-release-1.8/src/reversion/locale/ 0000775 0000000 0000000 00000000000 12236676246 0022334 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/src/reversion/locale/cs/ 0000775 0000000 0000000 00000000000 12236676246 0022741 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/src/reversion/locale/cs/LC_MESSAGES/ 0000775 0000000 0000000 00000000000 12236676246 0024526 5 ustar 00root root 0000000 0000000 django-reversion-release-1.8/src/reversion/locale/cs/LC_MESSAGES/django.mo 0000664 0000000 0000000 00000004575 12236676246 0026340 0 ustar 00root root 0000000 0000000 l L Q ; B D D U n 3 P ( < X e u 9 D J N
B B V ! ) . 7 ? ` w &