views.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. from datetime import datetime
  2. from django.contrib.auth import logout, login, update_session_auth_hash
  3. from django.contrib.auth.decorators import login_required
  4. from django.contrib.auth.forms import PasswordChangeForm
  5. from django.contrib.auth.models import User
  6. from django.core.paginator import Paginator
  7. from django.http.response import HttpResponse
  8. from django.shortcuts import render, get_object_or_404, redirect
  9. from django.urls.base import reverse
  10. from notifications.models import Notification
  11. from notifications.signals import notify
  12. from main.forms import StoryForm, EpicForm, RegisterForm, ProfileForm, \
  13. CommentForm, SprintForm
  14. from main.models import Story, Epic, Sprint, Comment, Project
  15. def register(request):
  16. if request.method == 'POST':
  17. form = RegisterForm(request.POST)
  18. if form.is_valid():
  19. user = form.save()
  20. login(request, user)
  21. return redirect("index")
  22. else:
  23. form = RegisterForm()
  24. return render(request, 'registration/register.html', {'form': form})
  25. @login_required
  26. def index(request):
  27. epics = Epic.objects.filter(closed=False)
  28. return render(request, 'index.html', {'current_sprint': Sprint.current(), 'epics': epics})
  29. @login_required
  30. def profile_update(request):
  31. if request.method == 'POST':
  32. user = get_object_or_404(User, username=request.user)
  33. form = ProfileForm(request.POST, instance=user)
  34. if form.is_valid():
  35. user = form.save()
  36. login(request, user)
  37. return redirect("index")
  38. else:
  39. user = get_object_or_404(User, username=request.user)
  40. form = ProfileForm(instance=user)
  41. return render(request, 'registration/register.html', {'form': form})
  42. @login_required
  43. def change_password(request):
  44. if request.method == 'POST':
  45. form = PasswordChangeForm(request.user, request.POST)
  46. if form.is_valid():
  47. user = form.save()
  48. update_session_auth_hash(request, user) # Important!
  49. return redirect('index')
  50. else:
  51. form = PasswordChangeForm(request.user)
  52. return render(request, 'registration/change_password.html', {'form': form})
  53. @login_required
  54. def logout(request):
  55. logout(request)
  56. return redirect("index")
  57. @login_required
  58. def backlog_editor(request):
  59. epics = Epic.objects.filter(closed=False)
  60. closed = Epic.objects.filter(closed=True)
  61. return render(request, 'backlog_editor.html', {'epics': epics, 'closed': closed})
  62. def sprint_end(request):
  63. current_sprint = Sprint.current()
  64. next_sprint = Sprint.next()
  65. if request.method == 'POST':
  66. current_sprint.retro = request.POST["retro"]
  67. current_sprint.closed = True
  68. current_sprint.save()
  69. notify_sprint_end(current_sprint, next_sprint, request.user)
  70. return redirect("index")
  71. form = SprintForm(instance=current_sprint)
  72. return render(request, 'sprint_end.html', {'sprint': current_sprint, 'next_sprint': next_sprint, 'form': form})
  73. @login_required
  74. def story_index(request):
  75. sprints = Sprint.objects.all()
  76. users = User.objects.all()
  77. stories = Story.objects
  78. filters = request.GET
  79. if 'state' in filters and filters['state']:
  80. stories = stories.filter(closed=(filters['state'] == 'closed'))
  81. if 'sprint' in filters and filters['sprint']:
  82. if filters['sprint'] == "None":
  83. stories = stories.filter(sprints=None)
  84. else:
  85. stories = stories.filter(sprints__id=filters['sprint'])
  86. if 'author' in filters and filters['author']:
  87. stories = stories.filter(author_id=filters['author'])
  88. if 'assignee' in filters and filters['assignee']:
  89. stories = stories.filter(assignees__id=filters['assignee'])
  90. paginator = Paginator(stories.all(), 20)
  91. page = request.GET.get('page')
  92. stories = paginator.get_page(page)
  93. return render(request, 'story_index.html', {'stories': stories, 'sprints': sprints, 'users': users, 'pages': range(1, paginator.num_pages + 1)})
  94. @login_required
  95. def story_details(request, story_id):
  96. story = get_object_or_404(Story, id=story_id)
  97. return render(request, 'story_details.html', {'story': story})
  98. @login_required
  99. def story_create(request, epic_id=None):
  100. if request.method == 'POST':
  101. form = StoryForm(request.POST)
  102. if form.is_valid():
  103. story = form.save()
  104. for assignee in story.assignees.all():
  105. if assignee != request.user:
  106. notify_story_assigned(story, request.user, assignee)
  107. return redirect("story_details", story.id)
  108. else:
  109. story = Story()
  110. if epic_id is not None:
  111. story.epic = get_object_or_404(Epic, id=epic_id)
  112. story.author = User.objects.get(username=request.user)
  113. form = StoryForm(instance=story)
  114. return render(request, 'story_form.html', {'form': form, 'current_sprint_id': Sprint.current().id})
  115. @login_required
  116. def story_edit(request, story_id):
  117. if request.method == 'POST':
  118. story = get_object_or_404(Story, id=story_id)
  119. former_assignees = list(story.assignees.all())
  120. form = StoryForm(request.POST, instance=story)
  121. if form.is_valid():
  122. form.save()
  123. for assignee in form.instance.assignees.all():
  124. if not assignee in former_assignees and assignee.id != request.user.id:
  125. notify_story_assigned(story, request.user, assignee)
  126. return redirect("story_details", story.id)
  127. else:
  128. story = get_object_or_404(Story, id=story_id)
  129. form = StoryForm(instance=story)
  130. return render(request, 'story_form.html', {'form': form, 'current_sprint_id': Sprint.current().id})
  131. @login_required
  132. def story_delete(request, story_id):
  133. if request.method == 'POST':
  134. story = get_object_or_404(Story, id=story_id)
  135. story.delete()
  136. return redirect("index")
  137. else:
  138. story = get_object_or_404(Story, id=story_id)
  139. return render(request, 'deletion.html', {'object': story})
  140. @login_required
  141. def story_close(request, story_id):
  142. story = get_object_or_404(Story, id=story_id)
  143. story.close()
  144. notify_story_closed(story, request.user)
  145. if request.is_ajax():
  146. return HttpResponse(story.to_json())
  147. else:
  148. return redirect('epic_details', story.epic.id)
  149. @login_required
  150. def story_reaffect(request, story_id):
  151. story = get_object_or_404(Story, id=story_id)
  152. next_sprint = Sprint.next()
  153. story.sprints.add(next_sprint)
  154. story.save()
  155. if request.is_ajax():
  156. return HttpResponse(story.to_json())
  157. else:
  158. return redirect('story_details', story.id)
  159. @login_required
  160. def story_reopen(request, story_id):
  161. story = get_object_or_404(Story, id=story_id)
  162. story.reopen()
  163. notify_story_reopened(story, request.user)
  164. return redirect('story_details', story_id)
  165. @login_required
  166. def epic_details(request, epic_id):
  167. epic = get_object_or_404(Epic, id=epic_id)
  168. return render(request, 'epic_details.html', {'epic': epic})
  169. @login_required
  170. def epic_create(request):
  171. if request.method == 'POST':
  172. form = EpicForm(request.POST)
  173. if form.is_valid():
  174. epic = form.save(commit=False)
  175. epic.author = User.objects.get(username=request.user)
  176. epic.save()
  177. return redirect("backlog_editor")
  178. else:
  179. form = EpicForm()
  180. return render(request, 'epic_form.html', {'form': form})
  181. @login_required
  182. def epic_edit(request, epic_id, from_=""):
  183. if request.method == 'POST':
  184. epic = get_object_or_404(Epic, id=epic_id)
  185. form = EpicForm(request.POST, instance=epic)
  186. if form.is_valid():
  187. form.save()
  188. if from_:
  189. return redirect(from_)
  190. else:
  191. return redirect("epic_details", epic.id)
  192. else:
  193. epic = get_object_or_404(Epic, id=epic_id)
  194. form = EpicForm(instance=epic)
  195. return render(request, 'epic_form.html', {'form': form})
  196. @login_required
  197. def epic_delete(request, epic_id):
  198. if request.method == 'POST':
  199. epic = get_object_or_404(Epic, id=epic_id)
  200. epic.delete()
  201. return redirect("index")
  202. else:
  203. epic = get_object_or_404(Epic, id=epic_id)
  204. return render(request, 'deletion.html', {'object': epic})
  205. @login_required
  206. def epic_value_update(request, epic_id):
  207. if request.method == 'POST':
  208. epic = get_object_or_404(Epic, id=epic_id)
  209. epic.value = request.POST["value"]
  210. epic.save()
  211. return redirect("backlog_editor")
  212. @login_required
  213. def epic_close(request, epic_id):
  214. epic = get_object_or_404(Epic, id=epic_id)
  215. epic.close()
  216. epic.save()
  217. notify_epic_closed(epic, request.user)
  218. return redirect("backlog_editor")
  219. @login_required
  220. def epic_reopen(request, epic_id):
  221. epic = get_object_or_404(Epic, id=epic_id)
  222. epic.reopen()
  223. notify_epic_reopened(epic, request.user)
  224. return redirect("backlog_editor")
  225. @login_required
  226. def reports(request):
  227. return render(request, 'reports/report_index.html')
  228. @login_required
  229. def report_sprints(request):
  230. sprints = Sprint.objects.all()
  231. return render(request, 'reports/report_sprints.html', {'sprints': sprints})
  232. @login_required
  233. def report_projects(request):
  234. epics = Epic.objects.filter(closed=False)
  235. return render(request, 'reports/report_projects.html', {'epics': epics})
  236. def report_activity(request):
  237. projects_activity = {}
  238. for project in Project.objects.all():
  239. projects_activity[project] = {}
  240. projects_activity[project]["current"] = 0
  241. projects_activity[project]["sixmonths"] = 0
  242. epics_activity = {}
  243. for epic in Epic.objects.all():
  244. epics_activity[epic] = {}
  245. epics_activity[epic]["current"] = 0
  246. epics_activity[epic]["sixmonths"] = 0
  247. current = True
  248. for sprint in Sprint.objects.filter(date_end__lt = datetime.today()).order_by('-date_start')[:12]:
  249. for story in sprint.stories.all():
  250. if not story.epic:
  251. continue
  252. if current:
  253. projects_activity[story.epic.project]["current"] += story.weight
  254. epics_activity[story.epic]["current"] += story.weight
  255. projects_activity[story.epic.project]["sixmonths"] += story.weight
  256. epics_activity[story.epic]["sixmonths"] += story.weight
  257. current = False
  258. epics_activity_cleaned = {epic: act for epic, act in epics_activity.items() if act["sixmonths"] > 0 }
  259. return render(request, 'reports/report_activity.html', {'projects_activity': projects_activity, 'epics_activity': epics_activity_cleaned})
  260. # comments
  261. @login_required
  262. def comment_post(request):
  263. form = CommentForm(request.POST)
  264. if form.is_valid():
  265. comment = form.save(commit=False)
  266. comment.author = request.user
  267. comment.save()
  268. notify_comment(comment)
  269. return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-{}".format(comment.id))
  270. else:
  271. return redirect(request.META['HTTP_REFERER'])
  272. @login_required
  273. def comment_edit(request, comment_id):
  274. comment = get_object_or_404(Comment, id=comment_id)
  275. form = CommentForm(request.POST, instance=comment)
  276. if form.is_valid():
  277. form.save()
  278. return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-{}".format(comment_id))
  279. else:
  280. return redirect(request.META['HTTP_REFERER'])
  281. @login_required
  282. def comment_del(request, comment_id):
  283. comment = get_object_or_404(Comment, id=comment_id)
  284. comment.delete()
  285. return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-section")
  286. @login_required
  287. def search(request):
  288. qstr = request.GET["q"]
  289. results = []
  290. results += Epic.objects.filter(name__icontains=qstr)
  291. results += Story.objects.filter(name__icontains=qstr)
  292. results += Epic.objects.filter(description__icontains=qstr)
  293. results += Story.objects.filter(description__icontains=qstr)
  294. if len(results) == 1:
  295. r = results[0]
  296. if isinstance(r, Epic):
  297. return redirect("epic_details", r.id)
  298. else:
  299. return redirect("story_details", r.id)
  300. else:
  301. paginator = Paginator(results, 10)
  302. page = request.GET.get('page')
  303. results = paginator.get_page(page)
  304. return render(request, 'search_results.html', {'results': results, 'pages': range(1, paginator.num_pages + 1)})
  305. # notifications
  306. @login_required
  307. def notif_seen(_, notif_id):
  308. notif = get_object_or_404(Notification, id=notif_id)
  309. notif.mark_as_read()
  310. return HttpResponse('{}')
  311. @login_required
  312. def notif_all_seen(request):
  313. for notif in request.user.notifications.all():
  314. notif.mark_as_read()
  315. return HttpResponse('{}')
  316. def notify_comment(comment):
  317. obj = comment.content_object
  318. target_url = reverse(f"{obj.model_name().lower()}_details", args=(obj.id,))
  319. notif_content= f"{comment.author.username} a commenté <a href='{target_url}#a-comment-{comment.id}' title='{obj.name}'>{obj.model_name()} #{obj.id}</a>"
  320. for user in obj.contributors():
  321. if user.id != comment.author.id:
  322. notify.send(comment.author, recipient=user, action_object = obj, verb=notif_content)
  323. def notify_story_closed(story, closed_by):
  324. target_url = reverse(f"story_details", args=(story.id,))
  325. notif_content= f"La <a href='{target_url}' title='{story.name}'>story #{story.id}</a> a été <b>clôturée</b> par {closed_by}"
  326. notify.send(closed_by, recipient=story.contributors(), action_object = story, verb=notif_content)
  327. def notify_story_reopened(story, opened_by):
  328. target_url = reverse(f"story_details", args=(story.id,))
  329. notif_content= f"La <a href='{target_url}' title='{story.name}'>story #{story.id}</a> a été <b>réouverte</b> par {opened_by}"
  330. notify.send(opened_by, recipient=story.contributors(), action_object = story, verb=notif_content)
  331. def notify_epic_closed(epic, closed_by):
  332. target_url = reverse(f"epic_details", args=(epic.id,))
  333. notif_content= f"La <a href='{target_url}' title='{epic.name}'>story #{epic.id}</a> a été <b>clôturée</b> par {closed_by}"
  334. notify.send(closed_by, recipient=User.objects.all(), action_object = epic, verb=notif_content)
  335. def notify_epic_reopened(epic, opened_by):
  336. target_url = reverse(f"epic_details", args=(epic.id,))
  337. notif_content= f"L'<a href='{target_url}' title='{epic.name}'>epic #{epic.id}</a> a été <b>réouverte</b> par {opened_by}"
  338. notify.send(opened_by, recipient=User.objects.all(), action_object = epic, verb=notif_content)
  339. def notify_story_assigned(story, assigned_by, assigned_to):
  340. target_url = reverse(f"story_details", args=(story.id,))
  341. notif_content= f"La <a href='{target_url}' title='{story.name}'>story #{story.id}</a> vous a été assignée par {assigned_by}"
  342. notify.send(assigned_by, recipient=assigned_to, action_object = story, verb=notif_content)
  343. def notify_sprint_end(sprint, next_sprint, ended_by):
  344. target_url = reverse(f"report_sprints")
  345. notify.send(ended_by, recipient=User.objects.all(), action_object = sprint, verb=f"Fin du <a href='{target_url}'>{sprint}</a>")
  346. notify.send(ended_by, recipient=User.objects.all(), action_object = sprint, verb=f"Démarrage du <a href='{target_url}'>{next_sprint}</a>")