mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-25 22:22:26 +00:00
Merge branch 'online_payement' into 'master'
Online payement See merge request federez/re2o!68
This commit is contained in:
commit
3dd87c9446
44 changed files with 1144 additions and 46 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ re2o.png
|
||||||
__pycache__/*
|
__pycache__/*
|
||||||
static_files/*
|
static_files/*
|
||||||
static/logo/*
|
static/logo/*
|
||||||
|
media/*
|
||||||
|
|
|
@ -26,9 +26,8 @@ importé par les views.
|
||||||
Permet de créer une nouvelle facture pour un user (NewFactureForm),
|
Permet de créer une nouvelle facture pour un user (NewFactureForm),
|
||||||
et de l'editer (soit l'user avec EditFactureForm,
|
et de l'editer (soit l'user avec EditFactureForm,
|
||||||
soit le trésorier avec TrezEdit qui a plus de possibilités que self
|
soit le trésorier avec TrezEdit qui a plus de possibilités que self
|
||||||
notamment sur le controle trésorier)
|
notamment sur le controle trésorier SelectArticleForm est utilisée
|
||||||
|
lors de la creation d'une facture en
|
||||||
SelectArticleForm est utilisée lors de la creation d'une facture en
|
|
||||||
parrallèle de NewFacture pour le choix des articles désirés.
|
parrallèle de NewFacture pour le choix des articles désirés.
|
||||||
(la vue correspondante est unique)
|
(la vue correspondante est unique)
|
||||||
|
|
||||||
|
@ -40,8 +39,10 @@ from __future__ import unicode_literals
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import ModelForm, Form
|
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 .models import Article, Paiement, Facture, Banque
|
||||||
|
from preferences.models import OptionalUser
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
from re2o.field_permissions import FieldPermissionFormMixin
|
from re2o.field_permissions import FieldPermissionFormMixin
|
||||||
|
|
||||||
|
@ -246,3 +247,58 @@ class DelBanqueForm(Form):
|
||||||
self.fields['banques'].queryset = instances
|
self.fields['banques'].queryset = instances
|
||||||
else:
|
else:
|
||||||
self.fields['banques'].queryset = Banque.objects.all()
|
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
|
||||||
|
|
113
cotisations/payment.py
Normal file
113
cotisations/payment.py
Normal file
|
@ -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
|
||||||
|
}
|
0
cotisations/payment_utils/__init__.py
Normal file
0
cotisations/payment_utils/__init__.py
Normal file
68
cotisations/payment_utils/comnpay.py
Normal file
68
cotisations/payment_utils/comnpay.py
Normal file
|
@ -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 += '<input type="hidden" name="'+key+'" value="'+array_tpe[key]+'"/>'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -34,6 +34,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<h3>Nouvelle facture</h3>
|
<h3>Nouvelle facture</h3>
|
||||||
|
<p>
|
||||||
|
Solde de l'utilisateur : {{ user.solde }} €
|
||||||
|
</p>
|
||||||
{% bootstrap_form factureform %}
|
{% bootstrap_form factureform %}
|
||||||
{{ venteform.management_form }}
|
{{ venteform.management_form }}
|
||||||
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
||||||
|
|
157
cotisations/templates/cotisations/new_facture_solde.html
Normal file
157
cotisations/templates/cotisations/new_facture_solde.html
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
|
||||||
|
{% 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 %}Création et modification de factures{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% bootstrap_form_errors venteform.management_form %}
|
||||||
|
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<h3>Nouvelle facture</h3>
|
||||||
|
{{ venteform.management_form }}
|
||||||
|
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
||||||
|
<h3>Articles de la facture</h3>
|
||||||
|
<div id="form_set" class="form-group">
|
||||||
|
{% for form in venteform.forms %}
|
||||||
|
<div class='product_to_sell form-inline'>
|
||||||
|
Article :
|
||||||
|
{% bootstrap_form form label_class='sr-only' %}
|
||||||
|
|
||||||
|
<button class="btn btn-danger btn-sm"
|
||||||
|
id="id_form-0-article-remove" type="button">
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un article" id="add_one">
|
||||||
|
<p>
|
||||||
|
Prix total : <span id="total_price">0,00</span> €
|
||||||
|
</p>
|
||||||
|
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var prices = {};
|
||||||
|
{% for article in articlelist %}
|
||||||
|
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
var template = `Article :
|
||||||
|
{% bootstrap_form venteform.empty_form label_class='sr-only' %}
|
||||||
|
|
||||||
|
<button class="btn btn-danger btn-sm"
|
||||||
|
id="id_form-__prefix__-article-remove" type="button">
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
</button>`
|
||||||
|
|
||||||
|
function add_article(){
|
||||||
|
// Index start at 0 => new_index = number of items
|
||||||
|
var new_index =
|
||||||
|
document.getElementsByClassName('product_to_sell').length;
|
||||||
|
document.getElementById('id_form-TOTAL_FORMS').value ++;
|
||||||
|
var new_article = document.createElement('div');
|
||||||
|
new_article.className = 'product_to_sell form-inline';
|
||||||
|
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
|
||||||
|
document.getElementById('form_set').appendChild(new_article);
|
||||||
|
add_listenner_for_id(new_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_price(){
|
||||||
|
var price = 0;
|
||||||
|
var product_count =
|
||||||
|
document.getElementsByClassName('product_to_sell').length;
|
||||||
|
var article, article_price, quantity;
|
||||||
|
for (i = 0; i < product_count; ++i){
|
||||||
|
article = document.getElementById(
|
||||||
|
'id_form-' + i.toString() + '-article').value;
|
||||||
|
if (article == '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
article_price = prices[article];
|
||||||
|
quantity = document.getElementById(
|
||||||
|
'id_form-' + i.toString() + '-quantity').value;
|
||||||
|
price += article_price * quantity;
|
||||||
|
}
|
||||||
|
document.getElementById('total_price').innerHTML =
|
||||||
|
price.toFixed(2).toString().replace('.', ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_listenner_for_id(i){
|
||||||
|
document.getElementById('id_form-' + i.toString() + '-article')
|
||||||
|
.addEventListener("change", update_price, true);
|
||||||
|
document.getElementById('id_form-' + i.toString() + '-article')
|
||||||
|
.addEventListener("onkeypress", update_price, true);
|
||||||
|
document.getElementById('id_form-' + i.toString() + '-quantity')
|
||||||
|
.addEventListener("change", update_price, true);
|
||||||
|
document.getElementById('id_form-' + i.toString() + '-article-remove')
|
||||||
|
.addEventListener("click", function(event) {
|
||||||
|
var article = event.target.parentNode;
|
||||||
|
article.parentNode.removeChild(article);
|
||||||
|
document.getElementById('id_form-TOTAL_FORMS').value --;
|
||||||
|
update_price();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_cheque_info_visibility() {
|
||||||
|
var paiement = document.getElementById("id_Facture-paiement");
|
||||||
|
var visible = paiement.value == paiement.getAttribute('data-cheque');
|
||||||
|
p = document.getElementById("id_Facture-paiement");
|
||||||
|
var display = 'none';
|
||||||
|
if (visible) {
|
||||||
|
display = 'block';
|
||||||
|
}
|
||||||
|
document.getElementById("id_Facture-cheque")
|
||||||
|
.parentNode.style.display = display;
|
||||||
|
document.getElementById("id_Facture-banque")
|
||||||
|
.parentNode.style.display = display;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add events manager when DOM is fully loaded
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
document.getElementById("add_one")
|
||||||
|
.addEventListener("click", add_article, true);
|
||||||
|
var product_count =
|
||||||
|
document.getElementsByClassName('product_to_sell').length;
|
||||||
|
for (i = 0; i < product_count; ++i){
|
||||||
|
add_listenner_for_id(i);
|
||||||
|
}
|
||||||
|
document.getElementById("id_Facture-paiement")
|
||||||
|
.addEventListener("change", set_cheque_info_visibility, true);
|
||||||
|
set_cheque_info_visibility();
|
||||||
|
update_price();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
37
cotisations/templates/cotisations/payment.html
Normal file
37
cotisations/templates/cotisations/payment.html
Normal file
|
@ -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 %}
|
||||||
|
<h3>Recharger de {{ amount }} €</h3>
|
||||||
|
<form class="form" method="{{ method }}" action="{{action}}">
|
||||||
|
{{ content | safe }}
|
||||||
|
{% bootstrap_button "Payer" button_type="submit" icon="piggy-bank" %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
39
cotisations/templates/cotisations/recharge.html
Normal file
39
cotisations/templates/cotisations/recharge.html
Normal file
|
@ -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 %}
|
||||||
|
<h2>Rechargement du solde</h2>
|
||||||
|
<h3>Solde : <span class="label label-default">{{ request.user.solde }} €</span></h3>
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form rechargeform %}
|
||||||
|
{% bootstrap_button "Valider" button_type="submit" icon="piggy-bank" %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -26,6 +26,7 @@ from django.conf.urls import url
|
||||||
|
|
||||||
import re2o
|
import re2o
|
||||||
from . import views
|
from . import views
|
||||||
|
from . import payment
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^new_facture/(?P<userid>[0-9]+)$',
|
url(r'^new_facture/(?P<userid>[0-9]+)$',
|
||||||
|
@ -110,5 +111,25 @@ urlpatterns = [
|
||||||
views.control,
|
views.control,
|
||||||
name='control'
|
name='control'
|
||||||
),
|
),
|
||||||
|
url(r'^new_facture_solde/(?P<userid>[0-9]+)$',
|
||||||
|
views.new_facture_solde,
|
||||||
|
name='new_facture_solde'
|
||||||
|
),
|
||||||
|
url(r'^recharge/$',
|
||||||
|
views.recharge,
|
||||||
|
name='recharge'
|
||||||
|
),
|
||||||
|
url(r'^payment/accept/(?P<factureid>[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'),
|
url(r'^$', views.index, name='index'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,6 +29,7 @@ import os
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
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.auth.decorators import login_required, permission_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
|
@ -36,6 +37,8 @@ from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import modelformset_factory, formset_factory
|
from django.forms import modelformset_factory, formset_factory
|
||||||
from django.utils import timezone
|
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 import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
# Import des models, forms et fonctions re2o
|
# Import des models, forms et fonctions re2o
|
||||||
|
@ -67,8 +70,11 @@ from .forms import (
|
||||||
NewFactureFormPdf,
|
NewFactureFormPdf,
|
||||||
SelectUserArticleForm,
|
SelectUserArticleForm,
|
||||||
SelectClubArticleForm,
|
SelectClubArticleForm,
|
||||||
CreditSoldeForm
|
CreditSoldeForm,
|
||||||
|
NewFactureSoldeForm,
|
||||||
|
RechargeForm
|
||||||
)
|
)
|
||||||
|
from . import payment
|
||||||
from .tex import render_invoice
|
from .tex import render_invoice
|
||||||
|
|
||||||
|
|
||||||
|
@ -584,3 +590,127 @@ def index(request):
|
||||||
return render(request, 'cotisations/index.html', {
|
return render(request, 'cotisations/index.html', {
|
||||||
'facture_list': facture_list
|
'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)
|
||||||
|
|
|
@ -28,8 +28,9 @@ setup_ldap() {
|
||||||
|
|
||||||
|
|
||||||
install_re2o_server() {
|
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
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
@ -236,7 +237,7 @@ install_base=$(dialog --clear \
|
||||||
$HEIGHT $WIDTH \
|
$HEIGHT $WIDTH \
|
||||||
2>&1 >/dev/tty)
|
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-bootstrap3
|
||||||
pip3 install django-ldapdb
|
pip3 install django-ldapdb
|
||||||
pip3 install django-macaddress
|
pip3 install django-macaddress
|
||||||
|
|
|
@ -2153,3 +2153,4 @@ def srv_post_save(sender, **kwargs):
|
||||||
def text_post_delete(sender, **kwargs):
|
def text_post_delete(sender, **kwargs):
|
||||||
"""Regeneration dns après modification d'un SRV"""
|
"""Regeneration dns après modification d'un SRV"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
55
preferences/aes_field.py
Normal file
55
preferences/aes_field.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
return binascii.b2a_base64(encrypt(
|
||||||
|
settings.AES_KEY,
|
||||||
|
value
|
||||||
|
))
|
|
@ -48,6 +48,9 @@ class EditOptionalUserForm(ModelForm):
|
||||||
téléphone'
|
téléphone'
|
||||||
self.fields['user_solde'].label = 'Activation du solde pour\
|
self.fields['user_solde'].label = 'Activation du solde pour\
|
||||||
les utilisateurs'
|
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):
|
class EditOptionalMachineForm(ModelForm):
|
||||||
|
@ -114,6 +117,7 @@ class EditGeneralOptionForm(ModelForm):
|
||||||
self.fields['site_name'].label = 'Nom du site web'
|
self.fields['site_name'].label = 'Nom du site web'
|
||||||
self.fields['email_from'].label = "Adresse mail d\
|
self.fields['email_from'].label = "Adresse mail d\
|
||||||
'expedition automatique"
|
'expedition automatique"
|
||||||
|
self.fields['GTU_sum_up'].label = "Résumé des CGU"
|
||||||
|
|
||||||
|
|
||||||
class EditAssoOptionForm(ModelForm):
|
class EditAssoOptionForm(ModelForm):
|
||||||
|
|
20
preferences/migrations/0028_auto_20180111_1129.py
Normal file
20
preferences/migrations/0028_auto_20180111_1129.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0029_auto_20180111_1134.py
Normal file
20
preferences/migrations/0029_auto_20180111_1134.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
24
preferences/migrations/0030_auto_20180111_2346.py
Normal file
24
preferences/migrations/0030_auto_20180111_2346.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0031_optionaluser_self_adhesion.py
Normal file
20
preferences/migrations/0031_optionaluser_self_adhesion.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0033_generaloption_gtu_sum_up.py
Normal file
20
preferences/migrations/0033_generaloption_gtu_sum_up.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
25
preferences/migrations/0034_auto_20180114_2025.py
Normal file
25
preferences/migrations/0034_auto_20180114_2025.py
Normal file
|
@ -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=''),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0035_auto_20180114_2132.py
Normal file
20
preferences/migrations/0035_auto_20180114_2132.py
Normal file
|
@ -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/'),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0036_auto_20180114_2141.py
Normal file
20
preferences/migrations/0036_auto_20180114_2141.py
Normal file
|
@ -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=''),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0037_auto_20180114_2156.py
Normal file
20
preferences/migrations/0037_auto_20180114_2156.py
Normal file
|
@ -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=''),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0038_auto_20180114_2209.py
Normal file
20
preferences/migrations/0038_auto_20180114_2209.py
Normal file
|
@ -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=''),
|
||||||
|
),
|
||||||
|
]
|
26
preferences/migrations/0039_auto_20180115_0003.py
Normal file
26
preferences/migrations/0039_auto_20180115_0003.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- 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),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment_pass',
|
||||||
|
field=preferences.aes_field.AESEncryptedField(max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
26
preferences/migrations/0040_auto_20180115_0010.py
Normal file
26
preferences/migrations/0040_auto_20180115_0010.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-01-14 23:10
|
||||||
|
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.AlterField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment_id',
|
||||||
|
field=models.CharField(default='', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment_pass',
|
||||||
|
field=preferences.aes_field.AESEncryptedField(default='', max_length=255),
|
||||||
|
),
|
||||||
|
]
|
|
@ -28,6 +28,8 @@ from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
import cotisations.models
|
import cotisations.models
|
||||||
|
|
||||||
|
from .aes_field import AESEncryptedField
|
||||||
|
|
||||||
|
|
||||||
class OptionalUser(models.Model):
|
class OptionalUser(models.Model):
|
||||||
"""Options pour l'user : obligation ou nom du telephone,
|
"""Options pour l'user : obligation ou nom du telephone,
|
||||||
|
@ -41,11 +43,25 @@ class OptionalUser(models.Model):
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=0
|
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)
|
gpg_fingerprint = models.BooleanField(default=True)
|
||||||
all_can_create = models.BooleanField(
|
all_can_create = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
help_text="Tous les users peuvent en créer d'autres",
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -107,7 +123,10 @@ class OptionalUser(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Creation du mode de paiement par solde"""
|
"""Creation du mode de paiement par solde"""
|
||||||
if self.user_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):
|
class OptionalMachine(models.Model):
|
||||||
|
@ -285,6 +304,16 @@ class GeneralOption(models.Model):
|
||||||
req_expire_hrs = models.IntegerField(default=48)
|
req_expire_hrs = models.IntegerField(default=48)
|
||||||
site_name = models.CharField(max_length=32, default="Re2o")
|
site_name = models.CharField(max_length=32, default="Re2o")
|
||||||
email_from = models.EmailField(default="www-data@serveur.net")
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -436,6 +465,23 @@ class AssoOption(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=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,
|
||||||
|
default='',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
|
|
@ -54,7 +54,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Creations d'users par tous</th>
|
<th>Creations d'users par tous</th>
|
||||||
<td>{{ useroptions.all_can_create }}</td>
|
<td>{{ useroptions.all_can_create }}</td>
|
||||||
|
<th>Auto inscription</th>
|
||||||
|
<td>{{ useroptions.self_adhesion }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if useroptions.user_solde %}
|
||||||
|
<tr>
|
||||||
|
<th>Solde maximum</th>
|
||||||
|
<td>{{ useroptions.max_solde }}</td>
|
||||||
|
<th>Montant minimal de rechargement en ligne</th>
|
||||||
|
<td>{{ useroptions.min_online_payment }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
<h4>Préférences machines</h4>
|
<h4>Préférences machines</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
|
||||||
|
@ -127,7 +137,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Message global affiché sur le site</th>
|
<th>Message global affiché sur le site</th>
|
||||||
<td>{{ generaloptions.general_message }}</td>
|
<td>{{ generaloptions.general_message }}</td>
|
||||||
|
<th>Résumé des CGU</th>
|
||||||
|
<td>{{ generaloptions.GTU_sum_up }}</td>
|
||||||
<tr>
|
<tr>
|
||||||
|
<tr>
|
||||||
|
<th>CGU</th>
|
||||||
|
<td>{{generaloptions.GTU}}</th>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h4>Données de l'association</h4>
|
<h4>Données de l'association</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'AssoOption' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'AssoOption' %}">
|
||||||
|
@ -159,7 +175,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Objet utilisateur de l'association</th>
|
<th>Objet utilisateur de l'association</th>
|
||||||
<td>{{ assooptions.utilisateur_asso }}</td>
|
<td>{{ assooptions.utilisateur_asso }}</td>
|
||||||
|
<th>Moyen de paiement automatique</th>
|
||||||
|
<td>{{ assooptions.payment }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
<h4>Messages personalisé dans les mails</h4>
|
<h4>Messages personalisé dans les mails</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'MailMessageOption' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'MailMessageOption' %}">
|
||||||
|
|
|
@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
<h3>Edition des préférences</h3>
|
<h3>Edition des préférences</h3>
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% massive_bootstrap_form options 'utilisateur_asso' %}
|
{% massive_bootstrap_form options 'utilisateur_asso' %}
|
||||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||||
|
|
|
@ -95,6 +95,7 @@ def edit_options(request, section):
|
||||||
return redirect(reverse('index'))
|
return redirect(reverse('index'))
|
||||||
options = form_instance(
|
options = form_instance(
|
||||||
request.POST or None,
|
request.POST or None,
|
||||||
|
request.FILES or None,
|
||||||
instance=options_instance
|
instance=options_instance
|
||||||
)
|
)
|
||||||
if options.is_valid():
|
if options.is_valid():
|
||||||
|
|
|
@ -45,11 +45,10 @@ def can_create(model):
|
||||||
def decorator(view):
|
def decorator(view):
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
can, msg = model.can_create(request.user, *args, **kwargs)
|
can, msg = model.can_create(request.user, *args, **kwargs)
|
||||||
|
#options, _created = OptionalUser.objects.get_or_create()
|
||||||
if not can:
|
if not can:
|
||||||
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
|
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||||
return redirect(reverse('users:profil',
|
return redirect(reverse('index'))
|
||||||
kwargs={'userid':str(request.user.id)}
|
|
||||||
))
|
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
|
|
@ -150,7 +150,7 @@ STATICFILES_DIRS = (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
MEDIA_ROOT = '/var/www/re2o/static'
|
MEDIA_ROOT = '/var/www/re2o/media'
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ SECRET_KEY = 'SUPER_SECRET_KEY'
|
||||||
|
|
||||||
DB_PASSWORD = 'SUPER_SECRET_DB'
|
DB_PASSWORD = 'SUPER_SECRET_DB'
|
||||||
|
|
||||||
|
# AES key for secret key encryption
|
||||||
|
AES_KEY = 'WHAT_A_WONDERFULL_KEY'
|
||||||
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
|
|
30
re2o/templatetags/self_adhesion.py
Normal file
30
re2o/templatetags/self_adhesion.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# 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.
|
||||||
|
from django import template
|
||||||
|
from preferences.models import OptionalUser, GeneralOption
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def self_adhesion():
|
||||||
|
options, _created = OptionalUser.objects.get_or_create()
|
||||||
|
return options.self_adhesion
|
|
@ -1,3 +1,4 @@
|
||||||
django-bootstrap3
|
django-bootstrap3
|
||||||
django-macaddress
|
django-macaddress
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
pycrypto
|
||||||
|
|
|
@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{# Load the tag library #}
|
{# Load the tag library #}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load acl %}
|
{% load acl %}
|
||||||
|
{% load self_adhesion %}
|
||||||
|
{% self_adhesion as var_sa %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head prefix="og: http://ogp.me/ns#">
|
<head prefix="og: http://ogp.me/ns#">
|
||||||
|
@ -102,17 +104,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li>
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
<li>
|
||||||
<a href="{% url 'logout' %}">
|
<a href="{% url 'logout' %}">
|
||||||
<span class="glyphicon glyphicon-log-out"></span> Logout
|
<span class="glyphicon glyphicon-log-out"></span> Logout
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% if var_sa %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'users:new-user' %}">
|
||||||
|
<span class="glyphicon glyphicon-user"></span> Créer un compte
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
<a href="{% url 'login' %}">
|
<a href="{% url 'login' %}">
|
||||||
<span class="glyphicon glyphicon-log-in"></span> Login
|
<span class="glyphicon glyphicon-log-in"></span> Login
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% can_view_app preferences %}
|
{% can_view_app preferences %}
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
|
|
@ -409,13 +409,12 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
|
||||||
options, _created = OptionalUser.objects.get_or_create()
|
options, _created = OptionalUser.objects.get_or_create()
|
||||||
user_solde = options.user_solde
|
user_solde = options.user_solde
|
||||||
if user_solde:
|
if user_solde:
|
||||||
solde_object, _created = Paiement.objects.get_or_create(
|
solde_objects = Paiement.objects.filter(moyen='Solde')
|
||||||
moyen='Solde'
|
|
||||||
)
|
|
||||||
somme_debit = Vente.objects.filter(
|
somme_debit = Vente.objects.filter(
|
||||||
facture__in=Facture.objects.filter(
|
facture__in=Facture.objects.filter(
|
||||||
user=self,
|
user=self,
|
||||||
paiement=solde_object
|
paiement__in=solde_objects,
|
||||||
|
valid=True
|
||||||
)
|
)
|
||||||
).aggregate(
|
).aggregate(
|
||||||
total=models.Sum(
|
total=models.Sum(
|
||||||
|
@ -424,7 +423,7 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
|
||||||
)
|
)
|
||||||
)['total'] or 0
|
)['total'] or 0
|
||||||
somme_credit = Vente.objects.filter(
|
somme_credit = Vente.objects.filter(
|
||||||
facture__in=Facture.objects.filter(user=self),
|
facture__in=Facture.objects.filter(user=self, valid=True),
|
||||||
name="solde"
|
name="solde"
|
||||||
).aggregate(
|
).aggregate(
|
||||||
total=models.Sum(
|
total=models.Sum(
|
||||||
|
@ -685,7 +684,10 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
|
||||||
an user or if the `options.all_can_create` is set.
|
an user or if the `options.all_can_create` is set.
|
||||||
"""
|
"""
|
||||||
options, _created = OptionalUser.objects.get_or_create()
|
options, _created = OptionalUser.objects.get_or_create()
|
||||||
if options.all_can_create:
|
if(not user_request.is_authenticated and not options.self_adhesion):
|
||||||
|
return False, None
|
||||||
|
else:
|
||||||
|
if(options.all_can_create or options.self_adhesion):
|
||||||
return True, None
|
return True, None
|
||||||
else:
|
else:
|
||||||
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
|
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
|
||||||
|
@ -863,7 +865,7 @@ class Club(User):
|
||||||
"""
|
"""
|
||||||
if user_request.has_perm('users.view_user'):
|
if user_request.has_perm('users.view_user'):
|
||||||
return True, None
|
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():
|
if user_request.adherent.club_administrator.all() or user_request.adherent.club_members.all():
|
||||||
return True, None
|
return True, None
|
||||||
return False, u"Vous n'avez pas accès à la liste des utilisateurs."
|
return False, u"Vous n'avez pas accès à la liste des utilisateurs."
|
||||||
|
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}Profil{% endblock %}
|
{% block title %}Profil{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ users.class_name }}</h2>
|
<h2>{{ users.class_name }} : {{ users.surname }} {{users.name}}</h2>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-info' users.id %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-info' users.id %}">
|
||||||
<i class="glyphicon glyphicon-edit"></i>
|
<i class="glyphicon glyphicon-edit"></i>
|
||||||
|
@ -132,16 +132,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>Aucun</td>
|
<td>Aucun</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% if user_solde %}
|
{% if allow_online_payment %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Solde</th>
|
<th>Solde</th>
|
||||||
<td>{{ users.solde }} €</td>
|
<td>{{ users.solde }} €
|
||||||
</tr>
|
<a class="btn btn-primary btn-sm" style='float:right' role="button" href="{% url 'cotisations:recharge' %}">
|
||||||
|
<i class="glyphicon glyphicon-piggy-bank"></i>
|
||||||
|
Recharger
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if users.shell %}
|
{% if users.shell %}
|
||||||
<th>Shell</th>
|
<th>Shell</th>
|
||||||
<td>{{ users.shell }}</td>
|
<td>{{ users.shell }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
{% if users.is_class_club %}
|
{% if users.is_class_club %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-club-admin-members' users.club.id %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-club-admin-members' users.club.id %}">
|
||||||
|
@ -191,7 +196,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<p>Aucune machine</p>
|
<p>Aucune machine</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Cotisations</h2>
|
<h2>Cotisations</h2>
|
||||||
<h4>{% can_create Facture %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:new-facture' users.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Ajouter une cotisation</a>{% acl_end %} {% if user_solde %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:credit-solde' users.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Modifier le solde</a>{% endif%}</h4>
|
<h4>{% can_create Facture %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:new-facture' users.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Ajouter une cotisation</a> {% if user_solde %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:credit-solde' users.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Modifier le solde</a>{% endif%}{% acl_else %}{% if user_solde %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:new_facture_solde' user.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Ajouter une cotisation par solde</a>{% endif %}{% acl_end %}</h4>
|
||||||
{% if facture_list %}
|
{% if facture_list %}
|
||||||
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
|
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% load acl %}
|
{% load acl %}
|
||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
|
{% if request.user.is_authenticated%}
|
||||||
{% can_create Club %}
|
{% can_create Club %}
|
||||||
<a class="list-group-item list-group-item-success" href="{% url "users:new-club" %}">
|
<a class="list-group-item list-group-item-success" href="{% url "users:new-club" %}">
|
||||||
<i class="glyphicon glyphicon-plus"></i>
|
<i class="glyphicon glyphicon-plus"></i>
|
||||||
|
@ -37,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
Créer un adhérent
|
Créer un adhérent
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
|
{% endif %}
|
||||||
{% can_view_all Club %}
|
{% can_view_all Club %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "users:index-clubs" %}">
|
<a class="list-group-item list-group-item-info" href="{% url "users:index-clubs" %}">
|
||||||
<i class="glyphicon glyphicon-list"></i>
|
<i class="glyphicon glyphicon-list"></i>
|
||||||
|
|
|
@ -36,6 +36,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% massive_bootstrap_form userform 'room,school,administrators,members' %}
|
{% massive_bootstrap_form userform 'room,school,administrators,members' %}
|
||||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||||
</form>
|
</form>
|
||||||
|
<br>
|
||||||
|
{% if showCGU %}
|
||||||
|
<p>En cliquant sur Créer ou modifier, l'utilisateur s'engage à respecter les <a href="/media/{{ GTU }}" download="CGU" >règles d'utilisation du réseau</a>.</p>
|
||||||
|
<h3>Résumé des règles d'utilisations</h3>
|
||||||
|
<p>{{ GTU_sum_up }}</p>
|
||||||
|
{% endif %}
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
|
@ -85,7 +85,7 @@ from users.forms import (
|
||||||
)
|
)
|
||||||
from cotisations.models import Facture
|
from cotisations.models import Facture
|
||||||
from machines.models import Machine
|
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.views import form
|
||||||
from re2o.utils import (
|
from re2o.utils import (
|
||||||
|
@ -117,12 +117,14 @@ def password_change_action(u_form, user, request, req=False):
|
||||||
kwargs={'userid':str(user.id)}
|
kwargs={'userid':str(user.id)}
|
||||||
))
|
))
|
||||||
|
|
||||||
@login_required
|
|
||||||
@can_create(Adherent)
|
@can_create(Adherent)
|
||||||
def new_user(request):
|
def new_user(request):
|
||||||
""" Vue de création d'un nouvel utilisateur,
|
""" Vue de création d'un nouvel utilisateur,
|
||||||
envoie un mail pour le mot de passe"""
|
envoie un mail pour le mot de passe"""
|
||||||
user = AdherentForm(request.POST or None, user=request.user)
|
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():
|
if user.is_valid():
|
||||||
user = user.save(commit=False)
|
user = user.save(commit=False)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -136,7 +138,7 @@ def new_user(request):
|
||||||
'users:profil',
|
'users:profil',
|
||||||
kwargs={'userid':str(user.id)}
|
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
|
@login_required
|
||||||
|
@ -158,7 +160,7 @@ def new_club(request):
|
||||||
'users:profil',
|
'users:profil',
|
||||||
kwargs={'userid':str(club.id)}
|
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
|
@login_required
|
||||||
|
@ -179,7 +181,7 @@ def edit_club_admin_members(request, club_instance, clubid):
|
||||||
'users:profil',
|
'users:profil',
|
||||||
kwargs={'userid':str(club_instance.id)}
|
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
|
@login_required
|
||||||
|
@ -780,6 +782,8 @@ def profil(request, users, userid):
|
||||||
)
|
)
|
||||||
options, _created = OptionalUser.objects.get_or_create()
|
options, _created = OptionalUser.objects.get_or_create()
|
||||||
user_solde = options.user_solde
|
user_solde = options.user_solde
|
||||||
|
options, _created = AssoOption.objects.get_or_create()
|
||||||
|
allow_online_payment = options.payment != 'NONE'
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'users/profil.html',
|
'users/profil.html',
|
||||||
|
@ -790,6 +794,7 @@ def profil(request, users, userid):
|
||||||
'ban_list': bans,
|
'ban_list': bans,
|
||||||
'white_list': whitelists,
|
'white_list': whitelists,
|
||||||
'user_solde': user_solde,
|
'user_solde': user_solde,
|
||||||
|
'allow_online_payment' : allow_online_payment,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue