由于多次涉及到了这个东东,又不是很理解机制,决定深入研究下。
what
django-revision到底啥玩意?readthedocs上只有一句话概括:django-reversion can be used to add a powerful rollback and recovery facility to your admin site
而且只是说明了能干嘛:给admin后台管理提供强大的回滚和恢复功能。
下面我们来看看,啥叫作强大的回滚和恢复。
首先,新建一个项目: test_revision,过程略。
新增模型: articles,包含以下字段:
序号 | 栏位 | 说明 |
1 | title | 标题 |
2 | content | 内容 |
3 | author | 作者 |
4 | status | 状态 |
5 | create_date | 创建日期 |
6 | publish_date | 发布日期 |
并注册到管理后台。因为只需要看后台,所以views/template/forms全部略。只需要写models 和 admin.py 如下:
models.py
from django.db import models # Create your models here. class article(models.Model): title = models.CharField(max_length=128) content = models.TextField() author = models.CharField(max_length=32) status_list=(('draft','draft'),('pending','pending'),('published','published')) status = models.CharField(max_length=16, choices=status_list) create_date = models.DateTimeField(auto_now_add=True) publish_date = models.DateTimeField()
admin.py
from django.contrib import admin import reversion from .models import article # Register your models here. class articleAdmin(reversion.VersionAdmin): list_display = ("id", "title", "author", "status","create_date") admin.site.register(article,articleAdmin)
project文件夹结构:
settings.py的installed_app中添加了“reversion",它涉及到的表是reversion_revision/ reversion_version,如下:
再把test_app加入到installed_app中并syncdb一下,过程略,直接runserver,查看效果,如下:
ok,测试下,
1). 回滚操作:
2). 数据恢复操作:
点击"recover deleted articles"(右侧),选择某个时间对应的对象进入详细,要恢复点击保存即可,如下:
awesome and impressive!
How?
原理:
权威解释看这里:http://django-reversion.readthedocs.org/en/latest/how-it-works.html
这里简单总结下:
被注册,标记,子类化的模型,在其被create/update时会在version表里面做相应的记录,记录的内容就是模型的数据序列化后的json表达式。当需要恢复时,将json表达式反序列化出来,填充到表单里面去,让用户自己选择回滚到哪。至于恢复,则是恢复到最后一次操作后的状态。
如何使用:
以下方式都是在Admin.py中进行。
1. 继承方式,需要继承reversion.VersionAdmin,适用于新开发的app,如下:
import reversion class YourModelAdmin(reversion.VersionAdmin): pass admin.site.register(YourModel, YourModelAdmin)
2. patch_admin注册方式,适用于已存在的尚未使用reversion的app,如下:
from reversion.helpers import patch_admin patch_admin(User)
对于尚未在admin.py中注册或者继承versionAdmin的模型,如果要实现版本控制有两个选择:
1). 在admin.py中注册或者继承VersionAdmin
2). 使用reversion的api进行控制,
使用reversion.register()进行注册/修饰对应的模型类.
具体参考这里。
后记
发现一个有意思的问题,对于一个已经使用了reversion支持的模型:如果非admin后台修改了记录(update/delete),则delete的数据可以还原,update则无记录。实现如下:
views.py
from django.shortcuts import render from django.views.generic.edit import UpdateView,DeleteView from .models import article from django.shortcuts import render_to_response # Create your views here. class articleDV(DeleteView): model = article success_url = "/admin/test_app/article/" template_name = "article_delete.html" class articleUV(UpdateView): model = article success_url = "/admin/test_app/article/" template_name = "article_update.html"
解决方案:
适用于django-reversion 1.8.4
采用中间件(可以不用在admin里面继承VersionAdmin),在installed_app中加入中间件"reversion.middleware.RevisionMiddleware",同时修改django-reversion的reversion.py文件(windows则在C:Python27Libsite-packagesdjango_reversion-1.8.4-py2.7.egg eversion evisions.py)的171~182行(原码如下):
manager.save_revision( dict( (obj, callable(data) and data() or data) for obj, data in manager_context.items() ), user = self._user, comment = self._comment, meta = self._meta, ignore_duplicates = self._ignore_duplicates, db = self._db, )
其中, self._user丢失,原因貌似由于多线程(至少有两个线程执行中间件的process_response方法),导致在实际写入数据的时候,self._user还没赋值,虽然逻辑上是先赋值给revision_context_manager._user(middleware.py 33~36行),后执行此段代码。鉴于这点,还需要修改middleware.py(C:Python27Libsite-packagesdjango_reversion-1.8.4-py2.7.egg eversionmiddleware.py)的类RevisionMiddleware的_close_revision方法,其原码如下:
def _close_revision(self, request): """Closes the revision.""" if request.META.get(REVISION_MIDDLEWARE_FLAG, False): del request.META[REVISION_MIDDLEWARE_FLAG] revision_context_manager.end()
新代码:
middleware.py(24~28)
def _close_revision(self, request): """Closes the revision.""" if request.META.get(REVISION_MIDDLEWARE_FLAG, False): del request.META[REVISION_MIDDLEWARE_FLAG] revision_context_manager.end(request=request)
即:将request传入到reversion.py的RevisionContextManager类的end方法。
reversion.py(171~182)
manager.save_revision( dict( (obj, callable(data) and data() or data) for obj, data in manager_context.items() ), user = self._user or (hasattr(request, "user") and request.user), comment = self._comment, meta = self._meta, ignore_duplicates = self._ignore_duplicates, db = self._db, )
即:重写user的赋值逻辑。
reversion.py(162行),函数定义改为:
def end(self,request=None):
即增加一个参数: request