Allows for deletion and modification of questions and options

This commit is contained in:
Tom Hubrecht 2020-12-20 02:32:26 +01:00
parent f4a2d1fb10
commit f8bc0c209d
6 changed files with 277 additions and 36 deletions

View file

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
from .models import Election, Option, Question from .models import Election, Option, Question
class ElectionCreateForm(forms.ModelForm): class ElectionForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
if cleaned_data["start_date"] < timezone.now(): if cleaned_data["start_date"] < timezone.now():
@ -24,6 +24,18 @@ class ElectionCreateForm(forms.ModelForm):
fields = ["name", "description", "start_date", "end_date"] fields = ["name", "description", "start_date", "end_date"]
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ["text"]
class OptionForm(forms.ModelForm):
class Meta:
model = Option
fields = ["text"]
class VoteForm(forms.ModelForm): class VoteForm(forms.ModelForm):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -83,17 +83,17 @@
{# Liste des questions #} {# Liste des questions #}
{% for q in election.questions.all %} {% for q in election.questions.all %}
<div class="panel"> <div class="panel" id="q_{{ q.pk }}">
<div class="panel-heading is-size-6"> <div class="panel-heading is-size-6">
<span>{{ q.text }}</span> <span>{{ q.text }}</span>
{% if election.start_date > current_time %} {% if election.start_date > current_time %}
<a class="tag is-small is-outlined is-light is-danger"> <a class="tag is-small is-outlined is-light is-danger" href="{% url 'election.del-question' q.pk %}">
<span class="icon"> <span class="icon">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</span> </span>
<span>{% trans "Supprimer" %}</span> <span>{% trans "Supprimer" %}</span>
</a> </a>
<a class="tag is-small is-outlined is-light is-info"> <a class="tag is-small is-outlined is-light is-info" href="{% url 'election.mod-question' q.pk %}">
<span class="icon"> <span class="icon">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</span> </span>
@ -104,15 +104,15 @@
{# Liste des options possibles #} {# Liste des options possibles #}
{% for o in q.options.all %} {% for o in q.options.all %}
<div class="panel-block"> <div class="panel-block" id="o_{{ o.pk }}">
{% if election.start_date > current_time %} {% if election.start_date > current_time %}
<div class="tags has-addons"> <div class="tags has-addons">
<a class="tag is-danger" title="{% trans "Supprimer" %}"> <a class="tag is-danger" title="{% trans "Supprimer" %}" href="{% url 'election.del-option' o.pk %}">
<span class="icon"> <span class="icon">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</span> </span>
</a> </a>
<a class="tag is-info" title="{% trans "Modifier" %}"> <a class="tag is-info" title="{% trans "Modifier" %}" href="{% url 'election.mod-option' o.pk %}">
<span class="icon"> <span class="icon">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</span> </span>
@ -132,7 +132,7 @@
{# Rajout d'une option #} {# Rajout d'une option #}
{% if election.start_date > current_time %} {% if election.start_date > current_time %}
<form action="" method="POST"> <form action="{% url 'election.add-option' q.pk %}" method="POST">
<div class="panel-block field has-addons"> <div class="panel-block field has-addons">
{% csrf_token %} {% csrf_token %}
@ -153,9 +153,9 @@
{# Rajout d'une question #} {# Rajout d'une question #}
{% if election.start_date > current_time %} {% if election.start_date > current_time %}
<div class="columns is-centered"> <div class="columns is-centered" id="q_add">
<div class="column is-two-thirds"> <div class="column is-two-thirds">
<form action="" method="POST"> <form action="{% url 'election.add-question' election.pk %}" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="field has-addons"> <div class="field has-addons">

View file

@ -0,0 +1,41 @@
{% extends "elections/base.html" %}
{% load i18n static %}
{% block content %}
{% for error in form.non_field_errors %}
<div class="notification is-danger">
{{ error }}
</div>
{% endfor %}
<h1 class="title">{% trans "Modification d'une option" %}</h1>
<hr>
<form action="" method="POST">
{% csrf_token %}
{% include "elections/forms/form.html" with errors=False %}
<div class="field is-grouped is-centered">
<div class="control is-expanded">
<button class="button is-fullwidth is-outlined is-primary is-light">
<span class="icon is-small">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Enregistrer" %}</span>
</button>
</div>
<div class="control">
<a class="button is-primary" href="{% url 'election.admin' option.question.election.pk %}#o_{{ option.pk }}">
<span class="icon is-small">
<i class="fas fa-undo-alt"></i>
</span>
<span>{% trans "Retour" %}</span>
</a>
</div>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "elections/base.html" %}
{% load i18n static %}
{% block content %}
{% for error in form.non_field_errors %}
<div class="notification is-danger">
{{ error }}
</div>
{% endfor %}
<h1 class="title">{% trans "Modification d'une question" %}</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
{% include "elections/forms/form.html" with errors=False %}
<div class="field is-grouped is-centered">
<div class="control is-expanded">
<button class="button is-fullwidth is-outlined is-primary is-light">
<span class="icon is-small">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Enregistrer" %}</span>
</button>
</div>
<div class="control">
<a class="button is-primary" href="{% url 'election.admin' question.election.pk %}#q_{{ question.pk }}">
<span class="icon is-small">
<i class="fas fa-undo-alt"></i>
</span>
<span>{% trans "Retour" %}</span>
</a>
</div>
</div>
</form>
{% endblock %}

View file

@ -3,6 +3,7 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
# Admin views
path("create/", views.ElectionCreateView.as_view(), name="election.create"), path("create/", views.ElectionCreateView.as_view(), name="election.create"),
path("created/", views.ElectionListView.as_view(), name="election.created"), path("created/", views.ElectionListView.as_view(), name="election.created"),
path("admin/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"), path("admin/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"),
@ -16,6 +17,39 @@ urlpatterns = [
path( path(
"archive/<int:pk>", views.ElectionArchiveView.as_view(), name="election.archive" "archive/<int:pk>", views.ElectionArchiveView.as_view(), name="election.archive"
), ),
# Question views
path(
"add-question/<int:pk>",
views.AddQuestionView.as_view(),
name="election.add-question",
),
path(
"mod-question/<int:pk>",
views.ModQuestionView.as_view(),
name="election.mod-question",
),
path(
"del-question/<int:pk>",
views.DelQuestionView.as_view(),
name="election.del-question",
),
# Option views
path(
"add-option/<int:pk>",
views.AddOptionView.as_view(),
name="election.add-option",
),
path(
"mod-option/<int:pk>",
views.ModOptionView.as_view(),
name="election.mod-option",
),
path(
"del-option/<int:pk>",
views.DelOptionView.as_view(),
name="election.del-option",
),
# Common views
path("view/<int:pk>", views.ElectionView.as_view(), name="election.view"), path("view/<int:pk>", views.ElectionView.as_view(), name="election.view"),
path("vote/<int:pk>", views.VoteView.as_view(), name="election.vote"), path("vote/<int:pk>", views.VoteView.as_view(), name="election.vote"),
] ]

View file

@ -13,14 +13,31 @@ from django.views.generic import (
RedirectView, RedirectView,
UpdateView, UpdateView,
) )
from django.views.generic.detail import SingleObjectMixin
from .forms import ElectionCreateForm, OptionFormSet from .forms import ElectionForm, OptionForm, OptionFormSet, QuestionForm
from .mixins import CreatorOnlyMixin from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin
from .models import Election, Option, Question from .models import Election, Option, Question
# TODO: access control *everywhere* # TODO: access control *everywhere*
# #############################################################################
# Utils Views
# #############################################################################
class BackgroundUpdateView(RedirectView):
success_message = ""
def get_success_message(self):
return self.success_message
def get(self, request, *args, **kwargs):
success_message = self.get_success_message()
if success_message:
messages.success(self.request, success_message)
return super().get(self, request, *args, **kwargs)
# ############################################################################# # #############################################################################
# Administration Views # Administration Views
# ############################################################################# # #############################################################################
@ -28,7 +45,7 @@ from .models import Election, Option, Question
class ElectionCreateView(SuccessMessageMixin, CreateView): class ElectionCreateView(SuccessMessageMixin, CreateView):
model = Election model = Election
form_class = ElectionCreateForm form_class = ElectionForm
template_name = "elections/election_create.html" template_name = "elections/election_create.html"
success_message = _("Élection créée avec succès !") success_message = _("Élection créée avec succès !")
@ -63,23 +80,20 @@ class ElectionListView(CreatorOnlyMixin, ListView):
template_name = "elections/election_list.html" template_name = "elections/election_list.html"
class ElectionUpdateView(SuccessMessageMixin, UpdateView): class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Election model = Election
fields = ["name", "description", "start_date", "end_date"] form_class = ElectionForm
success_message = _("Élection modifiée avec succès !") success_message = _("Élection modifiée avec succès !")
template_name = "elections/election_update.html" template_name = "elections/election_update.html"
def get_success_url(self): def get_success_url(self):
return reverse("election.admin", args=[self.object.pk]) return reverse("election.admin", args=[self.object.pk])
def get_queryset(self):
# On ne peut plus modifier une élection qui a déjà commencé
return super().get_queryset().filter(start_date__gt=timezone.now())
class ElectionTallyView(CreatorOnlyEditMixin, BackgroundUpdateView):
class ElectionTallyView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
model = Election model = Election
pattern_name = "election.admin" pattern_name = "election.admin"
success_message = _("Élection dépouillée avec succès !")
def get_queryset(self): def get_queryset(self):
return ( return (
@ -106,35 +120,32 @@ class ElectionTallyView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
Question.objects.bulk_update(questions, ["max_votes"]) Question.objects.bulk_update(questions, ["max_votes"])
election.tallied = True election.tallied = True
election.save() election.save()
messages.success(request, _("Élection dépouillée avec succès !"))
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
class ElectionChangePublicationView( class ElectionChangePublicationView(CreatorOnlyEditMixin, BackgroundUpdateView):
SuccessMessageMixin, SingleObjectMixin, RedirectView
):
model = Election model = Election
pattern_name = "election.admin" pattern_name = "election.admin"
def get_success_message(self):
if self.election.results_public:
return _("Élection publiée avec succès !")
return _("Élection dépubliée avec succès !")
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(end_date__lt=timezone.now()) return super().get_queryset().filter(end_date__lt=timezone.now())
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
election = self.get_object() self.election = self.get_object()
election.results_public = not election.results_public self.election.results_public = not self.election.results_public
election.save() self.election.save()
messages.success(
request,
_("Élection publiée avec succès !")
if election.results_public
else _("Élection dépubliée avec succès !"),
)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
class ElectionArchiveView(SuccessMessageMixin, SingleObjectMixin, RedirectView): class ElectionArchiveView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election model = Election
pattern_name = "election.admin" pattern_name = "election.admin"
success_message = _("Élection archivée avec succès !")
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(end_date__lt=timezone.now()) return super().get_queryset().filter(end_date__lt=timezone.now())
@ -143,10 +154,112 @@ class ElectionArchiveView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
election = self.get_object() election = self.get_object()
election.archived = True election.archived = True
election.save() election.save()
messages.success(request, _("Élection archivée avec succès !"))
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
# #############################################################################
# Question Views
# #############################################################################
class AddQuestionView(CreatorOnlyEditMixin, CreateView):
model = Election
form_class = QuestionForm
def get_success_url(self):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def form_valid(self, form):
self.election = self.get_object()
# On ajoute l'élection voulue à la question créée
form.instance.election = self.election
return super().form_valid(form)
class ModQuestionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Question
form_class = QuestionForm
success_message = _("Question modifiée avec succès !")
template_name = "elections/question_update.html"
def get_success_url(self):
return (
reverse("election.admin", args=[self.object.election.pk])
+ f"#q_{self.object.pk}"
)
class DelQuestionView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Question
success_message = _("Question supprimée !")
def get_redirect_url(self, *args, **kwargs):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def get(self, request, *args, **kwargs):
question = self.get_object()
self.election = question.election
question.delete()
return super().get(request, *args, **kwargs)
# #############################################################################
# Option Views
# #############################################################################
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])
+ f"#q_{self.question.pk}"
)
def form_valid(self, form):
self.question = self.get_object()
# On ajoute l'élection voulue à la question créée
form.instance.question = self.question
return super().form_valid(form)
class ModOptionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Option
form_class = OptionForm
success_message = _("Option modifiée avec succès !")
template_name = "elections/option_update.html"
def get_success_url(self):
return (
reverse("election.admin", args=[self.object.question.election.pk])
+ f"#o_{self.object.pk}"
)
class DelOptionView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Option
success_message = _("Option supprimée !")
def get_redirect_url(self, *args, **kwargs):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def get(self, request, *args, **kwargs):
option = self.get_object()
self.election = option.question.election
option.delete()
return super().get(request, *args, **kwargs)
# #############################################################################
# Common Views
# #############################################################################
class ElectionView(DetailView): class ElectionView(DetailView):
model = Election model = Election
template_name = "elections/election.html" template_name = "elections/election.html"