registry.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # coding=utf-8
  2. from __future__ import absolute_import
  3. from __future__ import division
  4. from __future__ import print_function
  5. from __future__ import unicode_literals
  6. from django.core.urlresolvers import reverse
  7. from django.db.utils import OperationalError
  8. from django.utils.encoding import python_2_unicode_compatible
  9. from django.conf.urls import url
  10. import inspect
  11. import logging
  12. from os import path
  13. from rapid.models import Application
  14. def _split_all_path(file_name):
  15. file_name = path.splitdrive(file_name)[1]
  16. p = 'a'
  17. while p:
  18. file_name, p = path.split(file_name)
  19. yield p
  20. def _caller_urls_module():
  21. st = inspect.stack()
  22. for rec in st:
  23. file_name = rec[1]
  24. segments = list(_split_all_path(file_name))
  25. for i in range(0, len(segments) - 2):
  26. p = path.normcase(segments[i])
  27. p = path.splitext(p)[0]
  28. if p == "urls":
  29. return segments[i+1]
  30. return None
  31. def _model_name(model):
  32. if hasattr(model, "url_name"):
  33. return model.url_name
  34. # noinspection PyProtectedMember
  35. return model._meta.verbose_name
  36. @python_2_unicode_compatible
  37. class MenuEntry(object):
  38. """
  39. The data that goes on a menu item.
  40. Model, permissions and url
  41. """
  42. def __init__(self, model, permission, url_name=None):
  43. self.url_name = url_name
  44. self.model = model
  45. self.permission = permission
  46. # noinspection PyUnresolvedReferences
  47. def get_url(self, instance=None):
  48. ats = [(x, getattr(instance, x)) for x in self.action.query_parameters]
  49. if self.action.pick_generator:
  50. return [(n, reverse(self.url, kwargs=dict(ats + p))) for
  51. n, p in self.action.pick_generator()]
  52. return reverse(self.url, kwargs=dict(ats))
  53. def __str__(self):
  54. return "Menu entry: " + self.url_name + " -> " + str(self.action)
  55. class ModuleEntry(object):
  56. """
  57. Module data used at menu construction
  58. """
  59. def __init__(self, python_name, menu_name):
  60. self.python_name = python_name
  61. self.menu_name = menu_name
  62. self.models = set()
  63. @python_2_unicode_compatible
  64. class Action(object):
  65. """
  66. An action to be done over a model.
  67. Default actions are "list", "view", "edit", "add", "delete", and "select",
  68. those are defined at the views module.
  69. """
  70. def __init__(self, name, url_parameters, query_parameters, view_factory,
  71. verbose_name=None, icon=None, visibility=None, pick_generator=None,
  72. overlay=None):
  73. self.name = name
  74. self.url_parameters = url_parameters
  75. self.query_parameters = query_parameters
  76. self.view_factory = view_factory
  77. self.verbose_name = verbose_name if verbose_name else name
  78. self.icon = icon
  79. self.visibility = self.Visibility.details if visibility is None else visibility
  80. self.pick_generator = pick_generator
  81. self.overlay = overlay if overlay else self.Overlay.better_in_overlay
  82. def __str__(self):
  83. return "Action: " + self.name
  84. class Visibility(object):
  85. hidden = 1
  86. details = 2
  87. list = 3
  88. class Overlay(object):
  89. better_in_overlay = 'better-in-overlay'
  90. this_overlay = ''
  91. clear_overlays = 'clear-overlays'
  92. class _Registry(object):
  93. """
  94. Registry of URLs, models and views present on the menu
  95. The registry must:
  96. -- for the menu creation
  97. list registered modules
  98. list registered models
  99. list menu entries by module
  100. list actions by model
  101. reverse url of action and model
  102. -- for crud generation
  103. query if model is registered
  104. list actions by model
  105. reverse url of action and model
  106. """
  107. def __init__(self):
  108. """
  109. Populates the menu registry
  110. """
  111. self._modules = {} # ModuleEntry by python_name
  112. self._models = {} # {'action name': MenuEntry} by model class
  113. try:
  114. for a in Application.objects.filter(enabled=True):
  115. m = ModuleEntry(a.python_name, a.name)
  116. self._modules[a.python_name] = m
  117. except OperationalError:
  118. # Should always get fixed by a migration
  119. logging.error("Can not query applications table. You may need to run \"manage.py migrate\".")
  120. def register_action(self, action, entry, **kwargs):
  121. """
  122. Registers an action at this registry, so it will appear on the menu
  123. and can be reversed at the CRUDs.
  124. :param action: Action type
  125. :param entry: The menu entry where it will appear
  126. :param kwargs: Arguments (besides model) that'll be passed to the view_factory of the action
  127. :return: A Django URL pattern that should be added to the patterns of a urls.py module
  128. """
  129. module_name = _caller_urls_module()
  130. model = entry.model
  131. if not module_name:
  132. raise Exception("Unidentified python module registering " + str(model))
  133. if module_name not in registry._modules:
  134. if Application.objects.filter(python_name=module_name):
  135. a = Application.objects.get(python_name=module_name)
  136. registry._modules[module_name] = ModuleEntry(a.python_name, a.name)
  137. else:
  138. a = Application(name=module_name, python_name=module_name)
  139. a.save()
  140. registry._modules[module_name] = ModuleEntry(a.python_name, a.name)
  141. module_entry = registry._modules[module_name]
  142. module_entry.models.add(model)
  143. if not entry.url_name:
  144. entry.url_name = _model_name(model)
  145. entry.action = action
  146. entry_url = module_entry.menu_name + '_' + entry.url_name + '_' + action.name
  147. entry.url = entry_url
  148. model_actions = self._models.get(model, {})
  149. if action.name in model_actions:
  150. raise Exception("Action " + action.name + " already registered for model " + str(model))
  151. model_actions[action.name] = entry
  152. self._models[model] = model_actions
  153. url_path = r'^%s/%s' % (entry.url_name, action.name)
  154. if action.url_parameters:
  155. url_path += '/%s' % action.url_parameters
  156. return url(url_path+'/?$', action.view_factory(model=entry.model, **kwargs), name=entry_url)
  157. def get_url_of_action(self, model, action_name, **kwargs):
  158. acts = self._models.get(model)
  159. if acts and action_name in acts:
  160. return reverse(acts[action_name].url, kwargs=kwargs)
  161. def modules(self):
  162. return self._modules.values()
  163. def entry_names(self):
  164. return [x.menu_name for x in self._modules.values()]
  165. def is_controlled(self, model):
  166. return model in self._models
  167. def model_entry(self, model):
  168. return self._models.get(model)
  169. def module_models(self, module):
  170. return self._modules[module].models
  171. registry = _Registry()