utils.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import logging
  2. from typing import List, Union, Dict, Set, Callable, Any
  3. from urllib.parse import urlencode
  4. from django.apps import apps
  5. from django.contrib.admin import ListFilter
  6. from django.contrib.admin.helpers import AdminForm
  7. from django.contrib.auth.models import AbstractUser
  8. from django.db.models.base import ModelBase, Model
  9. from django.db.models.options import Options
  10. from django.utils.translation import gettext
  11. from jazzmin.compat import NoReverseMatch, reverse
  12. logger = logging.getLogger(__name__)
  13. def order_with_respect_to(original: List, reference: List, getter: Callable = lambda x: x) -> List:
  14. """
  15. Order a list based on the location of items in the reference list, optionally, use a getter to pull values out of
  16. the first list
  17. """
  18. ranking = []
  19. max_num = len(original)
  20. for item in original:
  21. try:
  22. pos = reference.index(getter(item))
  23. except ValueError:
  24. pos = max_num
  25. ranking.append(pos)
  26. return [y for x, y in sorted(zip(ranking, original), key=lambda x: x[0])]
  27. def get_admin_url(instance: Any, admin_site: str = "admin", from_app: bool = False, **kwargs: str) -> str:
  28. """
  29. Return the admin URL for the given instance, model class or <app>.<model> string
  30. """
  31. url = "#"
  32. try:
  33. if type(instance) == str:
  34. app_label, model_name = instance.split(".")
  35. model_name = model_name.lower()
  36. url = reverse(
  37. "admin:{app_label}_{model_name}_changelist".format(app_label=app_label, model_name=model_name),
  38. current_app=admin_site,
  39. )
  40. # Model class
  41. elif instance.__class__ == ModelBase:
  42. app_label, model_name = instance._meta.app_label, instance._meta.model_name
  43. url = reverse(
  44. "admin:{app_label}_{model_name}_changelist".format(app_label=app_label, model_name=model_name),
  45. current_app=admin_site,
  46. )
  47. # Model instance
  48. elif instance.__class__.__class__ == ModelBase and isinstance(instance, instance.__class__):
  49. app_label, model_name = instance._meta.app_label, instance._meta.model_name
  50. url = reverse(
  51. "admin:{app_label}_{model_name}_change".format(app_label=app_label, model_name=model_name),
  52. args=(instance.pk,),
  53. current_app=admin_site,
  54. )
  55. except (NoReverseMatch, ValueError):
  56. # If we are not walking through the models within an app, let the user know this url cant be reversed
  57. if not from_app:
  58. logger.warning(gettext("Could not reverse url from {instance}".format(instance=instance)))
  59. if kwargs:
  60. url += "?{params}".format(params=urlencode(kwargs))
  61. return url
  62. def get_filter_id(spec: ListFilter) -> str:
  63. return getattr(spec, "field_path", getattr(spec, "parameter_name", spec.title))
  64. def get_custom_url(url: str, admin_site: str = "admin") -> str:
  65. """
  66. Take in a custom url, and try to reverse it
  67. """
  68. if not url:
  69. logger.warning("No url supplied in custom link")
  70. return "#"
  71. if "/" in url:
  72. return url
  73. try:
  74. url = reverse(url.lower(), current_app=admin_site)
  75. except NoReverseMatch:
  76. logger.warning("Couldnt reverse {url}".format(url=url))
  77. url = "#" + url
  78. return url
  79. def get_model_meta(model_str: str) -> Union[None, Options]:
  80. """
  81. Get model meta class
  82. """
  83. try:
  84. app, model = model_str.split(".")
  85. model_klass: Model = apps.get_registered_model(app, model)
  86. return model_klass._meta
  87. except (ValueError, LookupError):
  88. return None
  89. def get_app_admin_urls(app: str, admin_site: str = "admin") -> List[Dict]:
  90. """
  91. For the given app string, get links to all the app models admin views
  92. """
  93. if app not in apps.app_configs:
  94. logger.warning("{app} not found when generating links".format(app=app))
  95. return []
  96. models = []
  97. for model in apps.app_configs[app].get_models():
  98. url = get_admin_url(model, admin_site=admin_site, from_app=True)
  99. # We have no admin class
  100. if url == "#":
  101. continue
  102. models.append(
  103. {
  104. "url": url,
  105. "model": "{app}.{model}".format(app=model._meta.app_label, model=model._meta.model_name),
  106. "name": model._meta.verbose_name_plural.title(),
  107. }
  108. )
  109. return models
  110. def get_view_permissions(user: AbstractUser) -> Set[str]:
  111. """
  112. Get model names based on a users view/change permissions
  113. """
  114. perms = user.get_all_permissions()
  115. # the perm codenames should always be lower case
  116. lower_perms = []
  117. for perm in perms:
  118. app, perm_codename = perm.split(".")
  119. lower_perms.append("{app}.{perm_codename}".format(app=app, perm_codename=perm_codename.lower()))
  120. return {x.replace("view_", "") for x in lower_perms if "view" in x or "change" in x}
  121. def make_menu(
  122. user: AbstractUser, links: List[Dict], options: Dict, allow_appmenus: bool = True, admin_site: str = "admin"
  123. ) -> List[Dict]:
  124. """
  125. Make a menu from a list of user supplied links
  126. """
  127. if not user:
  128. return []
  129. model_permissions = get_view_permissions(user)
  130. menu = []
  131. for link in links:
  132. perm_matches = []
  133. for perm in link.get("permissions", []):
  134. perm_matches.append(user.has_perm(perm))
  135. if not all(perm_matches):
  136. continue
  137. # Url links
  138. if "url" in link:
  139. menu.append(
  140. {
  141. "name": link.get("name", "unspecified"),
  142. "url": get_custom_url(link["url"], admin_site=admin_site),
  143. "children": None,
  144. "new_window": link.get("new_window", False),
  145. "icon": link.get("icon", options["default_icon_children"]),
  146. }
  147. )
  148. # Model links
  149. elif "model" in link:
  150. if link["model"].lower() not in model_permissions:
  151. continue
  152. _meta = get_model_meta(link["model"])
  153. name = _meta.verbose_name_plural.title() if _meta else link["model"]
  154. menu.append(
  155. {
  156. "name": name,
  157. "url": get_admin_url(link["model"], admin_site=admin_site),
  158. "children": [],
  159. "new_window": link.get("new_window", False),
  160. "icon": options["icons"].get(link["model"], options["default_icon_children"]),
  161. }
  162. )
  163. # App links
  164. elif "app" in link and allow_appmenus:
  165. children = [
  166. {"name": child.get("verbose_name", child["name"]), "url": child["url"], "children": None}
  167. for child in get_app_admin_urls(link["app"], admin_site=admin_site)
  168. if child["model"] in model_permissions
  169. ]
  170. if len(children) == 0:
  171. continue
  172. menu.append(
  173. {
  174. "name": getattr(apps.app_configs[link["app"]], "verbose_name", link["app"]).title(),
  175. "url": "#",
  176. "children": children,
  177. "icon": options["icons"].get(link["app"], options["default_icon_children"]),
  178. }
  179. )
  180. return menu
  181. def has_fieldsets_check(adminform: AdminForm) -> bool:
  182. fieldsets = adminform.fieldsets
  183. if not fieldsets or (len(fieldsets) == 1 and fieldsets[0][0] is None):
  184. return False
  185. return True
  186. def attr(**kwargs) -> Callable:
  187. def decorator(func: Callable):
  188. for key, value in kwargs.items():
  189. setattr(func, key, value)
  190. return func
  191. return decorator