Commit 78e00c3b authored by Skia's avatar Skia
Browse files

Merge branch 'Elections' into 'master'

Election app

See merge request !27
parents 0280a656 3609952d
Pipeline #646 passed with stage
in 2 minutes and 58 seconds
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()
......@@ -85,6 +85,14 @@
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
{% endfor %}
</ul>
<hr>
<h4>{% trans %}Elections{% endtrans %}</h4>
<ul>
<li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li>
{%- if user.is_subscribed() -%}
<li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li>
{%- endif -%}
</ul>
{% endblock %}
......
from django.contrib import admin
# Register your models here.
# -*- 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),
),
]
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
{% 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 %}
{% 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;
}