123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- # coding=utf-8
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- from __future__ import unicode_literals
- from django.core.urlresolvers import reverse
- from django.db.utils import OperationalError
- from django.utils.encoding import python_2_unicode_compatible
- from django.conf.urls import url
- import inspect
- import logging
- from os import path
- from rapid.models import Application
- 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))
- for i in range(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
- # noinspection PyProtectedMember
- return model._meta.verbose_name
- @python_2_unicode_compatible
- class MenuEntry(object):
- """
- 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
- # noinspection PyUnresolvedReferences
- 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 __str__(self):
- return "Menu entry: " + self.url_name + " -> " + str(self.action)
- class ModuleEntry(object):
- """
- 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()
- @python_2_unicode_compatible
- class Action(object):
- """
- 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 __str__(self):
- return "Action: " + self.name
- 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(object):
- """
- 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
- """
- module_name = _caller_urls_module()
- model = entry.model
- if not module_name:
- raise Exception("Unidentified python module registering " + str(model))
- if module_name not in registry._modules:
- if Application.objects.filter(python_name=module_name):
- a = Application.objects.get(python_name=module_name)
- registry._modules[module_name] = ModuleEntry(a.python_name, a.name)
- else:
- a = Application(name=module_name, python_name=module_name)
- a.save()
- registry._modules[module_name] = ModuleEntry(a.python_name, a.name)
- 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 action.name in model_actions:
- 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 action_name in acts:
- 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 model in self._models
- def model_entry(self, model):
- return self._models.get(model)
- def module_models(self, module):
- return self._modules[module].models
- registry = _Registry()
|