Merge branch 'issue81' into 'k-fet'

Fix Issue81

Fix #81  
Fix touche H sur K-Psul

See merge request !85
This commit is contained in:
Aurélien Delobelle 2016-09-25 23:44:50 +02:00
commit 6108d58594
25 changed files with 338 additions and 96 deletions

View file

@ -47,28 +47,49 @@ gérer la machine virtuelle :
- `vagrant ssh` vous connecte en SSH à la machine virtuelle, dans le dossier - `vagrant ssh` vous connecte en SSH à la machine virtuelle, dans le dossier
où est installé GestioCOF. Vous pouvez utiliser les commandes Django où est installé GestioCOF. Vous pouvez utiliser les commandes Django
habituelles (`manage.py runserver` etc.); toutefois pour lancer le serveur il faut faire habituelles (`manage.py runserver` etc.) pour lancer
[le serveur en dev](#lancer-le-serveur-de-développement-standard) par
python manage.py runserver 0.0.0.0:8000 exemple
car par défaut Django n'écoute que sur l'adresse locale de la machine
virtuelle - or vous voudrez accéder à GestioCOF depuis votre machine
physique.
**Le dossier avec le code de GestioCOF est partagé entre la machine virtuelle **Le dossier avec le code de GestioCOF est partagé entre la machine virtuelle
et votre machine physique : vous pouvez donc utiliser votre éditeur favori pour et votre machine physique : vous pouvez donc utiliser votre éditeur favori pour
coder depuis l'extérieur de la machine virtuelle, et les changements seront coder depuis l'extérieur de la machine virtuelle, et les changements seront
répercutés dans la machine virtuelle.** répercutés dans la machine virtuelle.**
#### Lancer le serveur de développement standard
Pour lancer le serveur de développement, il faut faire
python manage.py runserver 0.0.0.0:8000
car par défaut Django n'écoute que sur l'adresse locale de la machine virtuelle
or vous voudrez accéder à GestioCOF depuis votre machine physique. L'url à
entrer dans le navigateur est `localhost:8000`.
#### Serveur de développement type production
Sur la VM Vagrant, un serveur apache est configuré pour servir GestioCOF de
façon similaire à la version en production : on utilise
[Daphne](https://github.com/django/daphne/) et `python manage.py runworker`
derrière un reverse-proxy apache. Le tout est monitoré par
[supervisor](http://supervisord.org/).
Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
`localhost:8080`. Toutefois il ne se recharge pas tout seul lorsque le code
change, il faut relancer le worker avec `sudo supervisorctl restart worker` pour
visualiser la dernière version du code.
### Installation manuelle ### Installation manuelle
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
est fortement conseillé d'utiliser un environnement virtuel pour Python. est fortement conseillé d'utiliser un environnement virtuel pour Python.
Il vous faudra installer mercurial, pip, les librairies de développement de Il vous faudra installer mercurial, pip, les librairies de développement de
python, ainsi qu'un client et un serveur MySQL ; sous Debian et dérivées (Ubuntu, ...) : python, un client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et
dérivées (Ubuntu, ...) :
sudo apt-get install mercurial python-pip python-dev libmysqlclient-dev sudo apt-get install mercurial python-pip python-dev libmysqlclient-dev
redis-server
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv; Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
@ -87,7 +108,7 @@ Vous pouvez maintenant installer les dépendances Python depuis les fichiers
pip install -r requirements.txt -r requirements-devel.txt pip install -r requirements.txt -r requirements-devel.txt
Enfin, copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`. Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
#### Installation avec MySQL #### Installation avec MySQL

View file

@ -1,7 +0,0 @@
import os, sys
sys.path.append (os.path.expanduser ('~gestion/www'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'cof.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

View file

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
"""
WSGI config for myproject project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
application = get_wsgi_application()

View file

@ -14,12 +14,21 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='choixspectacle', model_name='choixspectacle',
name='double_choice', name='double_choice',
field=models.CharField(verbose_name='Nombre de places', choices=[('1', '1 place'), ('autoquit', '2 places si possible, 1 sinon'), ('double', '2 places sinon rien')], default='1', max_length=10), field=models.CharField(
verbose_name='Nombre de places',
choices=[('1', '1 place'),
('autoquit', '2 places si possible, 1 sinon'),
('double', '2 places sinon rien')],
max_length=10, default='1'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='participant', model_name='participant',
name='paymenttype', name='paymenttype',
field=models.CharField(verbose_name='Moyen de paiement', choices=[('cash', 'Cash'), ('cb', 'CB'), ('cheque', 'Chèque'), ('autre', 'Autre')], max_length=6, blank=True), field=models.CharField(
blank=True,
choices=[('cash', 'Cash'), ('cb', 'CB'),
('cheque', 'Chèque'), ('autre', 'Autre')],
max_length=6, verbose_name='Moyen de paiement'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='salle', model_name='salle',
@ -44,7 +53,8 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='spectacle', model_name='spectacle',
name='listing', name='listing',
field=models.BooleanField(verbose_name='Les places sont sur listing'), field=models.BooleanField(
verbose_name='Les places sont sur listing'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='spectacle', model_name='spectacle',
@ -59,7 +69,8 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='spectacle', model_name='spectacle',
name='slots_description', name='slots_description',
field=models.TextField(verbose_name='Description des places', blank=True), field=models.TextField(verbose_name='Description des places',
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='spectacle', model_name='spectacle',
@ -69,17 +80,20 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='tirage', model_name='tirage',
name='active', name='active',
field=models.BooleanField(verbose_name='Tirage actif', default=False), field=models.BooleanField(verbose_name='Tirage actif',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tirage', model_name='tirage',
name='fermeture', name='fermeture',
field=models.DateTimeField(verbose_name='Date et heure de fermerture du tirage'), field=models.DateTimeField(
verbose_name='Date et heure de fermerture du tirage'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tirage', model_name='tirage',
name='ouverture', name='ouverture',
field=models.DateTimeField(verbose_name="Date et heure d'ouverture du tirage"), field=models.DateTimeField(
verbose_name="Date et heure d'ouverture du tirage"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tirage', model_name='tirage',

View file

@ -32,7 +32,8 @@ class Tirage(models.Model):
default=False) default=False)
def date_no_seconds(self): def date_no_seconds(self):
return self.fermeture.strftime('%d %b %Y %H:%M') return self.fermeture.astimezone(timezone.get_current_timezone()) \
.strftime('%d %b %Y %H:%M')
def __str__(self): def __str__(self):
return "%s - %s" % (self.title, self.date_no_seconds()) return "%s - %s" % (self.title, self.date_no_seconds())
@ -89,7 +90,8 @@ class Spectacle(models.Model):
return "%d" % calendar.timegm(self.date.utctimetuple()) return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self): def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M') return self.date.astimezone(timezone.get_current_timezone()) \
.strftime('%d %b %Y %H:%M')
def __str__(self): def __str__(self):
return "%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(), return "%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(),

View file

@ -3,6 +3,7 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<base target="_parent"/>
<style> <style>
@font-face { @font-face {
font-family: josefinsans; font-family: josefinsans;
@ -18,8 +19,9 @@
} }
.descTable{ .descTable{
width: auto; width: 100%;
margin: 0 auto 1em; margin: 0 auto 1em;
border-bottom: 2px solid;
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
font-size: 14px; font-size: 14px;
@ -30,9 +32,14 @@
font-weight: 700; font-weight: 700;
color: #5a5a5a; color: #5a5a5a;
} }
img{
max-width: 100%;
}
</style> </style>
<meta charset="utf8" /> <meta charset="utf8" />
</head> </head>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<body> <body>
{% for show in shows %} {% for show in shows %}
<table class="descTable"> <table class="descTable">
@ -46,26 +53,61 @@
<td><p style="text-align: left;">{{ show.location }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.category }}</p></td> <td><p style="text-align: left;">{{ show.location }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.category }}</p></td>
</tr> </tr>
<tr> <tr>
<td><p style="text-align: left;">{{ show.date }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %}- {{ show.price }}</p></td> <td><p style="text-align: left;">{{ show.date|date:"l j F Y - H\hi" }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %} - {{ show.price }} euro{{ show.price|pluralize}}</p></td>
</tr> </tr>
{% if show.vips %}
<tr> <tr>
<td colspan="2"><p style="text-align: justify;">{{ show.category }}</p></td> <td colspan="2"><p style="text-align: justify;">{{ show.vips }}</p></td>
</tr> </tr>
{% endif %}
<tr> <tr>
<td colspan="2"> <td colspan="2">
<p style="text-align: justify;">{{ show.description }}</p> <p style="text-align: justify;">{{ show.description }}</p>
{% for quote in show.quote_set.all %} {% for quote in show.quote_set.all %}
<p style="text-align:center; font-style: italic;">«{{ quote.text }}»{% if show.quote.author %} - {{ quote.author }}{% endif %}</p> <p style="text-align:center; font-style: italic;">«{{ quote.text }}»{% if quote.author %} - {{ quote.author }}{% endif %}</p>
{% endfor %} {% endfor %}
</td> </td>
</tr> </tr>
{% if show.image %} {% if show.image %}
<tr> <tr>
<td colspan="2"><p style="text-align:center;"><a href="{{ show.ext_link }}"><img style="display: inline;" src="{{ MEDIA_URL }}{{ show.image }}" alt="{{ show.title }}" width="200px"></a></p></td> <td colspan="2"><p style="text-align:center;"><a href="{{ show.ext_link }}"><img class="imgDesc" style="display: inline;" src="{{ MEDIA_URL }}{{ show.image }}" alt="{{ show.title }}"></a></p></td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
{% endfor %} {% endfor %}
<script>
// Correction de la taille des images
/*$(document).ready(function() {
$(".descTable").each(function() {
$(this).width($("body").width());
});
$(".imgDesc").on("load", function() {
// Dimensions
origHeight = 500; // Hauteur souhaitée
w = $(this).width();
h = $(this).height();
r = w/h; // Ratio de l'image
maxWidth = $("body").width();
if (r * origHeight > maxWidth)
{
$(this).width(maxWidth);
$(this).height(maxWidth/r);
}
else
{
$(this).width(r * origHeight);
$(this).height(origHeight);
}
});
});*/
</script>
</body> </body>
</html> </html>

View file

@ -66,7 +66,7 @@ def etat_places(request, tirage_id):
def _hash_queryset(queryset): def _hash_queryset(queryset):
data = serializers.serialize("json", queryset).encode() data = serializers.serialize("json", queryset).encode('utf-8')
hasher = hashlib.sha256() hasher = hashlib.sha256()
hasher.update(data) hasher.update(data)
return hasher.hexdigest() return hasher.hexdigest()

7
cof/asgi.py Normal file
View file

@ -0,0 +1,7 @@
import os
from channels.asgi import get_channel_layer
if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
channel_layer = get_channel_layer()

View file

@ -29,7 +29,7 @@ SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['127.0.0.1']
# Application definition # Application definition
@ -123,6 +123,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/1.8/howto/static-files/ # https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/static/'
STATICFILES_DIRS = ( STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static/'), os.path.join(BASE_DIR, 'static/'),
@ -151,12 +152,12 @@ PETITS_COURS_REPLYTO = "cof@ens.fr"
RAPPEL_FROM = 'Le BdA <bda@ens.fr>' RAPPEL_FROM = 'Le BdA <bda@ens.fr>'
RAPPEL_REPLY_TO = RAPPEL_FROM RAPPEL_REPLY_TO = RAPPEL_FROM
LOGIN_URL = "/login" LOGIN_URL = "/gestion/login"
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/gestion/"
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/' CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
CAS_IGNORE_REFERER = True CAS_IGNORE_REFERER = True
CAS_REDIRECT_URL = '/' CAS_REDIRECT_URL = '/gestion/'
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr" CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
@ -175,12 +176,13 @@ CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "asgi_redis.RedisChannelLayer", "BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": { "CONFIG": {
"hosts": [("redis://:password_redis@127.0.0.1:6379/0")], "hosts": [("localhost", 6379)],
}, },
"ROUTING": "cof.routing.channel_routing", "ROUTING": "cof.routing.channel_routing",
} }
} }
def show_toolbar(request): def show_toolbar(request):
""" """
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar

View file

@ -24,7 +24,7 @@ from gestioncof.autocomplete import autocomplete
autocomplete_light.autodiscover() autocomplete_light.autodiscover()
admin.autodiscover() admin.autodiscover()
urlpatterns = [ my_urlpatterns = [
# Page d'accueil # Page d'accueil
url(r'^$', gestioncof_views.home, name='home'), url(r'^$', gestioncof_views.home, name='home'),
# Le BdA # Le BdA
@ -88,3 +88,7 @@ urlpatterns = [
else []) else [])
# Si on est en production, MEDIA_ROOT est servi par Apache. # Si on est en production, MEDIA_ROOT est servi par Apache.
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement. # Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
urlpatterns = [
url(r'^gestion/', include(my_urlpatterns))
]

View file

@ -6,16 +6,18 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \ from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \
CofProfile, EventOption, EventOptionChoice, Event, Club, CustomMail, \ CofProfile, EventOption, EventOptionChoice, Event, Club, CustomMail, \
Survey, EventCommentField, EventRegistration Survey, EventCommentField, EventRegistration
from gestioncof.petits_cours_models import PetitCoursDemande, \ from gestioncof.petits_cours_models import PetitCoursDemande, \
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \ PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
PetitCoursAttributionCounter PetitCoursAttributionCounter
from django.contrib.auth.models import User from django.contrib.auth.models import User, Group, Permission
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.db.models import Q
import django.utils.six as six import django.utils.six as six
import autocomplete_light import autocomplete_light
@ -163,6 +165,7 @@ class UserProfileAdmin(UserAdmin):
return False return False
is_cof.short_description = 'Membre du COF' is_cof.short_description = 'Membre du COF'
is_cof.boolean = True is_cof.boolean = True
list_display = ('profile_num',) + UserAdmin.list_display \ list_display = ('profile_num',) + UserAdmin.list_display \
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation', + ('profile_login_clipper', 'profile_phone', 'profile_occupation',
'profile_mailing_cof', 'profile_mailing_bda', 'profile_mailing_cof', 'profile_mailing_bda',
@ -176,6 +179,40 @@ class UserProfileAdmin(UserAdmin):
CofProfileInline, CofProfileInline,
] ]
staff_fieldsets = [
(None, {'fields': ['username', 'password']}),
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
]
def get_fieldsets(self, request, user=None):
if not request.user.is_superuser:
return self.staff_fieldsets
return super(UserProfileAdmin, self).get_fieldsets(request, user)
def save_model(self, request, user, form, change):
cof_group, created = Group.objects.get_or_create(name='COF')
if created:
# Si le groupe COF n'était pas déjà dans la bdd
# On lui assigne les bonnes permissions
perms = Permission.objects.filter(
Q(content_type__app_label='gestioncof')
| Q(content_type__app_label='bda')
| (Q(content_type__app_label='auth')
& Q(content_type__model='user')))
cof_group.permissions = perms
# On y associe les membres du Burô
cof_group.user_set = User.objects.filter(profile__is_buro=True)
# Sauvegarde
cof_group.save()
# le Burô est staff et appartient au groupe COF
if user.profile.is_buro:
user.is_staff = True
user.groups.add(cof_group)
else:
user.is_staff = False
user.groups.remove(cof_group)
user.save()
# FIXME: This is absolutely horrible. # FIXME: This is absolutely horrible.
def user_unicode(self): def user_unicode(self):

View file

@ -10,8 +10,10 @@ from django.db.models import Q
from django.contrib.auth.models import User from django.contrib.auth.models import User
from gestioncof.models import CofProfile, Clipper from gestioncof.models import CofProfile, Clipper
from gestioncof.decorators import buro_required
@buro_required
def autocomplete(request): def autocomplete(request):
if "q" not in request.GET: if "q" not in request.GET:
raise Http404 raise Http404

View file

@ -4,6 +4,11 @@ from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
def forwards(apps, schema_editor):
Profile = apps.get_model("gestioncof", "CofProfile")
Profile.objects.update(comments="")
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
@ -24,57 +29,85 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='comments', name='comments',
field=models.TextField(verbose_name='Commentaires visibles uniquement par le Buro', blank=True), field=models.TextField(
verbose_name="Commentaires visibles par l'utilisateur",
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='is_cof', name='is_cof',
field=models.BooleanField(verbose_name='Membre du COF', default=False), field=models.BooleanField(verbose_name='Membre du COF',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='login_clipper', name='login_clipper',
field=models.CharField(verbose_name='Login clipper', max_length=8, blank=True), field=models.CharField(verbose_name='Login clipper', max_length=8,
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='mailing_bda', name='mailing_bda',
field=models.BooleanField(verbose_name='Recevoir les mails BdA', default=False), field=models.BooleanField(verbose_name='Recevoir les mails BdA',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='mailing_bda_revente', name='mailing_bda_revente',
field=models.BooleanField(verbose_name='Recevoir les mails de revente de places BdA', default=False), field=models.BooleanField(
verbose_name='Recevoir les mails de revente de places BdA',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='mailing_cof', name='mailing_cof',
field=models.BooleanField(verbose_name='Recevoir les mails COF', default=False), field=models.BooleanField(verbose_name='Recevoir les mails COF',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='occupation', name='occupation',
field=models.CharField(verbose_name='Occupation', choices=[('exterieur', 'Extérieur'), ('1A', '1A'), ('2A', '2A'), ('3A', '3A'), ('4A', '4A'), ('archicube', 'Archicube'), ('doctorant', 'Doctorant'), ('CST', 'CST')], default='1A', max_length=9), field=models.CharField(verbose_name='Occupation',
choices=[('exterieur', 'Extérieur'),
('1A', '1A'),
('2A', '2A'),
('3A', '3A'),
('4A', '4A'),
('archicube', 'Archicube'),
('doctorant', 'Doctorant'),
('CST', 'CST')],
max_length=9, default='1A'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='petits_cours_accept', name='petits_cours_accept',
field=models.BooleanField(verbose_name='Recevoir des petits cours', default=False), field=models.BooleanField(verbose_name='Recevoir des petits cours',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='petits_cours_remarques', name='petits_cours_remarques',
field=models.TextField(verbose_name='Remarques et précisions pour les petits cours', default='', blank=True), field=models.TextField(
blank=True,
verbose_name='Remarques et précisions pour les petits cours',
default=''),
), ),
migrations.AlterField( migrations.AlterField(
model_name='cofprofile', model_name='cofprofile',
name='type_cotiz', name='type_cotiz',
field=models.CharField(verbose_name='Type de cotisation', choices=[('etudiant', 'Normalien étudiant'), ('normalien', 'Normalien élève'), ('exterieur', 'Extérieur')], default='normalien', max_length=9), field=models.CharField(
verbose_name='Type de cotisation',
choices=[('etudiant', 'Normalien étudiant'),
('normalien', 'Normalien élève'),
('exterieur', 'Extérieur')],
max_length=9, default='normalien'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='custommail', model_name='custommail',
name='comments', name='comments',
field=models.TextField(verbose_name='Informations contextuelles sur le mail', blank=True), field=models.TextField(
verbose_name='Informations contextuelles sur le mail',
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='custommail', model_name='custommail',
@ -94,12 +127,14 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='end_date', name='end_date',
field=models.DateTimeField(verbose_name='Date de fin', null=True, blank=True), field=models.DateTimeField(null=True, verbose_name='Date de fin',
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='image', name='image',
field=models.ImageField(verbose_name='Image', upload_to='imgs/events/', null=True, blank=True), field=models.ImageField(upload_to='imgs/events/', null=True,
verbose_name='Image', blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
@ -109,7 +144,8 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='registration_open', name='registration_open',
field=models.BooleanField(verbose_name='Inscriptions ouvertes', default=True), field=models.BooleanField(verbose_name='Inscriptions ouvertes',
default=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
@ -119,7 +155,10 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='eventcommentfield', model_name='eventcommentfield',
name='fieldtype', name='fieldtype',
field=models.CharField(verbose_name='Type', choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10), field=models.CharField(verbose_name='Type',
choices=[('text', 'Texte long'),
('char', 'Texte court')],
max_length=10, default='text'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='eventcommentfield', model_name='eventcommentfield',
@ -129,12 +168,14 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='eventcommentvalue', model_name='eventcommentvalue',
name='content', name='content',
field=models.TextField(verbose_name='Contenu', null=True, blank=True), field=models.TextField(null=True, verbose_name='Contenu',
blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='eventoption', model_name='eventoption',
name='multi_choices', name='multi_choices',
field=models.BooleanField(verbose_name='Choix multiples', default=False), field=models.BooleanField(verbose_name='Choix multiples',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='eventoption', model_name='eventoption',
@ -149,7 +190,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='petitcoursability', model_name='petitcoursability',
name='niveau', name='niveau',
field=models.CharField(verbose_name='Niveau', choices=[('college', 'Collège'), ('lycee', 'Lycée'), ('prepa1styear', 'Prépa 1ère année / L1'), ('prepa2ndyear', 'Prépa 2ème année / L2'), ('licence3', 'Licence 3'), ('other', 'Autre (préciser dans les commentaires)')], max_length=12), field=models.CharField(
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
('prepa1styear', 'Prépa 1ère année / L1'),
('prepa2ndyear', 'Prépa 2ème année / L2'),
('licence3', 'Licence 3'),
('other', 'Autre (préciser dans les commentaires)')],
max_length=12, verbose_name='Niveau'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='petitcoursattribution', model_name='petitcoursattribution',
@ -159,22 +206,32 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='petitcoursattributioncounter', model_name='petitcoursattributioncounter',
name='count', name='count',
field=models.IntegerField(verbose_name="Nombre d'envois", default=0), field=models.IntegerField(verbose_name="Nombre d'envois",
default=0),
), ),
migrations.AlterField( migrations.AlterField(
model_name='petitcoursdemande', model_name='petitcoursdemande',
name='niveau', name='niveau',
field=models.CharField(verbose_name='Niveau', choices=[('college', 'Collège'), ('lycee', 'Lycée'), ('prepa1styear', 'Prépa 1ère année / L1'), ('prepa2ndyear', 'Prépa 2ème année / L2'), ('licence3', 'Licence 3'), ('other', 'Autre (préciser dans les commentaires)')], default='', max_length=12), field=models.CharField(
verbose_name='Niveau',
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
('prepa1styear', 'Prépa 1ère année / L1'),
('prepa2ndyear', 'Prépa 2ème année / L2'),
('licence3', 'Licence 3'),
('other', 'Autre (préciser dans les commentaires)')],
max_length=12, default=''),
), ),
migrations.AlterField( migrations.AlterField(
model_name='survey', model_name='survey',
name='old', name='old',
field=models.BooleanField(verbose_name='Archiver (sondage fini)', default=False), field=models.BooleanField(verbose_name='Archiver (sondage fini)',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='survey', model_name='survey',
name='survey_open', name='survey_open',
field=models.BooleanField(verbose_name='Sondage ouvert', default=True), field=models.BooleanField(verbose_name='Sondage ouvert',
default=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='survey', model_name='survey',
@ -184,11 +241,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='surveyquestion', model_name='surveyquestion',
name='multi_answers', name='multi_answers',
field=models.BooleanField(verbose_name='Choix multiples', default=False), field=models.BooleanField(verbose_name='Choix multiples',
default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='surveyquestion', model_name='surveyquestion',
name='question', name='question',
field=models.CharField(verbose_name='Question', max_length=200), field=models.CharField(verbose_name='Question', max_length=200),
), ),
migrations.RunPython(forwards, migrations.RunPython.noop),
] ]

View file

@ -62,7 +62,7 @@ class CofProfile(models.Model):
mailing_bda_revente = models.BooleanField( mailing_bda_revente = models.BooleanField(
"Recevoir les mails de revente de places BdA", default=False) "Recevoir les mails de revente de places BdA", default=False)
comments = models.TextField( comments = models.TextField(
"Commentaires visibles uniquement par le Buro", blank=True) "Commentaires visibles par l'utilisateur", blank=True)
is_buro = models.BooleanField("Membre du Burô", default=False) is_buro = models.BooleanField("Membre du Burô", default=False)
petits_cours_accept = models.BooleanField( petits_cours_accept = models.BooleanField(
"Recevoir des petits cours", default=False) "Recevoir des petits cours", default=False)

View file

@ -135,7 +135,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, redo=False, errors=None): unsatisfied, attribdata, redo=False, errors=None):
proposals = proposals.items() proposals = proposals.items()
proposed_for = proposed_for.items() proposed_for = proposed_for.items()
attribdata = attribdata.items() attribdata = list(attribdata.items())
proposed_mails = _generate_eleve_email(demande, proposed_for) proposed_mails = _generate_eleve_email(demande, proposed_for)
mainmail = render_template("petits-cours-mail-demandeur.txt", mainmail = render_template("petits-cours-mail-demandeur.txt",
{"proposals": proposals, {"proposals": proposals,
@ -153,7 +153,8 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
"proposed_mails": proposed_mails, "proposed_mails": proposed_mails,
"mainmail": mainmail, "mainmail": mainmail,
"attribdata": "attribdata":
base64.b64encode(simplejson.dumps(attribdata)), base64.b64encode(simplejson.dumps(attribdata)
.encode('utf_8')),
"redo": redo, "redo": redo,
"errors": errors, "errors": errors,
}) })

View file

@ -58,8 +58,7 @@ class COFCASBackend(CASBackend):
if not user.email: if not user.email:
user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper
user.save() user.save()
if profile.is_buro and not user.is_superuser: if profile.is_buro and not user.is_staff:
user.is_superuser = True
user.is_staff = True user.is_staff = True
user.save() user.save()
return user return user

View file

@ -8,6 +8,8 @@
<link type="text/css" rel="stylesheet" href="{% static "css/cof.css" %}" /> <link type="text/css" rel="stylesheet" href="{% static "css/cof.css" %}" />
<link href="https://fonts.googleapis.com/css?family=Dosis|Dosis:700|Raleway|Roboto:300,300i,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Dosis|Dosis:700|Raleway|Roboto:300,300i,700" rel="stylesheet">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block extra_head %}{% endblock %} {% block extra_head %}{% endblock %}
</head> </head>
<body> <body>

View file

@ -13,10 +13,18 @@
{% csrf_token %} {% csrf_token %}
<fieldset"center-block"> <fieldset"center-block">
{% for field in form %} {% for field in form %}
{{ field | bootstrap}} {{ field | bootstrap }}
{% endfor %} {% endfor %}
</fieldset> </fieldset>
</div> </div>
{% if user.profile.comments %}
<div class="row" style="margin: 0 15%;">
<h4>Commentaires</h4>
<p>
{{ user.profile.comments }}
</p>
</div>
{% endif %}
<div class="form-actions"> <div class="form-actions">
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" /> <input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
</div> </div>

View file

@ -346,7 +346,7 @@ def registration_form2(request, login_clipper=None, username=None):
registration_set_ro_fields(user_form, profile_form) registration_set_ro_fields(user_form, profile_form)
# events & clubs # events & clubs
event_formset = EventFormset(events=events, prefix='events') event_formset = EventFormset(events=events, prefix='events')
clubs_form = ClubsForm(initial={'clubs': member.clubs.all()}) clubs_form = ClubsForm()
if username: if username:
member = get_object_or_404(User, username=username) member = get_object_or_404(User, username=username)
(profile, _) = CofProfile.objects.get_or_create(user=member) (profile, _) = CofProfile.objects.get_or_create(user=member)

View file

@ -1240,9 +1240,10 @@ $(document).ready(function() {
case 72: case 72:
if (e.ctrlKey) { if (e.ctrlKey) {
// Ctrl+H - Display help // Ctrl+H - Display help
e.preventDefault();
$('.help').show('fast'); $('.help').show('fast');
} }
return false; return true;
case 112: case 112:
// F1 - Cool reset // F1 - Cool reset
coolReset(); coolReset();

29
provisioning/apache.conf Normal file
View file

@ -0,0 +1,29 @@
<VirtualHost *:80>
ServerName default
DocumentRoot /var/www/html
ProxyPreserveHost On
ProxyRequests Off
ProxyPass /static/ !
ProxyPass /media/ !
ProxyPass /ws/ ws://127.0.0.1:8001/ws/
ProxyPass / http://127.0.0.1:8001/
ProxyPassReverse / http://127.0.0.1:8001/
Alias /media /vagrant/media
Alias /static /var/www/static
<Directory /vagrant/media>
Order deny,allow
Allow from all
</Directory>
<Directory /var/www/static>
Order deny,allow
Allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

View file

@ -9,7 +9,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
# Installation de paquets utiles # Installation de paquets utiles
apt-get update && apt-get install -y mercurial python-pip python-dev \ apt-get update && apt-get install -y mercurial python-pip python-dev \
libmysqlclient-dev libjpeg-dev git libmysqlclient-dev libjpeg-dev git redis-server
# Configuration et installation de mysql. Le mot de passe root est le même que # Configuration et installation de mysql. Le mot de passe root est le même que
# le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance # le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance
@ -21,8 +21,15 @@ apt-get install -y mysql-server
mysql -uroot -p$DBPASSWD -e "CREATE DATABASE $DBNAME; GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASSWD'" mysql -uroot -p$DBPASSWD -e "CREATE DATABASE $DBNAME; GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASSWD'"
# Installation de redis-server. Todo: lui mettre un mot de passe # Installation et configuration d'Apache
apt-get install -y redis-server apt-get install -y apache2
a2enmod proxy proxy_http
cp /vagrant/provisioning/apache.conf /etc/apache2/sites-available/gestiocof.conf
a2ensite gestiocof
a2dissite 000-default
service apache2 restart
mkdir /var/www/static
chown -R vagrant:www-data /var/www/static
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh` # Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh`
cat > ~vagrant/.bash_profile <<EOF cat > ~vagrant/.bash_profile <<EOF
@ -53,3 +60,12 @@ sudo -H -u vagrant DJANGO_SETTINGS_MODULE='cof.settings_dev' DBUSER=$DBUSER DBNA
# Installation du cron pour les mails de rappels # Installation du cron pour les mails de rappels
sudo -H -u vagrant crontab provisioning/cron.dev sudo -H -u vagrant crontab provisioning/cron.dev
# On installe Daphne et on demande à supervisor de le lancer
pip install daphne
apt-get install -y supervisor
cp /vagrant/provisioning/supervisor.conf /etc/supervisor/conf.d/gestiocof.conf
sed "s/{DBUSER}/$DBUSER/" -i /etc/supervisor/conf.d/gestiocof.conf
sed "s/{DBNAME}/$DBNAME/" -i /etc/supervisor/conf.d/gestiocof.conf
sed "s/{DBPASSWD}/$DBPASSWD/" -i /etc/supervisor/conf.d/gestiocof.conf
service supervisor restart

View file

@ -2,3 +2,4 @@
python manage.py migrate python manage.py migrate
python manage.py loaddata users root bda gestion sites python manage.py loaddata users root bda gestion sites
python manage.py collectstatic --noinput

View file

@ -0,0 +1,20 @@
[program:worker]
command=/usr/bin/python /vagrant/manage.py runworker
directory=/vagrant/
user=vagrant
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
autostart=true
autorestart=true
redirect_stderr=true
stopasgroup=true
redirect_stderr=true
[program:interface]
command=/usr/local/bin/daphne -b 127.0.0.1 -p 8001 cof.asgi:channel_layer
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
directory=/vagrant/
redirect_stderr=true
autostart=true
autorestart=true
stopasgroup=true
user=vagrant

View file

@ -2,7 +2,7 @@ configparser==3.5.0
Django==1.8 Django==1.8
django-autocomplete-light==2.3.3 django-autocomplete-light==2.3.3
django-autoslug==1.9.3 django-autoslug==1.9.3
django-cas-ng==3.5.4 git+https://github.com/xapantu/django-cas-ng.git#egg=django-cas-ng
django-grappelli==2.8.1 django-grappelli==2.8.1
django-recaptcha==1.0.5 django-recaptcha==1.0.5
mysqlclient==1.3.7 mysqlclient==1.3.7