Allows for deletion and modification of questions and options
This commit is contained in:
parent
f4a2d1fb10
commit
f8bc0c209d
6 changed files with 277 additions and 36 deletions
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
41
elections/templates/elections/option_update.html
Normal file
41
elections/templates/elections/option_update.html
Normal 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 %}
|
41
elections/templates/elections/question_update.html
Normal file
41
elections/templates/elections/question_update.html
Normal 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 %}
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue