app/soc/modules/core.py
author Mario Ferraro <fadinlight@gmail.com>
Sun, 15 Nov 2009 22:12:20 +0100
changeset 3093 d1be59b6b627
parent 2963 e03e203130a6
permissions -rw-r--r--
GMaps related JS changed to use new google namespace. Google is going to change permanently in the future the way to load its services, so better stay safe. Also this commit shows uses of the new melange.js module. Fixes Issue 634.

# Copyright 2009 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""The Melange Core module.
"""

__authors__ = [
  '"Sverre Rabbelier" <sverre@rabbelier.nl>',
  '"Lennard de Rijk" <ljvderijk@gmail.com>',
  ]


import logging

from django.conf.urls import defaults

import settings
import soc.cache.sidebar


class Error(Exception):
  """Error class for the callback module.
  """

  pass


class APIVersionMismatch(Error):
  """Error raised when API version mismatches.
  """

  MISMATCH_MSG_FMT = "API mismatch, expected '%d', got '%d'."

  def __init__(self, expected, actual):
    """Instantiates a new exception with a customized message.
    """

    msg = self.MISMATCH_MSG_FMT % (expected, actual)
    super(APIVersionMismatch, self).__init__(msg)


class MissingService(Error):
  """Error raised when a required service is missing.
  """

  MISSING_SERVICE_FMT = "Required service '%s' is not registered, known: %s"

  def __init__(self, service, services):
    """Instantiates a new exception with a customized message.
    """

    msg = self.MISSING_SERVICE_FMT % (service, services)
    super(MissingService, self).__init__(msg)


class MissingCapability(Error):
  """Error raised when a required capability is missing.
  """

  MISSING_CAPABILITY_FMT = "Required capability '%s' " + \
      "is not registered, known: %s"

  def __init__(self, capability, capabilities):
    """Instantiates a new exception with a customized message.
    """

    msg = self.MISSING_CAPABILITY_FMT % (capability, capabilities)
    super(MissingCapability, self).__init__(msg)


class NonUniqueService(Error):
  """Error raised when a required service is missing.
  """

  NON_UNIQUE_SERVICE_FMT = "Unique service '%s' called a 2nd time, known: %s."

  def __init__(self, service, services):
    """Instantiates a new exception with a customized message.
    """

    msg = self.NON_UNIQUE_SERVICE_FMT % (service, services)
    super(NonUniqueService, self).__init__(msg)


class AlreadyRegisteredRight(Error):
  """Error raised when a right is registrated a second time
  """

  ALREADY_REGISTERED_RIGHT_FMT = "Tried to register right '%s' a second time."

  def __init__(self, right):
    """Instantiates a new exception with a customized message.
    """

    msg = self.ALREADY_REGISTERED_RIGHT_FMT % right
    super(AlreadyRegisteredRight, self).__init__(msg)


class Core(object):
  """The core handler that controls the Melange API.
  """

  def __init__(self):
    """Creates a new instance of the Core.
    """
    
    # pylint: disable-msg=C0103
    self.API_VERSION = 1

    self.registered_callbacks = []
    self.capabilities = []
    self.services = []

    self.sitemap = []
    self.sidebar = []
    self.per_request_cache = {}
    self.in_request = False
    self.rights = {}

  ##
  ## internal
  ##

  def getService(self, callback, service):
    """Retrieves the specified service from the callback if supported.

    Args:
     callback: the callback to retrieve the capability from
     service: the service to retrieve
    """

    if not hasattr(callback, service):
      return False

    func = getattr(callback, service)

    if not callable(func):
      return False

    return func

  def callService(self, service, unique, *args, **kwargs):
    """Calls the specified service on all callbacks.
    """

    if unique and (service in self.services):
      return

    results = []

    for callback in self.registered_callbacks:
      func = self.getService(callback, service)
      if not func:
        continue

      result = func(*args, **kwargs)
      results.append(result)

    self.services.append(service)
    return results

  ##
  ## Core code
  ##

  def getRequestValue(self, key, default=None):
    """Gets a per-request value.

    Args:
      key: the key of the to be retrieved value
      default: the default value (returned if no value is set)
    """

    assert self.in_request
    return self.per_request_value.get(key, default)

  def setRequestValue(self, key, value):
    """Sets a per-request value.

    Args:
      key: the key of the to be set value
      value: the value that should be set
    """

    assert self.in_request
    self.per_request_value[key] = value

  def getPatterns(self):
    """Returns the Django patterns for this site.
    """

    self.callService('registerWithSitemap', True)
    return defaults.patterns(None, *self.sitemap)

  @soc.cache.sidebar.cache
  def getSidebar(self, id, user):
    """Constructs a sidebar for the current user.
    """

    self.callService('registerWithSidebar', True)

    sidebar = []

    for i in self.sidebar:
      menus = i(id, user)

      for menu in (menus if menus else []):
        sidebar.append(menu)

    return sorted(sidebar, key=lambda x: x.get('group'))

  def getRightsChecker(self, prefix):
    """Returns a rights checker for the specified prefix.
    """

    from soc.logic import rights as rights_logic

    self.callService('registerRights', True)
    return rights_logic.Checker(self.rights, prefix)

  ###
  ### Core control code
  ###
  ### Called by other setup code to get the Core in a desired state.
  ###

  def startNewRequest(self, request):
    """Prepares core to handle a new request.

    Args:
      request: a Django HttpRequest object
    """

    self.in_request = True
    self.per_request_value = {}
    self.setRequestValue('request', request)

  def endRequest(self, request, optional):
    """Performs cleanup after current request.

    Args:
      request: a Django HttpRequest object
      optional: whether to noop when not in a request
    """

    # already cleaned up, as expected
    if optional and not self.in_request:
      return

    old_request = self.getRequestValue('request')
    self.per_request_value = {}
    self.in_request = False

    if id(old_request) != id(request):
      logging.error("ending request: \n'%s'\n != \n'%s'\n" % (
          old_request, request))

  def registerModuleCallbacks(self):
    """Retrieves all callbacks for the modules of this site.

    Callbacks for modules without a version number or the wrong API_VERSION
    number are dropped.  They won't be called.
    """

    fmt = settings.MODULE_FMT
    modules = ['soc_core'] + settings.MODULES
    modules = [__import__(fmt % i, fromlist=['']) for i in modules]

    for callback_class in [i.Callback for i in modules]:
      if callback_class.API_VERSION != self.API_VERSION:
        raise APIVersionMismatch(self.API_VERSION,
                                 callback_class.API_VERSION)


      callback = callback_class(self)
      self.registered_callbacks.append(callback)

    return True

  ##
  ## Module code
  ##

  def registerCapability(self, capability):
    """Registers the specified capability.
    """

    self.capabilities.append(capability)

  def requireCapability(self, capability):
    """Requires that the specified capability is present.
    """

    if capability in self.capabilities:
      return True

    raise MissingCapability(capability, self.capabilities)

  def requireService(self, service):
    """Requires that the specified service has been called.
    """

    if service in self.services:
      return True

    raise MissingService(service, self.services)

  def requireUniqueService(self, service):
    """Requires that the specified service is called exactly once.
    """

    if service not in self.services:
      return True

    raise NonUniqueService(service, self.services)

  def registerSitemapEntry(self, entries):
    """Registers the specified entries with the sitemap.
    """

    self.sitemap.extend(entries)

  def registerSidebarEntry(self, entry):
    """Registers the specified entry with the sidebar.
    """

    self.sidebar.append(entry)

  def registerRight(self, key, value):
    """Registers the specified right.
    """

    if key in self.rights:
      raise AlreadyRegisteredRight(key)

    self.rights[key] = value