diff --git a/core/fixtures/images/3.jpg b/core/fixtures/images/3.jpg
new file mode 100644
index 00000000..0e6a682e
Binary files /dev/null and b/core/fixtures/images/3.jpg differ
diff --git a/core/fixtures/images/5.jpg b/core/fixtures/images/5.jpg
new file mode 100644
index 00000000..76c1183d
Binary files /dev/null and b/core/fixtures/images/5.jpg differ
diff --git a/core/fixtures/images/6.jpg b/core/fixtures/images/6.jpg
new file mode 100644
index 00000000..a5b1e8e5
Binary files /dev/null and b/core/fixtures/images/6.jpg differ
diff --git a/core/fixtures/images/8.jpg b/core/fixtures/images/8.jpg
new file mode 100644
index 00000000..82df15f2
Binary files /dev/null and b/core/fixtures/images/8.jpg differ
diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py
index 65884269..5b88945a 100644
--- a/core/management/commands/populate.py
+++ b/core/management/commands/populate.py
@@ -1,6 +1,6 @@
import os
from datetime import date, datetime
-from io import StringIO
+from io import StringIO, BytesIO
from django.core.management.base import BaseCommand, CommandError
from django.core.management import call_command
@@ -8,13 +8,17 @@ from django.conf import settings
from django.db import connection
from django.contrib.sites.models import Site
+from PIL import Image
from core.models import Group, User, Page, PageRev, SithFile
from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company
+from core.utils import resize_image
from club.models import Club, Membership
from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter
from com.models import Sith
+from election.models import Election, Role, Candidature, ElectionList
+
class Command(BaseCommand):
help = "Populate a new instance of the Sith AE"
@@ -48,7 +52,8 @@ class Command(BaseCommand):
is_superuser=True, is_staff=True)
root.set_password("plop")
root.save()
- SithFile(parent=None, name="profiles", is_folder=True, owner=root).save()
+ profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root)
+ profiles_root.save()
home_root = SithFile(parent=None, name="users", is_folder=True, owner=root)
home_root.save()
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
@@ -122,6 +127,17 @@ Welcome to the wiki page!
skia.save()
skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
skia.save()
+ skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg')
+ with open(skia_profile_path, 'rb') as f:
+ name = str(skia.id) + "_profile.jpg"
+ skia_profile = SithFile(parent=profiles_root, name=name,
+ file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
+ owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path))
+ skia_profile.file.name = name
+ skia_profile.save()
+ skia.profile_pict = skia_profile
+ skia.save()
+
# Adding user public
public = User(username='public', last_name="Not subscribed", first_name="Public",
email="public@git.an",
@@ -329,5 +345,85 @@ Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site.
target_label=op[7], cheque_number=op[8])
operation.clean()
operation.save()
-
+
+ # Adding user sli
+ sli = User(username='sli', last_name="Li", first_name="S",
+ email="sli@git.an",
+ date_of_birth="1942-06-12")
+ sli.set_password("plop")
+ sli.save()
+ sli.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
+ sli.save()
+ sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg')
+ with open(sli_profile_path, 'rb') as f:
+ name = str(sli.id) + "_profile.jpg"
+ sli_profile = SithFile(parent=profiles_root, name=name,
+ file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
+ owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path))
+ sli_profile.file.name = name
+ sli_profile.save()
+ sli.profile_pict = sli_profile
+ sli.save()
+ # Adding user Krophil
+ krophil = User(username='krophil', last_name="Phil'", first_name="Kro",
+ email="krophil@git.an",
+ date_of_birth="1942-06-12")
+ krophil.set_password("plop")
+ krophil.save()
+ krophil_profile_path = os.path.join(root_path, 'core/fixtures/images/6.jpg')
+ with open(krophil_profile_path, 'rb') as f:
+ name = str(krophil.id) + "_profile.jpg"
+ krophil_profile = SithFile(parent=profiles_root, name=name,
+ file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
+ owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path))
+ krophil_profile.file.name = name
+ krophil_profile.save()
+ krophil.profile_pict = krophil_profile
+ krophil.save()
+ ## Adding subscription for sli
+ s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
+ payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
+ s.subscription_start = s.compute_start()
+ s.subscription_end = s.compute_end(
+ duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
+ start=s.subscription_start)
+ s.save()
+ ## Adding subscription for Krophil
+ s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
+ payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
+ s.subscription_start = s.compute_start()
+ s.subscription_end = s.compute_end(
+ duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
+ start=s.subscription_start)
+ s.save()
+
+ # Create an election
+ public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID)
+ subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
+ ae_board_gorup = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP)
+ el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01',
+ end_candidature='2042-06-12 10:28:45+01',start_date='1942-06-12 10:28:45+01',
+ end_date='7942-06-12 10:28:45+01')
+ el.save()
+ el.view_groups.add(public_group)
+ el.edit_groups.add(ae_board_gorup)
+ el.candidature_groups.add(subscriber_group)
+ el.vote_groups.add(subscriber_group)
+ el.save()
+ liste = ElectionList(title="Candidature Libre", election=el)
+ liste.save()
+ listeT = ElectionList(title="Troll", election=el)
+ listeT.save()
+ pres = Role(election=el, title="Président AE", description="Roi de l'AE")
+ pres.save()
+ resp = Role(election=el, title="Co Respo Info", max_choice=2, description="Ghetto++")
+ resp.save()
+ cand = Candidature(role=resp, user=skia, election_list=liste, program="Refesons le site AE")
+ cand.save()
+ cand = Candidature(role=resp, user=sli, election_list=liste, program="Vasy je deviens mon propre adjoint")
+ cand.save()
+ cand = Candidature(role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !")
+ cand.save()
+ cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC")
+ cand.save()
diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja
index 1bbc3107..371dcd3f 100644
--- a/core/templates/core/user_tools.jinja
+++ b/core/templates/core/user_tools.jinja
@@ -85,6 +85,14 @@
{{ m.club }}
{% endfor %}
+
+{% trans %}Elections{% endtrans %}
+
{% endblock %}
diff --git a/election/__init__.py b/election/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/election/admin.py b/election/admin.py
new file mode 100644
index 00000000..8c38f3f3
--- /dev/null
+++ b/election/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/election/migrations/0001_squashed_0006_auto_20161223_2315.py b/election/migrations/0001_squashed_0006_auto_20161223_2315.py
new file mode 100644
index 00000000..44c681ff
--- /dev/null
+++ b/election/migrations/0001_squashed_0006_auto_20161223_2315.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ replaces = [('election', '0001_initial'), ('election', '0002_role_max_choice'), ('election', '0003_auto_20161219_1832'), ('election', '0004_auto_20161219_2302'), ('election', '0005_auto_20161223_2240'), ('election', '0006_auto_20161223_2315')]
+
+ dependencies = [
+ ('core', '0016_auto_20161212_1922'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Candidature',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('program', models.TextField(null=True, verbose_name='description', blank=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Election',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('title', models.CharField(verbose_name='title', max_length=255)),
+ ('description', models.TextField(null=True, verbose_name='description', blank=True)),
+ ('start_candidature', models.DateTimeField(verbose_name='start candidature')),
+ ('end_candidature', models.DateTimeField(verbose_name='end candidature')),
+ ('start_date', models.DateTimeField(verbose_name='start date')),
+ ('end_date', models.DateTimeField(verbose_name='end date')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ElectionList',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('title', models.CharField(verbose_name='title', max_length=255)),
+ ('election', models.ForeignKey(related_name='election_lists', verbose_name='election', to='election.Election')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Role',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('title', models.CharField(verbose_name='title', max_length=255)),
+ ('description', models.TextField(null=True, verbose_name='description', blank=True)),
+ ('election', models.ForeignKey(related_name='role', verbose_name='election', to='election.Election')),
+ ('has_voted', models.ManyToManyField(related_name='has_voted', verbose_name='has voted', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Vote',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('candidature', models.ManyToManyField(related_name='votes', verbose_name='candidature', to='election.Candidature')),
+ ('role', models.ForeignKey(related_name='votes', verbose_name='role', to='election.Role')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='candidature',
+ name='election_list',
+ field=models.ForeignKey(related_name='candidatures', verbose_name='election_list', to='election.ElectionList'),
+ ),
+ migrations.AddField(
+ model_name='candidature',
+ name='role',
+ field=models.ForeignKey(related_name='candidatures', verbose_name='role', to='election.Role'),
+ ),
+ migrations.AddField(
+ model_name='candidature',
+ name='user',
+ field=models.ForeignKey(related_name='candidates', verbose_name='user', to=settings.AUTH_USER_MODEL, blank=True),
+ ),
+ migrations.AddField(
+ model_name='role',
+ name='max_choice',
+ field=models.IntegerField(default=1, verbose_name='max choice'),
+ ),
+ migrations.AddField(
+ model_name='election',
+ name='edit_groups',
+ field=models.ManyToManyField(related_name='editable_elections', verbose_name='edit groups', blank=True, to='core.Group'),
+ ),
+ migrations.AddField(
+ model_name='election',
+ name='view_groups',
+ field=models.ManyToManyField(related_name='viewable_elections', verbose_name='view groups', blank=True, to='core.Group'),
+ ),
+ migrations.AddField(
+ model_name='election',
+ name='candidature_groups',
+ field=models.ManyToManyField(related_name='candidate_elections', verbose_name='candidature groups', blank=True, to='core.Group'),
+ ),
+ migrations.AddField(
+ model_name='election',
+ name='vote_groups',
+ field=models.ManyToManyField(related_name='votable_elections', verbose_name='vote groups', blank=True, to='core.Group'),
+ ),
+ migrations.AlterField(
+ model_name='role',
+ name='election',
+ field=models.ForeignKey(related_name='roles', verbose_name='election', to='election.Election'),
+ ),
+ migrations.RemoveField(
+ model_name='role',
+ name='has_voted',
+ ),
+ migrations.AddField(
+ model_name='election',
+ name='voters',
+ field=models.ManyToManyField(related_name='has_voted', verbose_name='has voted', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/election/migrations/__init__.py b/election/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/election/models.py b/election/models.py
new file mode 100644
index 00000000..36c5301a
--- /dev/null
+++ b/election/models.py
@@ -0,0 +1,145 @@
+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
+
+
+class Election(models.Model):
+ """
+ This class allows to create a new election
+ """
+ title = models.CharField(_('title'), max_length=255)
+ description = models.TextField(_('description'), null=True, blank=True)
+ start_candidature = models.DateTimeField(_('start candidature'), blank=False)
+ end_candidature = models.DateTimeField(_('end candidature'), blank=False)
+ 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)
+ voters = models.ManyToManyField(User, verbose_name=('voters'), related_name='voted_elections')
+
+ def __str__(self):
+ return self.title
+
+ @property
+ def is_vote_active(self):
+ now = timezone.now()
+ return bool(now <= self.end_date and now >= self.start_date)
+
+ @property
+ def is_vote_finished(self):
+ return bool(timezone.now() > self.end_date)
+
+ @property
+ def is_candidature_active(self):
+ now = timezone.now()
+ return bool(now <= self.end_candidature and now >= self.start_candidature)
+
+ @property
+ def is_vote_editable(self):
+ return bool(timezone.now() <= self.end_candidature)
+
+ def can_candidate(self, user):
+ for group in self.candidature_groups.all():
+ if user.is_in_group(group):
+ return True
+ return False
+
+ def can_vote(self, user):
+ if not self.is_vote_active or self.has_voted(user):
+ return False
+ for group in self.vote_groups.all():
+ if user.is_in_group(group):
+ return True
+ return False
+
+ def has_voted(self, user):
+ return self.voters.filter(id=user.id).exists()
+
+ @property
+ def results(self):
+ results = {}
+ total_vote = self.voters.count()
+ for role in self.roles.all():
+ results[role.title] = role.results(total_vote)
+ return results
+
+ # Permissions
+
+
+class Role(models.Model):
+ """
+ This class allows to create a new role avaliable for a candidature
+ """
+ election = models.ForeignKey(Election, related_name='roles', verbose_name=_("election"))
+ title = models.CharField(_('title'), max_length=255)
+ description = models.TextField(_('description'), null=True, blank=True)
+ max_choice = models.IntegerField(_('max choice'), default=1)
+
+ def results(self, total_vote):
+ results = {}
+ total_vote *= self.max_choice
+ if total_vote == 0:
+ return None
+ non_blank = 0
+ for candidature in self.candidatures.all():
+ cand_results = {}
+ cand_results['vote'] = self.votes.filter(candidature=candidature).count()
+ cand_results['percent'] = cand_results['vote'] * 100 / total_vote
+ non_blank += cand_results['vote']
+ results[candidature.user.username] = cand_results
+ results['total vote'] = total_vote
+ results['blank vote'] = {'vote': total_vote - non_blank,
+ 'percent': (total_vote - non_blank) * 100 / total_vote}
+ return results
+
+ @property
+ def edit_groups(self):
+ return self.election.edit_groups
+
+ def __str__(self):
+ return ("%s : %s") % (self.election.title, self.title)
+
+
+class ElectionList(models.Model):
+ """
+ To allow per list vote
+ """
+ title = models.CharField(_('title'), max_length=255)
+ election = models.ForeignKey(Election, related_name='election_lists', verbose_name=_("election"))
+
+ def __str__(self):
+ return self.title
+
+
+class Candidature(models.Model):
+ """
+ This class is a component of responsability
+ """
+ role = models.ForeignKey(Role, related_name='candidatures', verbose_name=_("role"))
+ user = models.ForeignKey(User, verbose_name=_('user'), related_name='candidates', blank=True)
+ program = models.TextField(_('description'), null=True, blank=True)
+ election_list = models.ForeignKey(ElectionList, related_name='candidatures', verbose_name=_('election list'))
+
+ def can_be_edited_by(self, user):
+ return (user == self.user)
+
+ def __str__(self):
+ return "%s : %s" % (self.role.title, self.user.username)
+
+
+class Vote(models.Model):
+ """
+ This class allows to vote for candidates
+ """
+ role = models.ForeignKey(Role, related_name='votes', verbose_name=_("role"))
+ candidature = models.ManyToManyField(Candidature, related_name='votes', verbose_name=_("candidature"))
+
+ def __str__(self):
+ return "Vote"
\ No newline at end of file
diff --git a/election/templates/election/candidate_form.jinja b/election/templates/election/candidate_form.jinja
new file mode 100644
index 00000000..ad889b27
--- /dev/null
+++ b/election/templates/election/candidate_form.jinja
@@ -0,0 +1,19 @@
+{% extends "core/base.jinja" %}
+
+{% block title %}
+{% trans %}Candidate{% endtrans %}
+{% endblock %}
+
+{% block content %}
+ {%- if election.can_candidate(user) or user.can_edit(election) %}
+
+ {%- else -%}
+ {% trans %}Candidature are closed for this election{% endtrans %}
+ {%- endif %}
+{% endblock content %}
diff --git a/election/templates/election/election_detail.jinja b/election/templates/election/election_detail.jinja
new file mode 100644
index 00000000..dea749ff
--- /dev/null
+++ b/election/templates/election/election_detail.jinja
@@ -0,0 +1,400 @@
+{% extends "core/base.jinja" %}
+
+{% block title %}
+{{ object.title }}
+{% endblock %}
+
+{% block head %}
+{{ super() -}}
+
+{%- endblock %}
+
+{% block content %}
+ {{ election.title }}
+ {{ election.description }}
+
+
+
+ {%- if election.is_vote_active %}
+ {% trans %}Polls close {% endtrans %}
+ {%- elif election.is_vote_finished %}
+ {% trans %}Polls closed {% endtrans %}
+ {%- else %}
+ {% trans %}Polls will open {% endtrans %}
+ {{ election.start_date|localtime|date(DATETIME_FORMAT)}}
+ {% trans %} at {% endtrans %}{{ election.start_date|localtime|time(DATETIME_FORMAT)}}
+ {% trans %}and will close {% endtrans %}
+ {%- endif %}
+ {{ election.end_date|localtime|date(DATETIME_FORMAT)}}
+ {% trans %} at {% endtrans %}{{ election.end_date|localtime|time(DATETIME_FORMAT)}}
+
+ {%- if election.has_voted(user) %}
+
+ {%- if election.is_vote_active %}
+ {% trans %}You already have submitted your vote.{% endtrans %}
+ {%- else %}
+ {% trans %}You have voted in this election.{% endtrans %}
+ {%- endif %}
+
+ {%- endif %}
+
+
+ {%- if not election.has_voted(user) and election.can_vote(user) %}
+
+ {% trans %}Submit the vote !{% endtrans %}
+
+ {%- endif %}
+
+{% endblock %}
+
+{% block script %}
+{{ super() }}
+
+{% endblock %}
diff --git a/election/templates/election/election_list.jinja b/election/templates/election/election_list.jinja
new file mode 100644
index 00000000..3bcb0110
--- /dev/null
+++ b/election/templates/election/election_list.jinja
@@ -0,0 +1,48 @@
+{% extends "core/base.jinja" %}
+
+{% block title %}
+{%- trans %}Election list{% endtrans %}
+{%- endblock %}
+
+{% block head %}
+{{ super() -}}
+
+{%- endblock %}
+
+{% block content %}
+ {% trans %}Current elections{% endtrans %}
+ {%- for election in object_list %}
+
+
+
+
+ {% trans %}Applications open from{% endtrans %}
+ {{ election.start_candidature|localtime|date(DATETIME_FORMAT) }}
+ {% trans %} at {% endtrans %}{{ election.start_candidature|localtime|time(DATETIME_FORMAT) }}
+ {% trans %}to{% endtrans %}
+ {{ election.end_candidature|localtime|date(DATETIME_FORMAT) }}
+ {% trans %} at {% endtrans %}{{ election.end_candidature|time(DATETIME_FORMAT) }}
+
+
+ {% trans %}Polls open from{% endtrans %}
+ {{ election.start_date|localtime|date(DATETIME_FORMAT) }}
+ {% trans %} at {% endtrans %}{{ election.start_date|localtime|time(DATETIME_FORMAT) }}
+ {% trans %}to{% endtrans %}
+ {{ election.end_date|localtime|date(DATETIME_FORMAT) }}
+ {% trans %} at {% endtrans %}{{ election.end_date|localtime|time(DATETIME_FORMAT) }}
+
+ {{ election.description }}
+
+ {%- endfor %}
+{%- endblock %}
+
diff --git a/election/tests.py b/election/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/election/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/election/urls.py b/election/urls.py
new file mode 100644
index 00000000..8140cb49
--- /dev/null
+++ b/election/urls.py
@@ -0,0 +1,18 @@
+from django.conf.urls import url
+
+from election.views import *
+
+urlpatterns = [
+ url(r'^$', ElectionsListView.as_view(), name='list'),
+ url(r'^add$', ElectionCreateView.as_view(), name='create'),
+ url(r'^(?P[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'),
+ url(r'^(?P[0-9]+)/list/add$', ElectionListCreateView.as_view(), name='create_list'),
+ url(r'^(?P[0-9]+)/role/create$', RoleCreateView.as_view(), name='create_role'),
+ url(r'^(?P[0-9]+)/role/edit$', RoleUpdateView.as_view(), name='update_role'),
+ url(r'^(?P[0-9]+)/role/delete$', RoleDeleteView.as_view(), name='delete_role'),
+ url(r'^(?P[0-9]+)/candidate/add$', CandidatureCreateView.as_view(), name='candidate'),
+ url(r'^(?P[0-9]+)/candidate/edit$', CandidatureUpdateView.as_view(), name='update_candidate'),
+ url(r'^(?P[0-9]+)/candidate/delete$', CandidatureDeleteView.as_view(), name='delete_candidate'),
+ url(r'^(?P[0-9]+)/vote$', VoteFormView.as_view(), name='vote'),
+ url(r'^(?P[0-9]+)/detail$', ElectionDetailView.as_view(), name='detail'),
+]
diff --git a/election/views.py b/election/views.py
new file mode 100644
index 00000000..88235898
--- /dev/null
+++ b/election/views.py
@@ -0,0 +1,478 @@
+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.utils.translation import ugettext_lazy as _
+from django.forms.models import modelform_factory
+from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured
+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 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
+
+from ajax_select.fields import AutoCompleteSelectField
+
+
+# Custom form field
+
+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):
+ self.max_choice = max_choice
+ widget = forms.CheckboxSelectMultiple()
+ super(LimitedCheckboxField, self).__init__(queryset, None, required, widget, label,
+ initial, help_text, *args, **kwargs)
+
+ def clean(self, value):
+ qs = super(LimitedCheckboxField, self).clean(value)
+ self.validate(qs)
+ return qs
+
+ def validate(self, qs):
+ if qs.count() > self.max_choice:
+ raise forms.ValidationError(_("You have selected too much candidates."), code='invalid')
+
+
+# Forms
+
+
+class CandidateForm(forms.ModelForm):
+ """ Form to candidate """
+ class Meta:
+ model = Candidature
+ fields = ['user', 'role', 'program', 'election_list']
+ widgets = {
+ 'program': forms.Textarea
+ }
+
+ user = AutoCompleteSelectField('users', label=_('User to candidate'), help_text=None, required=True)
+
+ def __init__(self, *args, **kwargs):
+ election_id = kwargs.pop('election_id', None)
+ can_edit = kwargs.pop('can_edit', False)
+ super(CandidateForm, self).__init__(*args, **kwargs)
+ if election_id:
+ self.fields['role'].queryset = Role.objects.filter(election__id=election_id).all()
+ self.fields['election_list'].queryset = ElectionList.objects.filter(election__id=election_id).all()
+ if not can_edit:
+ self.fields['user'].widget = forms.HiddenInput()
+
+
+class VoteForm(forms.Form):
+ def __init__(self, election, user, *args, **kwargs):
+ super(VoteForm, self).__init__(*args, **kwargs)
+ if not election.has_voted(user):
+ for role in election.roles.all():
+ cand = role.candidatures
+ if role.max_choice > 1:
+ 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"))
+
+
+class RoleForm(forms.ModelForm):
+ """ Form for creating a role """
+ class Meta:
+ model = Role
+ fields = ['title', 'election', 'description', 'max_choice']
+
+ def __init__(self, *args, **kwargs):
+ election_id = kwargs.pop('election_id', None)
+ super(RoleForm, self).__init__(*args, **kwargs)
+ if election_id:
+ self.fields['election'].queryset = Election.objects.filter(id=election_id).all()
+
+ def clean(self):
+ cleaned_data = super(RoleForm, self).clean()
+ title = cleaned_data.get('title')
+ election = cleaned_data.get('election')
+ if Role.objects.filter(title=title, election=election).exists():
+ raise forms.ValidationError(_("This role already exists for this election"), code='invalid')
+
+
+class ElectionListForm(forms.ModelForm):
+ class Meta:
+ model = ElectionList
+ fields = ('title','election')
+
+ def __init__(self, *args, **kwargs):
+ election_id = kwargs.pop('election_id', None)
+ super(ElectionListForm, self).__init__(*args, **kwargs)
+ if election_id:
+ self.fields['election'].queryset = Election.objects.filter(id=election_id).all()
+
+
+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']
+ widgets = {
+ 'edit_groups': CheckboxSelectMultiple,
+ 'view_groups': CheckboxSelectMultiple,
+ 'edit_groups': CheckboxSelectMultiple,
+ 'vote_groups': CheckboxSelectMultiple,
+ '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)
+
+
+# Display elections
+
+
+class ElectionsListView(CanViewMixin, ListView):
+ """
+ A list with all responsabilities and their candidates
+ """
+ model = Election
+ template_name = 'election/election_list.jinja'
+
+
+class ElectionDetailView(CanViewMixin, DetailView):
+ """
+ Details an election responsability by responsability
+ """
+ model = Election
+ template_name = 'election/election_detail.jinja'
+ pk_url_kwarg = "election_id"
+
+ def get_context_data(self, **kwargs):
+ """ Add additionnal data to the template """
+ kwargs = super(ElectionDetailView, self).get_context_data(**kwargs)
+ kwargs['election_form'] = VoteForm(self.object, self.request.user)
+ kwargs['election_results'] = self.object.results
+ return kwargs
+
+
+# Form view
+
+class VoteFormView(CanCreateMixin, FormView):
+ """
+ Alows users to vote
+ """
+ form_class = VoteForm
+ template_name = 'election/election_detail.jinja'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.election = get_object_or_404(Election, pk=kwargs['election_id'])
+ return super(VoteFormView, self).dispatch(request, *arg, **kwargs)
+
+ def vote(self, election_data):
+ for role_title in election_data.keys():
+ # If we have a multiple choice field
+ if isinstance(election_data[role_title], QuerySet):
+ if election_data[role_title].count() > 0:
+ vote = Vote(role=election_data[role_title].first().role)
+ vote.save()
+ for el in election_data[role_title]:
+ vote.candidature.add(el)
+ # If we have a single choice
+ elif election_data[role_title] is not None:
+ vote = Vote(role=election_data[role_title].role)
+ vote.save()
+ vote.candidature.add(election_data[role_title])
+ self.election.voters.add(self.request.user)
+
+ def get_form_kwargs(self):
+ kwargs = super(VoteFormView, self).get_form_kwargs()
+ kwargs['election'] = self.election
+ kwargs['user'] = self.request.user
+ return kwargs
+
+ def form_valid(self, form):
+ """
+ Verify that the user is part in a vote group
+ """
+ data = form.clean()
+ res = super(FormView, self).form_valid(form)
+ for grp in self.election.vote_groups.all():
+ if self.request.user.is_in_group(grp):
+ self.vote(data)
+ return res
+ return res
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.election.id})
+
+ def get_context_data(self, **kwargs):
+ """ Add additionnal data to the template """
+ kwargs = super(VoteFormView, self).get_context_data(**kwargs)
+ kwargs['object'] = self.election
+ kwargs['election'] = self.election
+ kwargs['election_form'] = self.get_form()
+ return kwargs
+
+
+# Create views
+
+class CandidatureCreateView(CanCreateMixin, CreateView):
+ """
+ View dedicated to a cundidature creation
+ """
+ form_class = CandidateForm
+ model = Candidature
+ template_name = 'election/candidate_form.jinja'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.election = get_object_or_404(Election, pk=kwargs['election_id'])
+ return super(CandidatureCreateView, self).dispatch(request, *arg, **kwargs)
+
+ def get_initial(self):
+ init = {}
+ self.can_edit = self.request.user.can_edit(self.election)
+ init['user'] = self.request.user.id
+ return init
+
+ def get_form_kwargs(self):
+ kwargs = super(CandidatureCreateView, self).get_form_kwargs()
+ kwargs['election_id'] = self.election.id
+ kwargs['can_edit'] = self.can_edit
+ return kwargs
+
+ def form_valid(self, form):
+ """
+ Verify that the selected user is in candidate group
+ """
+ obj = form.instance
+ obj.election = Election.objects.get(id=self.election.id)
+ if(obj.election.can_candidate(obj.user)) and (obj.user == self.request.user or self.can_edit):
+ return super(CreateView, self).form_valid(form)
+ raise PermissionDenied
+
+ def get_context_data(self, **kwargs):
+ kwargs = super(CandidatureCreateView, self).get_context_data(**kwargs)
+ kwargs['election'] = self.election
+ return kwargs
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.election.id})
+
+
+class ElectionCreateView(CanCreateMixin, CreateView):
+ model = Election
+ form_class = ElectionForm
+ template_name = 'core/create.jinja'
+
+ def dispatch(self, request, *args, **kwargs):
+ if not request.user.is_subscribed():
+ raise PermissionDenied
+ return super(ElectionCreateView, self).dispatch(request, *args, **kwargs)
+
+ def form_valid(self, form):
+ """
+ Allow every users that had passed the dispatch to create an election
+ """
+ return super(CreateView, self).form_valid(form)
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.id})
+
+
+class RoleCreateView(CanCreateMixin, CreateView):
+ model = Role
+ form_class = RoleForm
+ template_name = 'core/create.jinja'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.election = get_object_or_404(Election, pk=kwargs['election_id'])
+ if not self.election.is_vote_editable:
+ raise PermissionDenied
+ return super(RoleCreateView, self).dispatch(request, *arg, **kwargs)
+
+ def get_initial(self):
+ init = {}
+ init['election'] = self.election
+ return init
+
+ def form_valid(self, form):
+ """
+ Verify that the user can edit proprely
+ """
+ obj = form.instance
+ if obj.election:
+ for grp in obj.election.edit_groups.all():
+ if self.request.user.is_in_group(grp):
+ return super(CreateView, self).form_valid(form)
+ raise PermissionDenied
+
+ def get_form_kwargs(self):
+ kwargs = super(RoleCreateView, self).get_form_kwargs()
+ kwargs['election_id'] = self.election.id
+ return kwargs
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id})
+
+
+class ElectionListCreateView(CanCreateMixin, CreateView):
+ model = ElectionList
+ form_class = ElectionListForm
+ template_name = 'core/create.jinja'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.election = get_object_or_404(Election, pk=kwargs['election_id'])
+ if not self.election.is_vote_editable:
+ raise PermissionDenied
+ return super(ElectionListCreateView, self).dispatch(request, *arg, **kwargs)
+
+ def get_initial(self):
+ init = {}
+ init['election'] = self.election
+ return init
+
+ def get_form_kwargs(self):
+ kwargs = super(ElectionListCreateView, self).get_form_kwargs()
+ kwargs['election_id'] = self.election.id
+ return kwargs
+
+ def form_valid(self, form):
+ """
+ Verify that the user can vote on this election
+ """
+ obj = form.instance
+ if obj.election:
+ for grp in obj.election.candidature_groups.all():
+ if self.request.user.is_in_group(grp):
+ return super(CreateView, self).form_valid(form)
+ for grp in obj.election.edit_groups.all():
+ if self.request.user.is_in_group(grp):
+ return super(CreateView, self).form_valid(form)
+ raise PermissionDenied
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id})
+
+# Update view
+
+
+class ElectionUpdateView(CanEditMixin, UpdateView):
+ model = Election
+ form_class = ElectionForm
+ template_name = 'core/edit.jinja'
+ pk_url_kwarg = 'election_id'
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.id})
+
+
+class CandidatureUpdateView(CanEditMixin, UpdateView):
+ model = Candidature
+ form_class = CandidateForm
+ template_name = 'core/edit.jinja'
+ pk_url_kwarg = 'candidature_id'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.object = self.get_object()
+ if not self.object.role.election.is_vote_editable:
+ raise PermissionDenied
+ return super(CandidatureUpdateView, self).dispatch(request, *arg, **kwargs)
+
+ def remove_fields(self):
+ self.form.fields.pop('role', None)
+
+ def get(self, request, *args, **kwargs):
+ self.form = self.get_form()
+ self.remove_fields()
+ return self.render_to_response(self.get_context_data(form=self.form))
+
+ def post(self, request, *args, **kwargs):
+ self.form = self.get_form()
+ self.remove_fields()
+ if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid():
+ return super(CandidatureUpdateView, self).form_valid(self.form)
+ return self.form_invalid(self.form)
+
+ def get_form_kwargs(self):
+ kwargs = super(CandidatureUpdateView, self).get_form_kwargs()
+ kwargs['election_id'] = self.object.role.election.id
+ return kwargs
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.role.election.id})
+
+
+class RoleUpdateView(CanEditMixin, UpdateView):
+ model = Role
+ form_class = RoleForm
+ template_name = 'core/edit.jinja'
+ pk_url_kwarg = 'role_id'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.object = self.get_object()
+ if not self.object.election.is_vote_editable:
+ raise PermissionDenied
+ return super(RoleUpdateView, self).dispatch(request, *arg, **kwargs)
+
+ def remove_fields(self):
+ self.form.fields.pop('election', None)
+
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ self.form = self.get_form()
+ self.remove_fields()
+ return self.render_to_response(self.get_context_data(form=self.form))
+
+ def post(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ self.form = self.get_form()
+ self.remove_fields()
+ if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid():
+ return super(RoleUpdateView, self).form_valid(self.form)
+ return self.form_invalid(self.form)
+
+ def get_form_kwargs(self):
+ kwargs = super(RoleUpdateView, self).get_form_kwargs()
+ kwargs['election_id'] = self.object.election.id
+ return kwargs
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id})
+
+# Delete Views
+
+
+class CandidatureDeleteView(CanEditMixin, DeleteView):
+ model = Candidature
+ template_name = 'core/delete_confirm.jinja'
+ pk_url_kwarg = 'candidature_id'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.object = self.get_object()
+ self.election = self.object.role.election
+ if not self.election.can_candidate:
+ raise PermissionDenied
+ return super(CandidatureDeleteView, self).dispatch(request, *arg, **kwargs)
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.election.id})
+
+
+class RoleDeleteView(CanEditMixin, DeleteView):
+ model = Role
+ template_name = 'core/delete_confirm.jinja'
+ pk_url_kwarg = 'role_id'
+
+ def dispatch(self, request, *arg, **kwargs):
+ self.object = self.get_object()
+ self.election = self.object.election
+ if not self.election.is_vote_editable:
+ raise PermissionDenied
+ return super(RoleDeleteView, self).dispatch(request, *arg, **kwargs)
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy('election:detail', kwargs={'election_id': self.election.id})
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 88ab8e69..002c873b 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-12-23 20:10+0100\n"
+"POT-Creation-Date: 2016-12-26 00:34+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia \n"
"Language-Team: AE info \n"
@@ -86,11 +86,12 @@ msgid "%(club_account)s on %(bank_account)s"
msgstr "%(club_account)s sur %(bank_account)s"
#: accounting/models.py:142 club/models.py:146 counter/models.py:399
-#: launderette/models.py:120
+#: election/models.py:18 launderette/models.py:120
msgid "start date"
msgstr "date de début"
#: accounting/models.py:143 club/models.py:147 counter/models.py:400
+#: election/models.py:19
msgid "end date"
msgstr "date de fin"
@@ -192,7 +193,7 @@ msgstr "Compte"
msgid "Company"
msgstr "Entreprise"
-#: accounting/models.py:207 sith/settings.py:296
+#: accounting/models.py:207 sith/settings.py:297
msgid "Other"
msgstr "Autre"
@@ -321,6 +322,8 @@ msgstr "Compte en banque : "
#: core/templates/core/user_edit.jinja:19
#: counter/templates/counter/last_ops.jinja:29
#: counter/templates/counter/last_ops.jinja:59
+#: election/templates/election/election_detail.jinja:280
+#: election/templates/election/election_detail.jinja:327
#: launderette/templates/launderette/launderette_admin.jinja:16
#: launderette/views.py:154 sas/templates/sas/album.jinja:26
#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:66
@@ -356,6 +359,9 @@ msgstr "Nouveau compte club"
#: counter/templates/counter/counter_list.jinja:17
#: counter/templates/counter/counter_list.jinja:32
#: counter/templates/counter/counter_list.jinja:47
+#: election/templates/election/election_detail.jinja:279
+#: election/templates/election/election_detail.jinja:324
+#: election/templates/election/election_detail.jinja:370
#: launderette/templates/launderette/launderette_list.jinja:16
#: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:92
msgid "Edit"
@@ -766,17 +772,19 @@ msgid "A club with that unix_name already exists"
msgstr "Un club avec ce nom UNIX existe déjà."
#: club/models.py:144 counter/models.py:397 counter/models.py:414
-#: eboutic/models.py:14 eboutic/models.py:47 launderette/models.py:87
-#: launderette/models.py:124 sas/models.py:131
+#: eboutic/models.py:14 eboutic/models.py:47 election/models.py:126
+#: launderette/models.py:87 launderette/models.py:124 sas/models.py:131
msgid "user"
msgstr "nom d'utilisateur"
-#: club/models.py:148 core/models.py:137
+#: club/models.py:148 core/models.py:137 election/models.py:125
+#: election/models.py:141
msgid "role"
msgstr "rôle"
#: club/models.py:150 core/models.py:33 counter/models.py:71
-#: counter/models.py:96
+#: counter/models.py:96 election/models.py:15 election/models.py:82
+#: election/models.py:127
msgid "description"
msgstr "description"
@@ -979,7 +987,7 @@ msgstr "Vous n'avez pas la permission de faire cela"
msgid "Begin date"
msgstr "Date de début"
-#: club/views.py:166 com/views.py:81 counter/views.py:909
+#: club/views.py:166 com/views.py:81 counter/views.py:909 election/views.py:130
msgid "End date"
msgstr "Date de fin"
@@ -1016,7 +1024,8 @@ msgstr "Hebdomadaire"
msgid "Call"
msgstr "Appel"
-#: com/models.py:30
+#: com/models.py:30 election/models.py:14 election/models.py:81
+#: election/models.py:114
msgid "title"
msgstr "titre"
@@ -1185,7 +1194,7 @@ msgstr "Message d'info"
msgid "Alert message"
msgstr "Message d'alerte"
-#: com/views.py:80
+#: com/views.py:80 election/views.py:129
msgid "Start date"
msgstr "Date de début"
@@ -1484,6 +1493,7 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
#: core/templates/core/user_detail.jinja:14
#: core/templates/core/user_detail.jinja:16
#: core/templates/core/user_edit.jinja:17
+#: election/templates/election/election_detail.jinja:316
msgid "Profile"
msgstr "Profil"
@@ -2421,6 +2431,18 @@ msgstr "Modérer les fichiers"
msgid "Moderate pictures"
msgstr "Modérer les photos"
+#: core/templates/core/user_tools.jinja:89
+msgid "Elections"
+msgstr "Élections"
+
+#: core/templates/core/user_tools.jinja:91
+msgid "See available elections"
+msgstr "Voir les élections disponibles"
+
+#: core/templates/core/user_tools.jinja:93
+msgid "Create a new election"
+msgstr "Créer une nouvelle élection"
+
#: core/views/files.py:49
msgid "Add a new folder"
msgstr "Ajouter un nouveau dossier"
@@ -2566,7 +2588,7 @@ msgstr "Bureau"
#: eboutic/templates/eboutic/eboutic_main.jinja:24
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8
#: eboutic/templates/eboutic/eboutic_payment_result.jinja:4
-#: sith/settings.py:295 sith/settings.py:303
+#: sith/settings.py:296 sith/settings.py:304
msgid "Eboutic"
msgstr "Eboutic"
@@ -2607,8 +2629,8 @@ msgstr "quantité"
msgid "Sith account"
msgstr "Compte utilisateur"
-#: counter/models.py:292 sith/settings.py:288 sith/settings.py:293
-#: sith/settings.py:315
+#: counter/models.py:292 sith/settings.py:289 sith/settings.py:294
+#: sith/settings.py:316
msgid "Credit card"
msgstr "Carte bancaire"
@@ -3091,6 +3113,168 @@ 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
+msgid "start candidature"
+msgstr "début des candidatures"
+
+#: election/models.py:17
+msgid "end candidature"
+msgstr "fin des candidatures"
+
+#: election/models.py:21
+msgid "edit groups"
+msgstr "groupe d'édition"
+
+#: election/models.py:22
+msgid "view groups"
+msgstr "groupe de vue"
+
+#: election/models.py:23
+msgid "vote groups"
+msgstr "groupe de vote"
+
+#: election/models.py:24
+msgid "candidature groups"
+msgstr "groupe de candidature"
+
+#: election/models.py:80 election/models.py:115
+msgid "election"
+msgstr "élection"
+
+#: election/models.py:83
+msgid "max choice"
+msgstr "nombre de choix maxi"
+
+#: election/models.py:128
+msgid "election list"
+msgstr "liste électorale"
+
+#: election/models.py:142
+msgid "candidature"
+msgstr "candidature"
+
+#: election/templates/election/candidate_form.jinja:4
+#: election/templates/election/candidate_form.jinja:13
+#: election/templates/election/election_detail.jinja:363
+msgid "Candidate"
+msgstr "Candidater"
+
+#: election/templates/election/candidate_form.jinja:17
+msgid "Candidature are closed for this election"
+msgstr "Les candidatures sont fermées pour cette élection"
+
+#: election/templates/election/election_detail.jinja:237
+msgid "Polls close "
+msgstr "Votes fermés"
+
+#: election/templates/election/election_detail.jinja:239
+msgid "Polls closed "
+msgstr "Votes fermés"
+
+#: election/templates/election/election_detail.jinja:241
+msgid "Polls will open "
+msgstr "Les votes ouvriront "
+
+#: election/templates/election/election_detail.jinja:243
+#: election/templates/election/election_detail.jinja:247
+#: election/templates/election/election_list.jinja:31
+#: election/templates/election/election_list.jinja:34
+#: election/templates/election/election_list.jinja:39
+#: election/templates/election/election_list.jinja:42
+msgid " at "
+msgstr " à "
+
+#: election/templates/election/election_detail.jinja:244
+msgid "and will close "
+msgstr "et fermeront"
+
+#: election/templates/election/election_detail.jinja:252
+msgid "You already have submitted your vote."
+msgstr "Vous avez déjà soumis votre vote."
+
+#: election/templates/election/election_detail.jinja:254
+msgid "You have voted in this election."
+msgstr "Vous avez déjà voté pour cette élection."
+
+#: election/templates/election/election_detail.jinja:266 election/views.py:81
+msgid "Blank vote"
+msgstr "Vote blanc"
+
+#: election/templates/election/election_detail.jinja:283
+msgid "You may choose up to"
+msgstr "Vous pouvez choisir jusqu'à"
+
+#: election/templates/election/election_detail.jinja:283
+msgid "people."
+msgstr "personne(s)"
+
+#: election/templates/election/election_detail.jinja:297
+msgid "Choose blank vote"
+msgstr "Choisir de voter blanc"
+
+#: election/templates/election/election_detail.jinja:304
+#: election/templates/election/election_detail.jinja:342
+msgid "votes"
+msgstr "votes"
+
+#: election/templates/election/election_detail.jinja:335
+#: launderette/templates/launderette/launderette_book.jinja:12
+msgid "Choose"
+msgstr "Choisir"
+
+#: election/templates/election/election_detail.jinja:358
+msgid "Submit the vote !"
+msgstr "Envoyer le vote !"
+
+#: election/templates/election/election_detail.jinja:365
+msgid "Add a new list"
+msgstr "Ajouter une nouvelle liste"
+
+#: election/templates/election/election_detail.jinja:368
+msgid "Add a new role"
+msgstr "Ajouter un nouveau rôle"
+
+#: election/templates/election/election_list.jinja:4
+msgid "Election list"
+msgstr "Liste des élections"
+
+#: election/templates/election/election_list.jinja:21
+msgid "Current elections"
+msgstr "Élections actuelles"
+
+#: election/templates/election/election_list.jinja:29
+msgid "Applications open from"
+msgstr "Candidatures ouvertes à partir du"
+
+#: election/templates/election/election_list.jinja:32
+#: election/templates/election/election_list.jinja:40
+msgid "to"
+msgstr "au"
+
+#: election/templates/election/election_list.jinja:37
+msgid "Polls open from"
+msgstr "Votes ouverts du"
+
+#: election/views.py:43
+msgid "You have selected too much candidates."
+msgstr "Vous avez sélectionné trop de candidats."
+
+#: election/views.py:58
+msgid "User to candidate"
+msgstr "Utilisateur se présentant"
+
+#: election/views.py:101
+msgid "This role already exists for this election"
+msgstr "Ce rôle existe déjà pour cette élection"
+
+#: election/views.py:131
+msgid "Start candidature"
+msgstr "Début des candidatures"
+
+#: election/views.py:132
+msgid "End candidature"
+msgstr "Fin des candidatures"
+
#: launderette/models.py:17
#: launderette/templates/launderette/launderette_book.jinja:5
#: launderette/templates/launderette/launderette_book_choose.jinja:4
@@ -3142,21 +3326,17 @@ msgstr "Machines"
msgid "New machine"
msgstr "Nouvelle machine"
-#: launderette/templates/launderette/launderette_book.jinja:12
-msgid "Choose"
-msgstr "Choisir"
-
#: launderette/templates/launderette/launderette_book.jinja:23
msgid "Washing and drying"
msgstr "Lavage et séchage"
#: launderette/templates/launderette/launderette_book.jinja:27
-#: sith/settings.py:436
+#: sith/settings.py:437
msgid "Washing"
msgstr "Lavage"
#: launderette/templates/launderette/launderette_book.jinja:31
-#: sith/settings.py:436
+#: sith/settings.py:437
msgid "Drying"
msgstr "Séchage"
@@ -3294,145 +3474,145 @@ msgstr "Ajouter une personne"
msgid "Apply rights recursively"
msgstr "Appliquer les droits récursivement"
-#: sith/settings.py:175
+#: sith/settings.py:176
msgid "English"
msgstr "Anglais"
-#: sith/settings.py:176
+#: sith/settings.py:177
msgid "French"
msgstr "Français"
-#: sith/settings.py:285 sith/settings.py:292 sith/settings.py:313
+#: sith/settings.py:286 sith/settings.py:293 sith/settings.py:314
msgid "Check"
msgstr "Chèque"
-#: sith/settings.py:286 sith/settings.py:294 sith/settings.py:314
+#: sith/settings.py:287 sith/settings.py:295 sith/settings.py:315
msgid "Cash"
msgstr "Espèces"
-#: sith/settings.py:287
+#: sith/settings.py:288
msgid "Transfert"
msgstr "Virement"
-#: sith/settings.py:300
+#: sith/settings.py:301
msgid "Belfort"
msgstr "Belfort"
-#: sith/settings.py:301
+#: sith/settings.py:302
msgid "Sevenans"
msgstr "Sevenans"
-#: sith/settings.py:302
+#: sith/settings.py:303
msgid "Montbéliard"
msgstr "Montbéliard"
-#: sith/settings.py:343
+#: sith/settings.py:344
msgid "One semester"
msgstr "Un semestre, 15 €"
-#: sith/settings.py:348
+#: sith/settings.py:349
msgid "Two semesters"
msgstr "Deux semestres, 28 €"
-#: sith/settings.py:353
+#: sith/settings.py:354
msgid "Common core cursus"
msgstr "Cursus tronc commun, 45 €"
-#: sith/settings.py:358
+#: sith/settings.py:359
msgid "Branch cursus"
msgstr "Cursus branche, 45 €"
-#: sith/settings.py:363
+#: sith/settings.py:364
msgid "Alternating cursus"
msgstr "Cursus alternant, 30 €"
-#: sith/settings.py:368
+#: sith/settings.py:369
msgid "Honorary member"
msgstr "Membre honoraire, 0 €"
-#: sith/settings.py:373
+#: sith/settings.py:374
msgid "Assidu member"
msgstr "Membre d'Assidu, 0 €"
-#: sith/settings.py:378
+#: sith/settings.py:379
msgid "Amicale/DOCEO member"
msgstr "Membre de l'Amicale/DOCEO, 0 €"
-#: sith/settings.py:383
+#: sith/settings.py:384
msgid "UT network member"
msgstr "Cotisant du réseau UT, 0 €"
-#: sith/settings.py:388
+#: sith/settings.py:389
msgid "CROUS member"
msgstr "Membres du CROUS, 0 €"
-#: sith/settings.py:393
+#: sith/settings.py:394
msgid "Sbarro/ESTA member"
msgstr "Membre de Sbarro ou de l'ESTA, 15 €"
-#: sith/settings.py:401
+#: sith/settings.py:402
msgid "President"
msgstr "Président"
-#: sith/settings.py:402
+#: sith/settings.py:403
msgid "Vice-President"
msgstr "Vice-Président"
-#: sith/settings.py:403
+#: sith/settings.py:404
msgid "Treasurer"
msgstr "Trésorier"
-#: sith/settings.py:404
+#: sith/settings.py:405
msgid "Communication supervisor"
msgstr "Responsable com"
-#: sith/settings.py:405
+#: sith/settings.py:406
msgid "Secretary"
msgstr "Secrétaire"
-#: sith/settings.py:406
+#: sith/settings.py:407
msgid "IT supervisor"
msgstr "Responsable info"
-#: sith/settings.py:407
+#: sith/settings.py:408
msgid "Board member"
msgstr "Membre du bureau"
-#: sith/settings.py:408
+#: sith/settings.py:409
msgid "Active member"
msgstr "Membre actif"
-#: sith/settings.py:409
+#: sith/settings.py:410
msgid "Curious"
msgstr "Curieux"
-#: sith/settings.py:443
+#: sith/settings.py:444
msgid "A fresh new to be moderated"
msgstr "Une nouvelle toute neuve à modérer"
-#: sith/settings.py:444
+#: sith/settings.py:445
msgid "New files to be moderated"
msgstr "Nouveaux fichiers à modérer"
-#: sith/settings.py:445
+#: sith/settings.py:446
msgid "New pictures/album to be moderated in the SAS"
msgstr "Nouvelles photos/albums à modérer dans le SAS"
-#: sith/settings.py:446
+#: sith/settings.py:447
msgid "You've been identified on some pictures"
msgstr "Vous avez été identifié sur des photos"
-#: sith/settings.py:447
+#: sith/settings.py:448
#, python-format
msgid "You just refilled of %s €"
msgstr "Vous avez rechargé votre compte de %s €"
-#: sith/settings.py:448
+#: sith/settings.py:449
#, python-format
msgid "You just bought %s"
msgstr "Vous avez acheté %s"
-#: sith/settings.py:449
+#: sith/settings.py:450
msgid "You have a notification"
msgstr "Vous avez une notification"
diff --git a/sith/settings.py b/sith/settings.py
index ba905351..c1680fbd 100644
--- a/sith/settings.py
+++ b/sith/settings.py
@@ -58,6 +58,7 @@ INSTALLED_APPS = (
'rootplace',
'sas',
'com',
+ 'election',
)
MIDDLEWARE_CLASSES = (
diff --git a/sith/urls.py b/sith/urls.py
index dc09cdbe..d759979e 100644
--- a/sith/urls.py
+++ b/sith/urls.py
@@ -39,6 +39,7 @@ urlpatterns = [
url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")),
url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")),
url(r'^api/v1/', include('api.urls', namespace="api", app_name="api")),
+ url(r'^election/', include('election.urls', namespace="election", app_name="election")),
url(r'^admin/', include(admin.site.urls)),
url(r'^ajax_select/', include(ajax_select_urls)),
url(r'^i18n/', include('django.conf.urls.i18n')),