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
|
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)
|
||||||
|
|
|
@ -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">
|
||||||
|
|
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
|
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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue