Commit 99ed1c0c authored by Skia's avatar Skia 🤘
Browse files

Merge branch 'elections' into 'master'

Amélioration des élections

See merge request !79
parents 969d5699 ea09604b
Pipeline #1034 passed with stage
in 4 minutes and 33 seconds
......@@ -72,3 +72,23 @@
<a href="{{ url("core:user_godfathers_delete", user_id=profile.id, godfather_id=godfather.id, is_father=is_father) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
{% endmacro %}
{% macro paginate(page_obj, paginator) %}
{% if page_obj.has_previous() %}
<a href="?page={{ page_obj.previous_page_number() }}">{% trans %}Previous{% endtrans %}</a>
{% else %}
<span class="disabled">{% trans %}Previous{% endtrans %}</span>
{% endif %}
{% for i in paginator.page_range %}
{% if page_obj.number == i %}
<span class="active">{{ i }} <span class="sr-only">({% trans %}current{% endtrans %})</span></span>
{% else %}
<a href="?page={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<a href="?page={{ page_obj.next_page_number() }}">{% trans %}Next{% endtrans %}</a>
{% else %}
<span class="disabled">{% trans %}Next{% endtrans %}</span>
{% endif %}
{% endmacro %}
\ No newline at end of file
......@@ -103,6 +103,7 @@
<h4>{% trans %}Elections{% endtrans %}</h4>
<ul>
<li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li>
<li><a href="{{ url('election:list_archived') }}">{% trans %}See archived elections{% endtrans %}</a></li>
{%- if user.is_subscribed -%}
<li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li>
{%- endif -%}
......
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import user_profile_link %}
{% from 'core/macros.jinja' import user_profile_link, paginate %}
{% block title %}
{% trans %}Cash register summary list{% endtrans %}
......@@ -57,23 +57,7 @@
</table>
<br>
{% if is_paginated %}
{% if page_obj.has_previous() %}
<a href="?page={{ page_obj.previous_page_number() }}">{% trans %}Previous{% endtrans %}</a>
{% else %}
<span class="disabled">{% trans %}Previous{% endtrans %}</span>
{% endif %}
{% for i in paginator.page_range %}
{% if page_obj.number == i %}
<span class="active">{{ i }} <span class="sr-only">({% trans %}current{% endtrans %})</span></span>
{% else %}
<a href="?page={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<a href="?page={{ page_obj.next_page_number() }}">{% trans %}Next{% endtrans %}</a>
{% else %}
<span class="disabled">{% trans %}Next{% endtrans %}</span>
{% endif %}
{{ paginate(page_obj, paginator) }}
{% endif %}
{% else %}
{% trans %}There is no cash register summary in this website.{% endtrans %}
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('election', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='election',
name='archived',
field=models.BooleanField(verbose_name='archived', default=False),
),
]
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings
from datetime import timedelta
from core.models import User, Group
......@@ -18,11 +16,24 @@ class Election(models.Model):
start_date = models.DateTimeField(_('start date'), blank=False)
end_date = models.DateTimeField(_('end date'), blank=False)
edit_groups = models.ManyToManyField(Group, related_name="editable_elections", verbose_name=_("edit groups"), blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_elections", verbose_name=_("view groups"), blank=True)
vote_groups = models.ManyToManyField(Group, related_name="votable_elections", verbose_name=_("vote groups"), blank=True)
candidature_groups = models.ManyToManyField(Group, related_name="candidate_elections", verbose_name=_("candidature groups"), blank=True)
edit_groups = models.ManyToManyField(
Group, related_name="editable_elections",
verbose_name=_("edit groups"), blank=True)
view_groups = models.ManyToManyField(
Group, related_name="viewable_elections",
verbose_name=_("view groups"), blank=True)
vote_groups = models.ManyToManyField(
Group, related_name="votable_elections",
verbose_name=_("vote groups"), blank=True)
candidature_groups = models.ManyToManyField(
Group, related_name="candidate_elections",
verbose_name=_("candidature groups"), blank=True)
voters = models.ManyToManyField(User, verbose_name=('voters'), related_name='voted_elections')
archived = models.BooleanField(_("archived"), default=False)
def __str__(self):
return self.title
......@@ -99,8 +110,7 @@ class Role(models.Model):
if total_vote == 0:
results['blank vote'] = {'vote': 0, 'percent': 0}
else:
results['blank vote'] = {'vote': total_vote - non_blank,
'percent': (total_vote - non_blank) * 100 / total_vote}
results['blank vote'] = {'vote': total_vote - non_blank, 'percent': (total_vote - non_blank) * 100 / total_vote}
return results
@property
......@@ -146,4 +156,4 @@ class Vote(models.Model):
candidature = models.ManyToManyField(Candidature, related_name='votes', verbose_name=_("candidature"))
def __str__(self):
return "Vote"
\ No newline at end of file
return "Vote"
......@@ -279,6 +279,7 @@ th {
<a href="{{url('election:update_role', role_id=role.id)}}">{% trans %}Edit{% endtrans %}</a>
<a href="{{url('election:delete_role', role_id=role.id)}}">{% trans %}Delete{% endtrans %}</a>
{%- endif -%}
<br><span>{{ role.description }}</span>
{%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %}
<strong class="role__multiple-choices-label">{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong>
{%- endif %}
......@@ -373,6 +374,9 @@ th {
{% endif %}
<a href="{{ url('election:update', election_id=object.id) }}">{% trans %}Edit{% endtrans %}</a>
{%- endif %}
{%- if user.is_root %}
<a href="{{ url('election:delete', election_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{%- endif %}
</section>
{% endblock %}
......
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import paginate %}
{% block title %}
{%- trans %}Election list{% endtrans %}
......@@ -44,5 +45,8 @@
<p>{{ election.description }}</p>
</section>
{%- endfor %}
{% if is_paginated %}
{{ paginate(page_obj, paginator) }}
{% endif %}
{%- endblock %}
from django.test import Client, TestCase
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
from django.core.management import call_command
from django.conf import settings
from datetime import date, datetime
from core.models import User, Group
from election.models import Election
......@@ -15,8 +13,10 @@ class MainElection(TestCase):
self.election = Election.objects.all().first()
self.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID)
self.subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
self.ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP)
self.subscriber_group = Group.objects.get(
name=settings.SITH_MAIN_MEMBERS_GROUP)
self.ae_board_group = Group.objects.get(
name=settings.SITH_MAIN_BOARD_GROUP)
self.sli = User.objects.get(username='sli')
self.subscriber = User.objects.get(username='subscriber')
self.public = User.objects.get(username='public')
......@@ -29,9 +29,9 @@ class ElectionDetailTest(MainElection):
self.election.save()
self.client.login(username=self.public.username, password='plop')
response_get = self.client.get(reverse('election:detail',
args=str(self.election.id)))
args=str(self.election.id)))
response_post = self.client.get(reverse('election:detail',
args=str(self.election.id)))
args=str(self.election.id)))
self.assertTrue(response_get.status_code == 403)
self.assertTrue(response_post.status_code == 403)
self.election.view_groups.remove(self.subscriber_group)
......@@ -41,9 +41,9 @@ class ElectionDetailTest(MainElection):
def test_permisson_granted(self):
self.client.login(username=self.public.username, password='plop')
response_get = self.client.get(reverse('election:detail',
args=str(self.election.id)))
args=str(self.election.id)))
response_post = self.client.post(reverse('election:detail',
args=str(self.election.id)))
args=str(self.election.id)))
self.assertFalse(response_get.status_code == 403)
self.assertFalse(response_post.status_code == 403)
self.assertTrue('La roue tourne' in str(response_get.content))
......@@ -53,8 +53,8 @@ class ElectionUpdateView(MainElection):
def test_permission_denied(self):
self.client.login(username=self.subscriber.username, password='plop')
response_get = self.client.get(reverse('election:update',
args=str(self.election.id)))
args=str(self.election.id)))
response_post = self.client.post(reverse('election:update',
args=str(self.election.id)))
args=str(self.election.id)))
self.assertTrue(response_get.status_code == 403)
self.assertTrue(response_post.status_code == 403)
......@@ -4,8 +4,10 @@ from election.views import *
urlpatterns = [
url(r'^$', ElectionsListView.as_view(), name='list'),
url(r'^archived$', ElectionListArchivedView.as_view(), name='list_archived'),
url(r'^add$', ElectionCreateView.as_view(), name='create'),
url(r'^(?P<election_id>[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'),
url(r'^(?P<election_id>[0-9]+)/delete$', ElectionDeleteView.as_view(), name='delete'),
url(r'^(?P<election_id>[0-9]+)/list/add$', ElectionListCreateView.as_view(), name='create_list'),
url(r'^(?P<election_id>[0-9]+)/role/create$', RoleCreateView.as_view(), name='create_role'),
url(r'^(?P<role_id>[0-9]+)/role/edit$', RoleUpdateView.as_view(), name='update_role'),
......
from django.shortcuts import redirect, get_object_or_404
from django.views.generic import ListView, DetailView, RedirectView
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
from django.core.urlresolvers import reverse_lazy, reverse
from django.shortcuts import get_object_or_404
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView
from django.views.generic.edit import DeleteView, FormView
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.forms.models import modelform_factory
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured
from django.db import DataError, transaction
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.forms import CheckboxSelectMultiple
from django.utils import timezone
from django.conf import settings
from django import forms
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
from core.views import CanViewMixin, CanEditMixin, CanCreateMixin
from django.db.models.query import QuerySet
from django.views.generic.edit import FormMixin
from core.views.forms import SelectDateTime
from election.models import Election, Role, Candidature, ElectionList, Vote
......@@ -27,12 +24,14 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
Used to replace ModelMultipleChoiceField but with
automatic backend verification
"""
def __init__(self, queryset, max_choice, required=True, widget=None, label=None,
initial=None, help_text='', *args, **kwargs):
def __init__(self, queryset, max_choice, required=True, widget=None,
label=None, initial=None, help_text='', *args, **kwargs):
self.max_choice = max_choice
widget = forms.CheckboxSelectMultiple()
super(LimitedCheckboxField, self).__init__(queryset, None, required, widget, label,
initial, help_text, *args, **kwargs)
super(LimitedCheckboxField,
self).__init__(queryset, None, required, widget,
label, initial, help_text, *args, **kwargs)
def clean(self, value):
qs = super(LimitedCheckboxField, self).clean(value)
......@@ -79,7 +78,8 @@ class VoteForm(forms.Form):
self.fields[role.title] = LimitedCheckboxField(cand, role.max_choice, required=False)
else:
self.fields[role.title] = forms.ModelChoiceField(cand, required=False,
widget=forms.RadioSelect(), empty_label=_("Blank vote"))
widget=forms.RadioSelect(),
empty_label=_("Blank vote"))
class RoleForm(forms.ModelForm):
......@@ -105,7 +105,7 @@ class RoleForm(forms.ModelForm):
class ElectionListForm(forms.ModelForm):
class Meta:
model = ElectionList
fields = ('title','election')
fields = ('title', 'election')
def __init__(self, *args, **kwargs):
election_id = kwargs.pop('election_id', None)
......@@ -117,8 +117,11 @@ class ElectionListForm(forms.ModelForm):
class ElectionForm(forms.ModelForm):
class Meta:
model = Election
fields = ['title', 'description', 'start_candidature', 'end_candidature', 'start_date', 'end_date',
'edit_groups', 'view_groups', 'vote_groups', 'candidature_groups']
fields = ['title', 'description', 'archived',
'start_candidature', 'end_candidature',
'start_date', 'end_date',
'edit_groups', 'view_groups',
'vote_groups', 'candidature_groups']
widgets = {
'edit_groups': CheckboxSelectMultiple,
'view_groups': CheckboxSelectMultiple,
......@@ -127,10 +130,14 @@ class ElectionForm(forms.ModelForm):
'candidature_groups': CheckboxSelectMultiple
}
start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=True)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=True)
start_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start candidature"), widget=SelectDateTime, required=True)
end_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End candidature"), widget=SelectDateTime, required=True)
start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"),
widget=SelectDateTime, required=True)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"),
widget=SelectDateTime, required=True)
start_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start candidature"),
widget=SelectDateTime, required=True)
end_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End candidature"),
widget=SelectDateTime, required=True)
# Display elections
......@@ -138,11 +145,29 @@ class ElectionForm(forms.ModelForm):
class ElectionsListView(CanViewMixin, ListView):
"""
A list with all responsabilities and their candidates
A list of all non archived elections visible
"""
model = Election
ordering = ["-id"]
paginate_by = 10
template_name = 'election/election_list.jinja'
def get_queryset(self):
return super(ElectionsListView, self).get_queryset().filter(archived=False).all()
class ElectionListArchivedView(CanViewMixin, ListView):
"""
A list of all archived elections visible
"""
model = Election
ordering = ["-id"]
paginate_by = 10
template_name = 'election/election_list.jinja'
def get_queryset(self):
return super(ElectionListArchivedView, self).get_queryset().filter(archived=True).all()
class ElectionDetailView(CanViewMixin, DetailView):
"""
......@@ -277,7 +302,8 @@ class ElectionCreateView(CanCreateMixin, CreateView):
def form_valid(self, form):
"""
Allow every users that had passed the dispatch to create an election
Allow every users that had passed the dispatch
to create an election
"""
return super(CreateView, self).form_valid(form)
......@@ -372,16 +398,20 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
init = {}
try:
init['start_date'] = self.object.start_date.strftime('%Y-%m-%d %H:%M:%S')
except:pass
except Exception:
pass
try:
init['end_date'] = self.object.end_date.strftime('%Y-%m-%d %H:%M:%S')
except:pass
except Exception:
pass
try:
init['start_candidature'] = self.object.start_candidature.strftime('%Y-%m-%d %H:%M:%S')
except:pass
except Exception:
pass
try:
init['end_candidature'] = self.object.end_candidature.strftime('%Y-%m-%d %H:%M:%S')
except:pass
except Exception:
pass
return init
def get_success_url(self, **kwargs):
......@@ -464,6 +494,20 @@ class RoleUpdateView(CanEditMixin, UpdateView):
# Delete Views
class ElectionDeleteView(DeleteView):
model = Election
template_name = 'core/delete_confirm.jinja'
pk_url_kwarg = 'election_id'
def dispatch(self, request, *args, **kwargs):
if request.user.is_root:
return super(ElectionDeleteView, self).dispatch(request, *args, **kwargs)
raise PermissionDenied
def get_success_url(self, **kwargs):
return reverse_lazy('election:list')
class CandidatureDeleteView(CanEditMixin, DeleteView):
model = Candidature
template_name = 'core/delete_confirm.jinja'
......
......@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-06 23:25+0200\n"
"POT-Creation-Date: 2017-06-07 17:32+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia <skia@libskia.so>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
......@@ -88,12 +88,12 @@ msgid "%(club_account)s on %(bank_account)s"
msgstr "%(club_account)s sur %(bank_account)s"
#: accounting/models.py:192 club/models.py:184 counter/models.py:433
#: election/models.py:18 launderette/models.py:144
#: election/models.py:17 launderette/models.py:144
msgid "start date"
msgstr "date de début"
#: accounting/models.py:193 club/models.py:185 counter/models.py:434
#: election/models.py:19
#: election/models.py:18
msgid "end date"
msgstr "date de fin"
......@@ -335,6 +335,7 @@ msgstr "Compte en banque : "
#: counter/templates/counter/last_ops.jinja:59
#: election/templates/election/election_detail.jinja:280
#: election/templates/election/election_detail.jinja:329
#: election/templates/election/election_detail.jinja:377
#: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:123
#: launderette/templates/launderette/launderette_admin.jinja:16
#: launderette/views.py:178 sas/templates/sas/album.jinja:26
......@@ -830,19 +831,19 @@ msgid "A club with that unix_name already exists"
msgstr "Un club avec ce nom UNIX existe déjà."
#: club/models.py:182 counter/models.py:431 counter/models.py:448
#: eboutic/models.py:38 eboutic/models.py:71 election/models.py:130
#: eboutic/models.py:38 eboutic/models.py:71 election/models.py:147
#: launderette/models.py:111 launderette/models.py:148 sas/models.py:156
msgid "user"
msgstr "nom d'utilisateur"
#: club/models.py:186 core/models.py:169 election/models.py:129
#: election/models.py:145
#: club/models.py:186 core/models.py:169 election/models.py:145
#: election/models.py:165
msgid "role"
msgstr "rôle"
#: club/models.py:188 core/models.py:61 counter/models.py:101
#: counter/models.py:126 election/models.py:15 election/models.py:82
#: election/models.py:131 forum/models.py:51 forum/models.py:184
#: counter/models.py:126 election/models.py:13 election/models.py:93
#: election/models.py:148 forum/models.py:51 forum/models.py:184
msgid "description"
msgstr "description"
......@@ -1069,6 +1070,7 @@ msgstr "Date de début"
#: club/views.py:196 com/views.py:123 counter/views.py:1020
#: election/views.py:131 subscription/views.py:50
#: election/views.py:149
msgid "End date"
msgstr "Date de fin"
......@@ -1109,8 +1111,8 @@ msgstr "Hebdomadaire"
msgid "Call"
msgstr "Appel"
#: com/models.py:60 com/models.py:102 com/models.py:149 election/models.py:14
#: election/models.py:81 election/models.py:118 forum/models.py:187
#: com/models.py:60 com/models.py:102 com/models.py:149 election/models.py:12
#: election/models.py:92 election/models.py:132 forum/models.py:187
#: forum/models.py:234
msgid "title"
msgstr "titre"
......@@ -1416,6 +1418,7 @@ msgid "Alert message"
msgstr "Message d'alerte"
#: com/views.py:122 election/views.py:130 subscription/views.py:47
#: com/views.py:122 election/views.py:146
msgid "Start date"
msgstr "Date de début"
......@@ -2145,6 +2148,18 @@ msgstr "Créneau"
msgid "Tokens"
msgstr "Jetons"
#: core/templates/core/macros.jinja:78 core/templates/core/macros.jinja:80
msgid "Previous"
msgstr "Précédent"
#: core/templates/core/macros.jinja:84
msgid "current"
msgstr "actuel"
#: core/templates/core/macros.jinja:90 core/templates/core/macros.jinja:92
msgid "Next"
msgstr "Suivant"
#: core/templates/core/new_user_email.jinja:2
msgid ""
"You're receiving this email because you subscribed to the UTBM student "
......@@ -2698,6 +2713,10 @@ msgstr "Élections"
msgid "See available elections"
msgstr "Voir les élections disponibles"
#: core/templates/core/user_tools.jinja:105
msgid "See archived elections"
msgstr "Voir les élections archivées"
#: core/templates/core/user_tools.jinja:107
msgid "Create a new election"
msgstr "Créer une nouvelle élection"
......@@ -2834,7 +2853,7 @@ msgstr "produit parent"
msgid "buying groups"
msgstr "groupe d'achat"
#: counter/models.py:140
#: counter/models.py:140 election/models.py:34
msgid "archived"
msgstr "archivé"
......@@ -3035,21 +3054,7 @@ msgstr "Coffre vidé"
msgid "yes"
msgstr "oui"
#: counter/templates/counter/cash_summary_list.jinja:61
#: counter/templates/counter/cash_summary_list.jinja:63
msgid "Previous"
msgstr "Précédent"
#: counter/templates/counter/cash_summary_list.jinja:67
msgid "current"
msgstr "actuel"
#: counter/templates/counter/cash_summary_list.jinja:73
#: counter/templates/counter/cash_summary_list.jinja:75
msgid "Next"
msgstr "Suivant"
#: counter/templates/counter/cash_summary_list.jinja:79
msgid "There is no cash register summary in this website."
msgstr "Il n'y a pas de relevé de caisse dans ce site web."
......@@ -3434,43 +3439,43 @@ msgstr "Retourner à l'eboutic"
msgid "You do not have enough money to buy the basket"
msgstr "Vous n'avez pas assez d'argent pour acheter le panier"
#: election/models.py:16
#: election/models.py:15
msgid "start candidature"
msgstr "début des candidatures"
#: election/models.py:17
#: election/models.py:16
msgid "end candidature"
msgstr "fin des candidatures"
#: election/models.py:21
#: election/models.py:22
msgid "edit groups"
msgstr "groupe d'édition"
#: election/models.py:22
#: election/models.py:25
msgid "view groups"
msgstr "groupe de vue"
#: election/models.py:23
#: election/models.py:28
msgid "vote groups"
msgstr "groupe de vote"
#: election/models.py:24
#: election/models.py:31
msgid "candidature groups"
msgstr "groupe de candidature"
#: election/models.py:80 election/models.py:119
#: election/models.py:91 election/models.py:134
msgid "election"
msgstr "élection"
#: election/models.py:83
#: election/models.py:94
msgid "max choice"
msgstr "nombre de choix maxi"