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
class ElectionCreateForm(forms.ModelForm):
class ElectionForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if cleaned_data["start_date"] < timezone.now():
@ -24,6 +24,18 @@ class ElectionCreateForm(forms.ModelForm):
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):
def __init__(self, **kwargs):
super().__init__(**kwargs)

View file

@ -83,17 +83,17 @@
{# Liste des questions #}
{% for q in election.questions.all %}
<div class="panel">
<div class="panel" id="q_{{ q.pk }}">
<div class="panel-heading is-size-6">
<span>{{ q.text }}</span>
{% 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">
<i class="fas fa-times"></i>
</span>
<span>{% trans "Supprimer" %}</span>
</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">
<i class="fas fa-edit"></i>
</span>
@ -104,15 +104,15 @@
{# Liste des options possibles #}
{% for o in q.options.all %}
<div class="panel-block">
<div class="panel-block" id="o_{{ o.pk }}">
{% if election.start_date > current_time %}
<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">
<i class="fas fa-times"></i>
</span>
</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">
<i class="fas fa-edit"></i>
</span>
@ -132,7 +132,7 @@
{# Rajout d'une option #}
{% 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">
{% csrf_token %}
@ -153,9 +153,9 @@
{# Rajout d'une question #}
{% 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">
<form action="" method="POST">
<form action="{% url 'election.add-question' election.pk %}" method="POST">
{% csrf_token %}
<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
urlpatterns = [
# Admin views
path("create/", views.ElectionCreateView.as_view(), name="election.create"),
path("created/", views.ElectionListView.as_view(), name="election.created"),
path("admin/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"),
@ -16,6 +17,39 @@ urlpatterns = [
path(
"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("vote/<int:pk>", views.VoteView.as_view(), name="election.vote"),
]

View file

@ -13,14 +13,31 @@ from django.views.generic import (
RedirectView,
UpdateView,
)
from django.views.generic.detail import SingleObjectMixin
from .forms import ElectionCreateForm, OptionFormSet
from .mixins import CreatorOnlyMixin
from .forms import ElectionForm, OptionForm, OptionFormSet, QuestionForm
from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin
from .models import Election, Option, Question
# 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
# #############################################################################
@ -28,7 +45,7 @@ from .models import Election, Option, Question
class ElectionCreateView(SuccessMessageMixin, CreateView):
model = Election
form_class = ElectionCreateForm
form_class = ElectionForm
template_name = "elections/election_create.html"
success_message = _("Élection créée avec succès !")
@ -63,23 +80,20 @@ class ElectionListView(CreatorOnlyMixin, ListView):
template_name = "elections/election_list.html"
class ElectionUpdateView(SuccessMessageMixin, UpdateView):
class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Election
fields = ["name", "description", "start_date", "end_date"]
form_class = ElectionForm
success_message = _("Élection modifiée avec succès !")
template_name = "elections/election_update.html"
def get_success_url(self):
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(SuccessMessageMixin, SingleObjectMixin, RedirectView):
class ElectionTallyView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
success_message = _("Élection dépouillée avec succès !")
def get_queryset(self):
return (
@ -106,35 +120,32 @@ class ElectionTallyView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
Question.objects.bulk_update(questions, ["max_votes"])
election.tallied = True
election.save()
messages.success(request, _("Élection dépouillée avec succès !"))
return super().get(request, *args, **kwargs)
class ElectionChangePublicationView(
SuccessMessageMixin, SingleObjectMixin, RedirectView
):
class ElectionChangePublicationView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
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):
return super().get_queryset().filter(end_date__lt=timezone.now())
def get(self, request, *args, **kwargs):
election = self.get_object()
election.results_public = not election.results_public
election.save()
messages.success(
request,
_("Élection publiée avec succès !")
if election.results_public
else _("Élection dépubliée avec succès !"),
)
self.election = self.get_object()
self.election.results_public = not self.election.results_public
self.election.save()
return super().get(request, *args, **kwargs)
class ElectionArchiveView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
class ElectionArchiveView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
success_message = _("Élection archivée avec succès !")
def get_queryset(self):
return super().get_queryset().filter(end_date__lt=timezone.now())
@ -143,10 +154,112 @@ class ElectionArchiveView(SuccessMessageMixin, SingleObjectMixin, RedirectView):
election = self.get_object()
election.archived = True
election.save()
messages.success(request, _("Élection archivée avec succès !"))
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):
model = Election
template_name = "elections/election.html"