diff --git a/core/fixtures/images/3.jpg b/core/fixtures/images/3.jpg new file mode 100644 index 00000000..0e6a682e Binary files /dev/null and b/core/fixtures/images/3.jpg differ diff --git a/core/fixtures/images/5.jpg b/core/fixtures/images/5.jpg new file mode 100644 index 00000000..76c1183d Binary files /dev/null and b/core/fixtures/images/5.jpg differ diff --git a/core/fixtures/images/6.jpg b/core/fixtures/images/6.jpg new file mode 100644 index 00000000..a5b1e8e5 Binary files /dev/null and b/core/fixtures/images/6.jpg differ diff --git a/core/fixtures/images/8.jpg b/core/fixtures/images/8.jpg new file mode 100644 index 00000000..82df15f2 Binary files /dev/null and b/core/fixtures/images/8.jpg differ diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 65884269..5b88945a 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -1,6 +1,6 @@ import os from datetime import date, datetime -from io import StringIO +from io import StringIO, BytesIO from django.core.management.base import BaseCommand, CommandError from django.core.management import call_command @@ -8,13 +8,17 @@ from django.conf import settings from django.db import connection from django.contrib.sites.models import Site +from PIL import Image from core.models import Group, User, Page, PageRev, SithFile from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company +from core.utils import resize_image from club.models import Club, Membership from subscription.models import Subscription from counter.models import Customer, ProductType, Product, Counter from com.models import Sith +from election.models import Election, Role, Candidature, ElectionList + class Command(BaseCommand): help = "Populate a new instance of the Sith AE" @@ -48,7 +52,8 @@ class Command(BaseCommand): is_superuser=True, is_staff=True) root.set_password("plop") root.save() - SithFile(parent=None, name="profiles", is_folder=True, owner=root).save() + profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root) + profiles_root.save() home_root = SithFile(parent=None, name="users", is_folder=True, owner=root) home_root.save() club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) @@ -122,6 +127,17 @@ Welcome to the wiki page! skia.save() skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] skia.save() + skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg') + with open(skia_profile_path, 'rb') as f: + name = str(skia.id) + "_profile.jpg" + skia_profile = SithFile(parent=profiles_root, name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), + owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path)) + skia_profile.file.name = name + skia_profile.save() + skia.profile_pict = skia_profile + skia.save() + # Adding user public public = User(username='public', last_name="Not subscribed", first_name="Public", email="public@git.an", @@ -329,5 +345,85 @@ Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site. target_label=op[7], cheque_number=op[8]) operation.clean() operation.save() - + + # Adding user sli + sli = User(username='sli', last_name="Li", first_name="S", + email="sli@git.an", + date_of_birth="1942-06-12") + sli.set_password("plop") + sli.save() + sli.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + sli.save() + sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg') + with open(sli_profile_path, 'rb') as f: + name = str(sli.id) + "_profile.jpg" + sli_profile = SithFile(parent=profiles_root, name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), + owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path)) + sli_profile.file.name = name + sli_profile.save() + sli.profile_pict = sli_profile + sli.save() + # Adding user Krophil + krophil = User(username='krophil', last_name="Phil'", first_name="Kro", + email="krophil@git.an", + date_of_birth="1942-06-12") + krophil.set_password("plop") + krophil.save() + krophil_profile_path = os.path.join(root_path, 'core/fixtures/images/6.jpg') + with open(krophil_profile_path, 'rb') as f: + name = str(krophil.id) + "_profile.jpg" + krophil_profile = SithFile(parent=profiles_root, name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), + owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path)) + krophil_profile.file.name = name + krophil_profile.save() + krophil.profile_pict = krophil_profile + krophil.save() + ## Adding subscription for sli + s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s.subscription_start = s.compute_start() + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], + start=s.subscription_start) + s.save() + ## Adding subscription for Krophil + s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s.subscription_start = s.compute_start() + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], + start=s.subscription_start) + s.save() + + # Create an election + public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) + subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + ae_board_gorup = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01', + end_candidature='2042-06-12 10:28:45+01',start_date='1942-06-12 10:28:45+01', + end_date='7942-06-12 10:28:45+01') + el.save() + el.view_groups.add(public_group) + el.edit_groups.add(ae_board_gorup) + el.candidature_groups.add(subscriber_group) + el.vote_groups.add(subscriber_group) + el.save() + liste = ElectionList(title="Candidature Libre", election=el) + liste.save() + listeT = ElectionList(title="Troll", election=el) + listeT.save() + pres = Role(election=el, title="Président AE", description="Roi de l'AE") + pres.save() + resp = Role(election=el, title="Co Respo Info", max_choice=2, description="Ghetto++") + resp.save() + cand = Candidature(role=resp, user=skia, election_list=liste, program="Refesons le site AE") + cand.save() + cand = Candidature(role=resp, user=sli, election_list=liste, program="Vasy je deviens mon propre adjoint") + cand.save() + cand = Candidature(role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !") + cand.save() + cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC") + cand.save() diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index 1bbc3107..371dcd3f 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -85,6 +85,14 @@
  • {{ m.club }}
  • {% endfor %} +
    +

    {% trans %}Elections{% endtrans %}

    + {% endblock %} diff --git a/election/__init__.py b/election/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/election/admin.py b/election/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/election/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/election/migrations/0001_squashed_0006_auto_20161223_2315.py b/election/migrations/0001_squashed_0006_auto_20161223_2315.py new file mode 100644 index 00000000..44c681ff --- /dev/null +++ b/election/migrations/0001_squashed_0006_auto_20161223_2315.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + replaces = [('election', '0001_initial'), ('election', '0002_role_max_choice'), ('election', '0003_auto_20161219_1832'), ('election', '0004_auto_20161219_2302'), ('election', '0005_auto_20161223_2240'), ('election', '0006_auto_20161223_2315')] + + dependencies = [ + ('core', '0016_auto_20161212_1922'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Candidature', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('program', models.TextField(null=True, verbose_name='description', blank=True)), + ], + ), + migrations.CreateModel( + name='Election', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('title', models.CharField(verbose_name='title', max_length=255)), + ('description', models.TextField(null=True, verbose_name='description', blank=True)), + ('start_candidature', models.DateTimeField(verbose_name='start candidature')), + ('end_candidature', models.DateTimeField(verbose_name='end candidature')), + ('start_date', models.DateTimeField(verbose_name='start date')), + ('end_date', models.DateTimeField(verbose_name='end date')), + ], + ), + migrations.CreateModel( + name='ElectionList', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('title', models.CharField(verbose_name='title', max_length=255)), + ('election', models.ForeignKey(related_name='election_lists', verbose_name='election', to='election.Election')), + ], + ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('title', models.CharField(verbose_name='title', max_length=255)), + ('description', models.TextField(null=True, verbose_name='description', blank=True)), + ('election', models.ForeignKey(related_name='role', verbose_name='election', to='election.Election')), + ('has_voted', models.ManyToManyField(related_name='has_voted', verbose_name='has voted', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Vote', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('candidature', models.ManyToManyField(related_name='votes', verbose_name='candidature', to='election.Candidature')), + ('role', models.ForeignKey(related_name='votes', verbose_name='role', to='election.Role')), + ], + ), + migrations.AddField( + model_name='candidature', + name='election_list', + field=models.ForeignKey(related_name='candidatures', verbose_name='election_list', to='election.ElectionList'), + ), + migrations.AddField( + model_name='candidature', + name='role', + field=models.ForeignKey(related_name='candidatures', verbose_name='role', to='election.Role'), + ), + migrations.AddField( + model_name='candidature', + name='user', + field=models.ForeignKey(related_name='candidates', verbose_name='user', to=settings.AUTH_USER_MODEL, blank=True), + ), + migrations.AddField( + model_name='role', + name='max_choice', + field=models.IntegerField(default=1, verbose_name='max choice'), + ), + migrations.AddField( + model_name='election', + name='edit_groups', + field=models.ManyToManyField(related_name='editable_elections', verbose_name='edit groups', blank=True, to='core.Group'), + ), + migrations.AddField( + model_name='election', + name='view_groups', + field=models.ManyToManyField(related_name='viewable_elections', verbose_name='view groups', blank=True, to='core.Group'), + ), + migrations.AddField( + model_name='election', + name='candidature_groups', + field=models.ManyToManyField(related_name='candidate_elections', verbose_name='candidature groups', blank=True, to='core.Group'), + ), + migrations.AddField( + model_name='election', + name='vote_groups', + field=models.ManyToManyField(related_name='votable_elections', verbose_name='vote groups', blank=True, to='core.Group'), + ), + migrations.AlterField( + model_name='role', + name='election', + field=models.ForeignKey(related_name='roles', verbose_name='election', to='election.Election'), + ), + migrations.RemoveField( + model_name='role', + name='has_voted', + ), + migrations.AddField( + model_name='election', + name='voters', + field=models.ManyToManyField(related_name='has_voted', verbose_name='has voted', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/election/migrations/__init__.py b/election/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/election/models.py b/election/models.py new file mode 100644 index 00000000..36c5301a --- /dev/null +++ b/election/models.py @@ -0,0 +1,145 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone +from django.conf import settings + +from datetime import timedelta +from core.models import User, Group + + +class Election(models.Model): + """ + This class allows to create a new election + """ + title = models.CharField(_('title'), max_length=255) + description = models.TextField(_('description'), null=True, blank=True) + start_candidature = models.DateTimeField(_('start candidature'), blank=False) + end_candidature = models.DateTimeField(_('end candidature'), blank=False) + start_date = models.DateTimeField(_('start date'), blank=False) + end_date = models.DateTimeField(_('end date'), blank=False) + + edit_groups = models.ManyToManyField(Group, related_name="editable_elections", verbose_name=_("edit groups"), blank=True) + view_groups = models.ManyToManyField(Group, related_name="viewable_elections", verbose_name=_("view groups"), blank=True) + vote_groups = models.ManyToManyField(Group, related_name="votable_elections", verbose_name=_("vote groups"), blank=True) + candidature_groups = models.ManyToManyField(Group, related_name="candidate_elections", verbose_name=_("candidature groups"), blank=True) + voters = models.ManyToManyField(User, verbose_name=('voters'), related_name='voted_elections') + + def __str__(self): + return self.title + + @property + def is_vote_active(self): + now = timezone.now() + return bool(now <= self.end_date and now >= self.start_date) + + @property + def is_vote_finished(self): + return bool(timezone.now() > self.end_date) + + @property + def is_candidature_active(self): + now = timezone.now() + return bool(now <= self.end_candidature and now >= self.start_candidature) + + @property + def is_vote_editable(self): + return bool(timezone.now() <= self.end_candidature) + + def can_candidate(self, user): + for group in self.candidature_groups.all(): + if user.is_in_group(group): + return True + return False + + def can_vote(self, user): + if not self.is_vote_active or self.has_voted(user): + return False + for group in self.vote_groups.all(): + if user.is_in_group(group): + return True + return False + + def has_voted(self, user): + return self.voters.filter(id=user.id).exists() + + @property + def results(self): + results = {} + total_vote = self.voters.count() + for role in self.roles.all(): + results[role.title] = role.results(total_vote) + return results + + # Permissions + + +class Role(models.Model): + """ + This class allows to create a new role avaliable for a candidature + """ + election = models.ForeignKey(Election, related_name='roles', verbose_name=_("election")) + title = models.CharField(_('title'), max_length=255) + description = models.TextField(_('description'), null=True, blank=True) + max_choice = models.IntegerField(_('max choice'), default=1) + + def results(self, total_vote): + results = {} + total_vote *= self.max_choice + if total_vote == 0: + return None + non_blank = 0 + for candidature in self.candidatures.all(): + cand_results = {} + cand_results['vote'] = self.votes.filter(candidature=candidature).count() + cand_results['percent'] = cand_results['vote'] * 100 / total_vote + non_blank += cand_results['vote'] + results[candidature.user.username] = cand_results + results['total vote'] = total_vote + results['blank vote'] = {'vote': total_vote - non_blank, + 'percent': (total_vote - non_blank) * 100 / total_vote} + return results + + @property + def edit_groups(self): + return self.election.edit_groups + + def __str__(self): + return ("%s : %s") % (self.election.title, self.title) + + +class ElectionList(models.Model): + """ + To allow per list vote + """ + title = models.CharField(_('title'), max_length=255) + election = models.ForeignKey(Election, related_name='election_lists', verbose_name=_("election")) + + def __str__(self): + return self.title + + +class Candidature(models.Model): + """ + This class is a component of responsability + """ + role = models.ForeignKey(Role, related_name='candidatures', verbose_name=_("role")) + user = models.ForeignKey(User, verbose_name=_('user'), related_name='candidates', blank=True) + program = models.TextField(_('description'), null=True, blank=True) + election_list = models.ForeignKey(ElectionList, related_name='candidatures', verbose_name=_('election list')) + + def can_be_edited_by(self, user): + return (user == self.user) + + def __str__(self): + return "%s : %s" % (self.role.title, self.user.username) + + +class Vote(models.Model): + """ + This class allows to vote for candidates + """ + role = models.ForeignKey(Role, related_name='votes', verbose_name=_("role")) + candidature = models.ManyToManyField(Candidature, related_name='votes', verbose_name=_("candidature")) + + def __str__(self): + return "Vote" \ No newline at end of file diff --git a/election/templates/election/candidate_form.jinja b/election/templates/election/candidate_form.jinja new file mode 100644 index 00000000..ad889b27 --- /dev/null +++ b/election/templates/election/candidate_form.jinja @@ -0,0 +1,19 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{% trans %}Candidate{% endtrans %} +{% endblock %} + +{% block content %} + {%- if election.can_candidate(user) or user.can_edit(election) %} +
    +
    + {% csrf_token %} + {{ form.as_p() }} +

    +
    +
    + {%- else -%} + {% trans %}Candidature are closed for this election{% endtrans %} + {%- endif %} +{% endblock content %} diff --git a/election/templates/election/election_detail.jinja b/election/templates/election/election_detail.jinja new file mode 100644 index 00000000..dea749ff --- /dev/null +++ b/election/templates/election/election_detail.jinja @@ -0,0 +1,400 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{{ object.title }} +{% endblock %} + +{% block head %} +{{ super() -}} + +{%- endblock %} + +{% block content %} +

    {{ election.title }}

    +

    {{ election.description }}

    +
    +
    +

    + {%- if election.is_vote_active %} + {% trans %}Polls close {% endtrans %} + {%- elif election.is_vote_finished %} + {% trans %}Polls closed {% endtrans %} + {%- else %} + {% trans %}Polls will open {% endtrans %} + + {% trans %} at {% endtrans %} + {% trans %}and will close {% endtrans %} + {%- endif %} + + {% trans %} at {% endtrans %} +

    + {%- if election.has_voted(user) %} +

    + {%- if election.is_vote_active %} + {% trans %}You already have submitted your vote.{% endtrans %} + {%- else %} + {% trans %}You have voted in this election.{% endtrans %} + {%- endif %} +

    + {%- endif %} +
    +
    +
    + {% csrf_token %} + + {%- set election_lists = election.election_lists.all() -%} + + + + {%- for election_list in election_lists %} + + {%- endfor %} + + {%- 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 [] %} + + + + + + + {%- for election_list in election_lists %} + + {%- endfor %} + + + {%- endfor %} +
    {% trans %}Blank vote{% endtrans %}{{ election_list.title }}
    + {{ role.title }} + {% if user.can_edit(role) and election.is_vote_editable -%} + {% trans %}Edit{% endtrans %} + {% trans %}Delete{% endtrans %} + {%- endif -%} + {%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %} + {% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %} + {%- endif %} + {%- if election_form.errors[role.title] is defined %} + {%- for error in election_form.errors.as_data()[role.title] %} + {{ error.message }} + {%- endfor %} + {%- endif %} +
    + {%- if role.max_choice == 1 and election.can_vote(user) %} + + + {%- set _ = count.append(count.pop() + 1) %} + {%- endif %} + {%- if election.is_vote_finished %} + {%- set results = election_results[role.title]['blank vote'] %} +
    + {{ results.vote }} {% trans %}votes{% endtrans %} ({{ results.percent }} %) +
    + {%- endif %} +
    +
      + {%- for candidature in election_list.candidatures.filter(role=role) %} +
    • +
      +
      + {%- if candidature.user.profile_pict and user.is_subscriber_viewable %} + {% trans %}Profile{% endtrans %} + {%- endif %} +
      +
      + {{ candidature.user.first_name }} {{candidature.user.nick_name or ''}} {{ candidature.user.last_name }} + {{ candidature.program or '' }} + {%- if user.can_edit(candidature) -%} + {% if election.is_vote_editable %} + {% trans %}Edit{% endtrans %} + {% endif %} + {% if election.can_candidate -%} + {% trans %}Delete{% endtrans %} + {%- endif -%} + {%- endif -%} +
      +
      + {%- if election.can_vote(user) %} + + + {%- set _ = count.append(count.pop() + 1) %} + {%- endif %} + {%- if election.is_vote_finished %} + {%- set results = election_results[role.title][candidature.user.username] %} +
      + {{ results.vote }} {% trans %}votes{% endtrans %} ({{ results.percent }} %) +
      + {%- endif %} +
    • + {%- endfor %} +
    +
    +
    +
    + {%- if not election.has_voted(user) and election.can_vote(user) %} +
    + +
    + {%- endif %} +
    + {%- if election.can_candidate(user) or user.can_edit(election) %} + {% trans %}Candidate{% endtrans %} + {%- endif %} + {% trans %}Add a new list{% endtrans %} + {%- if user.can_edit(election) %} + {% if election.is_vote_editable %} + {% trans %}Add a new role{% endtrans %} + {% endif %} + {% trans %}Edit{% endtrans %} + {%- endif %} +
    +{% endblock %} + +{% block script %} +{{ super() }} + +{% endblock %} diff --git a/election/templates/election/election_list.jinja b/election/templates/election/election_list.jinja new file mode 100644 index 00000000..3bcb0110 --- /dev/null +++ b/election/templates/election/election_list.jinja @@ -0,0 +1,48 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{%- trans %}Election list{% endtrans %} +{%- endblock %} + +{% block head %} +{{ super() -}} + +{%- endblock %} + +{% block content %} +

    {% trans %}Current elections{% endtrans %}

    + {%- for election in object_list %} +
    +
    +

    + {{ election }} +

    +

    + {% trans %}Applications open from{% endtrans %} + + {% trans %} at {% endtrans %} + {% trans %}to{% endtrans %} + + {% trans %} at {% endtrans %} +

    +

    + {% trans %}Polls open from{% endtrans %} + + {% trans %} at {% endtrans %} + {% trans %}to{% endtrans %} + + {% trans %} at {% endtrans %} +

    +

    {{ election.description }}

    +
    + {%- endfor %} +{%- endblock %} + diff --git a/election/tests.py b/election/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/election/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/election/urls.py b/election/urls.py new file mode 100644 index 00000000..8140cb49 --- /dev/null +++ b/election/urls.py @@ -0,0 +1,18 @@ +from django.conf.urls import url + +from election.views import * + +urlpatterns = [ + url(r'^$', ElectionsListView.as_view(), name='list'), + url(r'^add$', ElectionCreateView.as_view(), name='create'), + url(r'^(?P[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'), + url(r'^(?P[0-9]+)/list/add$', ElectionListCreateView.as_view(), name='create_list'), + url(r'^(?P[0-9]+)/role/create$', RoleCreateView.as_view(), name='create_role'), + url(r'^(?P[0-9]+)/role/edit$', RoleUpdateView.as_view(), name='update_role'), + url(r'^(?P[0-9]+)/role/delete$', RoleDeleteView.as_view(), name='delete_role'), + url(r'^(?P[0-9]+)/candidate/add$', CandidatureCreateView.as_view(), name='candidate'), + url(r'^(?P[0-9]+)/candidate/edit$', CandidatureUpdateView.as_view(), name='update_candidate'), + url(r'^(?P[0-9]+)/candidate/delete$', CandidatureDeleteView.as_view(), name='delete_candidate'), + url(r'^(?P[0-9]+)/vote$', VoteFormView.as_view(), name='vote'), + url(r'^(?P[0-9]+)/detail$', ElectionDetailView.as_view(), name='detail'), +] diff --git a/election/views.py b/election/views.py new file mode 100644 index 00000000..88235898 --- /dev/null +++ b/election/views.py @@ -0,0 +1,478 @@ +from django.shortcuts import redirect, get_object_or_404 +from django.views.generic import ListView, DetailView, RedirectView +from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView +from django.core.urlresolvers import reverse_lazy, reverse +from django.utils.translation import ugettext_lazy as _ +from django.forms.models import modelform_factory +from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured +from django.forms import CheckboxSelectMultiple +from django.utils import timezone +from django.conf import settings +from django import forms + +from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin +from django.db.models.query import QuerySet +from django.views.generic.edit import FormMixin +from core.views.forms import SelectDateTime +from election.models import Election, Role, Candidature, ElectionList, Vote + +from ajax_select.fields import AutoCompleteSelectField + + +# Custom form field + +class LimitedCheckboxField(forms.ModelMultipleChoiceField): + """ + Used to replace ModelMultipleChoiceField but with + automatic backend verification + """ + def __init__(self, queryset, max_choice, required=True, widget=None, label=None, + initial=None, help_text='', *args, **kwargs): + self.max_choice = max_choice + widget = forms.CheckboxSelectMultiple() + super(LimitedCheckboxField, self).__init__(queryset, None, required, widget, label, + initial, help_text, *args, **kwargs) + + def clean(self, value): + qs = super(LimitedCheckboxField, self).clean(value) + self.validate(qs) + return qs + + def validate(self, qs): + if qs.count() > self.max_choice: + raise forms.ValidationError(_("You have selected too much candidates."), code='invalid') + + +# Forms + + +class CandidateForm(forms.ModelForm): + """ Form to candidate """ + class Meta: + model = Candidature + fields = ['user', 'role', 'program', 'election_list'] + widgets = { + 'program': forms.Textarea + } + + user = AutoCompleteSelectField('users', label=_('User to candidate'), help_text=None, required=True) + + def __init__(self, *args, **kwargs): + election_id = kwargs.pop('election_id', None) + can_edit = kwargs.pop('can_edit', False) + super(CandidateForm, self).__init__(*args, **kwargs) + if election_id: + self.fields['role'].queryset = Role.objects.filter(election__id=election_id).all() + self.fields['election_list'].queryset = ElectionList.objects.filter(election__id=election_id).all() + if not can_edit: + self.fields['user'].widget = forms.HiddenInput() + + +class VoteForm(forms.Form): + def __init__(self, election, user, *args, **kwargs): + super(VoteForm, self).__init__(*args, **kwargs) + if not election.has_voted(user): + for role in election.roles.all(): + cand = role.candidatures + if role.max_choice > 1: + self.fields[role.title] = LimitedCheckboxField(cand, role.max_choice, required=False) + else: + self.fields[role.title] = forms.ModelChoiceField(cand, required=False, + widget=forms.RadioSelect(), empty_label=_("Blank vote")) + + +class RoleForm(forms.ModelForm): + """ Form for creating a role """ + class Meta: + model = Role + fields = ['title', 'election', 'description', 'max_choice'] + + def __init__(self, *args, **kwargs): + election_id = kwargs.pop('election_id', None) + super(RoleForm, self).__init__(*args, **kwargs) + if election_id: + self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + + def clean(self): + cleaned_data = super(RoleForm, self).clean() + title = cleaned_data.get('title') + election = cleaned_data.get('election') + if Role.objects.filter(title=title, election=election).exists(): + raise forms.ValidationError(_("This role already exists for this election"), code='invalid') + + +class ElectionListForm(forms.ModelForm): + class Meta: + model = ElectionList + fields = ('title','election') + + def __init__(self, *args, **kwargs): + election_id = kwargs.pop('election_id', None) + super(ElectionListForm, self).__init__(*args, **kwargs) + if election_id: + self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + + +class ElectionForm(forms.ModelForm): + class Meta: + model = Election + fields = ['title', 'description', 'start_candidature', 'end_candidature', 'start_date', 'end_date', + 'edit_groups', 'view_groups', 'vote_groups', 'candidature_groups'] + widgets = { + 'edit_groups': CheckboxSelectMultiple, + 'view_groups': CheckboxSelectMultiple, + 'edit_groups': CheckboxSelectMultiple, + 'vote_groups': CheckboxSelectMultiple, + 'candidature_groups': CheckboxSelectMultiple + } + + start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=True) + end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=True) + start_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start candidature"), widget=SelectDateTime, required=True) + end_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End candidature"), widget=SelectDateTime, required=True) + + +# Display elections + + +class ElectionsListView(CanViewMixin, ListView): + """ + A list with all responsabilities and their candidates + """ + model = Election + template_name = 'election/election_list.jinja' + + +class ElectionDetailView(CanViewMixin, DetailView): + """ + Details an election responsability by responsability + """ + model = Election + template_name = 'election/election_detail.jinja' + pk_url_kwarg = "election_id" + + def get_context_data(self, **kwargs): + """ Add additionnal data to the template """ + kwargs = super(ElectionDetailView, self).get_context_data(**kwargs) + kwargs['election_form'] = VoteForm(self.object, self.request.user) + kwargs['election_results'] = self.object.results + return kwargs + + +# Form view + +class VoteFormView(CanCreateMixin, FormView): + """ + Alows users to vote + """ + form_class = VoteForm + template_name = 'election/election_detail.jinja' + + def dispatch(self, request, *arg, **kwargs): + self.election = get_object_or_404(Election, pk=kwargs['election_id']) + return super(VoteFormView, self).dispatch(request, *arg, **kwargs) + + def vote(self, election_data): + for role_title in election_data.keys(): + # If we have a multiple choice field + if isinstance(election_data[role_title], QuerySet): + if election_data[role_title].count() > 0: + vote = Vote(role=election_data[role_title].first().role) + vote.save() + for el in election_data[role_title]: + vote.candidature.add(el) + # If we have a single choice + elif election_data[role_title] is not None: + vote = Vote(role=election_data[role_title].role) + vote.save() + vote.candidature.add(election_data[role_title]) + self.election.voters.add(self.request.user) + + def get_form_kwargs(self): + kwargs = super(VoteFormView, self).get_form_kwargs() + kwargs['election'] = self.election + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + """ + Verify that the user is part in a vote group + """ + data = form.clean() + res = super(FormView, self).form_valid(form) + for grp in self.election.vote_groups.all(): + if self.request.user.is_in_group(grp): + self.vote(data) + return res + return res + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + + def get_context_data(self, **kwargs): + """ Add additionnal data to the template """ + kwargs = super(VoteFormView, self).get_context_data(**kwargs) + kwargs['object'] = self.election + kwargs['election'] = self.election + kwargs['election_form'] = self.get_form() + return kwargs + + +# Create views + +class CandidatureCreateView(CanCreateMixin, CreateView): + """ + View dedicated to a cundidature creation + """ + form_class = CandidateForm + model = Candidature + template_name = 'election/candidate_form.jinja' + + def dispatch(self, request, *arg, **kwargs): + self.election = get_object_or_404(Election, pk=kwargs['election_id']) + return super(CandidatureCreateView, self).dispatch(request, *arg, **kwargs) + + def get_initial(self): + init = {} + self.can_edit = self.request.user.can_edit(self.election) + init['user'] = self.request.user.id + return init + + def get_form_kwargs(self): + kwargs = super(CandidatureCreateView, self).get_form_kwargs() + kwargs['election_id'] = self.election.id + kwargs['can_edit'] = self.can_edit + return kwargs + + def form_valid(self, form): + """ + Verify that the selected user is in candidate group + """ + obj = form.instance + obj.election = Election.objects.get(id=self.election.id) + if(obj.election.can_candidate(obj.user)) and (obj.user == self.request.user or self.can_edit): + return super(CreateView, self).form_valid(form) + raise PermissionDenied + + def get_context_data(self, **kwargs): + kwargs = super(CandidatureCreateView, self).get_context_data(**kwargs) + kwargs['election'] = self.election + return kwargs + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + + +class ElectionCreateView(CanCreateMixin, CreateView): + model = Election + form_class = ElectionForm + template_name = 'core/create.jinja' + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_subscribed(): + raise PermissionDenied + return super(ElectionCreateView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + """ + Allow every users that had passed the dispatch to create an election + """ + return super(CreateView, self).form_valid(form) + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + + +class RoleCreateView(CanCreateMixin, CreateView): + model = Role + form_class = RoleForm + template_name = 'core/create.jinja' + + def dispatch(self, request, *arg, **kwargs): + self.election = get_object_or_404(Election, pk=kwargs['election_id']) + if not self.election.is_vote_editable: + raise PermissionDenied + return super(RoleCreateView, self).dispatch(request, *arg, **kwargs) + + def get_initial(self): + init = {} + init['election'] = self.election + return init + + def form_valid(self, form): + """ + Verify that the user can edit proprely + """ + obj = form.instance + if obj.election: + for grp in obj.election.edit_groups.all(): + if self.request.user.is_in_group(grp): + return super(CreateView, self).form_valid(form) + raise PermissionDenied + + def get_form_kwargs(self): + kwargs = super(RoleCreateView, self).get_form_kwargs() + kwargs['election_id'] = self.election.id + return kwargs + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + + +class ElectionListCreateView(CanCreateMixin, CreateView): + model = ElectionList + form_class = ElectionListForm + template_name = 'core/create.jinja' + + def dispatch(self, request, *arg, **kwargs): + self.election = get_object_or_404(Election, pk=kwargs['election_id']) + if not self.election.is_vote_editable: + raise PermissionDenied + return super(ElectionListCreateView, self).dispatch(request, *arg, **kwargs) + + def get_initial(self): + init = {} + init['election'] = self.election + return init + + def get_form_kwargs(self): + kwargs = super(ElectionListCreateView, self).get_form_kwargs() + kwargs['election_id'] = self.election.id + return kwargs + + def form_valid(self, form): + """ + Verify that the user can vote on this election + """ + obj = form.instance + if obj.election: + for grp in obj.election.candidature_groups.all(): + if self.request.user.is_in_group(grp): + return super(CreateView, self).form_valid(form) + for grp in obj.election.edit_groups.all(): + if self.request.user.is_in_group(grp): + return super(CreateView, self).form_valid(form) + raise PermissionDenied + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + +# Update view + + +class ElectionUpdateView(CanEditMixin, UpdateView): + model = Election + form_class = ElectionForm + template_name = 'core/edit.jinja' + pk_url_kwarg = 'election_id' + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + + +class CandidatureUpdateView(CanEditMixin, UpdateView): + model = Candidature + form_class = CandidateForm + template_name = 'core/edit.jinja' + pk_url_kwarg = 'candidature_id' + + def dispatch(self, request, *arg, **kwargs): + self.object = self.get_object() + if not self.object.role.election.is_vote_editable: + raise PermissionDenied + return super(CandidatureUpdateView, self).dispatch(request, *arg, **kwargs) + + def remove_fields(self): + self.form.fields.pop('role', None) + + def get(self, request, *args, **kwargs): + self.form = self.get_form() + self.remove_fields() + return self.render_to_response(self.get_context_data(form=self.form)) + + def post(self, request, *args, **kwargs): + self.form = self.get_form() + self.remove_fields() + if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + return super(CandidatureUpdateView, self).form_valid(self.form) + return self.form_invalid(self.form) + + def get_form_kwargs(self): + kwargs = super(CandidatureUpdateView, self).get_form_kwargs() + kwargs['election_id'] = self.object.role.election.id + return kwargs + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.role.election.id}) + + +class RoleUpdateView(CanEditMixin, UpdateView): + model = Role + form_class = RoleForm + template_name = 'core/edit.jinja' + pk_url_kwarg = 'role_id' + + def dispatch(self, request, *arg, **kwargs): + self.object = self.get_object() + if not self.object.election.is_vote_editable: + raise PermissionDenied + return super(RoleUpdateView, self).dispatch(request, *arg, **kwargs) + + def remove_fields(self): + self.form.fields.pop('election', None) + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.form = self.get_form() + self.remove_fields() + return self.render_to_response(self.get_context_data(form=self.form)) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + self.form = self.get_form() + self.remove_fields() + if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + return super(RoleUpdateView, self).form_valid(self.form) + return self.form_invalid(self.form) + + def get_form_kwargs(self): + kwargs = super(RoleUpdateView, self).get_form_kwargs() + kwargs['election_id'] = self.object.election.id + return kwargs + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + +# Delete Views + + +class CandidatureDeleteView(CanEditMixin, DeleteView): + model = Candidature + template_name = 'core/delete_confirm.jinja' + pk_url_kwarg = 'candidature_id' + + def dispatch(self, request, *arg, **kwargs): + self.object = self.get_object() + self.election = self.object.role.election + if not self.election.can_candidate: + raise PermissionDenied + return super(CandidatureDeleteView, self).dispatch(request, *arg, **kwargs) + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + + +class RoleDeleteView(CanEditMixin, DeleteView): + model = Role + template_name = 'core/delete_confirm.jinja' + pk_url_kwarg = 'role_id' + + def dispatch(self, request, *arg, **kwargs): + self.object = self.get_object() + self.election = self.object.election + if not self.election.is_vote_editable: + raise PermissionDenied + return super(RoleDeleteView, self).dispatch(request, *arg, **kwargs) + + def get_success_url(self, **kwargs): + return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 88ab8e69..002c873b 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-12-23 20:10+0100\n" +"POT-Creation-Date: 2016-12-26 00:34+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -86,11 +86,12 @@ msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" #: accounting/models.py:142 club/models.py:146 counter/models.py:399 -#: launderette/models.py:120 +#: election/models.py:18 launderette/models.py:120 msgid "start date" msgstr "date de début" #: accounting/models.py:143 club/models.py:147 counter/models.py:400 +#: election/models.py:19 msgid "end date" msgstr "date de fin" @@ -192,7 +193,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:207 sith/settings.py:296 +#: accounting/models.py:207 sith/settings.py:297 msgid "Other" msgstr "Autre" @@ -321,6 +322,8 @@ msgstr "Compte en banque : " #: core/templates/core/user_edit.jinja:19 #: counter/templates/counter/last_ops.jinja:29 #: counter/templates/counter/last_ops.jinja:59 +#: election/templates/election/election_detail.jinja:280 +#: election/templates/election/election_detail.jinja:327 #: launderette/templates/launderette/launderette_admin.jinja:16 #: launderette/views.py:154 sas/templates/sas/album.jinja:26 #: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:66 @@ -356,6 +359,9 @@ msgstr "Nouveau compte club" #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:32 #: counter/templates/counter/counter_list.jinja:47 +#: election/templates/election/election_detail.jinja:279 +#: election/templates/election/election_detail.jinja:324 +#: election/templates/election/election_detail.jinja:370 #: launderette/templates/launderette/launderette_list.jinja:16 #: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:92 msgid "Edit" @@ -766,17 +772,19 @@ msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." #: club/models.py:144 counter/models.py:397 counter/models.py:414 -#: eboutic/models.py:14 eboutic/models.py:47 launderette/models.py:87 -#: launderette/models.py:124 sas/models.py:131 +#: eboutic/models.py:14 eboutic/models.py:47 election/models.py:126 +#: launderette/models.py:87 launderette/models.py:124 sas/models.py:131 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:148 core/models.py:137 +#: club/models.py:148 core/models.py:137 election/models.py:125 +#: election/models.py:141 msgid "role" msgstr "rôle" #: club/models.py:150 core/models.py:33 counter/models.py:71 -#: counter/models.py:96 +#: counter/models.py:96 election/models.py:15 election/models.py:82 +#: election/models.py:127 msgid "description" msgstr "description" @@ -979,7 +987,7 @@ msgstr "Vous n'avez pas la permission de faire cela" msgid "Begin date" msgstr "Date de début" -#: club/views.py:166 com/views.py:81 counter/views.py:909 +#: club/views.py:166 com/views.py:81 counter/views.py:909 election/views.py:130 msgid "End date" msgstr "Date de fin" @@ -1016,7 +1024,8 @@ msgstr "Hebdomadaire" msgid "Call" msgstr "Appel" -#: com/models.py:30 +#: com/models.py:30 election/models.py:14 election/models.py:81 +#: election/models.py:114 msgid "title" msgstr "titre" @@ -1185,7 +1194,7 @@ msgstr "Message d'info" msgid "Alert message" msgstr "Message d'alerte" -#: com/views.py:80 +#: com/views.py:80 election/views.py:129 msgid "Start date" msgstr "Date de début" @@ -1484,6 +1493,7 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" #: core/templates/core/user_detail.jinja:14 #: core/templates/core/user_detail.jinja:16 #: core/templates/core/user_edit.jinja:17 +#: election/templates/election/election_detail.jinja:316 msgid "Profile" msgstr "Profil" @@ -2421,6 +2431,18 @@ msgstr "Modérer les fichiers" msgid "Moderate pictures" msgstr "Modérer les photos" +#: core/templates/core/user_tools.jinja:89 +msgid "Elections" +msgstr "Élections" + +#: core/templates/core/user_tools.jinja:91 +msgid "See available elections" +msgstr "Voir les élections disponibles" + +#: core/templates/core/user_tools.jinja:93 +msgid "Create a new election" +msgstr "Créer une nouvelle élection" + #: core/views/files.py:49 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" @@ -2566,7 +2588,7 @@ msgstr "Bureau" #: eboutic/templates/eboutic/eboutic_main.jinja:24 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:8 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:295 sith/settings.py:303 +#: sith/settings.py:296 sith/settings.py:304 msgid "Eboutic" msgstr "Eboutic" @@ -2607,8 +2629,8 @@ msgstr "quantité" msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:292 sith/settings.py:288 sith/settings.py:293 -#: sith/settings.py:315 +#: counter/models.py:292 sith/settings.py:289 sith/settings.py:294 +#: sith/settings.py:316 msgid "Credit card" msgstr "Carte bancaire" @@ -3091,6 +3113,168 @@ msgstr "Retourner à l'eboutic" msgid "You do not have enough money to buy the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" +#: election/models.py:16 +msgid "start candidature" +msgstr "début des candidatures" + +#: election/models.py:17 +msgid "end candidature" +msgstr "fin des candidatures" + +#: election/models.py:21 +msgid "edit groups" +msgstr "groupe d'édition" + +#: election/models.py:22 +msgid "view groups" +msgstr "groupe de vue" + +#: election/models.py:23 +msgid "vote groups" +msgstr "groupe de vote" + +#: election/models.py:24 +msgid "candidature groups" +msgstr "groupe de candidature" + +#: election/models.py:80 election/models.py:115 +msgid "election" +msgstr "élection" + +#: election/models.py:83 +msgid "max choice" +msgstr "nombre de choix maxi" + +#: election/models.py:128 +msgid "election list" +msgstr "liste électorale" + +#: election/models.py:142 +msgid "candidature" +msgstr "candidature" + +#: election/templates/election/candidate_form.jinja:4 +#: election/templates/election/candidate_form.jinja:13 +#: election/templates/election/election_detail.jinja:363 +msgid "Candidate" +msgstr "Candidater" + +#: election/templates/election/candidate_form.jinja:17 +msgid "Candidature are closed for this election" +msgstr "Les candidatures sont fermées pour cette élection" + +#: election/templates/election/election_detail.jinja:237 +msgid "Polls close " +msgstr "Votes fermés" + +#: election/templates/election/election_detail.jinja:239 +msgid "Polls closed " +msgstr "Votes fermés" + +#: election/templates/election/election_detail.jinja:241 +msgid "Polls will open " +msgstr "Les votes ouvriront " + +#: election/templates/election/election_detail.jinja:243 +#: election/templates/election/election_detail.jinja:247 +#: election/templates/election/election_list.jinja:31 +#: election/templates/election/election_list.jinja:34 +#: election/templates/election/election_list.jinja:39 +#: election/templates/election/election_list.jinja:42 +msgid " at " +msgstr " à " + +#: election/templates/election/election_detail.jinja:244 +msgid "and will close " +msgstr "et fermeront" + +#: election/templates/election/election_detail.jinja:252 +msgid "You already have submitted your vote." +msgstr "Vous avez déjà soumis votre vote." + +#: election/templates/election/election_detail.jinja:254 +msgid "You have voted in this election." +msgstr "Vous avez déjà voté pour cette élection." + +#: election/templates/election/election_detail.jinja:266 election/views.py:81 +msgid "Blank vote" +msgstr "Vote blanc" + +#: election/templates/election/election_detail.jinja:283 +msgid "You may choose up to" +msgstr "Vous pouvez choisir jusqu'à" + +#: election/templates/election/election_detail.jinja:283 +msgid "people." +msgstr "personne(s)" + +#: election/templates/election/election_detail.jinja:297 +msgid "Choose blank vote" +msgstr "Choisir de voter blanc" + +#: election/templates/election/election_detail.jinja:304 +#: election/templates/election/election_detail.jinja:342 +msgid "votes" +msgstr "votes" + +#: election/templates/election/election_detail.jinja:335 +#: launderette/templates/launderette/launderette_book.jinja:12 +msgid "Choose" +msgstr "Choisir" + +#: election/templates/election/election_detail.jinja:358 +msgid "Submit the vote !" +msgstr "Envoyer le vote !" + +#: election/templates/election/election_detail.jinja:365 +msgid "Add a new list" +msgstr "Ajouter une nouvelle liste" + +#: election/templates/election/election_detail.jinja:368 +msgid "Add a new role" +msgstr "Ajouter un nouveau rôle" + +#: election/templates/election/election_list.jinja:4 +msgid "Election list" +msgstr "Liste des élections" + +#: election/templates/election/election_list.jinja:21 +msgid "Current elections" +msgstr "Élections actuelles" + +#: election/templates/election/election_list.jinja:29 +msgid "Applications open from" +msgstr "Candidatures ouvertes à partir du" + +#: election/templates/election/election_list.jinja:32 +#: election/templates/election/election_list.jinja:40 +msgid "to" +msgstr "au" + +#: election/templates/election/election_list.jinja:37 +msgid "Polls open from" +msgstr "Votes ouverts du" + +#: election/views.py:43 +msgid "You have selected too much candidates." +msgstr "Vous avez sélectionné trop de candidats." + +#: election/views.py:58 +msgid "User to candidate" +msgstr "Utilisateur se présentant" + +#: election/views.py:101 +msgid "This role already exists for this election" +msgstr "Ce rôle existe déjà pour cette élection" + +#: election/views.py:131 +msgid "Start candidature" +msgstr "Début des candidatures" + +#: election/views.py:132 +msgid "End candidature" +msgstr "Fin des candidatures" + #: launderette/models.py:17 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 @@ -3142,21 +3326,17 @@ msgstr "Machines" msgid "New machine" msgstr "Nouvelle machine" -#: launderette/templates/launderette/launderette_book.jinja:12 -msgid "Choose" -msgstr "Choisir" - #: launderette/templates/launderette/launderette_book.jinja:23 msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:436 +#: sith/settings.py:437 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:436 +#: sith/settings.py:437 msgid "Drying" msgstr "Séchage" @@ -3294,145 +3474,145 @@ msgstr "Ajouter une personne" msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: sith/settings.py:175 +#: sith/settings.py:176 msgid "English" msgstr "Anglais" -#: sith/settings.py:176 +#: sith/settings.py:177 msgid "French" msgstr "Français" -#: sith/settings.py:285 sith/settings.py:292 sith/settings.py:313 +#: sith/settings.py:286 sith/settings.py:293 sith/settings.py:314 msgid "Check" msgstr "Chèque" -#: sith/settings.py:286 sith/settings.py:294 sith/settings.py:314 +#: sith/settings.py:287 sith/settings.py:295 sith/settings.py:315 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:287 +#: sith/settings.py:288 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:300 +#: sith/settings.py:301 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:301 +#: sith/settings.py:302 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:302 +#: sith/settings.py:303 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:343 +#: sith/settings.py:344 msgid "One semester" msgstr "Un semestre, 15 €" -#: sith/settings.py:348 +#: sith/settings.py:349 msgid "Two semesters" msgstr "Deux semestres, 28 €" -#: sith/settings.py:353 +#: sith/settings.py:354 msgid "Common core cursus" msgstr "Cursus tronc commun, 45 €" -#: sith/settings.py:358 +#: sith/settings.py:359 msgid "Branch cursus" msgstr "Cursus branche, 45 €" -#: sith/settings.py:363 +#: sith/settings.py:364 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:368 +#: sith/settings.py:369 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:373 +#: sith/settings.py:374 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:378 +#: sith/settings.py:379 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:383 +#: sith/settings.py:384 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:388 +#: sith/settings.py:389 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:393 +#: sith/settings.py:394 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 15 €" -#: sith/settings.py:401 +#: sith/settings.py:402 msgid "President" msgstr "Président" -#: sith/settings.py:402 +#: sith/settings.py:403 msgid "Vice-President" msgstr "Vice-Président" -#: sith/settings.py:403 +#: sith/settings.py:404 msgid "Treasurer" msgstr "Trésorier" -#: sith/settings.py:404 +#: sith/settings.py:405 msgid "Communication supervisor" msgstr "Responsable com" -#: sith/settings.py:405 +#: sith/settings.py:406 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:406 +#: sith/settings.py:407 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:407 +#: sith/settings.py:408 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:408 +#: sith/settings.py:409 msgid "Active member" msgstr "Membre actif" -#: sith/settings.py:409 +#: sith/settings.py:410 msgid "Curious" msgstr "Curieux" -#: sith/settings.py:443 +#: sith/settings.py:444 msgid "A fresh new to be moderated" msgstr "Une nouvelle toute neuve à modérer" -#: sith/settings.py:444 +#: sith/settings.py:445 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:445 +#: sith/settings.py:446 msgid "New pictures/album to be moderated in the SAS" msgstr "Nouvelles photos/albums à modérer dans le SAS" -#: sith/settings.py:446 +#: sith/settings.py:447 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:447 +#: sith/settings.py:448 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s €" -#: sith/settings.py:448 +#: sith/settings.py:449 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:449 +#: sith/settings.py:450 msgid "You have a notification" msgstr "Vous avez une notification" diff --git a/sith/settings.py b/sith/settings.py index ba905351..c1680fbd 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -58,6 +58,7 @@ INSTALLED_APPS = ( 'rootplace', 'sas', 'com', + 'election', ) MIDDLEWARE_CLASSES = ( diff --git a/sith/urls.py b/sith/urls.py index dc09cdbe..d759979e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -39,6 +39,7 @@ urlpatterns = [ url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")), url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")), url(r'^api/v1/', include('api.urls', namespace="api", app_name="api")), + url(r'^election/', include('election.urls', namespace="election", app_name="election")), url(r'^admin/', include(admin.site.urls)), url(r'^ajax_select/', include(ajax_select_urls)), url(r'^i18n/', include('django.conf.urls.i18n')),