Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

Commit d911452a authored by chirac's avatar chirac

Merge branch 'moamoak/pep8_and_pylint' into 'master'

Pep8 and Pylint cleaning

See merge request federez/re2o!127
parents e56c80b7 f7f8f749
This diff is collapsed.
......@@ -19,7 +19,10 @@
# 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.
"""api.tests
The tests for the API module.
"""
from django.test import TestCase
# from django.test import TestCase
# Create your tests here.
......@@ -32,7 +32,10 @@ from . import views
urlpatterns = [
# Services
url(r'^services/$', views.services),
url(r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$', views.services_server_service_regen),
url(
r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$',
views.services_server_service_regen
),
url(r'^services/(?P<server_name>\w+)/$', views.services_server),
# DNS
......@@ -56,7 +59,13 @@ urlpatterns = [
# Mailings
url(r'^mailing/standard/$', views.mailing_standard),
url(r'^mailing/standard/(?P<ml_name>\w+)/members/$', views.mailing_standard_ml_members),
url(
r'^mailing/standard/(?P<ml_name>\w+)/members/$',
views.mailing_standard_ml_members
),
url(r'^mailing/club/$', views.mailing_club),
url(r'^mailing/club/(?P<ml_name>\w+)/members/$', views.mailing_club_ml_members),
url(
r'^mailing/club/(?P<ml_name>\w+)/members/$',
views.mailing_club_ml_members
),
]
......@@ -26,6 +26,7 @@ Set of various and usefull functions for the API app
from rest_framework.renderers import JSONRenderer
from django.http import HttpResponse
class JSONResponse(HttpResponse):
"""A JSON response that can be send as an HTTP response.
Usefull in case of REST API.
......@@ -51,23 +52,23 @@ class JSONResponse(HttpResponse):
class JSONError(JSONResponse):
"""A JSON response when the request failed.
"""
def __init__(self, error_msg, data=None, **kwargs):
"""Initialise a JSONError object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `error` and a field
`reason` containing `error_msg`. If `data` argument has been given,
a field `data` containing it is added to the JSON response.
A JSONResponse containing a field `status` set to `error` and a
field `reason` containing `error_msg`. If `data` argument has been
given, a field `data` containing it is added to the JSON response.
"""
response = {
'status' : 'error',
'reason' : error_msg
'status': 'error',
'reason': error_msg
}
if data is not None:
response['data'] = data
......@@ -77,22 +78,22 @@ class JSONError(JSONResponse):
class JSONSuccess(JSONResponse):
"""A JSON response when the request suceeded.
"""
def __init__(self, data=None, **kwargs):
"""Initialise a JSONSucess object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `sucess`. If `data`
argument has been given, a field `data` containing it is added to the
JSON response.
A JSONResponse containing a field `status` set to `sucess`. If
`data` argument has been given, a field `data` containing it is
added to the JSON response.
"""
response = {
'status' : 'success',
'status': 'success',
}
if data is not None:
response['data'] = data
......@@ -103,12 +104,20 @@ def accept_method(methods):
"""Decorator to set a list of accepted request method.
Check if the method used is accepted. If not, send a NotAllowed response.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.method in methods:
return view(request, *args, **kwargs)
else:
return JSONError('Invalid request method. Request methods authorize are '+str(methods))
return JSONError(
'Invalid request method. Request methods authorize are ' +
str(methods)
)
return view(request, *args, **kwargs)
return wrapper
return decorator
This diff is collapsed.
......@@ -20,5 +20,8 @@
# 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.
"""cotisations
The app in charge of all the members's cotisations
"""
from .acl import *
......@@ -27,6 +27,7 @@ Here are defined some functions to check acl on the application.
"""
from django.utils.translation import ugettext as _
def can_view(user):
"""Check if an user can view the application.
......@@ -38,4 +39,7 @@ def can_view(user):
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('cotisations')
return can, None if can else _("You don't have the rights to see this application.")
if can:
return can, None
else:
return can, _("You don't have the rights to see this application.")
......@@ -20,6 +20,9 @@
# 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.
"""cotisations.admin
The objects, fields and datastructures visible in the Django admin view
"""
from __future__ import unicode_literals
......
......@@ -20,7 +20,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
Forms for the 'cotisation' app of re2o. It highly depends on
Forms for the 'cotisation' app of re2o. It highly depends on
:cotisations:models and is mainly used by :cotisations:views.
The following forms are mainly used to create, edit or delete
......@@ -38,16 +38,15 @@ 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,MaxValueValidator
from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l
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.mixins import FormRevMixin
from re2o.mixins import FormRevMixin
from .models import Article, Paiement, Facture, Banque
class NewFactureForm(FormRevMixin, ModelForm):
"""
......@@ -109,12 +108,16 @@ class CreditSoldeForm(NewFactureForm):
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
class SelectUserArticleForm(FormRevMixin, Form):
class SelectUserArticleForm(
FormRevMixin, Form):
"""
Form used to select an article during the creation of an invoice for a member.
Form used to select an article during the creation of an invoice for a
member.
"""
article = forms.ModelChoiceField(
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')),
queryset=Article.objects.filter(
Q(type_user='All') | Q(type_user='Adherent')
),
label=_l("Article"),
required=True
)
......@@ -127,10 +130,13 @@ class SelectUserArticleForm(FormRevMixin, Form):
class SelectClubArticleForm(Form):
"""
Form used to select an article during the creation of an invoice for a club.
Form used to select an article during the creation of an invoice for a
club.
"""
article = forms.ModelChoiceField(
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Club')),
queryset=Article.objects.filter(
Q(type_user='All') | Q(type_user='Club')
),
label=_l("Article"),
required=True
)
......@@ -140,6 +146,7 @@ class SelectClubArticleForm(Form):
required=True
)
# TODO : change Facture to Invoice
class NewFactureFormPdf(Form):
"""
......@@ -147,9 +154,18 @@ class NewFactureFormPdf(Form):
"""
paid = forms.BooleanField(label=_l("Paid"), required=False)
# TODO : change dest field to recipient
dest = forms.CharField(required=True, max_length=255, label=_l("Recipient"))
dest = forms.CharField(
required=True,
max_length=255,
label=_l("Recipient")
)
# TODO : change chambre field to address
chambre = forms.CharField(required=False, max_length=10, label=_l("Address"))
chambre = forms.CharField(
required=False,
max_length=10,
label=_l("Address")
)
# TODO : change Facture to Invoice
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
......@@ -295,6 +311,11 @@ class NewFactureSoldeForm(NewFactureForm):
"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureSoldeForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
self.fields['cheque'].required = False
self.fields['banque'].required = False
self.fields['cheque'].label = _('Cheque number')
......@@ -313,7 +334,6 @@ class NewFactureSoldeForm(NewFactureForm):
# TODO : change paiement to payment and baque to bank
fields = ['paiement', 'banque']
def clean(self):
cleaned_data = super(NewFactureSoldeForm, self).clean()
# TODO : change paiement to payment
......@@ -342,7 +362,7 @@ class RechargeForm(FormRevMixin, Form):
value = forms.FloatField(
label=_l("Amount"),
min_value=0.01,
validators = []
validators=[]
)
def __init__(self, *args, **kwargs):
......@@ -350,6 +370,10 @@ class RechargeForm(FormRevMixin, Form):
super(RechargeForm, self).__init__(*args, **kwargs)
def clean_value(self):
"""
Returns a cleaned vlaue from the received form by validating
the value is well inside the possible limits
"""
value = self.cleaned_data['value']
if value < OptionalUser.get_cached_value('min_online_payment'):
raise forms.ValidationError(
......@@ -360,7 +384,8 @@ class RechargeForm(FormRevMixin, Form):
)
}
)
if value + self.user.solde > OptionalUser.get_cached_value('max_solde'):
if value + self.user.solde > \
OptionalUser.get_cached_value('max_solde'):
raise forms.ValidationError(
_("Requested amount is too high. Your balance can't exceed \
%(max_online_balance)s €.") % {
......
This diff is collapsed.
......@@ -2,6 +2,9 @@
Here are defined some views dedicated to online payement.
"""
from collections import OrderedDict
from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
......@@ -11,12 +14,11 @@ from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import ugettext as _
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):
......@@ -30,7 +32,10 @@ def accept_payment(request, factureid):
'amount': facture.prix()
}
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
return redirect(reverse(
'users:profil',
kwargs={'userid': request.user.id}
))
@csrf_exempt
......@@ -43,7 +48,11 @@ def refuse_payment(request):
request,
_("The payment has been refused.")
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
return redirect(reverse(
'users:profil',
kwargs={'userid': request.user.id}
))
@csrf_exempt
def ipn(request):
......@@ -105,7 +114,7 @@ def comnpay(facture, request):
str(AssoOption.get_cached_value('payment_pass')),
'https://' + host + reverse(
'cotisations:accept_payment',
kwargs={'factureid':facture.id}
kwargs={'factureid': facture.id}
),
'https://' + host + reverse('cotisations:refuse_payment'),
'https://' + host + reverse('cotisations:ipn'),
......@@ -113,20 +122,20 @@ def comnpay(facture, request):
"D"
)
r = {
'action' : 'https://secure.homologation.comnpay.com',
'method' : 'POST',
'content' : p.buildSecretHTML(
'action': 'https://secure.homologation.comnpay.com',
'method': 'POST',
'content': p.buildSecretHTML(
"Rechargement du solde",
facture.prix(),
idTransaction=str(facture.id)
),
'amount' : facture.prix,
'amount': facture.prix,
}
return r
# The payment systems supported by re2o
PAYMENT_SYSTEM = {
'COMNPAY' : comnpay,
'NONE' : None
'COMNPAY': comnpay,
'NONE': None
}
"""cotisations.payment_utils.comnpay
The module in charge of handling the negociation with Comnpay
for online payment
"""
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"
class Payment():
""" The class representing a transaction with all the functions
used during the negociation
"""
def __init__(self, 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
......@@ -23,46 +24,63 @@ class Payment():
self.urlIPN = urlIPN
self.source = source
self.typeTr = typeTr
self.idTransaction = ""
def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""):
def buildSecretHTML(self, produit="Produit", montant="0.00",
idTransaction=""):
""" Build an HTML hidden form with the different parameters for the
transaction
"""
if idTransaction == "":
self.idTransaction = str(time.time())+self.vad_number+str(randrange(999))
self.idTransaction = str(time.time())
self.idTransaction += self.vad_number
self.idTransaction += 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)
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!="":
if self.urlIPN != "":
array_tpe['urlIPN'] = self.urlIPN
array_tpe['key'] = self.secret_key;
strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8'))
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]+'"/>'
ret += '<input type="hidden" name="{k}" value="{v}"/>'.format(
k=key,
v=array_tpe[key]
)
return ret
def validSec(self, values, secret_key):
@staticmethod
def validSec(values, secret_key):
""" Check if the secret value is correct """
if "sec" in values:
sec = values['sec']
del values["sec"]
strWithKey = hashlib.sha512(base64.b64encode(bytes('|'.join(values.values()) +"|"+secret_key, 'utf-8'))).hexdigest()
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,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% if articlesformset %}
<h3>{% trans "Invoice's articles" %}</h3>
<div id="form_set" class="form-group">
{{ articlesformset.management_form }}
......@@ -54,11 +55,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Total price : <span id="total_price">0,00</span>
{% endblocktrans %}
</p>
{% endif %}
{% bootstrap_form factureform %}
{% bootstrap_button action_name button_type='submit' icon='star' %}
</form>
{% if articlesformset %}
<script type="text/javascript">
var prices = {};
{% for article in articles %}
......@@ -133,6 +135,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
update_price();
});
</script>
{% endif %}
{% endblock %}
......@@ -19,7 +19,10 @@
# 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.
"""cotisations.tests
The tests for the Cotisations module.
"""
from django.test import TestCase
# from django.test import TestCase
# Create your tests here.
......@@ -24,40 +24,44 @@ Module in charge of rendering some LaTex templates.
Used to generated PDF invoice.
"""
import tempfile
from subprocess import Popen, PIPE
import os
from datetime import datetime
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
from django.conf import settings
from django.utils.text import slugify
import tempfile
from subprocess import Popen, PIPE
import os
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex')
CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day
def render_invoice(request, ctx={}):
def render_invoice(_request, ctx={}):
"""
Render an invoice using some available information such as the current
date, the user, the articles, the prices, ...
"""
filename = '_'.join([
'invoice',
slugify(ctx['asso_name']),
slugify(ctx['recipient_name']),
str(ctx['DATE'].year),
str(ctx['DATE'].month),
str(ctx['DATE'].day),
'invoice',
slugify(ctx.get('asso_name', "")),
slugify(ctx.get('recipient_name', "")),
str(ctx.get('DATE', datetime.now()).year),
str(ctx.get('DATE', datetime.now()).month),
str(ctx.get('DATE', datetime.now()).day),
])
r = render_tex(request, 'cotisations/factures.tex', ctx)
r['Content-Disposition'] = ''.join(['attachment; filename="',filename,'.pdf"'])
r = render_tex(_request, 'cotisations/factures.tex', ctx)
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
name=filename
)
return r
def render_tex(request, template, ctx={}):
def render_tex(_request, template, ctx={}):
"""
Creates a PDF from a LaTex templates using pdflatex.
Writes it in a temporary directory and send back an HTTP response for
......@@ -66,13 +70,13 @@ def render_tex(request, template, ctx={}):
context = Context(ctx)
template = get_template(template)
rendered_tpl = template.render(context).encode('utf-8')
with tempfile.TemporaryDirectory() as tempdir:
for i in range(2):
process = Popen(
['pdflatex', '-output-directory', tempdir],
stdin = PIPE,
stdout = PIPE,
stdin=PIPE,
stdout=PIPE,
)
process.communicate(rendered_tpl)
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
......
......@@ -19,6 +19,9 @@
# 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.
"""cotisations.urls
The defined URLs for the Cotisations app
"""
from __future__ import unicode_literals
......@@ -29,107 +32,131 @@ from . import views
from . import payment
urlpatterns = [
url(r'^new_facture/(?P<userid>[0-9]+)$',
url(
r'^new_facture/(?P<userid>[0-9]+)$',
views.new_facture,
name='new-facture'
),
url(r'^edit_facture/(?P<factureid>[0-9]+)$',
),
url(
r'^edit_facture/(?P<factureid>[0-9]+)$',
views.edit_facture,
name='edit-facture'
),
url(r'^del_facture/(?P<factureid>[0-9]+)$',
),
url(
r'^del_facture/(?P<factureid>[0-9]+)$',
views.del_facture,
name='del-facture'
),
url(r'^facture_pdf/(?P<factureid>[0-9]+)$',
),
url(
r'^facture_pdf/(?P<factureid>[0-9]+)$',
views.facture_pdf,
name='facture-pdf'
),
url(r'^new_facture_pdf/$',
),
url(
r'^new_facture_pdf/$',
views.new_facture_pdf,
name='new-facture-pdf'
),
url(r'^credit_solde/(?P<userid>[0-9]+)$',
),
url(
r'^credit_solde/(?P<userid>[0-9]+)$',
views.credit_solde,
name='credit-solde'
),
url(r'^add_article/$',
),
url(
r'^add_article/$',
views.add_article,
name='add-article'
),
url(r'^edit_article/(?P<articleid>[0-9]+)$',
),
url(
r'^edit_article/(?P<articleid>[0-9]+)$',
views.edit_article,
name='edit-article'
),
url(r'^del_article/$',
),
url(
r'^del_article/$',
views.del_article,
name='del-article'
),
url(r'^add_paiement/$',
),
url(