from django.core.urlresolvers import reverse from django.db.utils import OperationalError from rapid.models import Application from django.conf.urls import url import inspect import logging from os import path def _split_all_path(file_name): file_name = path.splitdrive(file_name)[1] p = 'a' while p: file_name, p = path.split(file_name) yield p def _caller_urls_module(): st = inspect.stack() for rec in st: file_name = rec[1] segments = list(_split_all_path(file_name)) i = 0 for i in xrange(0, len(segments) - 2): p = path.normcase(segments[i]) p = path.splitext(p)[0] if p == "urls": return segments[i+1] return None def _model_name(model): if hasattr(model, "url_name"): return model.url_name return model._meta.verbose_name class MenuEntry: """ The data that goes on a menu item. Model, permissions and url """ def __init__(self, model, permission, url_name=None): self.url_name = url_name self.model = model self.permission = permission def get_url(self, instance=None): ats = [(x, getattr(instance, x)) for x in self.action.query_parameters] if self.action.pick_generator: return [(n, reverse(self.url, kwargs=dict(ats + p))) for n, p in self.action.pick_generator()] return reverse(self.url, kwargs=dict(ats)) def __unicode__(self): return u"Menu entry: " + self.url_name + " -> " + unicode(self.action) def __str__(self): return str(unicode(self)) class ModuleEntry: """ Module data used at menu construction """ def __init__(self, python_name, menu_name): self.python_name = python_name self.menu_name = menu_name self.models = set() class Action: """ An action to be done over a model. Default actions are "list", "view", "edit", "add", "delete", and "select", those are defined at the views module. """ def __init__(self, name, url_parameters, query_parameters, view_factory, verbose_name=None, icon=None, visibility=None, pick_generator=None, overlay=None): self.name = name self.url_parameters = url_parameters self.query_parameters = query_parameters self.view_factory = view_factory self.verbose_name = verbose_name if verbose_name else name self.icon = icon self.visibility = self.Visibility.details if visibility is None else visibility self.pick_generator = pick_generator self.overlay = overlay if overlay else self.Overlay.better_in_overlay def __unicode__(self): return u"Action: " + self.name def __str__(self): return str(unicode(self)) class Visibility(object): hidden = 1 details = 2 list = 3 class Overlay(object): better_in_overlay = 'better-in-overlay' this_overlay = '' clear_overlays = 'clear-overlays' class _Registry: """ Registry of URLs, models and views present on the menu The registry must: -- for the menu creation list registered modules list registered models list menu entries by module list actions by model reverse url of action and model -- for crud generation query if model is registered list actions by model reverse url of action and model """ def __init__(self): """ Populates the menu registry """ self._modules = {} # ModuleEntry by python_name self._models = {} # {'action name': MenuEntry} by model class try: for a in Application.objects.filter(enabled=True): m = ModuleEntry(a.python_name, a.name) self._modules[a.python_name] = m except OperationalError: #Should always get fixed by a migration logging.error("Can not query applications table. You may need to run \"manage.py migrate\".") def register_action(self, action, entry, **kwargs): """ Registers an action at this registry, so it will appear on the menu and can be reversed at the cruds. :param action: Action type :param entry: The menu entry where it will appear :param kwargs: Arguments (besides model) that'll be passed to the view_factory of the action :return: A Django URL pattern that should be added to the patterns of a urls.py module """ from django.contrib.auth.models import User module_name = _caller_urls_module() model = entry.model if not module_name: raise Exception("Unidentified python module registering " + str(model)) if not registry._modules.has_key(module_name): logging.error("Module " + module_name + " is not set-up for registering cruds") return None module_entry = registry._modules[module_name] module_entry.models.add(model) if not entry.url_name: entry.url_name = _model_name(model) entry.action = action entry_url = module_entry.menu_name + '_' + entry.url_name + '_' + action.name entry.url = entry_url model_actions = self._models.get(model, {}) if model_actions.has_key(action.name): raise Exception("Action " + action.name + " already registered for model " + str(model)) model_actions[action.name] = entry self._models[model] = model_actions url_path = r'^%s/%s' % (entry.url_name, action.name) if action.url_parameters: url_path += '/%s' % action.url_parameters return url(url_path+'/?$', action.view_factory(model=entry.model, **kwargs), name=entry_url) def get_url_of_action(self, model, action_name, **kwargs): acts = self._models.get(model) if acts and acts.has_key(action_name): return reverse(acts[action_name].url, kwargs=kwargs) def modules(self): return self._modules.values() def entry_names(self): return [x.menu_name for x in self._modules.values()] def is_controlled(self, model): return self._models.has_key(model) def model_entry(self, model): return self._models.get(model) def module_models(self, module): return self._modules[module].models registry = _Registry()