diff --git a/elections/forms.py b/elections/forms.py index 3ff80d3..ed2093c 100644 --- a/elections/forms.py +++ b/elections/forms.py @@ -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) diff --git a/elections/templates/elections/election_admin.html b/elections/templates/elections/election_admin.html index 04b1e19..e33db01 100644 --- a/elections/templates/elections/election_admin.html +++ b/elections/templates/elections/election_admin.html @@ -83,17 +83,17 @@ {# Liste des questions #} {% for q in election.questions.all %} -
+
{{ q.text }} {% if election.start_date > current_time %} - + {% trans "Supprimer" %} - + @@ -104,15 +104,15 @@ {# Liste des options possibles #} {% for o in q.options.all %} -
+
{% if election.start_date > current_time %}
- + - + @@ -132,7 +132,7 @@ {# Rajout d'une option #} {% if election.start_date > current_time %} -
+
{% csrf_token %} @@ -153,9 +153,9 @@ {# Rajout d'une question #} {% if election.start_date > current_time %} -
+
- + {% csrf_token %}
diff --git a/elections/templates/elections/option_update.html b/elections/templates/elections/option_update.html new file mode 100644 index 0000000..92f44aa --- /dev/null +++ b/elections/templates/elections/option_update.html @@ -0,0 +1,41 @@ +{% extends "elections/base.html" %} +{% load i18n static %} + + +{% block content %} + +{% for error in form.non_field_errors %} +
+ {{ error }} +
+{% endfor %} + +

{% trans "Modification d'une option" %}

+
+ + + {% csrf_token %} + + {% include "elections/forms/form.html" with errors=False %} + +
+ + +{% endblock %} diff --git a/elections/templates/elections/question_update.html b/elections/templates/elections/question_update.html new file mode 100644 index 0000000..139ae1a --- /dev/null +++ b/elections/templates/elections/question_update.html @@ -0,0 +1,41 @@ +{% extends "elections/base.html" %} +{% load i18n static %} + + +{% block content %} + +{% for error in form.non_field_errors %} +
+ {{ error }} +
+{% endfor %} + +

{% trans "Modification d'une question" %}

+
+ +
+ {% csrf_token %} + + {% include "elections/forms/form.html" with errors=False %} + + +
+ +{% endblock %} diff --git a/elections/urls.py b/elections/urls.py index 1de4200..24ca25e 100644 --- a/elections/urls.py +++ b/elections/urls.py @@ -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/", views.ElectionAdminView.as_view(), name="election.admin"), @@ -16,6 +17,39 @@ urlpatterns = [ path( "archive/", views.ElectionArchiveView.as_view(), name="election.archive" ), + # Question views + path( + "add-question/", + views.AddQuestionView.as_view(), + name="election.add-question", + ), + path( + "mod-question/", + views.ModQuestionView.as_view(), + name="election.mod-question", + ), + path( + "del-question/", + views.DelQuestionView.as_view(), + name="election.del-question", + ), + # Option views + path( + "add-option/", + views.AddOptionView.as_view(), + name="election.add-option", + ), + path( + "mod-option/", + views.ModOptionView.as_view(), + name="election.mod-option", + ), + path( + "del-option/", + views.DelOptionView.as_view(), + name="election.del-option", + ), + # Common views path("view/", views.ElectionView.as_view(), name="election.view"), path("vote/", views.VoteView.as_view(), name="election.vote"), ] diff --git a/elections/views.py b/elections/views.py index 07dfe4a..382db22 100644 --- a/elections/views.py +++ b/elections/views.py @@ -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"