Repository: django-cms/django-cms

Issue:
AttributeError when not using django-reversion (CMS 3)
Currently in the pageadmin.py we have 

``` python
if 'reversion' in settings.INSTALLED_APPS:
    from reversion.admin import VersionAdmin as ModelAdmin
    from reversion import create_revision
else:  # pragma: no cover
    from django.contrib.admin import ModelAdmin

    create_revision = lambda: lambda x: x
```

which means that when django reversion is not installed create_revision points to a lambda function that does not follow the context manager protocol which causes an `AttributeError __exit__` when using it with the `with` statement.



Discussion:

Resolving patch:
diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py
--- a/cms/admin/pageadmin.py
+++ b/cms/admin/pageadmin.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from distutils.version import LooseVersion
+from functools import wraps
 import sys
 from cms.admin.placeholderadmin import PlaceholderAdmin
 from cms.plugin_pool import plugin_pool
@@ -53,7 +54,34 @@
 else:  # pragma: no cover
     from django.contrib.admin import ModelAdmin
 
-    create_revision = lambda: lambda x: x
+    class ReversionContext(object):
+        def __enter__(self):
+            yield
+
+        def __exit__(self, exc_type, exc_val, exc_tb):
+            pass
+
+        def __call__(self, func):
+            """Allows this revision context to be used as a decorator."""
+            @wraps(func)
+            def do_revision_context(*args, **kwargs):
+                self.__enter__()
+                exception = False
+                try:
+                    try:
+                        return func(*args, **kwargs)
+                    except:
+                        exception = True
+                        if not self.__exit__(*sys.exc_info()):
+                            raise
+                finally:
+                    if not exception:
+                        self.__exit__(None, None, None)
+            return do_revision_context
+
+
+    def create_revision():
+        return ReversionContext()
 
 PUBLISH_COMMENT = "Publish"
 
@@ -1160,7 +1188,7 @@ def copy_plugins(self, *args, **kwargs):
 
     def edit_plugin(self, *args, **kwargs):
         with create_revision():
-            return super(PageAdmin, self).edit_plugin(*args, **kwargs)
+           return super(PageAdmin, self).edit_plugin(*args, **kwargs)
 
     def move_plugin(self, *args, **kwargs):
         with create_revision():
diff --git a/cms/toolbar/items.py b/cms/toolbar/items.py
--- a/cms/toolbar/items.py
+++ b/cms/toolbar/items.py
@@ -237,7 +237,7 @@ class SideframeItem(BaseItem):
     def __init__(self, name, url, active=False, disabled=False, extra_classes=None, close_on_url=None,
                  on_close=None, side=LEFT):
         super(SideframeItem, self).__init__(side)
-        self.name = name
+        self.name = "%s ..." % force_unicode(name)
         self.url = url
         self.active = active
         self.disabled = disabled
@@ -300,7 +300,7 @@ class ModalItem(BaseItem):
     def __init__(self, name, url, active=False, disabled=False, extra_classes=None, close_on_url=URL_CHANGE,
                  on_close=None, side=LEFT):
         super(ModalItem, self).__init__(side)
-        self.name = name
+        self.name = "%s ..." % force_unicode(name)
         self.url = url
         self.active = active
         self.disabled = disabled


Test patch:
diff --git a/cms/tests/reversion_tests.py b/cms/tests/reversion_tests.py
--- a/cms/tests/reversion_tests.py
+++ b/cms/tests/reversion_tests.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
 from __future__ import with_statement
 import shutil
-
+from cms.admin import pageadmin
+from django.contrib import admin
 from cms.models import Page, Title
 from cms.models.pluginmodel import CMSPlugin
 from djangocms_text_ckeditor.models import Text
@@ -63,7 +64,8 @@ def setUp(self):
             response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
             self.assertEquals(response.status_code, 200)
             # now edit the plugin
-            edit_url = URL_CMS_PLUGIN_EDIT + response.content.decode('utf8').split("edit-plugin/")[1].split("/")[0] + "/"
+            edit_url = URL_CMS_PLUGIN_EDIT + response.content.decode('utf8').split("edit-plugin/")[1].split("/")[
+                0] + "/"
             response = self.client.get(edit_url)
             self.assertEquals(response.status_code, 200)
             response = self.client.post(edit_url, {"body": "Hello World"})
@@ -159,7 +161,8 @@ def test_undo_redo(self):
             response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
             self.assertEquals(response.status_code, 200)
             # now edit the plugin
-            edit_url = URL_CMS_PLUGIN_EDIT + response.content.decode('utf8').split("edit-plugin/")[1].split("/")[0] + "/"
+            edit_url = URL_CMS_PLUGIN_EDIT + response.content.decode('utf8').split("edit-plugin/")[1].split("/")[
+                0] + "/"
             response = self.client.get(edit_url)
             self.assertEquals(response.status_code, 200)
             response = self.client.post(edit_url, {"body": "Hello World"})
@@ -169,7 +172,6 @@ def test_undo_redo(self):
             response = self.client.post(undo_url)
             self.assertEqual(2, CMSPlugin.objects.all().count())
 
-
     def test_recover(self):
         """
         Test that you can recover a page