forked from DGNum/gestioCOF
Merge branch 'Aufinal/bda_revente' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/bda_revente
This commit is contained in:
commit
37b4a178a7
39
README.md
39
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import os
|
|
||||||
from channels.asgi import get_channel_layer
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
|
|
||||||
|
|
||||||
channel_layer = get_channel_layer()
|
|
|
@ -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()
|
|
|
@ -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()
|
|
|
@ -14,6 +14,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
self.stdout.write(now)
|
||||||
reventes = SpectacleRevente.objects.all()
|
reventes = SpectacleRevente.objects.all()
|
||||||
for revente in reventes:
|
for revente in reventes:
|
||||||
# Check si < 24h
|
# Check si < 24h
|
||||||
|
@ -22,11 +23,14 @@ class Command(BaseCommand):
|
||||||
now >= revente.date + timedelta(minutes=15) and \
|
now >= revente.date + timedelta(minutes=15) and \
|
||||||
not revente.notif_sent:
|
not revente.notif_sent:
|
||||||
revente.mail_shotgun()
|
revente.mail_shotgun()
|
||||||
|
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
||||||
# Check si délai de retrait dépassé
|
# Check si délai de retrait dépassé
|
||||||
elif (now >= revente.date + timedelta(hours=1) and
|
elif (now >= revente.date + timedelta(hours=1) and
|
||||||
not revente.notif_sent):
|
not revente.notif_sent):
|
||||||
revente.send_notif()
|
revente.send_notif()
|
||||||
|
self.stdout.write("Mail d'inscription à une revente envoyé")
|
||||||
# Check si tirage à faire
|
# Check si tirage à faire
|
||||||
elif (now >= revente.expiration_time and
|
elif (now >= revente.expiration_time and
|
||||||
not revente.tirage_done):
|
not revente.tirage_done):
|
||||||
revente.tirage()
|
revente.tirage()
|
||||||
|
self.stdout.write("Tirage effectué, mails envoyés")
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -31,6 +32,10 @@
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #5a5a5a;
|
color: #5a5a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<meta charset="utf8" />
|
<meta charset="utf8" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -50,11 +55,16 @@
|
||||||
<tr>
|
<tr>
|
||||||
<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>
|
<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>
|
||||||
<tr>
|
{% if show.vips %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><p style="text-align: justify;">{{ show.vips }}</p></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<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>
|
||||||
|
@ -69,7 +79,7 @@
|
||||||
<script>
|
<script>
|
||||||
// Correction de la taille des images
|
// Correction de la taille des images
|
||||||
|
|
||||||
$(document).ready(function() {
|
/*$(document).ready(function() {
|
||||||
$(".descTable").each(function() {
|
$(".descTable").each(function() {
|
||||||
$(this).width($("body").width());
|
$(this).width($("body").width());
|
||||||
});
|
});
|
||||||
|
@ -95,7 +105,7 @@
|
||||||
$(this).height(origHeight);
|
$(this).height(origHeight);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
12
bda/views.py
12
bda/views.py
|
@ -71,7 +71,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()
|
||||||
|
@ -406,7 +406,7 @@ def list_revente(request, tirage_id):
|
||||||
if qset.exists():
|
if qset.exists():
|
||||||
# On l'inscrit à l'un des tirages au sort
|
# On l'inscrit à l'un des tirages au sort
|
||||||
for revente in qset.all():
|
for revente in qset.all():
|
||||||
if revente.shotgun:
|
if revente.shotgun and not revente.soldTo:
|
||||||
deja_revente = True
|
deja_revente = True
|
||||||
else:
|
else:
|
||||||
revente.interested.add(participant)
|
revente.interested.add(participant)
|
||||||
|
@ -435,12 +435,16 @@ def buy_revente(request, spectacle_id):
|
||||||
revente.delete()
|
revente.delete()
|
||||||
return HttpResponseRedirect(reverse("bda-liste-revente",
|
return HttpResponseRedirect(reverse("bda-liste-revente",
|
||||||
args=[tirage.id]))
|
args=[tirage.id]))
|
||||||
|
reventes_shotgun = []
|
||||||
|
for revente in reventes.all():
|
||||||
|
if revente.shotgun:
|
||||||
|
reventes_shotgun.append(revente)
|
||||||
|
|
||||||
if not reventes.exists():
|
if reventes_shotgun.empty():
|
||||||
return render(request, "bda-no-revente.html", {})
|
return render(request, "bda-no-revente.html", {})
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
revente = random.choice(reventes.all())
|
revente = random.choice(reventes_shotgun)
|
||||||
revente.soldTo = participant
|
revente.soldTo = participant
|
||||||
revente.save()
|
revente.save()
|
||||||
mail = """Bonjour !
|
mail = """Bonjour !
|
||||||
|
|
7
cof/asgi.py
Normal file
7
cof/asgi.py
Normal 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()
|
|
@ -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/'),
|
||||||
|
@ -154,12 +155,12 @@ RAPPEL_REPLY_TO = RAPPEL_FROM
|
||||||
REVENTE_FROM = 'BDA-Revente <bda-revente@ens.fr>'
|
REVENTE_FROM = 'BDA-Revente <bda-revente@ens.fr>'
|
||||||
REVENTE_REPLY_TO = REVENTE_FROM
|
REVENTE_REPLY_TO = REVENTE_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',
|
||||||
|
@ -178,7 +179,7 @@ CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [("redis://:redis_password@127.0.0.1:6379/0")],
|
"hosts": [("localhost", 6379)],
|
||||||
},
|
},
|
||||||
"ROUTING": "cof.routing.channel_routing",
|
"ROUTING": "cof.routing.channel_routing",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'kfet.apps.KFetConfig'
|
14
kfet/apps.py
Normal file
14
kfet/apps.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division,
|
||||||
|
print_function, unicode_literals)
|
||||||
|
from builtins import *
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class KFetConfig(AppConfig):
|
||||||
|
name = 'kfet'
|
||||||
|
verbose_name = "Application K-Fêt"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import kfet.signals
|
|
@ -8,8 +8,10 @@ from django.shortcuts import render
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from gestioncof.models import User, Clipper
|
from gestioncof.models import User, Clipper
|
||||||
|
from kfet.decorators import teamkfet_required
|
||||||
from kfet.models import Account
|
from kfet.models import Account
|
||||||
|
|
||||||
|
@teamkfet_required
|
||||||
def account_create(request):
|
def account_create(request):
|
||||||
if "q" not in request.GET:
|
if "q" not in request.GET:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
|
@ -18,7 +18,7 @@ class KFetBackend(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
password_sha256 = hashlib.sha256(password.encode()).hexdigest()
|
password_sha256 = hashlib.sha256(password.encode('utf-8')).hexdigest()
|
||||||
account = Account.objects.get(password=password_sha256)
|
account = Account.objects.get(password=password_sha256)
|
||||||
user = account.cofprofile.user
|
user = account.cofprofile.user
|
||||||
except Account.DoesNotExist:
|
except Account.DoesNotExist:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from channels.routing import route, route_class
|
||||||
from kfet import consumers
|
from kfet import consumers
|
||||||
|
|
||||||
channel_routing = [
|
channel_routing = [
|
||||||
route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"),
|
route_class(consumers.KPsul, path=r"^/gestion/ws/k-fet/k-psul/$"),
|
||||||
#route("websocket.connect", ws_kpsul_history_connect),
|
#route("websocket.connect", ws_kpsul_history_connect),
|
||||||
#route('websocket.receive', ws_message)
|
#route('websocket.receive', ws_message)
|
||||||
]
|
]
|
||||||
|
|
16
kfet/signals.py
Normal file
16
kfet/signals.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division,
|
||||||
|
print_function, unicode_literals)
|
||||||
|
from builtins import *
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.signals import user_logged_in
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
@receiver(user_logged_in)
|
||||||
|
def messages_on_login(sender, request, user, **kwargs):
|
||||||
|
if (not user.username == 'kfet_genericteam'
|
||||||
|
and user.has_perm('kfet.is_team')):
|
||||||
|
messages.info(request, '<a href="%s">Connexion en utilisateur partagé ?</a>' % reverse('kfet.login.genericteam'), extra_tags='safe')
|
|
@ -263,3 +263,81 @@ textarea {
|
||||||
display:block;
|
display:block;
|
||||||
padding:5px 20px;
|
padding:5px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Messages
|
||||||
|
*/
|
||||||
|
|
||||||
|
.messages .alert {
|
||||||
|
padding:10px 15px;
|
||||||
|
margin:0;
|
||||||
|
border:0;
|
||||||
|
border-radius:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages .alert-dismissible {
|
||||||
|
padding-right:35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages .alert .close {
|
||||||
|
top:0;
|
||||||
|
right:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages .alert-info {
|
||||||
|
color:inherit;
|
||||||
|
background-color:#ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages .alert-error {
|
||||||
|
color:inherit;
|
||||||
|
background-color:rgba(200,16,46,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages .alert-success {
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Help
|
||||||
|
*/
|
||||||
|
|
||||||
|
.help {
|
||||||
|
display:none;
|
||||||
|
position:fixed;
|
||||||
|
top:50px;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
bottom:0;
|
||||||
|
overflow:auto;
|
||||||
|
background:rgba(51,51,51,0.3);
|
||||||
|
z-index:500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-box {
|
||||||
|
margin-top:30px;
|
||||||
|
padding-top:1px;
|
||||||
|
padding-bottom:15px;
|
||||||
|
background:rgba(51,51,51,0.7);
|
||||||
|
color:#fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:768px) {
|
||||||
|
.help-box {
|
||||||
|
margin:20px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.help h2 {
|
||||||
|
padding:0 15px 20px;
|
||||||
|
border-bottom:1px solid #999;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help .row > div {
|
||||||
|
padding-right:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help h4 {
|
||||||
|
margin:15px 0;
|
||||||
|
}
|
||||||
|
|
|
@ -319,6 +319,11 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
padding-left:20px;
|
padding-left:20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#articles_data .article:hover {
|
||||||
|
background:rgba(200,16,46,0.3);
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Second part - Left - bottom */
|
/* Second part - Left - bottom */
|
||||||
|
|
||||||
.kpsul_middle_left_bottom {
|
.kpsul_middle_left_bottom {
|
||||||
|
|
|
@ -66,8 +66,11 @@ function getErrorsHtml(data) {
|
||||||
content += '</ul>';
|
content += '</ul>';
|
||||||
}
|
}
|
||||||
if ('negative' in data['errors']) {
|
if ('negative' in data['errors']) {
|
||||||
var url_base = "{% url 'kfet.account.update' LIQ}";
|
if (window.location.pathname.startsWith('/gestion/')) {
|
||||||
url_base = base_url(0, url_base.length-8);
|
var url_base = '/gestion/k-fet/accounts/';
|
||||||
|
} else {
|
||||||
|
var url_base = '/k-fet/accounts/';
|
||||||
|
}
|
||||||
for (var i=0; i<data['errors']['negative'].length; i++) {
|
for (var i=0; i<data['errors']['negative'].length; i++) {
|
||||||
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank" style="width:100%">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
|
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank" style="width:100%">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
|
||||||
}
|
}
|
||||||
|
@ -110,4 +113,3 @@ function requestAuth(data, callback, focus_next = null) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "kfet.account.create.fromclipper" clipper.username %}">
|
<a href="{% url "kfet.account.create.fromclipper" clipper.username %}">
|
||||||
{{ clipper|highlight_clipper:q }}
|
{{ clipper|highlight_clipper:q }}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<td>{{ neg.account.name }}</td>
|
<td>{{ neg.account.name }}</td>
|
||||||
<td class="text-right">{{ neg.account.balance|floatformat:2 }}€</td>
|
<td class="text-right">{{ neg.account.balance|floatformat:2 }}€</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if neg.account.balance_offset %}
|
{% if neg.balance_offset %}
|
||||||
{{ neg.account.real_balance|floatformat:2 }}€
|
{{ neg.account.real_balance|floatformat:2 }}€
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -40,5 +40,13 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
{% include "kfet/base_footer.html" %}
|
{% include "kfet/base_footer.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="help">
|
||||||
|
<div class="help-box col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2">
|
||||||
|
<div class="help-content">
|
||||||
|
<h2>Aide</h2>
|
||||||
|
{% block help %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<ul class="messages">
|
<div class="row messages">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
<div class="col-sm-12 item">
|
||||||
{% endfor %}
|
<div class="alert alert-{{ message.level_tag }} alert-dismissible fade in{% if message.tags %} {{ message.tags }}{% endif %}">
|
||||||
</ul>
|
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button>
|
||||||
|
{% if 'safe' in message.tags %}
|
||||||
|
{{ message|safe }}
|
||||||
|
{% else %}
|
||||||
|
{{ message }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -19,8 +19,59 @@
|
||||||
|
|
||||||
{% block content-header %}{% endblock %}
|
{% block content-header %}{% endblock %}
|
||||||
|
|
||||||
|
{% block help %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="block">
|
||||||
|
<h4>Opérations</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F3</b></div>
|
||||||
|
<div class="col-xs-9">Charge</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>Shift + F3</b></div>
|
||||||
|
<div class="col-xs-9">Retrait</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F8</b></div>
|
||||||
|
<div class="col-xs-9">Edition</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="block">
|
||||||
|
<h4>Général</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F1</b></div>
|
||||||
|
<div class="col-xs-9">Reset</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F2</b></div>
|
||||||
|
<div class="col-xs-9">Reset compte</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>Shift + F2</b></div>
|
||||||
|
<div class="col-xs-9">Reset panier</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F9</b></div>
|
||||||
|
<div class="col-xs-9">Majoration</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3"><b>F10</b></div>
|
||||||
|
<div class="col-xs-9">Hard reset</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% include 'kfet/base_messages.html' %}
|
||||||
|
|
||||||
<div class="row kpsul_top">
|
<div class="row kpsul_top">
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="row" id="account">
|
<div class="row" id="account">
|
||||||
|
@ -69,7 +120,7 @@
|
||||||
<button role="button" class="btn" id="ask_addcost">Major.</button>
|
<button role="button" class="btn" id="ask_addcost">Major.</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="article_selection">
|
<div id="article_selection">
|
||||||
<input type="text" id="article_autocomplete">
|
<input type="text" id="article_autocomplete" autocomplete="off">
|
||||||
<input type="number" id="article_number" step="1" min="1">
|
<input type="number" id="article_number" step="1" min="1">
|
||||||
<input type="hidden" id="article_id" value="">
|
<input type="hidden" id="article_id" value="">
|
||||||
</div>
|
</div>
|
||||||
|
@ -198,8 +249,9 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
if (account_data['id'] == 0) {
|
if (account_data['id'] == 0) {
|
||||||
var trigramme = triInput.val().toUpperCase();
|
var trigramme = triInput.val().toUpperCase();
|
||||||
|
var url_base = '{% url 'kfet.account.create' %}'
|
||||||
if (isValidTrigramme(trigramme)) {
|
if (isValidTrigramme(trigramme)) {
|
||||||
buttons += '<a href="/k-fet/accounts/new?trigramme='+trigramme+'" class="btn btn-primary" target="_blank" title="Créer"><span class="glyphicon glyphicon-plus"></span></a>';
|
buttons += '<a href="'+url_base+'?trigramme='+trigramme+'" class="btn btn-primary" target="_blank" title="Créer"><span class="glyphicon glyphicon-plus"></span></a>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
account_container.find('.buttons').html(buttons);
|
account_container.find('.buttons').html(buttons);
|
||||||
|
@ -504,17 +556,17 @@ $(document).ready(function() {
|
||||||
|
|
||||||
function addArticle(article) {
|
function addArticle(article) {
|
||||||
var article_html = $(article_default_html);
|
var article_html = $(article_default_html);
|
||||||
article_html.attr('data-article', article['id']);
|
article_html.attr('id', 'data-article-'+article['id']);
|
||||||
article_html.attr('data-category', article['category_id']);
|
article_html.addClass('data-category-'+article['category_id']);
|
||||||
for (var elem in article) {
|
for (var elem in article) {
|
||||||
article_html.find('.'+elem).text(article[elem])
|
article_html.find('.'+elem).text(article[elem])
|
||||||
}
|
}
|
||||||
article_html.find('.price').text(amountToUKF(article['price'], false));
|
article_html.find('.price').text(amountToUKF(article['price'], false));
|
||||||
var category_html = articles_container
|
var category_html = articles_container
|
||||||
.find('.category[data-category='+article['category_id']+']');
|
.find('#data-category-'+article['category_id']);
|
||||||
if (category_html.length == 0) {
|
if (category_html.length == 0) {
|
||||||
category_html = $(article_category_default_html);
|
category_html = $(article_category_default_html);
|
||||||
category_html.attr('data-category', article['category_id']);
|
category_html.attr('id', 'data-category-'+article['category_id']);
|
||||||
category_html.find('td').text(article['category__name']);
|
category_html.find('td').text(article['category__name']);
|
||||||
var added = false;
|
var added = false;
|
||||||
articles_container.find('.category').each(function() {
|
articles_container.find('.category').each(function() {
|
||||||
|
@ -526,16 +578,14 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
if (!added) articles_container.append(category_html);
|
if (!added) articles_container.append(category_html);
|
||||||
}
|
}
|
||||||
var added = false;
|
var $after = articles_container.find('#data-category-'+article['category_id']);
|
||||||
articles_container
|
articles_container
|
||||||
.find('.article[data-category='+article['category_id']+']').each(function() {
|
.find('.article.data-category-'+article['category_id']).each(function() {
|
||||||
if (article['name'].toLowerCase < $('.name', this).text().toLowerCase()) {
|
if (article['name'].toLowerCase < $('.name', this).text().toLowerCase())
|
||||||
$(this).before(article_html);
|
|
||||||
added = true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
$after = $(this);
|
||||||
});
|
});
|
||||||
if (!added) articles_container.find('.category[data-category='+article['category_id']+']').after(article_html);
|
$after.after(article_html);
|
||||||
// Pour l'autocomplétion
|
// Pour l'autocomplétion
|
||||||
articlesList.push([article['name'],article['id'],article['category_id'],article['price']]);
|
articlesList.push([article['name'],article['id'],article['category_id'],article['price']]);
|
||||||
}
|
}
|
||||||
|
@ -597,17 +647,17 @@ $(document).ready(function() {
|
||||||
var categories_to_display = [];
|
var categories_to_display = [];
|
||||||
for (var i=0; i<articlesList.length; i++) {
|
for (var i=0; i<articlesList.length; i++) {
|
||||||
if (array.indexOf(articlesList[i]) > -1) {
|
if (array.indexOf(articlesList[i]) > -1) {
|
||||||
articles_container.find('[data-article='+articlesList[i][1]+']').show();
|
articles_container.find('#data-article-'+articlesList[i][1]).show();
|
||||||
if (categories_to_display.indexOf(articlesList[i][2]) == -1)
|
if (categories_to_display.indexOf(articlesList[i][2]) == -1)
|
||||||
categories_to_display.push(articlesList[i][2]);
|
categories_to_display.push(articlesList[i][2]);
|
||||||
} else {
|
} else {
|
||||||
articles_container.find('[data-article='+articlesList[i][1]+']').hide();
|
articles_container.find('#data-article-'+articlesList[i][1]).hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
articles_container.find('.category').hide();
|
articles_container.find('.category').hide();
|
||||||
for (var i=0; i<categories_to_display.length; i++) {
|
for (var i=0; i<categories_to_display.length; i++) {
|
||||||
articles_container
|
articles_container
|
||||||
.find('.category[data-category='+categories_to_display[i]+']')
|
.find('#data-category-'+categories_to_display[i])
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -635,7 +685,13 @@ $(document).ready(function() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
articleSelect.on('keypress', function(e) {
|
// A utiliser après la sélection d'un article
|
||||||
|
function goToArticleNb() {
|
||||||
|
articleNb.val('1');
|
||||||
|
articleNb.focus().select();
|
||||||
|
}
|
||||||
|
|
||||||
|
articleSelect.on('keydown', function(e) {
|
||||||
var text = articleSelect.val();
|
var text = articleSelect.val();
|
||||||
// Comportement normal pour ces touches
|
// Comportement normal pour ces touches
|
||||||
if (normalKeys.test(e.keyCode) || e.ctrlKey) {
|
if (normalKeys.test(e.keyCode) || e.ctrlKey) {
|
||||||
|
@ -648,16 +704,28 @@ $(document).ready(function() {
|
||||||
articleSelect.val('');
|
articleSelect.val('');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (e.charCode !== 0) {
|
|
||||||
if (updateMatchedArticles(text+e.key)) {
|
|
||||||
articleNb.val('1');
|
|
||||||
articleNb.focus().select();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (updateMatchedArticles(text+e.key))
|
||||||
|
goToArticleNb();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getArticleId($article) {
|
||||||
|
return $article.attr('id').split('-')[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArticleName($article) {
|
||||||
|
return $article.find('.name').text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sélection des articles à la souris/tactile
|
||||||
|
articles_container.on('click', '.article', function() {
|
||||||
|
articleId.val(getArticleId($(this)));
|
||||||
|
articleSelect.val(getArticleName($(this)));
|
||||||
|
displayMatchedArticles(articlesList);
|
||||||
|
goToArticleNb();
|
||||||
|
});
|
||||||
|
|
||||||
function is_nb_ok(nb) {
|
function is_nb_ok(nb) {
|
||||||
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24;
|
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24;
|
||||||
}
|
}
|
||||||
|
@ -1088,7 +1156,9 @@ $(document).ready(function() {
|
||||||
websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}
|
websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}
|
||||||
|
|
||||||
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
||||||
socket = new ReconnectingWebSocket(websocket_protocol+"://" + window.location.host + "/ws/k-fet/k-psul/");
|
var location_host = window.location.host;
|
||||||
|
var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host;
|
||||||
|
socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + "/ws/k-fet/k-psul/");
|
||||||
socket.onmessage = function(e) {
|
socket.onmessage = function(e) {
|
||||||
data = $.extend({}, websocket_msg_default, JSON.parse(e.data));
|
data = $.extend({}, websocket_msg_default, JSON.parse(e.data));
|
||||||
|
|
||||||
|
@ -1139,7 +1209,7 @@ $(document).ready(function() {
|
||||||
|
|
||||||
function hardReset(give_tri_focus=true) {
|
function hardReset(give_tri_focus=true) {
|
||||||
coolReset(give_tri_focus);
|
coolReset(give_tri_focus);
|
||||||
resetCheckout();
|
checkoutInput.trigger('change');
|
||||||
resetArticles();
|
resetArticles();
|
||||||
khistory.reset();
|
khistory.reset();
|
||||||
resetSettings();
|
resetSettings();
|
||||||
|
@ -1163,6 +1233,17 @@ $(document).ready(function() {
|
||||||
|
|
||||||
$(document).on('keydown', function(e) {
|
$(document).on('keydown', function(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
|
case 27:
|
||||||
|
// Escape - Hide help
|
||||||
|
$('.help').hide('fast');
|
||||||
|
return false;
|
||||||
|
case 72:
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
// Ctrl+H - Display help
|
||||||
|
e.preventDefault();
|
||||||
|
$('.help').show('fast');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case 112:
|
case 112:
|
||||||
// F1 - Cool reset
|
// F1 - Cool reset
|
||||||
coolReset();
|
coolReset();
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Transferts{% endblock %}
|
{% block title %}Transferts{% endblock %}
|
||||||
{% block content-header-title %}Transferts{% endblock %}
|
{% block content-header-title %}Transferts{% endblock %}
|
||||||
|
@ -24,13 +33,13 @@
|
||||||
<h2>Liste des transferts</h2>
|
<h2>Liste des transferts</h2>
|
||||||
<div id="history">
|
<div id="history">
|
||||||
{% for transfergroup in transfergroups %}
|
{% for transfergroup in transfergroups %}
|
||||||
<div class="opegroup">
|
<div class="opegroup transfergroup" data-transfergroup="{{ transfergroup.pk }}">
|
||||||
<span>{{ transfergroup.at }}</span>
|
<span>{{ transfergroup.at }}</span>
|
||||||
<span>{{ transfergroup.valid_by.trigramme }}</span>
|
<span>{{ transfergroup.valid_by.trigramme }}</span>
|
||||||
<span>{{ transfergroup.comment }}</span>
|
<span>{{ transfergroup.comment }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% for transfer in transfergroup.transfers.all %}
|
{% for transfer in transfergroup.transfers.all %}
|
||||||
<div class="ope transfer">
|
<div class="ope transfer{% if transfer.canceled_at %} canceled{% endif %}" data-transfer="{{ transfer.pk }}" data-transfergroup="{{ transfergroup.pk }}">
|
||||||
<span class="amount">{{ transfer.amount }} €</span>
|
<span class="amount">{{ transfer.amount }} €</span>
|
||||||
<span class="from_acc">{{ transfer.from_acc.trigramme }}</span>
|
<span class="from_acc">{{ transfer.from_acc.trigramme }}</span>
|
||||||
<span class="glyphicon glyphicon-arrow-right"></span>
|
<span class="glyphicon glyphicon-arrow-right"></span>
|
||||||
|
@ -44,4 +53,93 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
lock = 0;
|
||||||
|
|
||||||
|
function displayErrors(html) {
|
||||||
|
$.alert({
|
||||||
|
title: 'Erreurs',
|
||||||
|
content: html,
|
||||||
|
backgroundDismiss: true,
|
||||||
|
animation: 'top',
|
||||||
|
closeAnimation: 'bottom',
|
||||||
|
keyboardEnabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cancelTransfers(transfers_array, password = '') {
|
||||||
|
if (lock == 1)
|
||||||
|
return false
|
||||||
|
lock = 1;
|
||||||
|
var data = { 'transfers' : transfers_array }
|
||||||
|
$.ajax({
|
||||||
|
dataType: "json",
|
||||||
|
url : "{% url 'kfet.transfers.cancel' %}",
|
||||||
|
method : "POST",
|
||||||
|
data : data,
|
||||||
|
beforeSend: function ($xhr) {
|
||||||
|
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
|
if (password != '')
|
||||||
|
$xhr.setRequestHeader("KFetPassword", password);
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
for (var i=0; i<data['canceled'].length; i++) {
|
||||||
|
$('#history').find('.transfer[data-transfer='+data['canceled'][i]+']')
|
||||||
|
.addClass('canceled');
|
||||||
|
}
|
||||||
|
$('#history').find('.ui-selected').removeClass('ui-selected');
|
||||||
|
lock = 0;
|
||||||
|
})
|
||||||
|
.fail(function($xhr) {
|
||||||
|
var data = $xhr.responseJSON;
|
||||||
|
switch ($xhr.status) {
|
||||||
|
case 403:
|
||||||
|
requestAuth(data, function(password) {
|
||||||
|
cancelTransfers(transfers_array, password);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
displayErrors(getErrorsHtml(data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lock = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#history').selectable({
|
||||||
|
filter: 'div.transfergroup, div.transfer',
|
||||||
|
selected: function(e, ui) {
|
||||||
|
$(ui.selected).each(function() {
|
||||||
|
if ($(this).hasClass('transfergroup')) {
|
||||||
|
var transfergroup = $(this).attr('data-transfergroup');
|
||||||
|
$(this).siblings('.ope').filter(function() {
|
||||||
|
return $(this).attr('data-transfergroup') == transfergroup
|
||||||
|
}).addClass('ui-selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('keydown', function (e) {
|
||||||
|
if (e.keyCode == 46) {
|
||||||
|
// DEL (Suppr)
|
||||||
|
var transfers_to_cancel = [];
|
||||||
|
$('#history').find('.transfer.ui-selected').each(function () {
|
||||||
|
transfers_to_cancel.push($(this).attr('data-transfer'));
|
||||||
|
});
|
||||||
|
if (transfers_to_cancel.length > 0)
|
||||||
|
cancelTransfers(transfers_to_cancel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -170,6 +170,8 @@ urlpatterns = [
|
||||||
name = 'kfet.transfers.create'),
|
name = 'kfet.transfers.create'),
|
||||||
url(r'^transfers/perform$', views.perform_transfers,
|
url(r'^transfers/perform$', views.perform_transfers,
|
||||||
name = 'kfet.transfers.perform'),
|
name = 'kfet.transfers.perform'),
|
||||||
|
url(r'^transfers/cancel$', views.cancel_transfers,
|
||||||
|
name = 'kfet.transfers.cancel'),
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Inventories urls
|
# Inventories urls
|
||||||
|
|
158
kfet/views.py
158
kfet/views.py
|
@ -219,7 +219,7 @@ def account_form_set_readonly_fields(user_form, cof_form):
|
||||||
def get_account_create_forms(request=None, username=None, login_clipper=None):
|
def get_account_create_forms(request=None, username=None, login_clipper=None):
|
||||||
user = None
|
user = None
|
||||||
clipper = None
|
clipper = None
|
||||||
if login_clipper and not username:
|
if login_clipper and (login_clipper == username or not username):
|
||||||
# à partir d'un clipper
|
# à partir d'un clipper
|
||||||
# le user associé à ce clipper ne devrait pas encore exister
|
# le user associé à ce clipper ne devrait pas encore exister
|
||||||
clipper = get_object_or_404(Clipper, username = login_clipper)
|
clipper = get_object_or_404(Clipper, username = login_clipper)
|
||||||
|
@ -399,7 +399,7 @@ def account_update(request, trigramme):
|
||||||
if (request.user.has_perm('kfet.change_account_password')
|
if (request.user.has_perm('kfet.change_account_password')
|
||||||
and pwd_form.is_valid()):
|
and pwd_form.is_valid()):
|
||||||
pwd = pwd_form.cleaned_data['pwd1']
|
pwd = pwd_form.cleaned_data['pwd1']
|
||||||
pwd_sha256 = hashlib.sha256(pwd.encode()).hexdigest()
|
pwd_sha256 = hashlib.sha256(pwd.encode('utf-8')).hexdigest()
|
||||||
Account.objects.filter(pk=account.pk).update(
|
Account.objects.filter(pk=account.pk).update(
|
||||||
password = pwd_sha256)
|
password = pwd_sha256)
|
||||||
messages.success(request, 'Mot de passe mis à jour')
|
messages.success(request, 'Mot de passe mis à jour')
|
||||||
|
@ -796,7 +796,15 @@ def kpsul(request):
|
||||||
data = {}
|
data = {}
|
||||||
data['operationgroup_form'] = KPsulOperationGroupForm()
|
data['operationgroup_form'] = KPsulOperationGroupForm()
|
||||||
data['trigramme_form'] = KPsulAccountForm()
|
data['trigramme_form'] = KPsulAccountForm()
|
||||||
data['checkout_form'] = KPsulCheckoutForm()
|
initial = {}
|
||||||
|
try:
|
||||||
|
checkout = Checkout.objects.filter(
|
||||||
|
is_protected=False, valid_from__lte=timezone.now(),
|
||||||
|
valid_to__gte=timezone.now()).get()
|
||||||
|
initial['checkout'] = checkout
|
||||||
|
except (Checkout.DoesNotExist, Checkout.MultipleObjectsReturned):
|
||||||
|
pass
|
||||||
|
data['checkout_form'] = KPsulCheckoutForm(initial=initial)
|
||||||
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
|
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
|
||||||
data['operation_formset'] = operation_formset
|
data['operation_formset'] = operation_formset
|
||||||
return render(request, 'kfet/kpsul.html', data)
|
return render(request, 'kfet/kpsul.html', data)
|
||||||
|
@ -827,28 +835,27 @@ def kpsul_checkout_data(request):
|
||||||
pk = request.POST.get('pk', 0)
|
pk = request.POST.get('pk', 0)
|
||||||
if not pk:
|
if not pk:
|
||||||
pk = 0
|
pk = 0
|
||||||
try:
|
data = (Checkout.objects
|
||||||
data = (Checkout.objects
|
.annotate(
|
||||||
.annotate(
|
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
|
||||||
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
|
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
|
||||||
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
|
last_statement_by_trigramme=F('statements__by__trigramme'),
|
||||||
last_statement_by_trigramme=F('statements__by__trigramme'),
|
last_statement_balance=F('statements__balance_new'),
|
||||||
last_statement_balance=F('statements__balance_new'),
|
last_statement_at=F('statements__at'))
|
||||||
last_statement_at=F('statements__at'))
|
.values(
|
||||||
.values(
|
'id', 'name', 'balance', 'valid_from', 'valid_to',
|
||||||
'id', 'name', 'balance', 'valid_from', 'valid_to',
|
'last_statement_balance', 'last_statement_at',
|
||||||
'last_statement_balance', 'last_statement_at',
|
'last_statement_by_trigramme', 'last_statement_by_last_name',
|
||||||
'last_statement_by_trigramme', 'last_statement_by_last_name',
|
'last_statement_by_first_name')
|
||||||
'last_statement_by_first_name')
|
.select_related(
|
||||||
.select_related(
|
'statements'
|
||||||
'statements'
|
'statements__by',
|
||||||
'statements__by',
|
'statements__by__cofprofile__user')
|
||||||
'statements__by__cofprofile__user')
|
.filter(pk=pk)
|
||||||
.filter(pk=pk)
|
.order_by('statements__at')
|
||||||
.order_by('statements__at')
|
.last())
|
||||||
.last())
|
if data is None:
|
||||||
except Checkout.DoesNotExist:
|
raise Http404
|
||||||
raise http404
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
|
@ -1402,7 +1409,7 @@ def perform_transfers(request):
|
||||||
transfers = transfer_formset.save(commit = False)
|
transfers = transfer_formset.save(commit = False)
|
||||||
|
|
||||||
# Initializing vars
|
# Initializing vars
|
||||||
required_perms = set('kfet.add_transfer') # Required perms to perform all transfers
|
required_perms = set(['kfet.add_transfer']) # Required perms to perform all transfers
|
||||||
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
|
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
|
||||||
|
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
|
@ -1468,6 +1475,105 @@ def perform_transfers(request):
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
@teamkfet_required
|
||||||
|
def cancel_transfers(request):
|
||||||
|
# Pour la réponse
|
||||||
|
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
||||||
|
|
||||||
|
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
||||||
|
try:
|
||||||
|
# Set pour virer les doublons
|
||||||
|
transfers_post = set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
|
||||||
|
except ValueError:
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
transfers_all = (
|
||||||
|
Transfer.objects
|
||||||
|
.select_related('group', 'from_acc', 'from_acc__negative',
|
||||||
|
'to_acc', 'to_acc__negative')
|
||||||
|
.filter(pk__in=transfers_post))
|
||||||
|
transfers_pk = [ transfer.pk for transfer in transfers_all ]
|
||||||
|
transfers_notexisting = [ transfer for transfer in transfers_post
|
||||||
|
if transfer not in transfers_pk ]
|
||||||
|
if transfers_notexisting:
|
||||||
|
data['errors']['transfers_notexisting'] = transfers_notexisting
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
|
transfers_already_canceled = [] # Déjà annulée
|
||||||
|
transfers = [] # Pas déjà annulée
|
||||||
|
required_perms = set()
|
||||||
|
stop_all = False
|
||||||
|
cancel_duration = Settings.CANCEL_DURATION()
|
||||||
|
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
||||||
|
for transfer in transfers_all:
|
||||||
|
if transfer.canceled_at:
|
||||||
|
# Transfert déjà annulé, va pour un warning en Response
|
||||||
|
transfers_already_canceled.append(transfer.pk)
|
||||||
|
else:
|
||||||
|
transfers.append(transfer.pk)
|
||||||
|
# Si transfer il y a plus de CANCEL_DURATION, permission requise
|
||||||
|
if transfer.group.at + cancel_duration < timezone.now():
|
||||||
|
required_perms.add('kfet.cancel_old_operations')
|
||||||
|
|
||||||
|
# Calcul de toutes modifs à faire en cas de validation
|
||||||
|
|
||||||
|
# Pour les balances de comptes
|
||||||
|
to_accounts_balances[transfer.from_acc] += transfer.amount
|
||||||
|
to_accounts_balances[transfer.to_acc] += -transfer.amount
|
||||||
|
|
||||||
|
if not transfers:
|
||||||
|
data['warnings']['already_canceled'] = transfers_already_canceled
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
negative_accounts = []
|
||||||
|
# Checking permissions or stop
|
||||||
|
for account in to_accounts_balances:
|
||||||
|
(perms, stop) = account.perms_to_perform_operation(
|
||||||
|
amount = to_accounts_balances[account])
|
||||||
|
required_perms |= perms
|
||||||
|
stop_all = stop_all or stop
|
||||||
|
if stop:
|
||||||
|
negative_accounts.append(account.trigramme)
|
||||||
|
|
||||||
|
print(required_perms)
|
||||||
|
print(request.user.get_all_permissions())
|
||||||
|
|
||||||
|
if stop_all or not request.user.has_perms(required_perms):
|
||||||
|
missing_perms = get_missing_perms(required_perms, request.user)
|
||||||
|
if missing_perms:
|
||||||
|
data['errors']['missing_perms'] = missing_perms
|
||||||
|
if stop_all:
|
||||||
|
data['errors']['negative'] = negative_accounts
|
||||||
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||||
|
canceled_at = timezone.now()
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
(Transfer.objects.filter(pk__in=transfers)
|
||||||
|
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||||
|
|
||||||
|
for account in to_accounts_balances:
|
||||||
|
Account.objects.filter(pk=account.pk).update(
|
||||||
|
balance = F('balance') + to_accounts_balances[account])
|
||||||
|
account.refresh_from_db()
|
||||||
|
if account.balance < 0:
|
||||||
|
if hasattr(account, 'negative'):
|
||||||
|
if not account.negative.start:
|
||||||
|
account.negative.start = timezone.now()
|
||||||
|
account.negative.save()
|
||||||
|
else:
|
||||||
|
negative = AccountNegative(
|
||||||
|
account = account, start = timezone.now())
|
||||||
|
negative.save()
|
||||||
|
elif (hasattr(account, 'negative')
|
||||||
|
and not account.negative.balance_offset):
|
||||||
|
account.negative.delete()
|
||||||
|
|
||||||
|
data['canceled'] = transfers
|
||||||
|
if transfers_already_canceled:
|
||||||
|
data['warnings']['already_canceled'] = transfers_already_canceled
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
class InventoryList(ListView):
|
class InventoryList(ListView):
|
||||||
queryset = (Inventory.objects
|
queryset = (Inventory.objects
|
||||||
.select_related('by', 'order')
|
.select_related('by', 'order')
|
||||||
|
|
29
provisioning/apache.conf
Normal file
29
provisioning/apache.conf
Normal 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
|
|
@ -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,9 +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
|
||||||
redis-cli config set requirepass redis_password
|
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
|
||||||
|
@ -54,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
|
||||||
|
|
|
@ -7,3 +7,4 @@ DBNAME="cof_gestion"
|
||||||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
|
||||||
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
||||||
|
*/5 * * * * python /vagrant/manage.py manage_revente >> /vagrant/reventes.log 2>&1
|
||||||
|
|
|
@ -14,3 +14,14 @@ envoyés).
|
||||||
- Garde les logs peut être une bonne idée.
|
- Garde les logs peut être une bonne idée.
|
||||||
|
|
||||||
Exemple : voir le fichier `provisioning/cron.dev`.
|
Exemple : voir le fichier `provisioning/cron.dev`.
|
||||||
|
|
||||||
|
## Gestion des mails de revente
|
||||||
|
|
||||||
|
Il faut effectuer très régulièrement la commande `manage_reventes` de GestioCOF,
|
||||||
|
qui gère toutes les actions associées à BdA-Revente : envoi des mails de notification,
|
||||||
|
tirages.
|
||||||
|
|
||||||
|
- Pour l'instant un délai de 5 min est hardcodé
|
||||||
|
- Garde des logs ; ils vont finir par être assez lourds si on a beaucoup de reventes.
|
||||||
|
|
||||||
|
Exemple : provisioning/cron.dev
|
||||||
|
|
|
@ -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
|
||||||
|
|
20
provisioning/supervisor.conf
Normal file
20
provisioning/supervisor.conf
Normal 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
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue