Merge branch 'Elections' into 'master'

Election app

See merge request !27
This commit is contained in:
Skia 2016-12-26 00:45:40 +01:00
commit 78e00c3bd4
20 changed files with 1574 additions and 57 deletions

BIN
core/fixtures/images/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
core/fixtures/images/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
core/fixtures/images/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
core/fixtures/images/8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -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",
@ -330,4 +346,84 @@ Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site.
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()

View File

@ -85,6 +85,14 @@
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
{% endfor %}
</ul>
<hr>
<h4>{% trans %}Elections{% endtrans %}</h4>
<ul>
<li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li>
{%- if user.is_subscribed() -%}
<li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li>
{%- endif -%}
</ul>
{% endblock %}

0
election/__init__.py Normal file
View File

3
election/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -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),
),
]

View File

145
election/models.py Normal file
View File

@ -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"

View File

@ -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) %}
<section class="election__add-candidature">
<form action="{{ url('election:candidate', election_id=election.id) }}" method="post">
{% csrf_token %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Candidate{% endtrans %}" /></p>
</form>
</section>
{%- else -%}
{% trans %}Candidature are closed for this election{% endtrans %}
{%- endif %}
{% endblock content %}

View File

@ -0,0 +1,400 @@
{% extends "core/base.jinja" %}
{% block title %}
{{ object.title }}
{% endblock %}
{% block head %}
{{ super() -}}
<style type="text/css">
time {
font-weight: bolder;
}
th {
padding: 5px;
margin: 5px;
border: solid 1px darkgrey;
border-collapse: collapse;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
}
.election__title {
margin: 0;
margin-bottom: 5px;
}
.election__description {
margin: 0;
}
.election__details {
margin-bottom: 5px;
}
.election__details p {
margin: 0;
}
.election__details p:not(:last-child) {
margin-bottom: 5px;
}
.election__elector-infos {
font-weight: bolder;
color: darkgreen;
}
.election__vote {
margin-bottom: 5px;
}
.election__vote-form {
width: auto;
}
.role {
}
.role .role__title {
background: lightgrey;
}
.role__multiple-choices-label {
color: darkgreen;
}
.role__error {
color: darkred;
}
.role .role_candidates {
background: white;
}
.list-per-role {
padding: 5px;
max-width: 310px;
}
.list-per-role__candidates {
list-style: none;
margin: 0;
}
.list-per-role__candidate:not(:last-child) {
margin-bottom: 15px;
}
.candidate__infos {
display: flex;
flex-flow: row nowrap;
}
.candidate__infos:not(:last-child) {
margin-bottom: 5px;
}
.candidate__picture-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
height: 150px;
min-width: 150px;
min-height: 150px;
background-color: lightgrey;
}
.candidate__picture {
max-width: 150px;
max-height: 150px;
}
.candidate__details {
margin-left: 5px;
}
.candidate__full-name {
display: block;
font-weight: bolder;
}
.candidate__nick-name {
font-style: italic;
}
.candidate__program {
display: block;
margin-top: 5px;
}
.candidate__vote-input {
position: absolute;
border: 0;
height: 1px;
width: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.candidate__vote-choice {
margin-top: 5px;
padding: 15px;
border: solid 1px darkgrey;
color: dimgray;
text-align: center;
font-weight: bolder;
}
.candidate__vote-input:not(:checked):not(:disabled) + .candidate__vote-choice:hover,
.candidate__vote-input:not(:checked):not(:disabled) + .candidate__vote-choice:focus {
background: lightgrey;
}
.candidate__vote-input:checked + .candidate__vote-choice {
padding: 14px;
border-width: 2px;
border-color: darkgreen;
color: darkgreen;
}
.candidate__vote-input:not(:disabled) + .candidate__vote-choice {
cursor: pointer;
}
.candidate__vote-input:checked:not(:disabled) + .candidate__vote-choice:hover,
.candidate__vote-input:checked:not(:disabled) + .candidate__vote-choice:focus {
background: palegreen;
}
.role_error .candidate__vote-input:checked + .candidate__vote-choice {
border-color: darkred;
color: darkred;
}
.role_error .candidate__vote-input:checked:not(:disabled) + .candidate__vote-choice:hover,
.role_error .candidate__vote-input:checked:not(:disabled) + .candidate__vote-choice:focus {
background: indianred;
}
.election__results {
text-align: center;
}
.election__sumbit-section {
margin-bottom: 5px;
}
.election__sumbit-button {
display: block;
width: 100%;
padding: 20px;
background: white;
border: solid 15px #4081cb;
text-align: center;
font-size: 200%;
font-weight: bolder;
cursor: pointer;
}
.election__sumbit-button:hover,
.election__sumbit-button:focus {
background-color: lightskyblue;
}
.election__add-elements {
margin-bottom: 5px;
}
.election__add-elements a {
display: inline-block;
border: solid 1px darkgrey;
height: 20px;
line-height: 20px;
padding: 10px;
}
</style>
{%- endblock %}
{% block content %}
<h3 class="election__title">{{ election.title }}</h3>
<p class="election__description">{{ election.description }}</p>
<hr>
<section class="election__details">
<p>
{%- if election.is_vote_active %}
{% trans %}Polls close {% endtrans %}
{%- elif election.is_vote_finished %}
{% trans %}Polls closed {% endtrans %}
{%- else %}
{% trans %}Polls will open {% endtrans %}
<time datetime="{{ election.start_date }}">{{ election.start_date|localtime|date(DATETIME_FORMAT)}}</time>
{% trans %} at {% endtrans %}<time>{{ election.start_date|localtime|time(DATETIME_FORMAT)}}</time>
{% trans %}and will close {% endtrans %}
{%- endif %}
<time datetime="{{ election.end_date }}">{{ election.end_date|localtime|date(DATETIME_FORMAT)}}</time>
{% trans %} at {% endtrans %}<time>{{ election.end_date|localtime|time(DATETIME_FORMAT)}}</time>
</p>
{%- if election.has_voted(user) %}
<p class="election__elector-infos">
{%- if election.is_vote_active %}
<span>{% trans %}You already have submitted your vote.{% endtrans %}</span>
{%- else %}
<span>{% trans %}You have voted in this election.{% endtrans %}</span>
{%- endif %}
</p>
{%- endif %}
</section>
<section class="election__vote">
<form action="{{ url('election:vote', election.id) }}" method="post" class="election__vote-form" name="vote-form" id="vote-form">
{% csrf_token %}
<table>
{%- set election_lists = election.election_lists.all() -%}
<caption></caption>
<thead>
<th>{% trans %}Blank vote{% endtrans %}</th>
{%- for election_list in election_lists %}
<th>{{ election_list.title }}</th>
{%- endfor %}
</thead>
{%- for role in election.roles.all() %}
{%- set count = [0] %}
{%- set role_data = election_form.data.getlist(role.title) if role.title in election_form.data else [] %}
<tbody data-max-choice="{{role.max_choice}}" class="role{{ ' role_error' if role.title in election_form.errors else '' }}{{ ' role__multiple-choices' if role.max_choice > 1 else ''}}">
<tr class="role__title">
<td colspan="{{ election_lists.count() + 1 }}">
<span>{{ role.title }}</span>
{% if user.can_edit(role) and election.is_vote_editable -%}
<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 -%}
{%- 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 %}
{%- if election_form.errors[role.title] is defined %}
{%- for error in election_form.errors.as_data()[role.title] %}
<strong class="role__error">{{ error.message }}</strong>
{%- endfor %}
{%- endif %}
</td>
</tr>
<tr class="role_candidates">
<td class="list-per-role">
{%- if role.max_choice == 1 and election.can_vote(user) %}
<input id="id_{{ role.title }}_{{ count[0] }}" class="candidate__vote-input" type="radio" name="{{ role.title }}" value {{ '' if role_data in election_form else 'checked' }} {{ 'disabled' if election.has_voted(user) else '' }}>
<label for="id_{{ role.title }}_{{ count[0] }}" class="candidate__vote-choice">
<span>{% trans %}Choose blank vote{% endtrans %}</span>
</label>
{%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title]['blank vote'] %}
<div class="election__results">
<strong>{{ results.vote }} {% trans %}votes{% endtrans %} ({{ results.percent }} %)</strong>
</div>
{%- endif %}
</td>
{%- for election_list in election_lists %}
<td class="list-per-role">
<ul class="list-per-role__candidates">
{%- for candidature in election_list.candidatures.filter(role=role) %}
<li class="list-per-role__candidate candidate">
<figure class="candidate__infos">
<div class="candidate__picture-wrapper">
{%- if candidature.user.profile_pict and user.is_subscriber_viewable %}
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
{%- endif %}
</div>
<figcaption class="candidate__details">
<cite class="candidate__full-name">{{ candidature.user.first_name }} <em class="candidate__nick-name">{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</cite>
<q class="candidate__program">{{ candidature.program or '' }}</q>
{%- if user.can_edit(candidature) -%}
{% if election.is_vote_editable %}
<a href="{{url('election:update_candidate', candidature_id=candidature.id)}}">{% trans %}Edit{% endtrans %}</a>
{% endif %}
{% if election.can_candidate -%}
<a href="{{url('election:delete_candidate', candidature_id=candidature.id)}}">{% trans %}Delete{% endtrans %}</a>
{%- endif -%}
{%- endif -%}
</figcaption>
</figure>
{%- if election.can_vote(user) %}
<input id="id_{{ role.title }}_{{ count[0] }}" type="{{ 'checkbox' if role.max_choice > 1 else 'radio' }}" {{ 'checked' if candidature.id|string in role_data else '' }} {{ 'disabled' if election.has_voted(user) else '' }} name="{{ role.title }}" value="{{ candidature.id }}" class="candidate__vote-input">
<label for="id_{{ role.title }}_{{ count[0] }}" class="candidate__vote-choice">
<span>{% trans %}Choose{% endtrans %} {{ candidature.user.nick_name or candidature.user.first_name }}</span>
</label>
{%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title][candidature.user.username] %}
<div class="election__results">
<strong>{{ results.vote }} {% trans %}votes{% endtrans %} ({{ results.percent }} %)</strong>
</div>
{%- endif %}
</li>
{%- endfor %}
</ul>
</td>
{%- endfor %}
</tr>
</tbody>
{%- endfor %}
</table>
</form>
</section>
{%- if not election.has_voted(user) and election.can_vote(user) %}
<section class="election__sumbit-section">
<button class="election__sumbit-button" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button>
</section>
{%- endif %}
<section class="election__add-elements">
{%- if election.can_candidate(user) or user.can_edit(election) %}
<a href="{{ url('election:candidate', election_id=object.id) }}">{% trans %}Candidate{% endtrans %}</a>
{%- endif %}
<a href="{{ url('election:create_list', election_id=object.id) }}">{% trans %}Add a new list{% endtrans %}</a>
{%- if user.can_edit(election) %}
{% if election.is_vote_editable %}
<a href="{{ url('election:create_role', election_id=object.id) }}">{% trans %}Add a new role{% endtrans %}</a>
{% endif %}
<a href="{{ url('election:update', election_id=object.id) }}">{% trans %}Edit{% endtrans %}</a>
{%- endif %}
</section>
{% endblock %}
{% block script %}
{{ super() }}
<script type="text/javascript">
document.querySelectorAll('.role__multiple-choices').forEach(setupRestrictions);
function setupRestrictions(role) {
var selectedChoices = [];
role.querySelectorAll('input').forEach(setupRestriction);
function setupRestriction(choice) {
if (choice.checked)
selectedChoices.push(choice);
choice.addEventListener('change', onChange);
function onChange() {
if (choice.checked)
selectedChoices.push(choice);
else
selectedChoices.splice(selectedChoices.indexOf(choice), 1);
while (selectedChoices.length > role.dataset.maxChoice)
selectedChoices.shift().checked = false;
}
}
}
</script>
{% endblock %}

View File

@ -0,0 +1,48 @@
{% extends "core/base.jinja" %}
{% block title %}
{%- trans %}Election list{% endtrans %}
{%- endblock %}
{% block head %}
{{ super() -}}
<style type="text/css">
small {
font-size: smaller;
}
time {
font-weight: bolder;
}
</style>
{%- endblock %}
{% block content %}
<h3>{% trans %}Current elections{% endtrans %}</h3>
{%- for election in object_list %}
<hr>
<section>
<h4>
<a href="{{ url('election:detail', election_id=election.id) }}">{{ election }}</a>
</h4>
<p>
{% trans %}Applications open from{% endtrans %}
<time datetime="{{ election.start_candidature }}">{{ election.start_candidature|localtime|date(DATETIME_FORMAT) }}</time>
{% trans %} at {% endtrans %}<time>{{ election.start_candidature|localtime|time(DATETIME_FORMAT) }}</time>
{% trans %}to{% endtrans %}
<time datetime="{{ election.end_candidature }}">{{ election.end_candidature|localtime|date(DATETIME_FORMAT) }}</time>
{% trans %} at {% endtrans %}<time>{{ election.end_candidature|time(DATETIME_FORMAT) }}</time>
</p>
<p>
{% trans %}Polls open from{% endtrans %}
<time datetime="{{ election.start_date }}">{{ election.start_date|localtime|date(DATETIME_FORMAT) }}</time>
{% trans %} at {% endtrans %}<time>{{ election.start_date|localtime|time(DATETIME_FORMAT) }}</time>
{% trans %}to{% endtrans %}
<time datetime="{{ election.end_date }}">{{ election.end_date|localtime|date(DATETIME_FORMAT) }}</time>
{% trans %} at {% endtrans %}<time>{{ election.end_date|localtime|time(DATETIME_FORMAT) }}</time>
</p>
<p>{{ election.description }}</p>
</section>
{%- endfor %}
{%- endblock %}

3
election/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

18
election/urls.py Normal file
View File

@ -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<election_id>[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'),
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'),
url(r'^(?P<role_id>[0-9]+)/role/delete$', RoleDeleteView.as_view(), name='delete_role'),
url(r'^(?P<election_id>[0-9]+)/candidate/add$', CandidatureCreateView.as_view(), name='candidate'),
url(r'^(?P<candidature_id>[0-9]+)/candidate/edit$', CandidatureUpdateView.as_view(), name='update_candidate'),
url(r'^(?P<candidature_id>[0-9]+)/candidate/delete$', CandidatureDeleteView.as_view(), name='delete_candidate'),
url(r'^(?P<election_id>[0-9]+)/vote$', VoteFormView.as_view(), name='vote'),
url(r'^(?P<election_id>[0-9]+)/detail$', ElectionDetailView.as_view(), name='detail'),
]

478
election/views.py Normal file
View File

@ -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})

View File

@ -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 <skia@libskia.so>\n"
"Language-Team: AE info <ae.info@utbm.fr>\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"

View File

@ -58,6 +58,7 @@ INSTALLED_APPS = (
'rootplace',
'sas',
'com',
'election',
)
MIDDLEWARE_CLASSES = (

View File

@ -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')),