Cache access checks and disable sidebar caching
authorSverre Rabbelier <srabbelier@gmail.com>
Tue, 27 Jan 2009 22:59:01 +0000
changeset 1017 6ad4fdb48840
parent 1016 15a2f644725f
child 1018 02ffb446da6e
Cache access checks and disable sidebar caching Patch by: Sverre Rabbelier
app/soc/cache/base.py
app/soc/cache/sidebar.py
app/soc/views/helper/access.py
app/soc/views/helper/decorators.py
app/soc/views/helper/responses.py
app/soc/views/models/base.py
app/soc/views/models/group.py
app/soc/views/models/program.py
app/soc/views/models/site.py
app/soc/views/models/user_self.py
app/soc/views/out_of_band.py
app/soc/views/sitemap/sidebar.py
--- a/app/soc/cache/base.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/cache/base.py	Tue Jan 27 22:59:01 2009 +0000
@@ -43,7 +43,7 @@
         return result
 
       result = func(*args, **kwargs)
-      put(result)
+      put(result, *args, **kwargs)
       return result
 
     return wrapper
--- a/app/soc/cache/sidebar.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/cache/sidebar.py	Tue Jan 27 22:59:01 2009 +0000
@@ -28,34 +28,33 @@
 import soc.cache.base
 
 
-def key(user):
+def key(id):
   """Returns the memcache key for the user's sidebar
   """
 
-  return 'sidebar_for_%s' % repr(user)
+  return 'sidebar_for_%s' % repr(id)
 
 
-def get():
+def get(id, user):
   """Retrieves the sidebar for the specified user from the memcache
   """
 
-  user = users.get_current_user()
-  return memcache.get(key(user))
+  memcache_key = key(id)
+  return memcache.get(memcache_key)
 
 
-def put(sidebar):
+def put(sidebar, id, user):
   """Sets the sidebar for the specified user in the memcache
 
   Args:
     sidebar: the sidebar to be cached
   """
 
-  # Store sidebar for an hour since new programs might get added
-  # etc. etc.
-  retention = 60*60
+  # Store sidebar for ten minutes since new programs might get added
+  retention = 10*60
 
-  user = users.get_current_user()
-  memcache.add(key(user), sidebar, retention)
+  memcache_key = key(id)
+  memcache.add(memcache_key, sidebar, retention)
 
 
 def flush(user=None):
@@ -68,7 +67,8 @@
   if not user:
     user = users.get_current_user()
 
-  memcache.delete(key(user))
+  memcache_key = key(user)
+  memcache.delete(memcache_key)
 
 
 # define the cache function
--- a/app/soc/views/helper/access.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/helper/access.py	Tue Jan 27 22:59:01 2009 +0000
@@ -32,6 +32,7 @@
 
 
 from google.appengine.api import users
+from google.appengine.api import memcache
 
 from django.core import urlresolvers
 from django.utils.translation import ugettext
@@ -117,6 +118,9 @@
 
     base = params.get('rights') if params else None
     self.rights = base.rights if base else {}
+    self.id = None
+    self.user = None
+    self.cached_rights = {}
 
   def __setitem__(self, key, value):
     """Sets a value only if no old value exists.
@@ -140,14 +144,81 @@
       # Be nice an repack so that it is always a list with tuples
       if isinstance(i, tuple):
         name, arg = i
-        tmp = (getattr(self, name), (arg if isinstance(arg, list) else [arg]))
+        tmp = (name, (arg if isinstance(arg, list) else [arg]))
         result.append(tmp)
       else:
-        tmp = (getattr(self, i), [])
+        tmp = (i, [])
         result.append(tmp)
 
     return result
 
+  def key(self, checker_name):
+    """Returns the key for the specified checker for the current user.
+    """
+
+    return "%s.%s" % (self.id, checker_name)
+
+  def put(self, checker_name, value):
+    """Puts the result for the specified checker in the cache.
+    """
+
+    retention = 30
+
+    memcache_key = self.key(checker_name)
+    memcache.add(memcache_key, value, retention)
+
+  def get(self, checker_name):
+    """Retrieves the result for the specified checker from cache.
+    """
+
+    memcache_key = self.key(checker_name)
+    return memcache.get(memcache_key)
+
+  def doCheck(self, checker_name, django_args, args):
+    """Runs the specified checker with the specified arguments.
+    """
+
+    checker = getattr(self, checker_name)
+    checker(django_args, *args)
+
+  def doCachedCheck(self, checker_name, django_args, args):
+    """Retrieves from cache or runs the specified checker.
+    """
+
+    cached = self.get(checker_name)
+
+    if cached is None:
+      try:
+        self.doCheck(checker_name, django_args, args)
+        self.put(checker_name, True)
+        return
+      except out_of_band.Error, e:
+        self.put(checker_name, e)
+        raise
+
+    if cached is True:
+      return
+
+    # re-raise the cached exception
+    raise cached
+
+  def check(self, use_cache, checker_name, django_args, args):
+    """Runs the checker, optionally using the cache.
+    """
+
+    if use_cache:
+      self.doCachedCheck(checker_name, django_args, args)
+    else:
+      self.doCheck(checker_name, django_args, args)
+
+  def setCurrentUser(self, id, user):
+    """Sets up everything for the current user.
+    """
+
+    self.id = id
+    self.user = user
+    self.cached_rights = {}
+
   def checkAccess(self, access_type, django_args):
     """Runs all the defined checks for the specified type.
 
@@ -167,20 +238,20 @@
       value are called.
     """
 
-    self.id = users.get_current_user()
+    use_cache = django_args.get('SIDEBAR_CALLING')
 
     # Call each access checker
-    for check, args in self['any_access']:
-      check(django_args, *args)
+    for checker_name, args in self['any_access']:
+      self.check(use_cache, checker_name, django_args, args)
 
     if access_type not in self.rights:
-      for check, args in self['unspecified']:
-        # No checks defined, so do the 'generic' checks and bail out
-        check(django_args, *args)
+      # No checks defined, so do the 'generic' checks and bail out
+      for checker_name, args in self['unspecified']:
+        self.check(use_cache, checker_name, django_args, args)
       return
 
-    for check, args in self[access_type]:
-      check(django_args, *args)
+    for checker_name, args in self[access_type]:
+      self.check(use_cache, checker_name, django_args, args)
 
   def allow(self, django_args):
     """Never raises an alternate HTTP response.  (an access no-op, basically).
@@ -253,12 +324,10 @@
 
     self.checkIsLoggedIn(django_args)
 
-    user = user_logic.getForCurrentAccount()
-
-    if not user:
+    if not self.user:
       raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG_FMT)
 
-    if user_logic.agreesToSiteToS(user):
+    if user_logic.agreesToSiteToS(self.user):
       return
 
     # Would not reach this point of site-wide ToS did not exist, since
--- a/app/soc/views/helper/decorators.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/helper/decorators.py	Tue Jan 27 22:59:01 2009 +0000
@@ -97,10 +97,16 @@
     check_kwargs = kwargs.copy()
     context = responses.getUniversalContext(request)
 
+    id = context['account']
+    user = context['user']
+
     check_kwargs['GET'] = request.GET
     check_kwargs['POST'] = request.POST
     check_kwargs['context'] = context
 
+    # reset and pre-fill the Checker's cache
+    rights.setCurrentUser(id, user)
+
     # Do the access check dance
     try:
       rights.checkAccess(access_type, check_kwargs)
--- a/app/soc/views/helper/responses.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/helper/responses.py	Tue Jan 27 22:59:01 2009 +0000
@@ -96,21 +96,26 @@
   """
 
   account = users.get_current_user()
+  user = None
+  is_admin = False
 
   context = {}
   context['request'] = request
 
   if account:
-    context['account'] = account
-    context['user'] = soc.logic.models.user.logic.getForFields(
+    user = soc.logic.models.user.logic.getForFields(
         {'account': account}, unique=True)
-    context['is_admin'] = accounts.isDeveloper(account=account)
+    is_admin = accounts.isDeveloper(account=account)
+
+  context['account'] = account
+  context['user'] = user
+  context['is_admin'] = is_admin
 
   context['is_debug'] = system.isDebug()
   context['sign_in'] = users.create_login_url(request.path)
   context['sign_out'] = users.create_logout_url(request.path)
 
-  context['sidebar_menu_items'] = sidebar.getSidebar()
+  context['sidebar_menu_items'] = sidebar.getSidebar(account, user)
 
   context['soc_release'] = release.RELEASE_TAG
   context['gae_version'] = system.getAppVersion()
--- a/app/soc/views/models/base.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/models/base.py	Tue Jan 27 22:59:01 2009 +0000
@@ -726,7 +726,7 @@
     return self._params
 
   @decorators.merge_params
-  def getSidebarMenus(self, params=None):
+  def getSidebarMenus(self, id, user, params=None):
     """Returns an dictionary with one sidebar entry.
 
     Args:
@@ -738,7 +738,7 @@
       of _getSidebarItems on how it uses it.
     """
 
-    return sitemap.sidebar.getSidebarMenus(params=params)
+    return sitemap.sidebar.getSidebarMenus(id, user, params=params)
 
   @decorators.merge_params
   def getDjangoURLPatterns(self, params=None):
--- a/app/soc/views/models/group.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/models/group.py	Tue Jan 27 22:59:01 2009 +0000
@@ -262,7 +262,7 @@
     role_views = self._params['role_views']
     role_views[role_name] = role_view
 
-  def getExtraMenus(self, params=None):
+  def getExtraMenus(self, id, user, params=None):
     """Returns the extra menu's for this view.
 
     A menu item is generated for each group that the user has an active
@@ -276,11 +276,8 @@
     params = dicts.merge(params, self._params)
     logic = params['logic']
 
-    # get the current user
-    user_entity = user_logic.logic.getForCurrentAccount()
-
     # set fields to match every active role this user has
-    fields = {'user': user_entity,
+    fields = {'user': user,
               'state' : 'active'}
 
     # get the role views and start filling group_entities
--- a/app/soc/views/models/program.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/models/program.py	Tue Jan 27 22:59:01 2009 +0000
@@ -116,7 +116,7 @@
     timeline = timeline_logic.updateOrCreateFromFields(properties, properties)
     return timeline
 
-  def getExtraMenus(self, params=None):
+  def getExtraMenus(self, id, user, params=None):
     """Returns the extra menu's for this view.
 
     A menu item is generated for each program that is currently
--- a/app/soc/views/models/site.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/models/site.py	Tue Jan 27 22:59:01 2009 +0000
@@ -87,13 +87,13 @@
 
     super(View, self).__init__(params=params)
 
-  def getSidebarMenus(self, params=None):
+  def getSidebarMenus(self, id, user, params=None):
     """See base.View.getSidebarMenus.
 
     Returns a custom sidebar entry for the 'site' singleton.
     """
 
-    entity = self._logic.getFromFields(link_id=self._logic.DEF_SITE_LINK_ID)
+    entity = self._logic.getSingleton()
 
     submenus = []
 
@@ -104,7 +104,7 @@
     new_params['sidebar_additional'] = submenus
 
     params = dicts.merge(params, new_params)
-    return super(View, self).getSidebarMenus(params=params)
+    return super(View, self).getSidebarMenus(id, user, params=params)
 
   def mainPublic(self, request, page_name=None, **kwargs):
     """Displays the main site settings page.
--- a/app/soc/views/models/user_self.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/models/user_self.py	Tue Jan 27 22:59:01 2009 +0000
@@ -252,14 +252,12 @@
 
     super(View, self)._editPost(request, entity, fields)
 
-  def getSidebarMenus(self, params=None):
+  def getSidebarMenus(self, id, user, params=None):
     """See base.View.getSidebarMenus().
     """
 
     link_title = ugettext('Notifications')
 
-    user = user_logic.getForCurrentAccount()
-
     filter = {
         'scope': user,
         'unread': True,
@@ -279,7 +277,7 @@
 
     params = dicts.merge(params, new_params)
 
-    return super(View, self).getSidebarMenus(params=params)
+    return super(View, self).getSidebarMenus(id, user, params=params)
 
 
 view = View()
--- a/app/soc/views/out_of_band.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/out_of_band.py	Tue Jan 27 22:59:01 2009 +0000
@@ -32,7 +32,7 @@
   TEMPLATE_NAME = 'error.html'
   DEF_TEMPLATE = 'soc/error.html'
 
-  def __init__(self, message_fmt, context=None, **response_args):
+  def __init__(self, message_fmt=None, context=None, **response_args):
     """Constructor used to set response message and HTTP response arguments.
   
     Args:
@@ -46,6 +46,9 @@
         set the HTTP status code for the response
     """
 
+    if not message_fmt:
+      message_fmt = ""
+
     self.message_fmt = message_fmt
     self.context = context
     self.response_args = response_args
--- a/app/soc/views/sitemap/sidebar.py	Tue Jan 27 22:57:19 2009 +0000
+++ b/app/soc/views/sitemap/sidebar.py	Tue Jan 27 22:59:01 2009 +0000
@@ -44,14 +44,14 @@
 
 
 @soc.cache.sidebar.cache
-def getSidebar():
+def getSidebar(id, user):
   """Constructs a sidebar for the current user.
   """
 
   sidebar = []
 
   for callback in SIDEBAR:
-    menus = callback()
+    menus = callback(id, user)
 
     for menu in (menus if menus else []):
       sidebar.append(menu)
@@ -102,7 +102,7 @@
   return result
 
 
-def getSidebarMenu(items, params):
+def getSidebarMenu(id, user, items, params):
   """Returns an dictionary with one sidebar entry.
 
   Items is expected to be a tuple with an url, a menu_text, and an
@@ -138,6 +138,9 @@
   args = SIDEBAR_ACCESS_ARGS
   kwargs = SIDEBAR_ACCESS_KWARGS
 
+  # reset and pre-fill the Checker's cache
+  rights.setCurrentUser(id, user)
+
   for url, menu_text, access_type in items:
     try:
       rights.checkAccess(access_type, kwargs)
@@ -148,7 +151,7 @@
   return submenus
 
 
-def getSidebarMenus(params=None):
+def getSidebarMenus(id, user, params=None):
   """Constructs the default sidebar menu for a View.
 
   Calls getSidebarItems to retrieve the items that should be in the
@@ -160,7 +163,7 @@
   """
 
   items = getSidebarItems(params)
-  submenus = getSidebarMenu(items, params)
+  submenus = getSidebarMenu(id, user, items, params)
 
   if not submenus:
     return