site.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from django.urls import reverse
  2. class Node:
  3. def __init__(self, *, name=None, description=None, icon=None, link=None):
  4. self.parent = None
  5. self.name = name
  6. self.description = description
  7. self.icon = icon
  8. self.link = link
  9. self._children = []
  10. self._children_dict = {}
  11. self._resolved_namespace = None
  12. @property
  13. def namespace(self):
  14. if self._resolved_namespace:
  15. return self._resolved_namespace
  16. bits = self.link.split(":")
  17. self._resolved_namespace = ":".join(bits[:-1])
  18. return self._resolved_namespace
  19. def children(self):
  20. return self._children
  21. def children_as_dicts(self):
  22. childrens = []
  23. for children in self._children:
  24. childrens.append(
  25. {
  26. "name": children.name,
  27. "description": children.description,
  28. "icon": children.icon,
  29. "link": reverse(children.link),
  30. "namespace": children.namespace,
  31. }
  32. )
  33. return childrens
  34. def add_node(self, node, after=None, before=None):
  35. if after:
  36. return self.add_node_after(node, after)
  37. if before:
  38. return self.add_node_before(node, before)
  39. node.parent = self
  40. self._children.append(node)
  41. self._children_dict[node.link] = node
  42. return True
  43. def add_node_after(self, node, after):
  44. success = False
  45. new_children_list = []
  46. for children in self._children:
  47. new_children_list.append(children)
  48. if children.link == after:
  49. new_children_list.append(node)
  50. success = True
  51. if success:
  52. node.parent = self
  53. self._children_dict[node.link] = node
  54. self._children = new_children_list
  55. return success
  56. def add_node_before(self, node, before):
  57. success = False
  58. new_children_list = []
  59. for children in self._children:
  60. if children.link == before:
  61. new_children_list.append(node)
  62. success = True
  63. new_children_list.append(children)
  64. if success:
  65. node.parent = self
  66. self._children_dict[node.link] = node
  67. self._children = new_children_list
  68. return success
  69. def child(self, namespace):
  70. try:
  71. return self._children_dict[namespace]
  72. except KeyError:
  73. raise ValueError(
  74. "Node %s is not a child of node %s" % (namespace, self.name)
  75. )
  76. def is_root(self):
  77. return False
  78. class AdminSite:
  79. def __init__(self):
  80. self.nodes_record = []
  81. self.nodes_dict = {}
  82. def build_nodes_dict(self):
  83. nodes_dict = {"misago:admin": Node(link="misago:admin:index")}
  84. iterations = 0
  85. while self.nodes_record:
  86. iterations += 1
  87. if iterations > 512:
  88. message = (
  89. "Misago Admin hierarchy is invalid or too complex to resolve. "
  90. "Nodes left: %s"
  91. )
  92. raise ValueError(message % self.nodes_record)
  93. for index, node in enumerate(self.nodes_record):
  94. if node["parent"] in nodes_dict:
  95. node_obj = Node(
  96. name=node["name"],
  97. description=node["description"],
  98. icon=node["icon"],
  99. link=node["link"],
  100. )
  101. parent = nodes_dict[node["parent"]]
  102. if node["after"]:
  103. node_added = parent.add_node(node_obj, after=node["after"])
  104. elif node["before"]:
  105. node_added = parent.add_node(node_obj, before=node["before"])
  106. else:
  107. node_added = parent.add_node(node_obj)
  108. if node_added:
  109. namespace = node.get("namespace") or node_obj.namespace
  110. if namespace not in nodes_dict:
  111. nodes_dict[namespace] = node_obj
  112. del self.nodes_record[index]
  113. break
  114. return nodes_dict
  115. def add_node(
  116. self,
  117. *,
  118. name=None,
  119. description=None,
  120. icon=None,
  121. parent=None,
  122. after=None,
  123. before=None,
  124. namespace=None,
  125. link="index",
  126. ):
  127. if self.nodes_dict:
  128. raise RuntimeError(
  129. "Misago admin site has already been initialized. "
  130. "You can't add new nodes to it."
  131. )
  132. if after and before:
  133. raise ValueError("after and before arguments are exclusive")
  134. self.nodes_record.append(
  135. {
  136. "name": name,
  137. "description": description,
  138. "icon": icon,
  139. "parent": join_namespace(parent),
  140. "after": join_namespace(parent, after) if after else None,
  141. "before": join_namespace(parent, before) if before else None,
  142. "namespace": join_namespace(parent, namespace),
  143. "link": join_namespace(parent, namespace, link),
  144. }
  145. )
  146. def visible_branches(self, request):
  147. if not self.nodes_dict:
  148. self.nodes_dict = self.build_nodes_dict()
  149. branches = []
  150. try:
  151. namespace = request.resolver_match.namespace
  152. except AttributeError:
  153. namespace = "misago:admin"
  154. if namespace in self.nodes_dict:
  155. node = self.nodes_dict[namespace]
  156. while node:
  157. children = node.children_as_dicts()
  158. if children:
  159. branches.append(children)
  160. node = node.parent
  161. try:
  162. namespaces = request.resolver_match.namespaces
  163. except AttributeError:
  164. namespaces = ["misago", "admin"]
  165. branches.reverse()
  166. for depth, branch in enumerate(branches):
  167. depth_namespace = namespaces[2 : 3 + depth]
  168. for node in branch:
  169. node_namespace = node["namespace"].split(":")[2 : 3 + depth]
  170. if request.resolver_match:
  171. node["is_active"] = depth_namespace == node_namespace
  172. else:
  173. node["is_active"] = False
  174. return branches
  175. def join_namespace(*args):
  176. parts = list(filter(None, args))
  177. parts.insert(0, "misago:admin")
  178. return ":".join(parts)
  179. site = AdminSite()