diff --git a/.gitignore b/.gitignore
index 438dfbcd..c65c2cc3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ re2o.png
__pycache__/*
static_files/*
static/logo/*
+media/*
diff --git a/cotisations/forms.py b/cotisations/forms.py
index 5845611a..c4e02397 100644
--- a/cotisations/forms.py
+++ b/cotisations/forms.py
@@ -26,9 +26,8 @@ importé par les views.
Permet de créer une nouvelle facture pour un user (NewFactureForm),
et de l'editer (soit l'user avec EditFactureForm,
soit le trésorier avec TrezEdit qui a plus de possibilités que self
-notamment sur le controle trésorier)
-
-SelectArticleForm est utilisée lors de la creation d'une facture en
+notamment sur le controle trésorier SelectArticleForm est utilisée
+lors de la creation d'une facture en
parrallèle de NewFacture pour le choix des articles désirés.
(la vue correspondante est unique)
@@ -40,8 +39,10 @@ from __future__ import unicode_literals
from django import forms
from django.db.models import Q
from django.forms import ModelForm, Form
-from django.core.validators import MinValueValidator
+from django.core.validators import MinValueValidator,MaxValueValidator
from .models import Article, Paiement, Facture, Banque
+from preferences.models import OptionalUser
+from users.models import User
from re2o.field_permissions import FieldPermissionFormMixin
@@ -246,3 +247,58 @@ class DelBanqueForm(Form):
self.fields['banques'].queryset = instances
else:
self.fields['banques'].queryset = Banque.objects.all()
+
+
+class NewFactureSoldeForm(NewFactureForm):
+ """Creation d'une facture, moyen de paiement, banque et numero
+ de cheque"""
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ self.fields['cheque'].required = False
+ self.fields['banque'].required = False
+ self.fields['cheque'].label = 'Numero de chèque'
+ self.fields['banque'].empty_label = "Non renseigné"
+ self.fields['paiement'].empty_label = "Séléctionner\
+ une bite de paiement"
+ paiement_list = Paiement.objects.filter(type_paiement=1)
+ if paiement_list:
+ self.fields['paiement'].widget\
+ .attrs['data-cheque'] = paiement_list.first().id
+
+ class Meta:
+ model = Facture
+ fields = ['paiement', 'banque']
+
+
+ def clean(self):
+ cleaned_data = super(NewFactureSoldeForm, self).clean()
+ paiement = cleaned_data.get("paiement")
+ cheque = cleaned_data.get("cheque")
+ banque = cleaned_data.get("banque")
+ if not paiement:
+ raise forms.ValidationError("Le moyen de paiement est obligatoire")
+ elif paiement.type_paiement == "check" and not (cheque and banque):
+ raise forms.ValidationError("Le numéro de chèque et\
+ la banque sont obligatoires.")
+ return cleaned_data
+
+
+class RechargeForm(Form):
+ value = forms.FloatField(
+ label='Valeur',
+ min_value=0.01,
+ validators = []
+ )
+
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop('user')
+ super(RechargeForm, self).__init__(*args, **kwargs)
+
+ def clean_value(self):
+ value = self.cleaned_data['value']
+ options, _created = OptionalUser.objects.get_or_create()
+ if value < options.min_online_payment:
+ raise forms.ValidationError("Montant inférieur au montant minimal de paiement en ligne (%s) €" % options.min_online_payment)
+ if value + self.user.solde > options.max_solde:
+ raise forms.ValidationError("Le solde ne peux excéder %s " % options.max_solde)
+ return value
diff --git a/cotisations/models.py b/cotisations/models.py
index a775237c..07c3b8fa 100644
--- a/cotisations/models.py
+++ b/cotisations/models.py
@@ -289,7 +289,7 @@ class Vente(models.Model):
if not user_request.has_perm('cotisations.change_vente'):
return False, u"Vous n'avez pas le droit d'éditer les ventes"
elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]:
- return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
+ return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
elif not user_request.has_perm('cotisations.change_all_vente') and\
(self.facture.control or not self.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une vente\
diff --git a/cotisations/payment.py b/cotisations/payment.py
new file mode 100644
index 00000000..07cbe5dc
--- /dev/null
+++ b/cotisations/payment.py
@@ -0,0 +1,113 @@
+"""Payment
+
+Here are defined some views dedicated to online payement.
+"""
+from django.urls import reverse
+from django.shortcuts import redirect, get_object_or_404
+from django.contrib.auth.decorators import login_required
+from django.contrib import messages
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.datastructures import MultiValueDictKeyError
+from django.http import HttpResponse, HttpResponseBadRequest
+
+from collections import OrderedDict
+
+from preferences.models import AssoOption
+from .models import Facture
+from .payment_utils.comnpay import Payment as ComnpayPayment
+
+@csrf_exempt
+@login_required
+def accept_payment(request, factureid):
+ facture = get_object_or_404(Facture, id=factureid)
+ messages.success(
+ request,
+ "Le paiement de {} € a été accepté.".format(facture.prix())
+ )
+ return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
+
+
+@csrf_exempt
+@login_required
+def refuse_payment(request):
+ messages.error(
+ request,
+ "Le paiement a été refusé."
+ )
+ return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
+
+@csrf_exempt
+def ipn(request):
+ option, _created = AssoOption.objects.get_or_create()
+ p = ComnpayPayment()
+ order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', )
+ try:
+ data = OrderedDict([(f, request.POST[f]) for f in order])
+ except MultiValueDictKeyError:
+ return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
+
+ if not p.validSec(data, option.payment_pass):
+ return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
+
+ result = True if (request.POST['result'] == 'OK') else False
+ idTpe = request.POST['idTpe']
+ idTransaction = request.POST['idTransaction']
+
+ # On vérifie que le paiement nous est destiné
+ if not idTpe == option.payment_id:
+ return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
+
+ try:
+ factureid = int(idTransaction)
+ except ValueError:
+ return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
+
+ facture = get_object_or_404(Facture, id=factureid)
+
+ # On vérifie que le paiement est valide
+ if not result:
+ # Le paiement a échoué : on effectue les actions nécessaires (On indique qu'elle a échoué)
+ facture.delete()
+
+ # On notifie au serveur ComNPay qu'on a reçu les données pour traitement
+ return HttpResponse("HTTP/1.1 200 OK")
+
+ facture.valid = True
+ facture.save()
+
+ # A nouveau, on notifie au serveur qu'on a bien traité les données
+ return HttpResponse("HTTP/1.0 200 OK")
+
+
+def comnpay(facture, request):
+ host = request.get_host()
+ option, _created = AssoOption.objects.get_or_create()
+ p = ComnpayPayment(
+ str(option.payment_id),
+ str(option.payment_pass),
+ 'https://' + host + reverse(
+ 'cotisations:accept_payment',
+ kwargs={'factureid':facture.id}
+ ),
+ 'https://' + host + reverse('cotisations:refuse_payment'),
+ 'https://' + host + reverse('cotisations:ipn'),
+ "",
+ "D"
+ )
+ r = {
+ 'action' : 'https://secure.homologation.comnpay.com',
+ 'method' : 'POST',
+ 'content' : p.buildSecretHTML(
+ "Rechargement du solde",
+ facture.prix(),
+ idTransaction=str(facture.id)
+ ),
+ 'amount' : facture.prix,
+ }
+ return r
+
+
+PAYMENT_SYSTEM = {
+ 'COMNPAY' : comnpay,
+ 'NONE' : None
+}
diff --git a/cotisations/payment_utils/__init__.py b/cotisations/payment_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/cotisations/payment_utils/comnpay.py b/cotisations/payment_utils/comnpay.py
new file mode 100644
index 00000000..6c1701d3
--- /dev/null
+++ b/cotisations/payment_utils/comnpay.py
@@ -0,0 +1,68 @@
+import time
+from random import randrange
+import base64
+import hashlib
+from collections import OrderedDict
+from itertools import chain
+
+class Payment():
+
+ vad_number = ""
+ secret_key = ""
+ urlRetourOK = ""
+ urlRetourNOK = ""
+ urlIPN = ""
+ source = ""
+ typeTr = "D"
+
+ def __init__(self, vad_number = "", secret_key = "", urlRetourOK = "", urlRetourNOK = "", urlIPN = "", source="", typeTr="D"):
+ self.vad_number = vad_number
+ self.secret_key = secret_key
+ self.urlRetourOK = urlRetourOK
+ self.urlRetourNOK = urlRetourNOK
+ self.urlIPN = urlIPN
+ self.source = source
+ self.typeTr = typeTr
+
+ def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""):
+ if idTransaction == "":
+ self.idTransaction = str(time.time())+self.vad_number+str(randrange(999))
+ else:
+ self.idTransaction = idTransaction
+
+ array_tpe = OrderedDict(
+ montant= str(montant),
+ idTPE= self.vad_number,
+ idTransaction= self.idTransaction,
+ devise= "EUR",
+ lang= 'fr',
+ nom_produit= produit,
+ source= self.source,
+ urlRetourOK= self.urlRetourOK,
+ urlRetourNOK= self.urlRetourNOK,
+ typeTr= str(self.typeTr)
+ )
+
+ if self.urlIPN!="":
+ array_tpe['urlIPN'] = self.urlIPN
+
+ array_tpe['key'] = self.secret_key;
+ strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8'))
+ del array_tpe["key"]
+ array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest()
+
+ ret = ""
+ for key in array_tpe:
+ ret += ''
+
+ return ret
+
+ def validSec(self, values, secret_key):
+ if "sec" in values:
+ sec = values['sec']
+ del values["sec"]
+ strWithKey = hashlib.sha512(base64.b64encode(bytes('|'.join(values.values()) +"|"+secret_key, 'utf-8'))).hexdigest()
+ return strWithKey.upper() == sec.upper()
+ else:
+ return False
+
diff --git a/cotisations/templates/cotisations/new_facture.html b/cotisations/templates/cotisations/new_facture.html
index f2586e8b..9d466cee 100644
--- a/cotisations/templates/cotisations/new_facture.html
+++ b/cotisations/templates/cotisations/new_facture.html
@@ -34,6 +34,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+
+
+
+{% endblock %}
+
diff --git a/cotisations/templates/cotisations/payment.html b/cotisations/templates/cotisations/payment.html
new file mode 100644
index 00000000..46f26784
--- /dev/null
+++ b/cotisations/templates/cotisations/payment.html
@@ -0,0 +1,37 @@
+{% extends "cotisations/sidebar.html" %}
+{% comment %}
+Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+se veut agnostique au réseau considéré, de manière à être installable en
+quelques clics.
+
+Copyright © 2017 Gabriel Détraz
+Copyright © 2017 Goulven Kermarec
+Copyright © 2017 Augustin Lemesle
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+{% endcomment %}
+
+{% load bootstrap3 %}
+{% load staticfiles%}
+
+{% block title %}Rechargement du solde{% endblock %}
+
+{% block content %}
+Recharger de {{ amount }} €
+
+{% endblock %}
diff --git a/cotisations/templates/cotisations/recharge.html b/cotisations/templates/cotisations/recharge.html
new file mode 100644
index 00000000..d38b7614
--- /dev/null
+++ b/cotisations/templates/cotisations/recharge.html
@@ -0,0 +1,39 @@
+{% extends "cotisations/sidebar.html" %}
+{% comment %}
+Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+se veut agnostique au réseau considéré, de manière à être installable en
+quelques clics.
+
+Copyright © 2017 Gabriel Détraz
+Copyright © 2017 Goulven Kermarec
+Copyright © 2017 Augustin Lemesle
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+{% endcomment %}
+
+{% load bootstrap3 %}
+{% load staticfiles%}
+
+{% block title %}Rechargement du solde{% endblock %}
+
+{% block content %}
+Rechargement du solde
+Solde : {{ request.user.solde }} €
+
+{% endblock %}
diff --git a/cotisations/urls.py b/cotisations/urls.py
index 2a0c0163..0040e48c 100644
--- a/cotisations/urls.py
+++ b/cotisations/urls.py
@@ -26,6 +26,7 @@ from django.conf.urls import url
import re2o
from . import views
+from . import payment
urlpatterns = [
url(r'^new_facture/(?P[0-9]+)$',
@@ -110,5 +111,25 @@ urlpatterns = [
views.control,
name='control'
),
+ url(r'^new_facture_solde/(?P[0-9]+)$',
+ views.new_facture_solde,
+ name='new_facture_solde'
+ ),
+ url(r'^recharge/$',
+ views.recharge,
+ name='recharge'
+ ),
+ url(r'^payment/accept/(?P[0-9]+)$',
+ payment.accept_payment,
+ name='accept_payment'
+ ),
+ url(r'^payment/refuse/$',
+ payment.refuse_payment,
+ name='refuse_payment'
+ ),
+ url(r'^payment/ipn/$',
+ payment.ipn,
+ name='ipn'
+ ),
url(r'^$', views.index, name='index'),
]
diff --git a/cotisations/views.py b/cotisations/views.py
index d7d953c9..16d25295 100644
--- a/cotisations/views.py
+++ b/cotisations/views.py
@@ -29,6 +29,7 @@ import os
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+from django.core.validators import MaxValueValidator
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.db.models import ProtectedError
@@ -36,6 +37,8 @@ from django.db import transaction
from django.db.models import Q
from django.forms import modelformset_factory, formset_factory
from django.utils import timezone
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.debug import sensitive_variables
from reversion import revisions as reversion
from reversion.models import Version
# Import des models, forms et fonctions re2o
@@ -67,8 +70,11 @@ from .forms import (
NewFactureFormPdf,
SelectUserArticleForm,
SelectClubArticleForm,
- CreditSoldeForm
+ CreditSoldeForm,
+ NewFactureSoldeForm,
+ RechargeForm
)
+from . import payment
from .tex import render_invoice
@@ -584,3 +590,127 @@ def index(request):
return render(request, 'cotisations/index.html', {
'facture_list': facture_list
})
+
+
+@login_required
+def new_facture_solde(request, userid):
+ """Creation d'une facture pour un user. Renvoie la liste des articles
+ et crée des factures dans un formset. Utilise un peu de js coté template
+ pour ajouter des articles.
+ Parse les article et boucle dans le formset puis save les ventes,
+ enfin sauve la facture parente.
+ TODO : simplifier cette fonction, déplacer l'intelligence coté models
+ Facture et Vente."""
+ user = request.user
+ facture = Facture(user=user)
+ paiement, _created = Paiement.objects.get_or_create(moyen='Solde')
+ facture.paiement = paiement
+ # Le template a besoin de connaitre les articles pour le js
+ article_list = Article.objects.filter(
+ Q(type_user='All') | Q(type_user=request.user.class_name)
+ )
+ if request.user.is_class_club:
+ article_formset = formset_factory(SelectClubArticleForm)(request.POST or None)
+ else:
+ article_formset = formset_factory(SelectUserArticleForm)(request.POST or None)
+ if article_formset.is_valid():
+ articles = article_formset
+ # Si au moins un article est rempli
+ if any(art.cleaned_data for art in articles):
+ options, _created = OptionalUser.objects.get_or_create()
+ user_solde = options.user_solde
+ solde_negatif = options.solde_negatif
+ # Si on paye par solde, que l'option est activée,
+ # on vérifie que le négatif n'est pas atteint
+ if user_solde:
+ prix_total = 0
+ for art_item in articles:
+ if art_item.cleaned_data:
+ prix_total += art_item.cleaned_data['article']\
+ .prix*art_item.cleaned_data['quantity']
+ if float(user.solde) - float(prix_total) < solde_negatif:
+ messages.error(request, "Le solde est insuffisant pour\
+ effectuer l'opération")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': userid}
+ ))
+ with transaction.atomic(), reversion.create_revision():
+ facture.save()
+ reversion.set_user(request.user)
+ reversion.set_comment("Création")
+ for art_item in articles:
+ if art_item.cleaned_data:
+ article = art_item.cleaned_data['article']
+ quantity = art_item.cleaned_data['quantity']
+ new_vente = Vente.objects.create(
+ facture=facture,
+ name=article.name,
+ prix=article.prix,
+ type_cotisation=article.type_cotisation,
+ duration=article.duration,
+ number=quantity
+ )
+ with transaction.atomic(), reversion.create_revision():
+ new_vente.save()
+ reversion.set_user(request.user)
+ reversion.set_comment("Création")
+ if any(art_item.cleaned_data['article'].type_cotisation
+ for art_item in articles if art_item.cleaned_data):
+ messages.success(
+ request,
+ "La cotisation a été prolongée\
+ pour l'adhérent %s jusqu'au %s" % (
+ user.pseudo, user.end_adhesion()
+ )
+ )
+ else:
+ messages.success(request, "La facture a été crée")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': userid}
+ ))
+ messages.error(
+ request,
+ u"Il faut au moins un article valide pour créer une facture"
+ )
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': userid}
+ ))
+
+ return form({
+ 'venteform': article_formset,
+ 'articlelist': article_list
+ }, 'cotisations/new_facture_solde.html', request)
+
+
+@login_required
+def recharge(request):
+ options, _created = AssoOption.objects.get_or_create()
+ if options.payment == 'NONE':
+ messages.error(
+ request,
+ "Le paiement en ligne est désactivé."
+ )
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': request.user.id}
+ ))
+ f = RechargeForm(request.POST or None, user=request.user)
+ if f.is_valid():
+ facture = Facture(user=request.user)
+ paiement, _created = Paiement.objects.get_or_create(moyen='Rechargement en ligne')
+ facture.paiement = paiement
+ facture.valid = False
+ facture.save()
+ v = Vente.objects.create(
+ facture=facture,
+ name='solde',
+ prix=f.cleaned_data['value'],
+ number=1,
+ )
+ v.save()
+ content = payment.PAYMENT_SYSTEM[options.payment](facture, request)
+ return render(request, 'cotisations/payment.html', content)
+ return form({'rechargeform':f}, 'cotisations/recharge.html', request)
diff --git a/install_re2o.sh b/install_re2o.sh
index 5c39345b..e2fc60ac 100755
--- a/install_re2o.sh
+++ b/install_re2o.sh
@@ -28,8 +28,9 @@ setup_ldap() {
install_re2o_server() {
-
-
+echo "Installation de Re2o !
+Cet utilitaire va procéder à l'installation initiale de re2o. Le serveur présent doit être vierge.
+Preconfiguration..."
export DEBIAN_FRONTEND=noninteractive
@@ -107,7 +108,7 @@ clear
if [ $sql_is_local == 2 ]
-then
+then
TITLE="Login sql"
sql_login=$(dialog --title "$TITLE" \
--backtitle "$BACKTITLE" \
@@ -169,7 +170,7 @@ ldap_password=$(dialog --title "$TITLE" \
2>&1 >/dev/tty)
clear
if [ $ldap_is_local == 2 ]
-then
+then
TITLE="Cn ldap admin"
ldap_cn=$(dialog --title "$TITLE" \
--backtitle "$BACKTITLE" \
@@ -209,7 +210,7 @@ email_port=$(dialog --clear \
2>&1 >/dev/tty)
clear
if [ $ldap_is_local == 2 ]
-then
+then
TITLE="Cn ldap admin"
ldap_cn=$(dialog --title "$TITLE" \
--backtitle "$BACKTITLE" \
@@ -236,7 +237,7 @@ install_base=$(dialog --clear \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
-apt-get -y install python3-django python3-dateutil texlive-latex-base texlive-fonts-recommended python3-djangorestframework python3-django-reversion python3-pip libsasl2-dev libldap2-dev libssl-dev
+apt-get -y install python3-django python3-dateutil texlive-latex-base texlive-fonts-recommended python3-djangorestframework python3-django-reversion python3-pip libsasl2-dev libldap2-dev libssl-dev python3-crypto
pip3 install django-bootstrap3
pip3 install django-ldapdb
pip3 install django-macaddress
@@ -253,7 +254,7 @@ then
echo $mysql_command
while true; do
read -p "Continue (y/n)?" choice
- case "$choice" in
+ case "$choice" in
y|Y ) break;;
n|N ) exit;;
* ) echo "invalid";;
@@ -276,14 +277,14 @@ else
echo sudo -u postgres psql $pgsql_command3
while true; do
read -p "Continue (y/n)?" choice
- case "$choice" in
+ case "$choice" in
y|Y ) break;;
n|N ) exit;;
* ) echo "invalid";;
esac
done
fi
-fi
+fi
if [ $ldap_is_local == 1 ]
then
@@ -430,7 +431,7 @@ if [ ! -z "$1" ]
then
if [ $1 == ldap ]
then
-if [ ! -z "$2" ]
+if [ ! -z "$2" ]
then
echo Installation du ldap
setup_ldap $2 $3
diff --git a/machines/models.py b/machines/models.py
index 53e73ae6..0b6e2171 100644
--- a/machines/models.py
+++ b/machines/models.py
@@ -2271,3 +2271,4 @@ def srv_post_save(sender, **kwargs):
def text_post_delete(sender, **kwargs):
"""Regeneration dns après modification d'un SRV"""
regen('dns')
+
diff --git a/preferences/aes_field.py b/preferences/aes_field.py
new file mode 100644
index 00000000..81f8accc
--- /dev/null
+++ b/preferences/aes_field.py
@@ -0,0 +1,59 @@
+import string
+import binascii
+from random import choice
+from Crypto.Cipher import AES
+
+from django.db import models
+from django.conf import settings
+
+EOD = '`%EofD%`' # This should be something that will not occur in strings
+
+
+def genstring(length=16, chars=string.printable):
+ return ''.join([choice(chars) for i in range(length)])
+
+
+def encrypt(key, s):
+ obj = AES.new(key)
+ datalength = len(s) + len(EOD)
+ if datalength < 16:
+ saltlength = 16 - datalength
+ else:
+ saltlength = 16 - datalength % 16
+ ss = ''.join([s, EOD, genstring(saltlength)])
+ return obj.encrypt(ss)
+
+
+def decrypt(key, s):
+ obj = AES.new(key)
+ ss = obj.decrypt(s)
+ print(ss)
+ return ss.split(bytes(EOD, 'utf-8'))[0]
+
+
+class AESEncryptedField(models.CharField):
+ def save_form_data(self, instance, data):
+ if value is None:
+ return value
+ setattr(instance, self.name,
+ binascii.b2a_base64(encrypt(settings.AES_KEY, data)))
+
+ def to_python(self, value):
+ if value is None:
+ return None
+ return decrypt(settings.AES_KEY,
+ binascii.a2b_base64(value)).decode('utf-8')
+
+ def from_db_value(self, value, expression, connection, *args):
+ if value is None:
+ return value
+ return decrypt(settings.AES_KEY,
+ binascii.a2b_base64(value)).decode('utf-8')
+
+ def get_prep_value(self, value):
+ if value is None:
+ return value
+ return binascii.b2a_base64(encrypt(
+ settings.AES_KEY,
+ value
+ ))
diff --git a/preferences/forms.py b/preferences/forms.py
index 7dda8620..fc214f52 100644
--- a/preferences/forms.py
+++ b/preferences/forms.py
@@ -48,6 +48,9 @@ class EditOptionalUserForm(ModelForm):
téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\
les utilisateurs'
+ self.fields['max_solde'].label = 'Solde maximum'
+ self.fields['min_online_payment'].label = 'Montant de rechargement minimum en ligne'
+ self.fields['self_adhesion'].label = 'Auto inscription'
class EditOptionalMachineForm(ModelForm):
@@ -114,6 +117,7 @@ class EditGeneralOptionForm(ModelForm):
self.fields['site_name'].label = 'Nom du site web'
self.fields['email_from'].label = "Adresse mail d\
'expedition automatique"
+ self.fields['GTU_sum_up'].label = "Résumé des CGU"
class EditAssoOptionForm(ModelForm):
diff --git a/preferences/migrations/0028_auto_20180111_1129.py b/preferences/migrations/0028_auto_20180111_1129.py
new file mode 100644
index 00000000..c6c03719
--- /dev/null
+++ b/preferences/migrations/0028_auto_20180111_1129.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-11 10:29
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0027_merge_20180106_2019'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='optionaluser',
+ name='max_recharge',
+ field=models.DecimalField(decimal_places=2, default=100, max_digits=5),
+ ),
+ ]
diff --git a/preferences/migrations/0029_auto_20180111_1134.py b/preferences/migrations/0029_auto_20180111_1134.py
new file mode 100644
index 00000000..92220312
--- /dev/null
+++ b/preferences/migrations/0029_auto_20180111_1134.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-11 10:34
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0028_auto_20180111_1129'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assooption',
+ name='payment',
+ field=models.CharField(choices=[('NONE', 'NONE'), ('COMNPAY', 'COMNPAY')], default='NONE', max_length=255),
+ ),
+ ]
diff --git a/preferences/migrations/0030_auto_20180111_2346.py b/preferences/migrations/0030_auto_20180111_2346.py
new file mode 100644
index 00000000..7f912bbd
--- /dev/null
+++ b/preferences/migrations/0030_auto_20180111_2346.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-11 22:46
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0029_auto_20180111_1134'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='optionaluser',
+ name='max_recharge',
+ ),
+ migrations.AddField(
+ model_name='optionaluser',
+ name='max_solde',
+ field=models.DecimalField(decimal_places=2, default=50, max_digits=5),
+ ),
+ ]
diff --git a/preferences/migrations/0031_optionaluser_self_adhesion.py b/preferences/migrations/0031_optionaluser_self_adhesion.py
new file mode 100644
index 00000000..48a95044
--- /dev/null
+++ b/preferences/migrations/0031_optionaluser_self_adhesion.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-12 11:34
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0030_auto_20180111_2346'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='optionaluser',
+ name='self_adhesion',
+ field=models.BooleanField(default=False, help_text='Un nouvel utilisateur peut se créer son compte sur re2o'),
+ ),
+ ]
diff --git a/preferences/migrations/0032_optionaluser_min_online_payment.py b/preferences/migrations/0032_optionaluser_min_online_payment.py
new file mode 100644
index 00000000..ef78d012
--- /dev/null
+++ b/preferences/migrations/0032_optionaluser_min_online_payment.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-13 16:43
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0031_optionaluser_self_adhesion'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='optionaluser',
+ name='min_online_payment',
+ field=models.DecimalField(decimal_places=2, default=10, max_digits=5),
+ ),
+ ]
diff --git a/preferences/migrations/0033_generaloption_gtu_sum_up.py b/preferences/migrations/0033_generaloption_gtu_sum_up.py
new file mode 100644
index 00000000..63c2df5e
--- /dev/null
+++ b/preferences/migrations/0033_generaloption_gtu_sum_up.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 19:12
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0032_optionaluser_min_online_payment'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='generaloption',
+ name='GTU_sum_up',
+ field=models.TextField(blank=True, default='', help_text='Résumé des CGU'),
+ ),
+ ]
diff --git a/preferences/migrations/0034_auto_20180114_2025.py b/preferences/migrations/0034_auto_20180114_2025.py
new file mode 100644
index 00000000..b6969021
--- /dev/null
+++ b/preferences/migrations/0034_auto_20180114_2025.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 19:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0033_generaloption_gtu_sum_up'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='generaloption',
+ name='GTU',
+ field=models.FileField(default='', upload_to='GTU'),
+ ),
+ migrations.AlterField(
+ model_name='generaloption',
+ name='GTU_sum_up',
+ field=models.TextField(blank=True, default=''),
+ ),
+ ]
diff --git a/preferences/migrations/0035_auto_20180114_2132.py b/preferences/migrations/0035_auto_20180114_2132.py
new file mode 100644
index 00000000..e3767828
--- /dev/null
+++ b/preferences/migrations/0035_auto_20180114_2132.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 20:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0034_auto_20180114_2025'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='generaloption',
+ name='GTU',
+ field=models.FileField(default='', upload_to='/var/www/static/'),
+ ),
+ ]
diff --git a/preferences/migrations/0036_auto_20180114_2141.py b/preferences/migrations/0036_auto_20180114_2141.py
new file mode 100644
index 00000000..1b844ac8
--- /dev/null
+++ b/preferences/migrations/0036_auto_20180114_2141.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 20:41
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0035_auto_20180114_2132'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='generaloption',
+ name='GTU',
+ field=models.FileField(default='', upload_to=''),
+ ),
+ ]
diff --git a/preferences/migrations/0037_auto_20180114_2156.py b/preferences/migrations/0037_auto_20180114_2156.py
new file mode 100644
index 00000000..efafa806
--- /dev/null
+++ b/preferences/migrations/0037_auto_20180114_2156.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 20:56
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0036_auto_20180114_2141'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='generaloption',
+ name='GTU',
+ field=models.FileField(default='', null=True, upload_to=''),
+ ),
+ ]
diff --git a/preferences/migrations/0038_auto_20180114_2209.py b/preferences/migrations/0038_auto_20180114_2209.py
new file mode 100644
index 00000000..3077ebff
--- /dev/null
+++ b/preferences/migrations/0038_auto_20180114_2209.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 21:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0037_auto_20180114_2156'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='generaloption',
+ name='GTU',
+ field=models.FileField(blank=True, default='', null=True, upload_to=''),
+ ),
+ ]
diff --git a/preferences/migrations/0039_auto_20180115_0003.py b/preferences/migrations/0039_auto_20180115_0003.py
new file mode 100644
index 00000000..3dbe2b4c
--- /dev/null
+++ b/preferences/migrations/0039_auto_20180115_0003.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-14 23:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import preferences.aes_field
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0038_auto_20180114_2209'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assooption',
+ name='payment_id',
+ field=models.CharField(max_length=255, null=True),
+ ),
+ ]
diff --git a/preferences/migrations/0040_auto_20180129_1745.py b/preferences/migrations/0040_auto_20180129_1745.py
new file mode 100644
index 00000000..dc7800f4
--- /dev/null
+++ b/preferences/migrations/0040_auto_20180129_1745.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-29 16:45
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import preferences.aes_field
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0039_auto_20180115_0003'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assooption',
+ name='payment_pass',
+ field=preferences.aes_field.AESEncryptedField(blank=True, max_length=255, null=True),
+ ),
+ migrations.AlterField(
+ model_name='assooption',
+ name='payment_id',
+ field=models.CharField(default='', max_length=255),
+ ),
+ ]
diff --git a/preferences/models.py b/preferences/models.py
index af437cf7..764e5332 100644
--- a/preferences/models.py
+++ b/preferences/models.py
@@ -29,6 +29,8 @@ from django.utils.functional import cached_property
from django.db import models
import cotisations.models
+from .aes_field import AESEncryptedField
+
class OptionalUser(models.Model):
"""Options pour l'user : obligation ou nom du telephone,
@@ -42,11 +44,25 @@ class OptionalUser(models.Model):
decimal_places=2,
default=0
)
+ max_solde = models.DecimalField(
+ max_digits=5,
+ decimal_places=2,
+ default=50
+ )
+ min_online_payment = models.DecimalField(
+ max_digits=5,
+ decimal_places=2,
+ default=10
+ )
gpg_fingerprint = models.BooleanField(default=True)
all_can_create = models.BooleanField(
default=False,
help_text="Tous les users peuvent en créer d'autres",
)
+ self_adhesion = models.BooleanField(
+ default=False,
+ help_text="Un nouvel utilisateur peut se créer son compte sur re2o"
+ )
class Meta:
permissions = (
@@ -108,7 +124,10 @@ class OptionalUser(models.Model):
def clean(self):
"""Creation du mode de paiement par solde"""
if self.user_solde:
- cotisations.models.Paiement.objects.get_or_create(moyen="Solde")
+ p = cotisations.models.Paiement.objects.filter(moyen="Solde")
+ if not len(p):
+ c = cotisations.models.Paiement(moyen="Solde")
+ c.save()
class OptionalMachine(models.Model):
@@ -303,6 +322,16 @@ class GeneralOption(models.Model):
req_expire_hrs = models.IntegerField(default=48)
site_name = models.CharField(max_length=32, default="Re2o")
email_from = models.EmailField(default="www-data@serveur.net")
+ GTU_sum_up = models.TextField(
+ default="",
+ blank=True,
+ )
+ GTU = models.FileField(
+ upload_to = '',
+ default="",
+ null=True,
+ blank=True,
+ )
class Meta:
permissions = (
@@ -454,6 +483,24 @@ class AssoOption(models.Model):
blank=True,
null=True
)
+ PAYMENT = (
+ ('NONE', 'NONE'),
+ ('COMNPAY', 'COMNPAY'),
+ )
+ payment = models.CharField(max_length=255,
+ choices=PAYMENT,
+ default='NONE',
+ )
+ payment_id = models.CharField(
+ max_length=255,
+ default='',
+ )
+ payment_pass = AESEncryptedField(
+ max_length=255,
+ null=True,
+ blank=True,
+ )
+
class Meta:
permissions = (
diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html
index 7ea53c40..222a7921 100644
--- a/preferences/templates/preferences/display_preferences.html
+++ b/preferences/templates/preferences/display_preferences.html
@@ -54,7 +54,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Creations d'users par tous |
{{ useroptions.all_can_create }} |
+ Auto inscription |
+ {{ useroptions.self_adhesion }} |
+ {% if useroptions.user_solde %}
+
+ Solde maximum |
+ {{ useroptions.max_solde }} |
+ Montant minimal de rechargement en ligne |
+ {{ useroptions.min_online_payment }} |
+
+ {% endif %}
Préférences machines
@@ -127,7 +137,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Message global affiché sur le site |
{{ generaloptions.general_message }} |
+ Résumé des CGU |
+ {{ generaloptions.GTU_sum_up }} |
+
+ CGU |
+ {{generaloptions.GTU}}
+ |
Données de l'association
@@ -159,7 +175,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Objet utilisateur de l'association |
{{ assooptions.utilisateur_asso }} |
+ Moyen de paiement automatique |
+ {{ assooptions.payment }} |
+
Messages personalisé dans les mails
diff --git a/preferences/templates/preferences/edit_preferences.html b/preferences/templates/preferences/edit_preferences.html
index 02f006c1..8d75f289 100644
--- a/preferences/templates/preferences/edit_preferences.html
+++ b/preferences/templates/preferences/edit_preferences.html
@@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Edition des préférences
-
{% can_view_app preferences %}
diff --git a/users/models.py b/users/models.py
index 998678cd..d3c1d175 100644
--- a/users/models.py
+++ b/users/models.py
@@ -153,7 +153,7 @@ class UserManager(BaseUserManager):
user.set_password(password)
if su:
user.is_superuser=True
- user.save(using=self._db)
+ user.save(using=self._db)
return user
def create_user(self, pseudo, surname, email, password=None):
@@ -409,13 +409,12 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
options, _created = OptionalUser.objects.get_or_create()
user_solde = options.user_solde
if user_solde:
- solde_object, _created = Paiement.objects.get_or_create(
- moyen='Solde'
- )
+ solde_objects = Paiement.objects.filter(moyen='Solde')
somme_debit = Vente.objects.filter(
facture__in=Facture.objects.filter(
user=self,
- paiement=solde_object
+ paiement__in=solde_objects,
+ valid=True
)
).aggregate(
total=models.Sum(
@@ -424,7 +423,7 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
)
)['total'] or 0
somme_credit = Vente.objects.filter(
- facture__in=Facture.objects.filter(user=self),
+ facture__in=Facture.objects.filter(user=self, valid=True),
name="solde"
).aggregate(
total=models.Sum(
@@ -685,10 +684,13 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
an user or if the `options.all_can_create` is set.
"""
options, _created = OptionalUser.objects.get_or_create()
- if options.all_can_create:
- return True, None
+ if(not user_request.is_authenticated and not options.self_adhesion):
+ return False, None
else:
- return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
+ if(options.all_can_create or options.self_adhesion):
+ return True, None
+ else:
+ return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
droit de créer un utilisateur"
def can_edit(self, user_request, *args, **kwargs):
@@ -863,7 +865,7 @@ class Club(User):
"""
if user_request.has_perm('users.view_user'):
return True, None
- if user_request.is_class_adherent:
+ if hasattr(user_request,'is_class_adherent') and user_request.is_class_adherent:
if user_request.adherent.club_administrator.all() or user_request.adherent.club_members.all():
return True, None
return False, u"Vous n'avez pas accès à la liste des utilisateurs."
diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html
index 0e5f30e4..8d72b983 100644
--- a/users/templates/users/profil.html
+++ b/users/templates/users/profil.html
@@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block title %}Profil{% endblock %}
{% block content %}
-{{ users.class_name }}
+{{ users.class_name }} : {{ users.surname }} {{users.name}}
@@ -132,16 +132,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Aucun |
{% endif %}
- {% if user_solde %}
+ {% if allow_online_payment %}
Solde |
- {{ users.solde }} € |
-
+
{{ users.solde }} €
+
+
+ Recharger
+
+ |
{% endif %}
{% if users.shell %}
Shell |
{{ users.shell }} |
{% endif %}
+
{% if users.is_class_club %}
@@ -191,7 +196,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Aucune machine
{% endif %}
Cotisations
-
+
{% if facture_list %}
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
{% else %}
diff --git a/users/templates/users/sidebar.html b/users/templates/users/sidebar.html
index 27f6b2b1..71727243 100644
--- a/users/templates/users/sidebar.html
+++ b/users/templates/users/sidebar.html
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load acl %}
{% block sidebar %}
+ {% if request.user.is_authenticated%}
{% can_create Club %}
@@ -37,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Créer un adhérent
{% acl_end %}
+ {% endif %}
{% can_view_all Club %}
diff --git a/users/templates/users/user.html b/users/templates/users/user.html
index 26b7e0ee..fc8d06d0 100644
--- a/users/templates/users/user.html
+++ b/users/templates/users/user.html
@@ -36,6 +36,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% massive_bootstrap_form userform 'room,school,administrators,members' %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
+
+{% if showCGU %}
+En cliquant sur Créer ou modifier, l'utilisateur s'engage à respecter les règles d'utilisation du réseau.
+Résumé des règles d'utilisations
+{{ GTU_sum_up }}
+{% endif %}
diff --git a/users/views.py b/users/views.py
index bff33907..40fd80d0 100644
--- a/users/views.py
+++ b/users/views.py
@@ -85,7 +85,7 @@ from users.forms import (
)
from cotisations.models import Facture
from machines.models import Machine
-from preferences.models import OptionalUser, GeneralOption
+from preferences.models import OptionalUser, GeneralOption, AssoOption
from re2o.views import form
from re2o.utils import (
@@ -117,12 +117,14 @@ def password_change_action(u_form, user, request, req=False):
kwargs={'userid':str(user.id)}
))
-@login_required
@can_create(Adherent)
def new_user(request):
""" Vue de création d'un nouvel utilisateur,
envoie un mail pour le mot de passe"""
user = AdherentForm(request.POST or None, user=request.user)
+ options, _created = GeneralOption.objects.get_or_create()
+ GTU_sum_up = options.GTU_sum_up
+ GTU = options.GTU
if user.is_valid():
user = user.save(commit=False)
with transaction.atomic(), reversion.create_revision():
@@ -136,7 +138,7 @@ def new_user(request):
'users:profil',
kwargs={'userid':str(user.id)}
))
- return form({'userform': user}, 'users/user.html', request)
+ return form({'userform': user,'GTU_sum_up':GTU_sum_up,'GTU':GTU,'showCGU':True}, 'users/user.html', request)
@login_required
@@ -158,7 +160,7 @@ def new_club(request):
'users:profil',
kwargs={'userid':str(club.id)}
))
- return form({'userform': club}, 'users/user.html', request)
+ return form({'userform': club, 'showCGU':False}, 'users/user.html', request)
@login_required
@@ -179,7 +181,7 @@ def edit_club_admin_members(request, club_instance, clubid):
'users:profil',
kwargs={'userid':str(club_instance.id)}
))
- return form({'userform': club}, 'users/user.html', request)
+ return form({'userform': club, 'showCGU':False}, 'users/user.html', request)
@login_required
@@ -364,7 +366,7 @@ def add_ban(request, user, userid):
request,
"Attention, cet utilisateur a deja un bannissement actif"
)
- return form({'userform': ban}, 'users/user.html', request)
+ return form({'userform': ban}, 'users/user.html', request)
@login_required
@can_edit(Ban)
@@ -413,7 +415,7 @@ def add_whitelist(request, user, userid):
request,
"Attention, cet utilisateur a deja un accès gracieux actif"
)
- return form({'userform': whitelist}, 'users/user.html', request)
+ return form({'userform': whitelist}, 'users/user.html', request)
@login_required
@@ -780,6 +782,8 @@ def profil(request, users, userid):
)
options, _created = OptionalUser.objects.get_or_create()
user_solde = options.user_solde
+ options, _created = AssoOption.objects.get_or_create()
+ allow_online_payment = options.payment != 'NONE'
return render(
request,
'users/profil.html',
@@ -790,6 +794,7 @@ def profil(request, users, userid):
'ban_list': bans,
'white_list': whitelists,
'user_solde': user_solde,
+ 'allow_online_payment' : allow_online_payment,
}
)