from datetime import datetime from django.contrib.auth import logout, login, update_session_auth_hash from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.models import User from django.core.paginator import Paginator from django.http.response import HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.urls.base import reverse from notifications.models import Notification from notifications.signals import notify from main.forms import StoryForm, EpicForm, RegisterForm, ProfileForm, \ CommentForm, SprintForm from main.models import Story, Epic, Sprint, Comment, Project def register(request): if request.method == 'POST': form = RegisterForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect("index") else: form = RegisterForm() return render(request, 'registration/register.html', {'form': form}) @login_required def index(request): epics = Epic.objects.filter(closed=False) return render(request, 'index.html', {'current_sprint': Sprint.current(), 'epics': epics}) @login_required def profile_update(request): if request.method == 'POST': user = get_object_or_404(User, username=request.user) form = ProfileForm(request.POST, instance=user) if form.is_valid(): user = form.save() login(request, user) return redirect("index") else: user = get_object_or_404(User, username=request.user) form = ProfileForm(instance=user) return render(request, 'registration/register.html', {'form': form}) @login_required def change_password(request): if request.method == 'POST': form = PasswordChangeForm(request.user, request.POST) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) # Important! return redirect('index') else: form = PasswordChangeForm(request.user) return render(request, 'registration/change_password.html', {'form': form}) @login_required def logout(request): logout(request) return redirect("index") @login_required def backlog_editor(request): epics = Epic.objects.filter(closed=False) closed = Epic.objects.filter(closed=True) return render(request, 'backlog_editor.html', {'epics': epics, 'closed': closed}) def sprint_end(request): current_sprint = Sprint.current() next_sprint = Sprint.next() if request.method == 'POST': current_sprint.retro = request.POST["retro"] current_sprint.closed = True current_sprint.save() notify_sprint_end(current_sprint, next_sprint, request.user) return redirect("index") form = SprintForm(instance=current_sprint) return render(request, 'sprint_end.html', {'sprint': current_sprint, 'next_sprint': next_sprint, 'form': form}) @login_required def story_index(request): sprints = Sprint.objects.all() users = User.objects.all() stories = Story.objects filters = request.GET if 'state' in filters and filters['state']: stories = stories.filter(closed=(filters['state'] == 'closed')) if 'sprint' in filters and filters['sprint']: if filters['sprint'] == "None": stories = stories.filter(sprints=None) else: stories = stories.filter(sprints__id=filters['sprint']) if 'author' in filters and filters['author']: stories = stories.filter(author_id=filters['author']) if 'assignee' in filters and filters['assignee']: stories = stories.filter(assignees__id=filters['assignee']) paginator = Paginator(stories.all(), 20) page = request.GET.get('page') stories = paginator.get_page(page) return render(request, 'story_index.html', {'stories': stories, 'sprints': sprints, 'users': users, 'pages': range(1, paginator.num_pages + 1)}) @login_required def story_details(request, story_id): story = get_object_or_404(Story, id=story_id) return render(request, 'story_details.html', {'story': story}) @login_required def story_create(request, epic_id=None): if request.method == 'POST': form = StoryForm(request.POST) if form.is_valid(): story = form.save() for assignee in story.assignees.filter(id != request.user.id): notify_story_assigned(story, request.user, assignee) return redirect("story_details", story.id) else: story = Story() if epic_id is not None: story.epic = get_object_or_404(Epic, id=epic_id) story.author = User.objects.get(username=request.user) form = StoryForm(instance=story) return render(request, 'story_form.html', {'form': form, 'current_sprint_id': Sprint.current().id}) @login_required def story_edit(request, story_id): if request.method == 'POST': story = get_object_or_404(Story, id=story_id) former_assignees = list(story.assignees.all()) form = StoryForm(request.POST, instance=story) if form.is_valid(): form.save() for assignee in form.instance.assignees.all(): if not assignee in former_assignees and assignee.id != request.user.id: notify_story_assigned(story, request.user, assignee) return redirect("story_details", story.id) else: story = get_object_or_404(Story, id=story_id) form = StoryForm(instance=story) return render(request, 'story_form.html', {'form': form, 'current_sprint_id': Sprint.current().id}) @login_required def story_delete(request, story_id): if request.method == 'POST': story = get_object_or_404(Story, id=story_id) story.delete() return redirect("index") else: story = get_object_or_404(Story, id=story_id) return render(request, 'deletion.html', {'object': story}) @login_required def story_close(request, story_id): story = get_object_or_404(Story, id=story_id) story.close() notify_story_closed(story, request.user) if request.is_ajax(): return HttpResponse(story.to_json()) else: return redirect('epic_details', story.epic.id) @login_required def story_reaffect(request, story_id): story = get_object_or_404(Story, id=story_id) next_sprint = Sprint.next() story.sprints.add(next_sprint) story.save() if request.is_ajax(): return HttpResponse(story.to_json()) else: return redirect('story_details', story.id) @login_required def story_reopen(request, story_id): story = get_object_or_404(Story, id=story_id) story.reopen() notify_story_reopened(story, request.user) return redirect('story_details', story_id) @login_required def epic_details(request, epic_id): epic = get_object_or_404(Epic, id=epic_id) return render(request, 'epic_details.html', {'epic': epic}) @login_required def epic_create(request): if request.method == 'POST': form = EpicForm(request.POST) if form.is_valid(): epic = form.save(commit=False) epic.author = User.objects.get(username=request.user) epic.save() return redirect("backlog_editor") else: form = EpicForm() return render(request, 'epic_form.html', {'form': form}) @login_required def epic_edit(request, epic_id, from_=""): if request.method == 'POST': epic = get_object_or_404(Epic, id=epic_id) form = EpicForm(request.POST, instance=epic) if form.is_valid(): form.save() if from_: return redirect(from_) else: return redirect("epic_details", epic.id) else: epic = get_object_or_404(Epic, id=epic_id) form = EpicForm(instance=epic) return render(request, 'epic_form.html', {'form': form}) @login_required def epic_delete(request, epic_id): if request.method == 'POST': epic = get_object_or_404(Epic, id=epic_id) epic.delete() return redirect("index") else: epic = get_object_or_404(Epic, id=epic_id) return render(request, 'deletion.html', {'object': epic}) @login_required def epic_value_update(request, epic_id): if request.method == 'POST': epic = get_object_or_404(Epic, id=epic_id) epic.value = request.POST["value"] epic.save() return redirect("backlog_editor") @login_required def epic_close(request, epic_id): epic = get_object_or_404(Epic, id=epic_id) epic.close() epic.save() notify_epic_closed(epic, request.user) return redirect("backlog_editor") @login_required def epic_reopen(request, epic_id): epic = get_object_or_404(Epic, id=epic_id) epic.reopen() notify_epic_reopened(epic, request.user) return redirect("backlog_editor") @login_required def reports(request): return render(request, 'reports/report_index.html') @login_required def report_sprints(request): sprints = Sprint.objects.all() return render(request, 'reports/report_sprints.html', {'sprints': sprints}) @login_required def report_projects(request): epics = Epic.objects.filter(closed=False) return render(request, 'reports/report_projects.html', {'epics': epics}) def report_activity(request): projects_activity = {} for project in Project.objects.all(): projects_activity[project] = {} projects_activity[project]["current"] = 0 projects_activity[project]["sixmonths"] = 0 epics_activity = {} for epic in Epic.objects.all(): epics_activity[epic] = {} epics_activity[epic]["current"] = 0 epics_activity[epic]["sixmonths"] = 0 current = True for sprint in Sprint.objects.filter(date_end__lt = datetime.today()).order_by('-date_start')[:12]: for story in sprint.stories.all(): if not story.epic: continue if current: projects_activity[story.epic.project]["current"] += story.weight epics_activity[story.epic]["current"] += story.weight projects_activity[story.epic.project]["sixmonths"] += story.weight epics_activity[story.epic]["sixmonths"] += story.weight current = False epics_activity_cleaned = {epic: act for epic, act in epics_activity.items() if act["sixmonths"] > 0 } return render(request, 'reports/report_activity.html', {'projects_activity': projects_activity, 'epics_activity': epics_activity_cleaned}) # comments @login_required def comment_post(request): form = CommentForm(request.POST) if form.is_valid(): comment = form.save(commit=False) comment.author = request.user comment.save() notify_comment(comment) return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-{}".format(comment.id)) else: return redirect(request.META['HTTP_REFERER']) @login_required def comment_edit(request, comment_id): comment = get_object_or_404(Comment, id=comment_id) form = CommentForm(request.POST, instance=comment) if form.is_valid(): form.save() return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-{}".format(comment_id)) else: return redirect(request.META['HTTP_REFERER']) @login_required def comment_del(request, comment_id): comment = get_object_or_404(Comment, id=comment_id) comment.delete() return redirect(request.META['HTTP_REFERER'].split("#")[0] + "#a-comment-section") @login_required def search(request): qstr = request.GET["q"] results = [] results += Epic.objects.filter(name__icontains=qstr) results += Story.objects.filter(name__icontains=qstr) results += Epic.objects.filter(description__icontains=qstr) results += Story.objects.filter(description__icontains=qstr) if len(results) == 1: r = results[0] if isinstance(r, Epic): return redirect("epic_details", r.id) else: return redirect("story_details", r.id) else: paginator = Paginator(results, 10) page = request.GET.get('page') results = paginator.get_page(page) return render(request, 'search_results.html', {'results': results, 'pages': range(1, paginator.num_pages + 1)}) # notifications @login_required def notif_seen(_, notif_id): notif = get_object_or_404(Notification, id=notif_id) notif.mark_as_read() return HttpResponse('{}') @login_required def notif_all_seen(request): for notif in request.user.notifications.all(): notif.mark_as_read() return HttpResponse('{}') def notify_comment(comment): obj = comment.content_object target_url = reverse(f"{obj.model_name().lower()}_details", args=(obj.id,)) notif_content= f"{comment.author.username} a commenté {obj.model_name()} #{obj.id}" for user in obj.contributors(): if user.id != comment.author.id: notify.send(comment.author, recipient=user, action_object = obj, verb=notif_content) def notify_story_closed(story, closed_by): target_url = reverse(f"story_details", args=(story.id,)) notif_content= f"La story #{story.id} a été clôturée par {closed_by}" notify.send(closed_by, recipient=story.contributors(), action_object = story, verb=notif_content) def notify_story_reopened(story, opened_by): target_url = reverse(f"story_details", args=(story.id,)) notif_content= f"La story #{story.id} a été réouverte par {opened_by}" notify.send(opened_by, recipient=story.contributors(), action_object = story, verb=notif_content) def notify_epic_closed(epic, closed_by): target_url = reverse(f"epic_details", args=(epic.id,)) notif_content= f"La story #{epic.id} a été clôturée par {closed_by}" notify.send(closed_by, recipient=User.objects.all(), action_object = epic, verb=notif_content) def notify_epic_reopened(epic, opened_by): target_url = reverse(f"epic_details", args=(epic.id,)) notif_content= f"L'epic #{epic.id} a été réouverte par {opened_by}" notify.send(opened_by, recipient=User.objects.all(), action_object = epic, verb=notif_content) def notify_story_assigned(story, assigned_by, assigned_to): target_url = reverse(f"story_details", args=(story.id,)) notif_content= f"La story #{story.id} vous a été assignée par {assigned_by}" notify.send(assigned_by, recipient=assigned_to, action_object = story, verb=notif_content) def notify_sprint_end(sprint, next_sprint, ended_by): target_url = reverse(f"report_sprints") notify.send(ended_by, recipient=User.objects.all(), action_object = sprint, verb=f"Fin du {sprint}") notify.send(ended_by, recipient=User.objects.all(), action_object = sprint, verb=f"Démarrage du {next_sprint}")