Explorar o código

HUE-724 [core] Integrate Django messages for notifications

Adding a middleware for easing the sending of notifications
Testing it in useradmin when editing a user
Romain Rigaux %!s(int64=13) %!d(string=hai) anos
pai
achega
fb9491b8a0

+ 6 - 5
apps/beeswax/src/beeswax/views.py

@@ -22,6 +22,7 @@ import logging
 import re
 
 from django import forms
+from django.contrib import messages
 from django.core import urlresolvers
 from django.db.models import Q
 from django.http import HttpResponse, QueryDict
@@ -221,7 +222,7 @@ def safe_get_design(request, design_type, design_id=None):
   """
   Return a new design, if design_id is None,
   Return the design with the given id and type. If the design is not found,
-  flash a message and return a new design.
+  display a notification and return a new design.
   """
   design = None
 
@@ -229,7 +230,7 @@ def safe_get_design(request, design_type, design_id=None):
     try:
       design = models.SavedQuery.get(design_id, request.user, design_type)
     except models.SavedQuery.DoesNotExist:
-      request.flash.put(_t('Design does not exist'))
+      messages.error(request, _t('Design does not exist'))
   if design is None:
     design = models.SavedQuery(owner=request.user, type=design_type)
   return design
@@ -542,7 +543,7 @@ def save_design(request, form, type, design, explicit_save):
   LOG.info('Saved %sdesign "%s" (id %s) for %s' %
            (explicit_save and '' or 'auto ', design.name, design.id, design.owner))
   if explicit_save:
-    request.flash.put(_t('Saved design "%(name)s"') % {'name': design.name})
+    messages.error(request, _t('Saved design "%(name)s"') % {'name': design.name})
   # Design may now have a new/different id
   return design
 
@@ -676,7 +677,7 @@ def clone_design(request, design_id):
   copy.name = design.name + ' (copy)'
   copy.owner = request.user
   copy.save()
-  request.flash.put(_t('Copied design: %(name)s') % {'name': design.name})
+  messages.error(request, _t('Copied design: %(name)s') % {'name': design.name})
   return format_preserving_redirect(
       request, urlresolvers.reverse(execute_query, kwargs={'design_id': copy.id}))
 
@@ -1079,7 +1080,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
     table_loc = request.fs.urlsplit(table_obj.sd.location)[2]
     request.fs.rename_star(result_meta.table_dir, table_loc)
     LOG.debug("Moved results from %s to %s" % (result_meta.table_dir, table_loc))
-    request.flash.put(_t('Saved query results as new table %(table)s') % {'table': target_table})
+    messages.error(request, _t('Saved query results as new table %(table)s') % {'table': target_table})
     query_history.save_state(models.QueryHistory.STATE.expired)
   except Exception, ex:
     LOG.error('Error moving data into storage of table %s. Will drop table.' % (target_table,))

+ 2 - 1
apps/filebrowser/src/filebrowser/views.py

@@ -29,6 +29,7 @@ import stat as stat_module
 import os
 import simplejson
 
+from django.contrib import messages
 from django.core import urlresolvers
 from django.http import Http404, HttpResponse, HttpResponseNotModified
 from django.views.static import was_modified_since
@@ -212,7 +213,7 @@ def save_file(request):
                          form.cleaned_data['contents'],
                          form.cleaned_data['encoding'])
 
-    request.flash.put(_('Saved %(path)s.') % {'path': os.path.basename(path)})
+    messages.info(request, _('Saved %(path)s.') % {'path': os.path.basename(path)})
     """ Changing path to reflect the request path of the JFrame that will actually be returned."""
     request.path = urlresolvers.reverse("filebrowser.views.edit", kwargs=dict(path=path))
     return edit(request, path, form)

+ 1 - 1
apps/useradmin/src/useradmin/templates/list_users.mako

@@ -153,4 +153,4 @@ ${layout.menubar(section='users', _=_)}
         });
     </script>
 
-${commonfooter()}
+${commonfooter(messages)}

+ 5 - 2
apps/useradmin/src/useradmin/tests.py

@@ -227,7 +227,7 @@ def test_group_admin():
                     members=[User.objects.get(username="test").pk],
                     save="Save"), follow=True)
   assert_true("You must be a superuser" in response.content)
- 
+
   # Should be one group left, because we created the other group
   response = c.post('/useradmin/groups/delete/testgroup')
   assert_true(len(Group.objects.all()) == 1)
@@ -259,7 +259,10 @@ def test_user_admin():
                          first_name=u"Inglés",
                          last_name=u"Español",
                          is_superuser="True",
-                         is_active="True"))
+                         is_active="True"),
+                    follow=True)
+  assert_true("User information updated" in response.content,
+              "Notification should be displayed in: %s" % response.content)
   # Now make sure that those were materialized
   response = c.get('/useradmin/users/edit/test')
   assert_equal(smart_unicode("Inglés"), response.context["form"].instance.first_name)

+ 6 - 5
apps/useradmin/src/useradmin/views.py

@@ -36,7 +36,7 @@ from useradmin.models import GroupPermission, HuePermission, UserProfile, LdapGr
 from useradmin.models import get_profile, get_default_user_group
 import ldap_access
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext as _
 
 LOG = logging.getLogger(__name__)
 
@@ -69,7 +69,7 @@ def delete_user(request, username):
       finally:
         __users_lock.release()
 
-      # Send a flash message saying "deleted"?
+      request.info(_('The user was deleted !'))
       return redirect(reverse(list_users))
     except User.DoesNotExist:
       raise PopupException(_("User not found."))
@@ -95,7 +95,7 @@ def delete_group(request, name):
       finally:
         __groups_lock.release()
 
-      # Send a flash message saying "deleted"?
+      request.info(_('The group was deleted !'))
       return redirect(reverse(list_groups))
     except Group.DoesNotExist:
       raise PopupException(_("Group not found."))
@@ -211,6 +211,7 @@ def edit_user(request, username=None):
 
           # All ok
           form.save()
+          request.info(_('User information updated'))
         finally:
           __users_lock.release()
 
@@ -242,7 +243,7 @@ def edit_group(request, name=None):
     form = GroupEditForm(request.POST, instance=instance)
     if form.is_valid():
       form.save()
-      request.flash.put('Group information updated')
+      request.info(_('Group information updated'))
       return list_groups(request)
 
   else:
@@ -271,7 +272,7 @@ def edit_permission(request, app=None, priv=None):
     form = PermissionsEditForm(request.POST, instance=instance)
     if form.is_valid():
       form.save()
-      request.flash.put('Permission information updated')
+      request.info(_('Permission information updated'))
       return render("list_permissions.mako", request, dict(permissions=HuePermission.objects.all()))
 
   else:

+ 24 - 54
desktop/core/src/desktop/middleware.py

@@ -21,6 +21,7 @@ import re
 import tempfile
 
 from django.conf import settings
+from django.contrib import messages
 from django.contrib.auth import REDIRECT_FIELD_NAME
 from django.core import exceptions, urlresolvers
 import django.db
@@ -29,7 +30,6 @@ from django.utils.http import urlquote
 from django.utils.encoding import iri_to_uri
 import django.views.static
 import django.views.generic.simple
-import django.contrib.auth.views
 
 import desktop.conf
 from desktop.lib import apputil, i18n
@@ -144,6 +144,29 @@ class ClusterMiddleware(object):
       request.jt = None
 
 
+class NotificationMiddleware(object):
+  """
+  Manages setting request.info and request.error
+  """
+  def process_view(self, request, view_func, view_args, view_kwargs):
+
+    def message(title, detail=None):
+      if detail is None:
+        detail = ''
+      else:
+        detail = '<br/>%s' % detail
+      return '%s %s' % (title, detail)
+
+    def info(title, detail=None):
+      messages.info(request, message(title, detail))
+
+    def error(title, detail=None):
+      messages.error(request, message(title, detail))
+
+    request.info = info
+    request.error = error
+
+
 class AppSpecificMiddleware(object):
   @classmethod
   def augment_request_with_app(cls, request, view_func):
@@ -330,59 +353,6 @@ class SessionOverPostMiddleware(object):
       request.COOKIES[cookie_key] = request.POST[cookie_key]
       del request.POST[cookie_key]
 
-class FlashMessageMiddleware(object):
-  """
-  Builds request.flash for manipulating flash messages.
-  request.flash is an instance of the FlashMessenger object.
-  """
-  def process_request(self, request):
-    assert not hasattr(request, "flash")
-    request.flash = FlashMessenger(request)
-    return None
-
-  def process_exception(self, request, exception):
-    request.flash.warn_if_non_empty()
-    return None
-
-  def process_response(self, request, response):
-    # This seems to happen if common middleware's "trailing slash" support gets
-    # activated.
-    if not hasattr(request, "flash"):
-      return response
-
-    request.flash.warn_if_non_empty()
-    return response
-
-class FlashMessenger(object):
-  """
-  Manages storing and rendering flash messages.
-  """
-
-  def __init__(self,request):
-    self.request = request
-
-  def put(self, text):
-    """
-    Places flash messages to be rendered. Note that this will not be HTML-escaped.
-    """
-    self.request.session.setdefault("flashMessages", []).append(text)
-    self.request.session.modified = True
-
-  def get(self):
-    """
-    Gets messages to be displayed, only messages that have not been displayed prior.
-    Always returns a list, though that list may be empty.
-    """
-    messages = self.request.session.get('flashMessages')
-    if messages is not None:
-      del self.request.session['flashMessages']
-      return messages
-    else:
-      return []
-
-  def warn_if_non_empty(self):
-    if self.request.session.get('flashMessages') is not None and len(self.request.session['flashMessages']) > 0:
-      logging.warning("Returning request with unmaterialized flash messages: %s" % repr(self.request.session['flashMessages']))
 
 class DatabaseLoggingMiddleware(object):
   """

+ 2 - 1
desktop/core/src/desktop/settings.py

@@ -111,7 +111,8 @@ MIDDLEWARE_CLASSES = [
     'desktop.middleware.AjaxMiddleware',
     # Must be after Session, Auth, and Ajax.  Before everything else.
     'desktop.middleware.LoginAndPermissionMiddleware',
-    'desktop.middleware.FlashMessageMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'desktop.middleware.NotificationMiddleware',
     'desktop.middleware.JFrameMiddleware',
     'desktop.middleware.ExceptionMiddleware',
     'desktop.middleware.ClusterMiddleware',

+ 13 - 0
desktop/core/src/desktop/templates/common_footer.html

@@ -16,5 +16,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 {% endcomment %}
 
+
+{% if messages %}
+    <script>
+      {% for message in messages %}
+          {% if message.tags == 'error' %}
+              $.jHueNotify.error('{{ message|safe }}');
+          {% else %}
+              $.jHueNotify.info('{{ message|safe }}');
+          {% endif %}
+      {% endfor %}
+    </script>
+{% endif %}
+
 	</body>
 </html>

+ 4 - 2
desktop/core/src/desktop/views.py

@@ -276,11 +276,13 @@ def commonheader(title, section, padding="60px"):
     padding=padding
   ))
 
-def commonfooter():
+def commonfooter(messages=None):
   """
   Returns the rendered common footer
   """
-  return render_to_string("common_footer.html")
+  if messages is None:
+    messages = {}
+  return render_to_string("common_footer.html", {'messages': messages})
 
 def commonheader_iframe():
   """