You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nav.py 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import re
  2. from django.core.urlresolvers import reverse, resolve, NoReverseMatch
  3. from django.core.exceptions import ImproperlyConfigured
  4. from django.http import Http404
  5. from oscar.core.loading import get_class, AppNotFoundError
  6. from oscar.views.decorators import check_permissions
  7. class Node(object):
  8. """
  9. A node in the dashboard navigation menu
  10. """
  11. def __init__(self, label, url_name=None, url_args=None, url_kwargs=None,
  12. access_fn=None, icon=None):
  13. self.label = label
  14. self.icon = icon
  15. self.url_name = url_name
  16. self.url_args = url_args
  17. self.url_kwargs = url_kwargs
  18. self.access_fn = access_fn
  19. self.children = []
  20. @property
  21. def is_heading(self):
  22. return self.url_name is None
  23. @property
  24. def url(self):
  25. return reverse(self.url_name, args=self.url_args,
  26. kwargs=self.url_kwargs)
  27. def add_child(self, node):
  28. self.children.append(node)
  29. def is_visible(self, user):
  30. return self.access_fn is None or self.access_fn(
  31. user, self.url_name, self.url_args, self.url_kwargs)
  32. def filter(self, user):
  33. if not self.is_visible(user):
  34. return None
  35. node = Node(
  36. label=self.label, url_name=self.url_name, url_args=self.url_args,
  37. url_kwargs=self.url_kwargs, access_fn=self.access_fn,
  38. icon=self.icon
  39. )
  40. for child in self.children:
  41. if child.is_visible(user):
  42. node.add_child(child)
  43. return node
  44. def has_children(self):
  45. return len(self.children) > 0
  46. def default_access_fn(user, url_name, url_args=None, url_kwargs=None):
  47. """
  48. Given a url_name and a user, this function tries to assess whether the
  49. user has the right to access the URL.
  50. The application instance of the view is fetched via dynamic imports,
  51. and those assumptions will only hold true if the standard Oscar layout
  52. is followed.
  53. Once the permissions for the view are known, the access logic used
  54. by the dashboard decorator is evaluated
  55. This function might seem costly, but a simple comparison with DTT
  56. did not show any change in response time
  57. """
  58. exception = ImproperlyConfigured(
  59. "Please follow Oscar's default dashboard app layout or set a "
  60. "custom access_fn")
  61. if url_name is None: # it's a heading
  62. return True
  63. # get view module string
  64. try:
  65. url = reverse(url_name, args=url_args, kwargs=url_kwargs)
  66. view_module = resolve(url).func.__module__
  67. except (NoReverseMatch, Http404):
  68. # if there's no match, no need to display it
  69. return False
  70. # We can't assume that the view has the same parent module as the app,
  71. # as either the app or view can be customised. So we turn the module
  72. # string (e.g. 'oscar.apps.dashboard.catalogue.views') into an app
  73. # label that can be loaded by get_class (e.g.
  74. # 'dashboard.catalogue.app), which then essentially checks
  75. # INSTALLED_APPS for the right module to load
  76. match = re.search('(dashboard[\w\.]*)\.views$', view_module)
  77. if not match:
  78. raise exception
  79. app_label_str = match.groups()[0] + '.app'
  80. try:
  81. app_instance = get_class(app_label_str, 'application')
  82. except AppNotFoundError:
  83. raise exception
  84. # handle name-spaced view names
  85. if ':' in url_name:
  86. view_name = url_name.split(':')[1]
  87. else:
  88. view_name = url_name
  89. permissions = app_instance.get_permissions(view_name)
  90. return check_permissions(user, permissions)