freeze_impl.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. from contextlib import suppress
  17. from functools import partial
  18. from django.apps import apps
  19. from django.contrib.contenttypes.models import ContentType
  20. from django.core.exceptions import ObjectDoesNotExist
  21. from easy_thumbnails.files import get_thumbnailer
  22. from easy_thumbnails.exceptions import InvalidImageFormatError
  23. from taiga.base.utils.urls import get_absolute_url
  24. from taiga.base.utils.iterators import as_tuple
  25. from taiga.base.utils.iterators import as_dict
  26. from taiga.mdrender.service import render as mdrender
  27. import os
  28. ####################
  29. # Values
  30. ####################
  31. @as_dict
  32. def _get_generic_values(ids:tuple, *, typename=None, attr:str="name") -> tuple:
  33. app_label, model_name = typename.split(".", 1)
  34. content_type = ContentType.objects.get(app_label=app_label, model=model_name)
  35. model_cls = content_type.model_class()
  36. ids = filter(lambda x: x is not None, ids)
  37. qs = model_cls.objects.filter(pk__in=ids)
  38. for instance in qs:
  39. yield str(instance.pk), getattr(instance, attr)
  40. @as_dict
  41. def _get_users_values(ids:set) -> dict:
  42. user_model = apps.get_model("users", "User")
  43. ids = filter(lambda x: x is not None, ids)
  44. qs = user_model.objects.filter(pk__in=tuple(ids))
  45. for user in qs:
  46. yield str(user.pk), user.get_full_name()
  47. @as_dict
  48. def _get_user_story_values(ids:set) -> dict:
  49. userstory_model = apps.get_model("userstories", "UserStory")
  50. ids = filter(lambda x: x is not None, ids)
  51. qs = userstory_model.objects.filter(pk__in=tuple(ids))
  52. for userstory in qs:
  53. yield str(userstory.pk), "#{} {}".format(userstory.ref, userstory.subject)
  54. _get_us_status_values = partial(_get_generic_values, typename="projects.userstorystatus")
  55. _get_task_status_values = partial(_get_generic_values, typename="projects.taskstatus")
  56. _get_issue_status_values = partial(_get_generic_values, typename="projects.issuestatus")
  57. _get_issue_type_values = partial(_get_generic_values, typename="projects.issuetype")
  58. _get_role_values = partial(_get_generic_values, typename="users.role")
  59. _get_points_values = partial(_get_generic_values, typename="projects.points")
  60. _get_priority_values = partial(_get_generic_values, typename="projects.priority")
  61. _get_severity_values = partial(_get_generic_values, typename="projects.severity")
  62. _get_milestone_values = partial(_get_generic_values, typename="milestones.milestone")
  63. def _common_users_values(diff):
  64. """
  65. Groups common values resolver logic of userstories,
  66. issues and tasks.
  67. """
  68. values = {}
  69. users = set()
  70. if "owner" in diff:
  71. users.update(diff["owner"])
  72. if "watchers" in diff:
  73. for ids in diff["watchers"]:
  74. if not ids:
  75. continue
  76. users.update(ids)
  77. if "assigned_to" in diff:
  78. users.update(diff["assigned_to"])
  79. if users:
  80. values["users"] = _get_users_values(users)
  81. return values
  82. def project_values(diff):
  83. values = _common_users_values(diff)
  84. return values
  85. def milestone_values(diff):
  86. values = _common_users_values(diff)
  87. return values
  88. def userstory_values(diff):
  89. values = _common_users_values(diff)
  90. if "status" in diff:
  91. values["status"] = _get_us_status_values(diff["status"])
  92. if "milestone" in diff:
  93. values["milestone"] = _get_milestone_values(diff["milestone"])
  94. if "points" in diff:
  95. points, roles = set(), set()
  96. for pointsentry in diff["points"]:
  97. if pointsentry is None:
  98. continue
  99. for role_id, point_id in pointsentry.items():
  100. points.add(point_id)
  101. roles.add(role_id)
  102. values["roles"] = _get_role_values(roles)
  103. values["points"] = _get_points_values(points)
  104. return values
  105. def issue_values(diff):
  106. values = _common_users_values(diff)
  107. if "status" in diff:
  108. values["status"] = _get_issue_status_values(diff["status"])
  109. if "milestone" in diff:
  110. values["milestone"] = _get_milestone_values(diff["milestone"])
  111. if "priority" in diff:
  112. values["priority"] = _get_priority_values(diff["priority"])
  113. if "severity" in diff:
  114. values["severity"] = _get_severity_values(diff["severity"])
  115. if "type" in diff:
  116. values["type"] = _get_issue_type_values(diff["type"])
  117. return values
  118. def task_values(diff):
  119. values = _common_users_values(diff)
  120. if "status" in diff:
  121. values["status"] = _get_task_status_values(diff["status"])
  122. if "milestone" in diff:
  123. values["milestone"] = _get_milestone_values(diff["milestone"])
  124. if "user_story" in diff:
  125. values["user_story"] = _get_user_story_values(diff["user_story"])
  126. return values
  127. def wikipage_values(diff):
  128. values = _common_users_values(diff)
  129. return values
  130. ####################
  131. # Freezes
  132. ####################
  133. def _generic_extract(obj:object, fields:list, default=None) -> dict:
  134. result = {}
  135. for fieldname in fields:
  136. result[fieldname] = getattr(obj, fieldname, default)
  137. return result
  138. @as_tuple
  139. def extract_attachments(obj) -> list:
  140. for attach in obj.attachments.all():
  141. try:
  142. thumb_url = get_thumbnailer(attach.attached_file)['timeline-image'].url
  143. thumb_url = get_absolute_url(thumb_url)
  144. except InvalidImageFormatError as e:
  145. thumb_url = None
  146. yield {"id": attach.id,
  147. "filename": os.path.basename(attach.attached_file.name),
  148. "url": attach.attached_file.url,
  149. "thumb_url": thumb_url,
  150. "is_deprecated": attach.is_deprecated,
  151. "description": attach.description,
  152. "order": attach.order}
  153. @as_tuple
  154. def extract_user_story_custom_attributes(obj) -> list:
  155. with suppress(ObjectDoesNotExist):
  156. custom_attributes_values = obj.custom_attributes_values.attributes_values
  157. for attr in obj.project.userstorycustomattributes.all():
  158. with suppress(KeyError):
  159. value = custom_attributes_values[str(attr.id)]
  160. yield {"id": attr.id,
  161. "name": attr.name,
  162. "value": value}
  163. @as_tuple
  164. def extract_task_custom_attributes(obj) -> list:
  165. with suppress(ObjectDoesNotExist):
  166. custom_attributes_values = obj.custom_attributes_values.attributes_values
  167. for attr in obj.project.taskcustomattributes.all():
  168. with suppress(KeyError):
  169. value = custom_attributes_values[str(attr.id)]
  170. yield {"id": attr.id,
  171. "name": attr.name,
  172. "value": value}
  173. @as_tuple
  174. def extract_issue_custom_attributes(obj) -> list:
  175. with suppress(ObjectDoesNotExist):
  176. custom_attributes_values = obj.custom_attributes_values.attributes_values
  177. for attr in obj.project.issuecustomattributes.all():
  178. with suppress(KeyError):
  179. value = custom_attributes_values[str(attr.id)]
  180. yield {"id": attr.id,
  181. "name": attr.name,
  182. "value": value}
  183. def project_freezer(project) -> dict:
  184. fields = ("name",
  185. "slug",
  186. "created_at",
  187. "owner_id",
  188. "public",
  189. "total_milestones",
  190. "total_story_points",
  191. "tags",
  192. "is_backlog_activated",
  193. "is_kanban_activated",
  194. "is_wiki_activated",
  195. "is_issues_activated")
  196. return _generic_extract(project, fields)
  197. def milestone_freezer(milestone) -> dict:
  198. snapshot = {
  199. "name": milestone.name,
  200. "slug": milestone.slug,
  201. "owner": milestone.owner_id,
  202. "estimated_start": milestone.estimated_start,
  203. "estimated_finish": milestone.estimated_finish,
  204. "closed": milestone.closed,
  205. "disponibility": milestone.disponibility
  206. }
  207. return snapshot
  208. def userstory_freezer(us) -> dict:
  209. rp_cls = apps.get_model("userstories", "RolePoints")
  210. rpqsd = rp_cls.objects.filter(user_story=us)
  211. points = {}
  212. for rp in rpqsd:
  213. points[str(rp.role_id)] = rp.points_id
  214. snapshot = {
  215. "ref": us.ref,
  216. "owner": us.owner_id,
  217. "status": us.status_id,
  218. "is_closed": us.is_closed,
  219. "finish_date": str(us.finish_date),
  220. "backlog_order": us.backlog_order,
  221. "sprint_order": us.sprint_order,
  222. "kanban_order": us.kanban_order,
  223. "subject": us.subject,
  224. "description": us.description,
  225. "description_html": mdrender(us.project, us.description),
  226. "assigned_to": us.assigned_to_id,
  227. "milestone": us.milestone_id,
  228. "client_requirement": us.client_requirement,
  229. "team_requirement": us.team_requirement,
  230. "watchers": [x.id for x in us.get_watchers()],
  231. "attachments": extract_attachments(us),
  232. "tags": us.tags,
  233. "points": points,
  234. "from_issue": us.generated_from_issue_id,
  235. "is_blocked": us.is_blocked,
  236. "blocked_note": us.blocked_note,
  237. "blocked_note_html": mdrender(us.project, us.blocked_note),
  238. "custom_attributes": extract_user_story_custom_attributes(us),
  239. }
  240. return snapshot
  241. def issue_freezer(issue) -> dict:
  242. snapshot = {
  243. "ref": issue.ref,
  244. "owner": issue.owner_id,
  245. "status": issue.status_id,
  246. "priority": issue.priority_id,
  247. "severity": issue.severity_id,
  248. "type": issue.type_id,
  249. "milestone": issue.milestone_id,
  250. "subject": issue.subject,
  251. "description": issue.description,
  252. "description_html": mdrender(issue.project, issue.description),
  253. "assigned_to": issue.assigned_to_id,
  254. "watchers": [x.pk for x in issue.get_watchers()],
  255. "attachments": extract_attachments(issue),
  256. "tags": issue.tags,
  257. "is_blocked": issue.is_blocked,
  258. "blocked_note": issue.blocked_note,
  259. "blocked_note_html": mdrender(issue.project, issue.blocked_note),
  260. "custom_attributes": extract_issue_custom_attributes(issue),
  261. }
  262. return snapshot
  263. def task_freezer(task) -> dict:
  264. snapshot = {
  265. "ref": task.ref,
  266. "owner": task.owner_id,
  267. "status": task.status_id,
  268. "milestone": task.milestone_id,
  269. "subject": task.subject,
  270. "description": task.description,
  271. "description_html": mdrender(task.project, task.description),
  272. "assigned_to": task.assigned_to_id,
  273. "watchers": [x.pk for x in task.get_watchers()],
  274. "attachments": extract_attachments(task),
  275. "taskboard_order": task.taskboard_order,
  276. "us_order": task.us_order,
  277. "tags": task.tags,
  278. "user_story": task.user_story_id,
  279. "is_iocaine": task.is_iocaine,
  280. "is_blocked": task.is_blocked,
  281. "blocked_note": task.blocked_note,
  282. "blocked_note_html": mdrender(task.project, task.blocked_note),
  283. "custom_attributes": extract_task_custom_attributes(task),
  284. }
  285. return snapshot
  286. def wikipage_freezer(wiki) -> dict:
  287. snapshot = {
  288. "slug": wiki.slug,
  289. "owner": wiki.owner_id,
  290. "content": wiki.content,
  291. "content_html": mdrender(wiki.project, wiki.content),
  292. "watchers": [x.pk for x in wiki.get_watchers()],
  293. "attachments": extract_attachments(wiki),
  294. }
  295. return snapshot