mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 14:13:21 +00:00
Merge branch 'Elections' into 'master'
Election app See merge request !27
This commit is contained in:
commit
78e00c3bd4
BIN
core/fixtures/images/3.jpg
Normal file
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
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
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
BIN
core/fixtures/images/8.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from datetime import date, datetime
|
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.base import BaseCommand, CommandError
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
@ -8,13 +8,17 @@ from django.conf import settings
|
|||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from core.models import Group, User, Page, PageRev, SithFile
|
from core.models import Group, User, Page, PageRev, SithFile
|
||||||
from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company
|
from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company
|
||||||
|
from core.utils import resize_image
|
||||||
from club.models import Club, Membership
|
from club.models import Club, Membership
|
||||||
from subscription.models import Subscription
|
from subscription.models import Subscription
|
||||||
from counter.models import Customer, ProductType, Product, Counter
|
from counter.models import Customer, ProductType, Product, Counter
|
||||||
from com.models import Sith
|
from com.models import Sith
|
||||||
|
from election.models import Election, Role, Candidature, ElectionList
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Populate a new instance of the Sith AE"
|
help = "Populate a new instance of the Sith AE"
|
||||||
@ -48,7 +52,8 @@ class Command(BaseCommand):
|
|||||||
is_superuser=True, is_staff=True)
|
is_superuser=True, is_staff=True)
|
||||||
root.set_password("plop")
|
root.set_password("plop")
|
||||||
root.save()
|
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 = SithFile(parent=None, name="users", is_folder=True, owner=root)
|
||||||
home_root.save()
|
home_root.save()
|
||||||
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
|
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
|
||||||
@ -122,6 +127,17 @@ Welcome to the wiki page!
|
|||||||
skia.save()
|
skia.save()
|
||||||
skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
|
skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
|
||||||
skia.save()
|
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
|
# Adding user public
|
||||||
public = User(username='public', last_name="Not subscribed", first_name="Public",
|
public = User(username='public', last_name="Not subscribed", first_name="Public",
|
||||||
email="public@git.an",
|
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])
|
target_label=op[7], cheque_number=op[8])
|
||||||
operation.clean()
|
operation.clean()
|
||||||
operation.save()
|
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()
|
||||||
|
|
||||||
|
@ -85,6 +85,14 @@
|
|||||||
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
|
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
||||||
|
0
election/__init__.py
Normal file
0
election/__init__.py
Normal file
3
election/admin.py
Normal file
3
election/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
117
election/migrations/0001_squashed_0006_auto_20161223_2315.py
Normal file
117
election/migrations/0001_squashed_0006_auto_20161223_2315.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
0
election/migrations/__init__.py
Normal file
0
election/migrations/__init__.py
Normal file
145
election/models.py
Normal file
145
election/models.py
Normal 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"
|
19
election/templates/election/candidate_form.jinja
Normal file
19
election/templates/election/candidate_form.jinja
Normal 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 %}
|
400
election/templates/election/election_detail.jinja
Normal file
400
election/templates/election/election_detail.jinja
Normal 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 %}
|
48
election/templates/election/election_list.jinja
Normal file
48
election/templates/election/election_list.jinja
Normal 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
3
election/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
18
election/urls.py
Normal file
18
election/urls.py
Normal 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
478
election/views.py
Normal 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})
|
@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"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"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Skia <skia@libskia.so>\n"
|
"Last-Translator: Skia <skia@libskia.so>\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\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"
|
msgstr "%(club_account)s sur %(bank_account)s"
|
||||||
|
|
||||||
#: accounting/models.py:142 club/models.py:146 counter/models.py:399
|
#: 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"
|
msgid "start date"
|
||||||
msgstr "date de début"
|
msgstr "date de début"
|
||||||
|
|
||||||
#: accounting/models.py:143 club/models.py:147 counter/models.py:400
|
#: accounting/models.py:143 club/models.py:147 counter/models.py:400
|
||||||
|
#: election/models.py:19
|
||||||
msgid "end date"
|
msgid "end date"
|
||||||
msgstr "date de fin"
|
msgstr "date de fin"
|
||||||
|
|
||||||
@ -192,7 +193,7 @@ msgstr "Compte"
|
|||||||
msgid "Company"
|
msgid "Company"
|
||||||
msgstr "Entreprise"
|
msgstr "Entreprise"
|
||||||
|
|
||||||
#: accounting/models.py:207 sith/settings.py:296
|
#: accounting/models.py:207 sith/settings.py:297
|
||||||
msgid "Other"
|
msgid "Other"
|
||||||
msgstr "Autre"
|
msgstr "Autre"
|
||||||
|
|
||||||
@ -321,6 +322,8 @@ msgstr "Compte en banque : "
|
|||||||
#: core/templates/core/user_edit.jinja:19
|
#: core/templates/core/user_edit.jinja:19
|
||||||
#: counter/templates/counter/last_ops.jinja:29
|
#: counter/templates/counter/last_ops.jinja:29
|
||||||
#: counter/templates/counter/last_ops.jinja:59
|
#: 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/templates/launderette/launderette_admin.jinja:16
|
||||||
#: launderette/views.py:154 sas/templates/sas/album.jinja:26
|
#: launderette/views.py:154 sas/templates/sas/album.jinja:26
|
||||||
#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:66
|
#: 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:17
|
||||||
#: counter/templates/counter/counter_list.jinja:32
|
#: counter/templates/counter/counter_list.jinja:32
|
||||||
#: counter/templates/counter/counter_list.jinja:47
|
#: 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
|
#: launderette/templates/launderette/launderette_list.jinja:16
|
||||||
#: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:92
|
#: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:92
|
||||||
msgid "Edit"
|
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à."
|
msgstr "Un club avec ce nom UNIX existe déjà."
|
||||||
|
|
||||||
#: club/models.py:144 counter/models.py:397 counter/models.py:414
|
#: club/models.py:144 counter/models.py:397 counter/models.py:414
|
||||||
#: eboutic/models.py:14 eboutic/models.py:47 launderette/models.py:87
|
#: eboutic/models.py:14 eboutic/models.py:47 election/models.py:126
|
||||||
#: launderette/models.py:124 sas/models.py:131
|
#: launderette/models.py:87 launderette/models.py:124 sas/models.py:131
|
||||||
msgid "user"
|
msgid "user"
|
||||||
msgstr "nom d'utilisateur"
|
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"
|
msgid "role"
|
||||||
msgstr "rôle"
|
msgstr "rôle"
|
||||||
|
|
||||||
#: club/models.py:150 core/models.py:33 counter/models.py:71
|
#: 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"
|
msgid "description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
@ -979,7 +987,7 @@ msgstr "Vous n'avez pas la permission de faire cela"
|
|||||||
msgid "Begin date"
|
msgid "Begin date"
|
||||||
msgstr "Date de début"
|
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"
|
msgid "End date"
|
||||||
msgstr "Date de fin"
|
msgstr "Date de fin"
|
||||||
|
|
||||||
@ -1016,7 +1024,8 @@ msgstr "Hebdomadaire"
|
|||||||
msgid "Call"
|
msgid "Call"
|
||||||
msgstr "Appel"
|
msgstr "Appel"
|
||||||
|
|
||||||
#: com/models.py:30
|
#: com/models.py:30 election/models.py:14 election/models.py:81
|
||||||
|
#: election/models.py:114
|
||||||
msgid "title"
|
msgid "title"
|
||||||
msgstr "titre"
|
msgstr "titre"
|
||||||
|
|
||||||
@ -1185,7 +1194,7 @@ msgstr "Message d'info"
|
|||||||
msgid "Alert message"
|
msgid "Alert message"
|
||||||
msgstr "Message d'alerte"
|
msgstr "Message d'alerte"
|
||||||
|
|
||||||
#: com/views.py:80
|
#: com/views.py:80 election/views.py:129
|
||||||
msgid "Start date"
|
msgid "Start date"
|
||||||
msgstr "Date de début"
|
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:14
|
||||||
#: core/templates/core/user_detail.jinja:16
|
#: core/templates/core/user_detail.jinja:16
|
||||||
#: core/templates/core/user_edit.jinja:17
|
#: core/templates/core/user_edit.jinja:17
|
||||||
|
#: election/templates/election/election_detail.jinja:316
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
@ -2421,6 +2431,18 @@ msgstr "Modérer les fichiers"
|
|||||||
msgid "Moderate pictures"
|
msgid "Moderate pictures"
|
||||||
msgstr "Modérer les photos"
|
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
|
#: core/views/files.py:49
|
||||||
msgid "Add a new folder"
|
msgid "Add a new folder"
|
||||||
msgstr "Ajouter un nouveau dossier"
|
msgstr "Ajouter un nouveau dossier"
|
||||||
@ -2566,7 +2588,7 @@ msgstr "Bureau"
|
|||||||
#: eboutic/templates/eboutic/eboutic_main.jinja:24
|
#: eboutic/templates/eboutic/eboutic_main.jinja:24
|
||||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8
|
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8
|
||||||
#: eboutic/templates/eboutic/eboutic_payment_result.jinja:4
|
#: 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"
|
msgid "Eboutic"
|
||||||
msgstr "Eboutic"
|
msgstr "Eboutic"
|
||||||
|
|
||||||
@ -2607,8 +2629,8 @@ msgstr "quantité"
|
|||||||
msgid "Sith account"
|
msgid "Sith account"
|
||||||
msgstr "Compte utilisateur"
|
msgstr "Compte utilisateur"
|
||||||
|
|
||||||
#: counter/models.py:292 sith/settings.py:288 sith/settings.py:293
|
#: counter/models.py:292 sith/settings.py:289 sith/settings.py:294
|
||||||
#: sith/settings.py:315
|
#: sith/settings.py:316
|
||||||
msgid "Credit card"
|
msgid "Credit card"
|
||||||
msgstr "Carte bancaire"
|
msgstr "Carte bancaire"
|
||||||
|
|
||||||
@ -3091,6 +3113,168 @@ msgstr "Retourner à l'eboutic"
|
|||||||
msgid "You do not have enough money to buy the basket"
|
msgid "You do not have enough money to buy the basket"
|
||||||
msgstr "Vous n'avez pas assez d'argent pour acheter le panier"
|
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/models.py:17
|
||||||
#: launderette/templates/launderette/launderette_book.jinja:5
|
#: launderette/templates/launderette/launderette_book.jinja:5
|
||||||
#: launderette/templates/launderette/launderette_book_choose.jinja:4
|
#: launderette/templates/launderette/launderette_book_choose.jinja:4
|
||||||
@ -3142,21 +3326,17 @@ msgstr "Machines"
|
|||||||
msgid "New machine"
|
msgid "New machine"
|
||||||
msgstr "Nouvelle machine"
|
msgstr "Nouvelle machine"
|
||||||
|
|
||||||
#: launderette/templates/launderette/launderette_book.jinja:12
|
|
||||||
msgid "Choose"
|
|
||||||
msgstr "Choisir"
|
|
||||||
|
|
||||||
#: launderette/templates/launderette/launderette_book.jinja:23
|
#: launderette/templates/launderette/launderette_book.jinja:23
|
||||||
msgid "Washing and drying"
|
msgid "Washing and drying"
|
||||||
msgstr "Lavage et séchage"
|
msgstr "Lavage et séchage"
|
||||||
|
|
||||||
#: launderette/templates/launderette/launderette_book.jinja:27
|
#: launderette/templates/launderette/launderette_book.jinja:27
|
||||||
#: sith/settings.py:436
|
#: sith/settings.py:437
|
||||||
msgid "Washing"
|
msgid "Washing"
|
||||||
msgstr "Lavage"
|
msgstr "Lavage"
|
||||||
|
|
||||||
#: launderette/templates/launderette/launderette_book.jinja:31
|
#: launderette/templates/launderette/launderette_book.jinja:31
|
||||||
#: sith/settings.py:436
|
#: sith/settings.py:437
|
||||||
msgid "Drying"
|
msgid "Drying"
|
||||||
msgstr "Séchage"
|
msgstr "Séchage"
|
||||||
|
|
||||||
@ -3294,145 +3474,145 @@ msgstr "Ajouter une personne"
|
|||||||
msgid "Apply rights recursively"
|
msgid "Apply rights recursively"
|
||||||
msgstr "Appliquer les droits récursivement"
|
msgstr "Appliquer les droits récursivement"
|
||||||
|
|
||||||
#: sith/settings.py:175
|
#: sith/settings.py:176
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr "Anglais"
|
msgstr "Anglais"
|
||||||
|
|
||||||
#: sith/settings.py:176
|
#: sith/settings.py:177
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr "Français"
|
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"
|
msgid "Check"
|
||||||
msgstr "Chèque"
|
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"
|
msgid "Cash"
|
||||||
msgstr "Espèces"
|
msgstr "Espèces"
|
||||||
|
|
||||||
#: sith/settings.py:287
|
#: sith/settings.py:288
|
||||||
msgid "Transfert"
|
msgid "Transfert"
|
||||||
msgstr "Virement"
|
msgstr "Virement"
|
||||||
|
|
||||||
#: sith/settings.py:300
|
#: sith/settings.py:301
|
||||||
msgid "Belfort"
|
msgid "Belfort"
|
||||||
msgstr "Belfort"
|
msgstr "Belfort"
|
||||||
|
|
||||||
#: sith/settings.py:301
|
#: sith/settings.py:302
|
||||||
msgid "Sevenans"
|
msgid "Sevenans"
|
||||||
msgstr "Sevenans"
|
msgstr "Sevenans"
|
||||||
|
|
||||||
#: sith/settings.py:302
|
#: sith/settings.py:303
|
||||||
msgid "Montbéliard"
|
msgid "Montbéliard"
|
||||||
msgstr "Montbéliard"
|
msgstr "Montbéliard"
|
||||||
|
|
||||||
#: sith/settings.py:343
|
#: sith/settings.py:344
|
||||||
msgid "One semester"
|
msgid "One semester"
|
||||||
msgstr "Un semestre, 15 €"
|
msgstr "Un semestre, 15 €"
|
||||||
|
|
||||||
#: sith/settings.py:348
|
#: sith/settings.py:349
|
||||||
msgid "Two semesters"
|
msgid "Two semesters"
|
||||||
msgstr "Deux semestres, 28 €"
|
msgstr "Deux semestres, 28 €"
|
||||||
|
|
||||||
#: sith/settings.py:353
|
#: sith/settings.py:354
|
||||||
msgid "Common core cursus"
|
msgid "Common core cursus"
|
||||||
msgstr "Cursus tronc commun, 45 €"
|
msgstr "Cursus tronc commun, 45 €"
|
||||||
|
|
||||||
#: sith/settings.py:358
|
#: sith/settings.py:359
|
||||||
msgid "Branch cursus"
|
msgid "Branch cursus"
|
||||||
msgstr "Cursus branche, 45 €"
|
msgstr "Cursus branche, 45 €"
|
||||||
|
|
||||||
#: sith/settings.py:363
|
#: sith/settings.py:364
|
||||||
msgid "Alternating cursus"
|
msgid "Alternating cursus"
|
||||||
msgstr "Cursus alternant, 30 €"
|
msgstr "Cursus alternant, 30 €"
|
||||||
|
|
||||||
#: sith/settings.py:368
|
#: sith/settings.py:369
|
||||||
msgid "Honorary member"
|
msgid "Honorary member"
|
||||||
msgstr "Membre honoraire, 0 €"
|
msgstr "Membre honoraire, 0 €"
|
||||||
|
|
||||||
#: sith/settings.py:373
|
#: sith/settings.py:374
|
||||||
msgid "Assidu member"
|
msgid "Assidu member"
|
||||||
msgstr "Membre d'Assidu, 0 €"
|
msgstr "Membre d'Assidu, 0 €"
|
||||||
|
|
||||||
#: sith/settings.py:378
|
#: sith/settings.py:379
|
||||||
msgid "Amicale/DOCEO member"
|
msgid "Amicale/DOCEO member"
|
||||||
msgstr "Membre de l'Amicale/DOCEO, 0 €"
|
msgstr "Membre de l'Amicale/DOCEO, 0 €"
|
||||||
|
|
||||||
#: sith/settings.py:383
|
#: sith/settings.py:384
|
||||||
msgid "UT network member"
|
msgid "UT network member"
|
||||||
msgstr "Cotisant du réseau UT, 0 €"
|
msgstr "Cotisant du réseau UT, 0 €"
|
||||||
|
|
||||||
#: sith/settings.py:388
|
#: sith/settings.py:389
|
||||||
msgid "CROUS member"
|
msgid "CROUS member"
|
||||||
msgstr "Membres du CROUS, 0 €"
|
msgstr "Membres du CROUS, 0 €"
|
||||||
|
|
||||||
#: sith/settings.py:393
|
#: sith/settings.py:394
|
||||||
msgid "Sbarro/ESTA member"
|
msgid "Sbarro/ESTA member"
|
||||||
msgstr "Membre de Sbarro ou de l'ESTA, 15 €"
|
msgstr "Membre de Sbarro ou de l'ESTA, 15 €"
|
||||||
|
|
||||||
#: sith/settings.py:401
|
#: sith/settings.py:402
|
||||||
msgid "President"
|
msgid "President"
|
||||||
msgstr "Président"
|
msgstr "Président"
|
||||||
|
|
||||||
#: sith/settings.py:402
|
#: sith/settings.py:403
|
||||||
msgid "Vice-President"
|
msgid "Vice-President"
|
||||||
msgstr "Vice-Président"
|
msgstr "Vice-Président"
|
||||||
|
|
||||||
#: sith/settings.py:403
|
#: sith/settings.py:404
|
||||||
msgid "Treasurer"
|
msgid "Treasurer"
|
||||||
msgstr "Trésorier"
|
msgstr "Trésorier"
|
||||||
|
|
||||||
#: sith/settings.py:404
|
#: sith/settings.py:405
|
||||||
msgid "Communication supervisor"
|
msgid "Communication supervisor"
|
||||||
msgstr "Responsable com"
|
msgstr "Responsable com"
|
||||||
|
|
||||||
#: sith/settings.py:405
|
#: sith/settings.py:406
|
||||||
msgid "Secretary"
|
msgid "Secretary"
|
||||||
msgstr "Secrétaire"
|
msgstr "Secrétaire"
|
||||||
|
|
||||||
#: sith/settings.py:406
|
#: sith/settings.py:407
|
||||||
msgid "IT supervisor"
|
msgid "IT supervisor"
|
||||||
msgstr "Responsable info"
|
msgstr "Responsable info"
|
||||||
|
|
||||||
#: sith/settings.py:407
|
#: sith/settings.py:408
|
||||||
msgid "Board member"
|
msgid "Board member"
|
||||||
msgstr "Membre du bureau"
|
msgstr "Membre du bureau"
|
||||||
|
|
||||||
#: sith/settings.py:408
|
#: sith/settings.py:409
|
||||||
msgid "Active member"
|
msgid "Active member"
|
||||||
msgstr "Membre actif"
|
msgstr "Membre actif"
|
||||||
|
|
||||||
#: sith/settings.py:409
|
#: sith/settings.py:410
|
||||||
msgid "Curious"
|
msgid "Curious"
|
||||||
msgstr "Curieux"
|
msgstr "Curieux"
|
||||||
|
|
||||||
#: sith/settings.py:443
|
#: sith/settings.py:444
|
||||||
msgid "A fresh new to be moderated"
|
msgid "A fresh new to be moderated"
|
||||||
msgstr "Une nouvelle toute neuve à modérer"
|
msgstr "Une nouvelle toute neuve à modérer"
|
||||||
|
|
||||||
#: sith/settings.py:444
|
#: sith/settings.py:445
|
||||||
msgid "New files to be moderated"
|
msgid "New files to be moderated"
|
||||||
msgstr "Nouveaux fichiers à modérer"
|
msgstr "Nouveaux fichiers à modérer"
|
||||||
|
|
||||||
#: sith/settings.py:445
|
#: sith/settings.py:446
|
||||||
msgid "New pictures/album to be moderated in the SAS"
|
msgid "New pictures/album to be moderated in the SAS"
|
||||||
msgstr "Nouvelles photos/albums à modérer dans le 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"
|
msgid "You've been identified on some pictures"
|
||||||
msgstr "Vous avez été identifié sur des photos"
|
msgstr "Vous avez été identifié sur des photos"
|
||||||
|
|
||||||
#: sith/settings.py:447
|
#: sith/settings.py:448
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You just refilled of %s €"
|
msgid "You just refilled of %s €"
|
||||||
msgstr "Vous avez rechargé votre compte de %s €"
|
msgstr "Vous avez rechargé votre compte de %s €"
|
||||||
|
|
||||||
#: sith/settings.py:448
|
#: sith/settings.py:449
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You just bought %s"
|
msgid "You just bought %s"
|
||||||
msgstr "Vous avez acheté %s"
|
msgstr "Vous avez acheté %s"
|
||||||
|
|
||||||
#: sith/settings.py:449
|
#: sith/settings.py:450
|
||||||
msgid "You have a notification"
|
msgid "You have a notification"
|
||||||
msgstr "Vous avez une notification"
|
msgstr "Vous avez une notification"
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ INSTALLED_APPS = (
|
|||||||
'rootplace',
|
'rootplace',
|
||||||
'sas',
|
'sas',
|
||||||
'com',
|
'com',
|
||||||
|
'election',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
|
@ -39,6 +39,7 @@ urlpatterns = [
|
|||||||
url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")),
|
url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")),
|
||||||
url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")),
|
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'^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'^admin/', include(admin.site.urls)),
|
||||||
url(r'^ajax_select/', include(ajax_select_urls)),
|
url(r'^ajax_select/', include(ajax_select_urls)),
|
||||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
|
Loading…
Reference in New Issue
Block a user