- {{ v.full_name }} |
-
+ | {{ v.full_name }} ({{ v.get_username }}) |
+
{% if v in voters %}
@@ -67,13 +70,21 @@
{% endfor %}
{% else %}
{% for v in voters %}
-
- {{ v.full_name }} |
-
+ |
+ {{ v.full_name }} ({{ v.get_username }}) |
+
|
+ {% if can_delete %}
+
+
+
+
+
+
+ {% endif %}
|
{% endfor %}
{% endif %}
diff --git a/elections/urls.py b/elections/urls.py
index 2440d38..df3ccf3 100644
--- a/elections/urls.py
+++ b/elections/urls.py
@@ -16,6 +16,11 @@ urlpatterns = [
views.ElectionUploadVotersView.as_view(),
name="election.upload-voters",
),
+ path(
+ "delete-vote///",
+ views.DeleteVoteView.as_view(),
+ name="election.delete-vote",
+ ),
path("update/", views.ElectionUpdateView.as_view(), name="election.update"),
path("tally/", views.ElectionTallyView.as_view(), name="election.tally"),
path(
diff --git a/elections/views.py b/elections/views.py
index 1179c49..e2b31da 100644
--- a/elections/views.py
+++ b/elections/views.py
@@ -1,5 +1,7 @@
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
+from django.core.mail import EmailMessage
+from django.db import transaction
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone
@@ -17,6 +19,7 @@ from django.views.generic import (
)
from .forms import (
+ DeleteVoteForm,
ElectionForm,
OptionForm,
QuestionForm,
@@ -31,8 +34,8 @@ from .mixins import (
NotArchivedMixin,
OpenElectionOnlyMixin,
)
-from .models import Election, Option, Question
-from .staticdefs import MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES
+from .models import Election, Option, Question, User, Vote
+from .staticdefs import MAIL_VOTE_DELETED, MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES
from .utils import create_users, send_mail
# TODO: access control *everywhere*
@@ -172,6 +175,66 @@ class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
return reverse("election.admin", args=[self.object.pk])
+class DeleteVoteView(ClosedElectionMixin, FormView):
+ model = Election
+ template_name = "elections/delete_vote.html"
+ form_class = DeleteVoteForm
+
+ def get_success_url(self):
+ return reverse("election.voters", args=[self.object.pk]) + "#v_{anchor}".format(
+ **self.kwargs
+ )
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs["voter"] = self.voter
+ return kwargs
+
+ def get_queryset(self):
+ # On n'affiche la page que pour les élections ouvertes à toustes
+ return super().get_queryset().filter(restricted=False)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["anchor"] = self.kwargs["anchor"]
+ return context
+
+ def get(self, request, *args, **kwargs):
+ self.object = super().get_object()
+ self.voter = User.objects.get(pk=self.kwargs["user_pk"])
+ return super().get(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ self.object = super().get_object()
+ self.voter = User.objects.get(pk=self.kwargs["user_pk"])
+ return super().post(request, *args, **kwargs)
+
+ @transaction.atomic
+ def form_valid(self, form):
+ if form.cleaned_data["delete"] == "oui":
+ # On envoie un mail à la personne lui indiquant que le vote est supprimé
+ EmailMessage(
+ subject="Vote removed",
+ body=MAIL_VOTE_DELETED.format(
+ full_name=self.voter.full_name,
+ election_name=self.object.name,
+ ),
+ to=[self.voter.email],
+ ).send()
+
+ # On supprime les votes
+ Vote.objects.filter(
+ user=self.voter,
+ option__question__election=self.object,
+ ).delete()
+
+ # On marque les questions comme non votées
+ self.voter.cast_elections.remove(self.object)
+ self.voter.cast_questions.remove(*list(self.object.questions.all()))
+
+ return super().form_valid(form)
+
+
class ElectionTallyView(ClosedElectionMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
@@ -280,9 +343,6 @@ class AddOptionView(CreatorOnlyEditMixin, CreateView):
model = Question
form_class = OptionForm
- def get_queryset(self):
- return super().get_queryset()
-
def get_success_url(self):
return (
reverse("election.admin", args=[self.question.election.pk])
@@ -366,6 +426,17 @@ class ElectionVotersView(NotArchivedMixin, DetailView):
model = Election
template_name = "elections/election_voters.html"
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ election = context["election"]
+ context["can_delete"] = (
+ not election.restricted
+ and election.created_by == self.request.user
+ and election.end_date < timezone.now()
+ and not election.tallied
+ )
+ return context
+
class VoteView(OpenElectionOnlyMixin, DetailView):
model = Question
|