Ajax pour la supression de vote et modifications de patrons

This commit is contained in:
Tom Hubrecht 2021-08-24 17:54:35 +02:00
parent 6a797d3357
commit bca8d03400
12 changed files with 227 additions and 89 deletions

View file

@ -4,38 +4,16 @@
{% block content %}
<div class="level mb-2">
<div class="level mb-2 is-mobile">
{# Titre de l'élection #}
<div class="level-left is-flex-shrink-1">
<div class="level-left is-flex-shrink-1 pr-3">
<h1 class="title">{{ election.name }}</h1>
</div>
<div class="level-right is-flex">
{# Liste des votant·e·s #}
<div class="level-item is-flex-grow-1 mb-0">
<a class="button is-primary is-light is-outlined" href="{% url 'election.voters' election.pk %}">
<span class="icon">
<i class="fas fa-clipboard-list"></i>
</span>
<span>{% trans "Votant·e·s" %}</span>
</a>
</div>
{# Liste des bulletins #}
{% if election.results_public %}
<div class="level-item is-flex-grow-1 mb-0">
<a class="button is-primary is-light is-outlined" href="{% url 'election.ballots' election.pk %}">
<span class="icon">
<i class="fas fa-list"></i>
</span>
<span>{% trans "Bulletins" %}</span>
</a>
</div>
{% endif %}
{% if election.start_date < current_time %}
<div class="level-right is-flex is-flex-shrink-1">
{# Statut de l'élection #}
<div class="level-item is-flex-grow-1 mb-0">
{% if election.start_date < current_time %}
<div class="level-item is-flex-shrink-1">
<span class="tag is-medium is-outlined is-light is-primary">
{% if election.end_date < current_time %}
{% trans "Élection terminée" %}
@ -46,51 +24,84 @@
</div>
{% endif %}
{# Lien vers la page d'administration #}
{% if election.created_by == user %}
<div class="level-item is-flex-grow-1">
<a class="button has-tooltip-primary" href="{% url 'election.admin' election.pk %}" data-tooltip="{% trans "Administrer" %}">
<span class="icon">
<i class="fas fa-cog"></i>
</span>
</a>
<div class="level-item">
<div class="dropdown is-right">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<i class="fas fa-ellipsis-v" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
{# Lien vers la page d'administration #}
{% if election.created_by == user %}
<a class="dropdown-item" href="{% url 'election.admin' election.pk %}">
<span class="icon">
<i class="fas fa-cog"></i>
</span>
<span>{% trans "Administrer" %}</span>
</a>
<hr class="dropdown-divider">
{% endif %}
{# Liste des votant·e·s #}
<a class="dropdown-item" href="{% url 'election.voters' election.pk %}">
<span class="icon">
<i class="fas fa-clipboard-list"></i>
</span>
<span>{% trans "Votant·e·s" %}</span>
</a>
{# Liste des bulletins #}
{% if election.results_public %}
<a class="dropdown-item" href="{% url 'election.ballots' election.pk %}">
<span class="icon">
<i class="fas fa-list"></i>
</span>
<span>{% trans "Bulletins" %}</span>
</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="level mb-2">
<div class="level">
{# Dates d'ouverture de l'élection #}
<div class="level-left is-flex">
<div class="level-item is-flex-grow-1 mb-0">
<span class="tag is-medium is-primary">
<span class="icon-text">
<div class="level-left is-flex-shrink-1 pr-3">
<div class="level is-mobile">
<div class="level-item">
<span class="tag is-medium is-primary">
<span>{{ election.start_date|date:"d/m/Y H:i" }}</span>
<span class="icon">
<i class="fas fa-long-arrow-alt-right"></i>
</span>
<span>{{ election.end_date|date:"d/m/Y H:i" }}</span>
</span>
</span>
</div>
</div>
{# Créateurice de l'élection #}
<div class="level-item is-flex-grow-1 mb-0">
<span class="tag is-primary is-light is-outlined">{% blocktrans with creator=election.created_by.full_name %}Créé par {{ creator }}{% endblocktrans %}</span>
{# Créateurice de l'élection #}
<div class="level-item is-flex-shrink-1">
<span class="tag is-primary is-light is-outlined">{% blocktrans with creator=election.created_by.full_name %}Créé par {{ creator }}{% endblocktrans %}</span>
</div>
</div>
</div>
{# Confirmation de vote #}
{% if has_voted %}
<div class="level-right">
<div class="level-item">
<div class="level-right is-flex-shrink-1">
<div class="level-item is-flex-shrink-1">
<div class="tag is-medium is-outlined is-success is-light">
<span class="icon-text">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Votre vote a bien été enregistré." %}</span>
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Votre vote a bien été enregistré." %}</span>
</div>
</div>
</div>

View file

@ -8,7 +8,6 @@
b.addEventListener('click', () => {
const f = _$('form', _id(b.dataset.target), false);
f.dataset.next = b.dataset.next;
f.dataset.modal = b.dataset.target;
f.dataset.origin = b.dataset.parent
if (b.dataset.type == 'question') {

View file

@ -3,9 +3,9 @@
{% block content %}
<div class="level">
<div class="level is-mobile">
{# Titre de l'élection #}
<div class="level-left is-flex-shrink-1">
<div class="level-left is-flex-shrink-1 pr-3">
<h1 class="title">{{ election.name }}</h1>
</div>

View file

@ -26,42 +26,38 @@
{% block content %}
<div class="level is-flex-widescreen">
<div class="level-left">
<div class="item-level">
<div class="level is-mobile">
<div class="level-left is-flex-shrink-1">
<div class="item-level is-flex-shrink-1 pr-3">
<h1 class="title">{% trans "Gestion de la liste de votant·e·s" %}</h1>
</div>
</div>
<div class="level-right">
{% if election.sent_mail is False %}
<div class="level-item">
<div class="level-item is-hidden-touch">
{% if election.sent_mail is False %}
<a class="button is-light is-outlined is-primary" href="{% url 'election.mail-voters' election.pk %}">
<span class="icon">
<i class="fas fa-envelope-open"></i>
</span>
<span>{% trans "Envoyer le mail d'annonce" %}</span>
</a>
</div>
{% elif election.sent_mail is None %}
<div class="level-item">
{% elif election.sent_mail is None %}
<a class="button is-light is-outlined is-warning" href="javascript:location.reload();">
<span class="icon">
<i class="fas fa-sync-alt"></i>
</span>
<span>{% trans "Mail en cours de distribution" %}</span>
</a>
</div>
{% else %}
<div class="level-item">
{% else %}
<span class="button is-light is-outlined is-success">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Mail envoyé" %}</span>
</span>
{% endif %}
</div>
{% endif %}
<div class="level-item">
<a class="button is-primary" href="{% url 'election.admin' election.pk %}">
@ -73,6 +69,31 @@
</div>
</div>
</div>
<div class="level-item is-hidden-desktop">
{% if election.sent_mail is False %}
<a class="button is-light is-outlined is-primary" href="{% url 'election.mail-voters' election.pk %}">
<span class="icon">
<i class="fas fa-envelope-open"></i>
</span>
<span>{% trans "Envoyer le mail d'annonce" %}</span>
</a>
{% elif election.sent_mail is None %}
<a class="button is-light is-outlined is-warning" href="javascript:location.reload();">
<span class="icon">
<i class="fas fa-sync-alt"></i>
</span>
<span>{% trans "Mail en cours de distribution" %}</span>
</a>
{% else %}
<span class="button is-light is-outlined is-success">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Mail envoyé" %}</span>
</span>
{% endif %}
</div>
<hr>
{# Si on a déjà envoyé le mail avec les identifiants, on ne peut plus changer la liste #}
@ -122,7 +143,7 @@
<hr>
<div class="columns is-centered">
<div class="column">
<div class="column is-12">
<div class="table-container">
<table class="table is-fullwidth is-bordered is-striped has-text-centered">
<thead>

View file

@ -2,11 +2,60 @@
{% load i18n markdown %}
{% block custom_js %}
{% if can_delete %}
<script>
_$('.modal-button').forEach(b => {
b.addEventListener('click', () => {
const f = _$('form', _id(b.dataset.target), false);
f.dataset.target = b.dataset.origin;
_$('[name="delete"]', f, false).value = 'non';
});
});
_$('form').forEach(f => {
f.addEventListener('submit', event => {
event.preventDefault();
if (_$('[name="delete"]', f, false).value == 'oui') {
_get(f.action, r => {
if (r.success && r.action == 'delete') {
{% if election.restricted %}
const r = _id(f.dataset.target);
_$('.modal-button', r, false).remove();
const i = _$('.fas', r, false);
i.classList.remove('fa-check');
i.classList.add('fa-times');
{% else %}
_id(f.dataset.target).remove()
{% endif %}
// On ferme le modal
document.documentElement.classList.remove('is-clipped');
_id(f.dataset.modal).classList.remove('is-active');
}
if (r.message) {
_notif(r.message.content, r.message.class);
}
});
} else {
document.documentElement.classList.remove('is-clipped');
_id(f.dataset.modal).classList.remove('is-active');
}
});
});
</script>
{% endif %}
{% endblock %}
{% block content %}
<div class="level">
<div class="level is-mobile">
{# Titre de l'élection #}
<div class="level-left is-flex-shrink-1">
<div class="level-left is-flex-shrink-1 mr-3">
<h1 class="title">{{ election.name }}</h1>
</div>
@ -47,7 +96,7 @@
</div>
<div class="columns is-centered">
<div class="column is-two-thirds">
<div class="column is-narrow">
{% if can_delete %}
{% include "forms/modal-form.html" with modal_id="delete" form=d_form %}
{% endif %}
@ -62,23 +111,43 @@
{% endif %}
<tr>
</thead>
<tbody>
{% if election.restricted %}
{% for v in election.registered_voters.all %}
<tr>
<tr id="v_{{ forloop.counter }}">
<td>{{ v.full_name }} ({{ v.base_username }})</td>
{% if v in voters %}
<td class="has-text-centered">
<span class="icon">
{% if v in voters %}
<i class="fas fa-check"></i>
{% else %}
<i class="fas fa-times"></i>
{% endif %}
</span>
</td>
{% if can_delete %}
<td class="has-text-centered">
{% blocktrans with v_name=v.full_name asvar v_delete %}Supprimer le vote de {{ v_name }}{% endblocktrans %}
<a class="tag is-danger modal-button delete-vote" data-target="modal-delete" data-post_url="{% url 'election.delete-vote' election.pk v.pk forloop.counter %}" data-title="{{ v_delete }}" data-origin="v_{{ forloop.counter }}">
<span class="icon">
<i class="fas fa-user-minus"></i>
</span>
</a>
</td>
{% endif %}
{% else %}
<td class="has-text-centered">
<span class="icon">
<i class="fas fa-times"></i>
</span>
</td>
{% if can_delete %}
<td></td>
{% endif %}
{% endif %}
</tr>
{% endfor %}
{% else %}
{% for v in voters %}
<tr id="v_{{ forloop.counter }}">
<td>{{ v.full_name }} ({{ v.base_username }})</td>
@ -90,12 +159,13 @@
{% if can_delete %}
<td class="has-text-centered">
{% blocktrans with v_name=v.full_name asvar v_delete %}Supprimer le vote de {{ v_name }}{% endblocktrans %}
<a class="tag is-danger has-tooltip-primary modal-button delete-vote" data-target="modal-delete" data-tooltip="{{ v_delete }}" data-post_url="{% url 'election.delete-vote' election.pk v.pk forloop.counter %}" data-title="{{ v_delete }}">
<a class="tag is-danger modal-button delete-vote" data-target="modal-delete" data-post_url="{% url 'election.delete-vote' election.pk v.pk forloop.counter %}" data-title="{{ v_delete }}" data-origin="v_{{ forloop.counter }}">
<span class="icon">
<i class="fas fa-user-minus"></i>
</span>
</a>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
{% endif %}

View file

@ -29,7 +29,7 @@
<form action="" method="post">
{% csrf_token %}
{% block vote_form %}{% endblock %}
{% block vote_form %}{% endblock %}
<div class="field is-grouped is-centered">
<div class="control is-expanded">

View file

@ -214,7 +214,43 @@ class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
return super().form_valid(form)
class DeleteVoteView(ClosedElectionMixin, FormView):
class DeleteVoteView(ClosedElectionMixin, JsonDeleteView):
model = Election
def get_message(self):
return {
"content": _("Vote de {} supprimé !").format(self.voter.full_name),
"class": "success",
}
@transaction.atomic
def get(self, request, *args, **kwargs):
election = self.get_object()
self.voter = User.objects.get(pk=self.kwargs["user_pk"])
# On envoie un mail à la personne lui indiquant que le vote est supprimé
EmailMessage(
subject="Vote removed",
body=MAIL_VOTE_DELETED.format(
full_name=self.voter.full_name,
election_name=election.name,
),
to=[self.voter.email],
).send()
# On supprime les votes
Vote.objects.filter(
user=self.voter,
option__question__election=election,
).delete()
# On marque les questions comme non votées
self.voter.cast_elections.remove(election)
self.voter.cast_questions.remove(*list(election.questions.all()))
return self.render_to_json(action="delete")
class DeleteVoteViewa(ClosedElectionMixin, FormView):
model = Election
template_name = "elections/delete_vote.html"
form_class = DeleteVoteForm
@ -453,8 +489,7 @@ class ElectionVotersView(NotArchivedMixin, DetailView):
if user.is_authenticated:
can_delete = (
not election.restricted
and election.created_by == user
election.created_by == user
and election.end_date < timezone.now()
and not election.tallied
)

View file

@ -10577,6 +10577,7 @@ body {
height: auto;
min-height: 2em;
white-space: unset;
text-align: center;
}
.message.is-primary .message-body hr {

File diff suppressed because one or more lines are too long

View file

@ -36,6 +36,7 @@ body
height: auto
min-height: 2em
white-space: unset
text-align: center
.message
&.is-primary

View file

@ -150,7 +150,7 @@
</div>
<div class="dropdown-menu pr-1">
<div class="dropdown-content">
<div class="dropdown-content pb-0">
<a class="dropdown-item" href="{% url 'election.list' %}">
<span class="icon is-size-5 mr-3">
<i class="fas fa-vote-yea"></i>
@ -200,14 +200,14 @@
{% for lang in langs %}
{% with lang_svg="images/"|add:lang.code|add:".svg" %}
<hr class="dropdown-divider">
<hr class="dropdown-divider {% if forloop.first %}mb-0{% else %}my-0{% endif %}">
<a class="dropdown-item lang-selector" data-lang="{{ lang.code }}">
<span class="icon-text">
<span class="icon mr-4" style="border-radius:3px">
<span class="icon mr-4">
<img style="border-radius:3px" src="{% static lang_svg %}">
</span>
<span class="is-size-5">{{ lang.name_translated }}</span>
<span class="is-size-">{{ lang.name_translated }}</span>
</span>
{% endwith %}
</a>

View file

@ -3,7 +3,7 @@
<div class="modal" id="modal-{{ modal_id }}">
<div class="modal-background" data-closes="modal-{{ modal_id }}"></div>
<div class="modal-card">
<form method="post" action="{{ post_url }}">
<form method="post" action="{{ post_url }}" data-modal="modal-{{ modal_id }}">
{% csrf_token %}
<header class="modal-card-head">