From aa6a2bc0bbe099a1148cf96cfb6fb5406161aea8 Mon Sep 17 00:00:00 2001 From: Quentin Rouland Date: Fri, 31 Mar 2017 00:19:02 +0200 Subject: [PATCH 1/7] TG-126 Securiser les methodes de l'api --- backend/app/api/GroupAPI.py | 7 ++++--- backend/app/api/LivretAPI.py | 8 ++++---- backend/app/api/LoginAPI.py | 12 ++++++++++++ backend/app/api/PdfAPI.py | 18 +++++++++--------- backend/app/api/UserAPI.py | 6 +++--- backend/app/api/UserInfoAPI.py | 7 ++++--- backend/app/model.py | 8 ++++++++ 7 files changed, 44 insertions(+), 22 deletions(-) diff --git a/backend/app/api/GroupAPI.py b/backend/app/api/GroupAPI.py index a8b13c3..deaa810 100644 --- a/backend/app/api/GroupAPI.py +++ b/backend/app/api/GroupAPI.py @@ -3,15 +3,16 @@ import os from flask_restful import Resource, request from app.api import mailsModels -from app.model import * -from app.utils import * +from app.api.LoginAPI import login_required +from app.model import Roles, getGroup, getParam, getUser, USER, GROUP, TUTORSHIP +from app.utils import send_mail, checkParams class GroupAPI(Resource): """ Group Api Resource """ - + @login_required(roles=[Roles.resp_formation]) def post(self): args = request.get_json(cache=False, force=True) if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): diff --git a/backend/app/api/LivretAPI.py b/backend/app/api/LivretAPI.py index ecd87cd..0667f1c 100644 --- a/backend/app/api/LivretAPI.py +++ b/backend/app/api/LivretAPI.py @@ -3,15 +3,15 @@ import os from flask_restful import Resource, request from app.api import mailsModels -from app.model import * -from app.utils import * - +from app.model import Roles, getParam, getGroup, getUser, USER, GROUP, TUTORSHIP +from app.utils import send_mail, checkParams +from app.api.LoginAPI import login_required class LivretAPI(Resource): """ Livret Api Resource """ - + @login_required(roles=[Roles.etudiant]) def post(self): args = request.get_json(cache=False, force=True) if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): diff --git a/backend/app/api/LoginAPI.py b/backend/app/api/LoginAPI.py index c426fb3..b388529 100644 --- a/backend/app/api/LoginAPI.py +++ b/backend/app/api/LoginAPI.py @@ -49,3 +49,15 @@ class LoginAPI(Resource): session['user'] = None return {'AUTH_RESULT': 'OK'}, 200 + + +def login_required(roles=[]): + def my_login_required(func): + def wrapper(*args): + current_user = session.get('user', None) + if current_user is None or (len(roles) != 0 and not sum([1 for x in current_user['role'].split("-") if int(x) in roles]) > 0): + return {"msg": "UNAUTHORIZED"}, 401 + return func(*args) + return wrapper + return my_login_required + diff --git a/backend/app/api/PdfAPI.py b/backend/app/api/PdfAPI.py index a1e7105..bf831d1 100644 --- a/backend/app/api/PdfAPI.py +++ b/backend/app/api/PdfAPI.py @@ -1,29 +1,29 @@ -import os - -from flask import request from flask_restful import Resource from flask_restful.reqparse import RequestParser +from app.tools.LibPdf import delete_file from model import getParam +from werkzeug.utils import secure_filename from app.model import getGroup -from app.tools.LibPdf import delete_file, upload_file, allowed_file +from app.tools.LibPdf import upload_file, allowed_file +from app.api.LoginAPI import login_required +import os +import request class PdfAPI(Resource): """ Pdf Api Resource """ - + @login_required() def delete(self): parser = RequestParser() parser.add_argument('templateName', required=True, help="Template name is required !") args = parser.parse_args() - if ".." in args: - return {"msg": ".. not allowed in path"}, 400 - - delete_file(os.path.join(getParam('TEMPLATES_DIRECTORY'), args['templateName'])) + delete_file(os.path.join(getParam('TEMPLATES_DIRECTORY'), secure_filename(args['templateName']))) + @login_required() def post(self): """ Upload d'un template diff --git a/backend/app/api/UserAPI.py b/backend/app/api/UserAPI.py index f9bfbcb..5b61425 100644 --- a/backend/app/api/UserAPI.py +++ b/backend/app/api/UserAPI.py @@ -2,15 +2,15 @@ from hashlib import sha256 from flask_restful import Resource, request -from app.model import * +from app.model import Roles, getUser, hashExists, USER from app.utils import checkParams, get_random_string - +from app.api.LoginAPI import login_required class UserAPI(Resource): """ User Api Resource """ - + @login_required(roles=[Roles.resp_formation]) def post(self): args = request.get_json(cache=False, force=True) if not checkParams(['role', 'email', 'name'], args): diff --git a/backend/app/api/UserInfoAPI.py b/backend/app/api/UserInfoAPI.py index c1807e9..5e2f6cc 100644 --- a/backend/app/api/UserInfoAPI.py +++ b/backend/app/api/UserInfoAPI.py @@ -1,7 +1,7 @@ from flask import session from flask_restful import Resource - -from app.model import * +from app.api.LoginAPI import login_required +from app.model import LIVRET, TUTORSHIP, and_ class UserInfoAPI(Resource): @@ -9,6 +9,7 @@ class UserInfoAPI(Resource): UserInfo Api Resource """ + @login_required() def get(self): user = session.get("user", None) return {'USER': user}, 200 @@ -18,7 +19,7 @@ class UserGroupsAPI(Resource): """ UserGroups Api Resource """ - + @login_required() def get(self): user = session.get("user", None) if user is not None: diff --git a/backend/app/model.py b/backend/app/model.py index 70cc838..6179978 100644 --- a/backend/app/model.py +++ b/backend/app/model.py @@ -91,3 +91,11 @@ def hashExists(test): rows = query.execute() res = rows.first() return res is not None + + +class Roles: + secretaire = 1 + resp_formation = 2 + tuteur_univ = 3 + etudiant = 4 + tuteur_entreprise = 5 From ab33fb2bbee86f455c09c938125d25aa27217db6 Mon Sep 17 00:00:00 2001 From: DonRenando Date: Fri, 31 Mar 2017 12:28:00 +0200 Subject: [PATCH 2/7] TG-62 template et API insert --- ...nt_Accueil_Page1_Couverture_NOM_Prenom.pdf | Bin 112272 -> 112283 bytes ...ommaire_FicheRenseignements_NOM_Prenom.pdf | Bin 90701 -> 90658 bytes ...t_Accueil_Page4_PosteOccupe_NOM_Prenom.pdf | Bin 21674 -> 22914 bytes ...Alternant_PEntreprise_Bilan_NOM_Prenom.pdf | Bin 53549 -> 53481 bytes ...ernant_PFormation_Bis_Bilan_NOM_Prenom.pdf | Bin 36104 -> 37466 bytes ...eil_Page1_Couverture_NOM_Prenom_backup.pdf | Bin 108484 -> 112276 bytes ..._FicheRenseignements_NOM_Prenom_backup.pdf | Bin 89159 -> 90701 bytes Template_PDF/specification_template.txt | 74 ++++++++-------- backend/app/api/ExportPdfAPI.py | 82 ++++++++++++++++++ backend/app/tools/LibPdf.py | 5 +- .../tools/libPdf/UploadPDF/testUploadPdf.py | 17 ++-- 11 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 backend/app/api/ExportPdfAPI.py diff --git a/Template_PDF/LivretAlternant_Accueil_Page1_Couverture_NOM_Prenom.pdf b/Template_PDF/LivretAlternant_Accueil_Page1_Couverture_NOM_Prenom.pdf index 18226d55559ce17ab0fb3013f9111a8ce4691651..bb47f6d5b5eff3a2f98216cbee7baf027259bfc0 100644 GIT binary patch delta 4438 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 delta 4496 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 diff --git a/Template_PDF/LivretAlternant_Accueil_Page4_PosteOccupe_NOM_Prenom.pdf b/Template_PDF/LivretAlternant_Accueil_Page4_PosteOccupe_NOM_Prenom.pdf index c236e5f96e712ea212518ad81690092253ee3189..e9285545be0eaf24b845bd51a0ab3aeb8e5d2c49 100644 GIT binary patch delta 1592 zcmV-82FLlTsR4qs0k8{9lcr3e2QN=zYEyJ=lTb|}lgLRJvx`kW1pzpdI87WEFfT@F za&vSbF*G(WOl59obZ8(kGB`DpYfU15+iu%95d9UtH9?UKuevOP0KN$=lBU_%ZMRzl zg(B0AP+9WoQe;u|-}g|mUeH$HO;8F)+z{0E###iZ2uKqWoHWWb~jB$J_QJ*HrPqQihG z3>oaBU}~UL8m$pbVTy+xPflp@_tw&7ytWlx*{W#D#8%D=&aN1}vdc6c7k3{ZayTZL z3C%T~rOWCA6RpiPO^PP3nK(V|SBr|gp4uuY(`{Xpt#}b{EKNr4@A)Lo(wj0pdRe2B zP?W{%ig`h6H*PmO@}*`+(#Ji2NaL_~AMRf)yEp^@Y^bNwTUWhU|V zg-zGDwVS7CUfa!E7VviYD9tR+&^rFmUz#^JwG$R)W82YjT-jFh9kzNaG?~?r_leVK4|oT(t)nG-shNAENMIFbI9|6a<3* z90pH1{1gJ>5dz2n&-dCy)1hHHXfuYnWbh0^!E?zWzPVZC%%iIXOoPXfa9P_LL=xG& zR}?N2y7;2YvPkB({y>+1(}))KuD)(L`e;P368-4hKI+%jf-Om(&ERU zpI3v!2up0B<#TxJ9Hs~XJi3H?7l3((3oYIpeEGt*;5dDJ-;;rIc zlV&;MA)kD>?NdjxR;b;#6bd~4Q^bF&VbOc!F%9;b?+v|C|`6_+RqmCijuf2 zauh5|(lTi_tIXb^^3k%Wmb&L22f)81q~y zrJE+pEOHg5%Xq!ENO{SAMNxfraa~?_d;qZ2T3BkHGY=2KFY%%5SCWHV=PBf z?k(pCt7Wfm7)uNOh_T#@tt&V`tA>yiwAmTSY85lw0 z>gO52`fnOCFoF1*Az&>G3gQrT+ov)xgTy^s82*C!H4H3Zdl(oP;WSX*>n6i5ko-1v z26ha2Bz4|C4BtTNwr*kIX8H#dy~XeqD$f9?K@2qp9(?BbJZ1O@GG~i4g8*K2r3`OD qe71fh^9zBPHJ5<_0LY#uK$DS76_bxt4h=FiGBXVdB_%~qMhyzpH1*2> delta 287 zcmV+)0pR|EvjM890k8{90x~s|4NM*iJUlN(Z*FuTGB%T(ODGFAE;ltGF*GhRHC>az zNgtE!OTd#mQVo;0OdgYFQX8}HOgsevHv;;M@v;y2wH z7(wFd7a73%Z|XBJf%uwX4F5pt6vQFwwohhY28nyNG5iJds~A|o_AoFo!fBwq*KLMh zAo*>o4D1;4Nb0=(7`}njZQaDcjV$i-oZ$;de2XLlFHZf)>YEtegVonD2;o!bE5z^` l#AltvzyJWO4+17=lMezFlV@2D4KXn}G7SnPB}Gq04GQ1ga6JG3 diff --git a/Template_PDF/LivretAlternant_PEntreprise_Bilan_NOM_Prenom.pdf b/Template_PDF/LivretAlternant_PEntreprise_Bilan_NOM_Prenom.pdf index 091741bb8830d5383b4c98993b2baabb408c532a..c752d3bc038729561c6fefd836519cbdb24cb4f1 100644 GIT binary patch delta 2301 zcmVefybOQsSZ#0HMiSOf*>6n{*!#XD4~79*mXIQj>xU%bnG(VC((ux z!%?ukW)6?)NHT{doe9{{5ug2j=lMmv@+JS^%e*e;zNB*q_JQ&Dev!l{`Ta+Tf`u^8 zM)rS_ze^V7N7m|G@=;!A6_dxu>(wTCR-O8CUL_~)#iA&$iJT^5c%4&2I7ck*0?z&R* zpOZy(11PKj_VBZb0ys2;(<5dPBo+}xV@kQEuc7CpZO4P?@)1;s#|HLU{$rNR^Mz;O z9<%pBJAPpAK?_=kFyPsOI6$}@sK8P{Q*_Hm7z6%ipp57C!YswhaDKw>eQ?9BZMc64 zMiT&Tgtws-GpdCh8ElacTG$qLlA8~5lJbpm(nPJ?=_6;FwQQ)j^r2^wHz9HmDMuem zU_Ejfc;k`Ju<-MoHhOsYlRRDQO$r&3S6r7Af%*eU5y-vXdSO=VHF%$|)I(oc!j8C) zC=%R7eefZ;Eo^*bB1#j`UC`fj*S?6WB+sV4NS12| z9|W_}1d|4WboGW$1#=k5E&Qb0#xL9(%<6@~{FKdm!81L20eB(+RJd9$#^mmA}~TYeauINkE?#v2ADxT-&;#8*&HG^^Ix)06ib{v1xLQ^|NE4mGV{b^kJSEqkdLIy%hEsb?jy%F*|A-;RP zap^G2@`}HIcXV`o%ul8m{uzNiRYm+MuJ5l9 zRE57QlG4BC7hN5Le0s&-`t*~pl6j1w*%Du?*Qko-)kkIyEnRL#s6&6;ov7I^`TO2r z@Ny)b3g5t5GMG=m;KK`;cg}Rs`GwDZeD(P=yBPPIAMV=2zI=K8R0QKoZo;=Wm5(!o zPLi$g6wR(h0Fe!|GU?^hWV!T(&rsl@c;j=dQsn7$_7A3d;fER1YhjET(+ilfy>R|_ zWOXMDNAFm$7EZ=zEbM=UDT~(qw==-E7S1M2_6GcfncjfOgvzQ;7=56`>rKOT)eGM< z*&A?rHpUImOCt&P-$;}?8;R=jjf5M%H{*Qx4h-0D@XI;aZ-c}MvW{OD{u5FZR>Vb~ zF$D{fVqV`aQ-6JYVIR|CN8Lm;%l~h0T+cE-E~S zuaIBYY3fl}L3I(YR-Woh(RCNsDJ02#nkIK;QbJJN-!9_v23^g4)^!bEra9I}*Yaf% z&ndScIKHZrw8X~PWQkpLuo)@ZicW|8HpyyQJmPo006o-YoGsu3!GEjs%EOFn1Q-Tn zKhQ2`R6o$IW^{i$(#@MzT2ItgC^oCEU}WcJY6pl@N%sZ0edI-xGr*pkmjJqCi_l5 zbS#Qy!wGtQ!yuU_iBIVzNVg13Z*om*RM)mdPH${Y>)L;-wB_DCOa4g=NBqtj+pg5J zxeafWhs_I#JEZqiu$*-m*kw2^_c5FiJuH9e*$N7d-)etV)s?S{=TkSmwM&V!g<2dE zdE3L?!{If4@8R^*^!>=7p1c1#2p0ajuC9=r35tbh1E2#-mzJep2g0c78iQsBgGida z7(`EF@W6kfmmomDVKlbmmawkvJ_CVel5Nia^Hw1Wkh$ewc#FuR>JNjXlJ$v`rrQ9XQ)zl-AU8FW@ya zZhu2VIWa{+K{7-}GC@Q_LpM1_L^3utGB`CfG&n{#H$gsKK0b4Fa%Ev{4GMUieUD8j z1YsP9pLZ;~#6cUjD7m1P3&m0S%*{z@4;#iK^LEIc=eQbLXirZdXNJ&1D z!xq-s@&4a;+Jf@EM%r{;x>PFMG7%e&4j&)%Vg zM+Y#^XD@K1_iRuUuQzSIon5Mg&n)t7`Z@vWv33gLb?2?O{6Mwv)qcKAzobTb{D8c8 ztqJSRr3eb&n&s2k7oOkKtgVdS-164}BANG-xee8;cvR>Wq zZS!Wl7>#~@G{md$(F|cfKfdNg_(&&BPEpf+p1qw?%FQIr#$V#e_z7A(|FMD}`R_YV z>baNq4CiR8==uFkZjC=rhj;Ex*|~pH`{@H*zgeF(plR&-|8x0C`bYXII XcyeWC4GJ|fI5jg33MC~)Peu(2GG~9O delta 2382 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 E3c~(lHUIzs diff --git a/Template_PDF/backup/LivretAlternant_Accueil_Page1_Couverture_NOM_Prenom_backup.pdf b/Template_PDF/backup/LivretAlternant_Accueil_Page1_Couverture_NOM_Prenom_backup.pdf index 2aec054dda9aaf9b3c85bbb4f9288487cd83a866..11b5577d4b91ec004e010f47c9c539985c72c040 100644 GIT binary patch delta 9084 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 delta 5318 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 T0S*m0I5Ra33MC~)Peu(2WYKUR delta 2794 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 z 0: + + user = getUser(uid=uid) + + group = getGroup(gid) + + if user is None: + return {"ERROR": "The user with id " + str(gid) + " does not exists !"}, 400 + + prenom = user["name"].split(" ", 1) + nom = user["name"].split(" ", 2) + + group["name"] + + annee1 = group["year"] + annee2 = int(group["year"]) + 1 + promo = group["class_short"] + + group["class_long"] + group["departement"] + group["resp_id"] + group["sec_id"] + group["ressources_dir"] + + self.data = { + 'page1.nom_master': 'Renan', + 'page1.nom_complet_master': 'Husson', + 'page1.annee_1': 'Husson', + 'page1.annee_2': 'Jean Jaures', + 'page1.nom_prenom': 'Panda', + 'page1.email': 'Panda', + 'page1.telephone': 'Panda', + 'page1.entreprise': 'Panda', + 'page1.tuteur_pedagogique': 'Panda', + 'page1.tuteur_entreprise': 'Panda', + 'page2.type_contrat_apprentissage': True, + 'page2.type_contrat_professionnalisation': True, + 'page2.type_contrat_stage': True, + 'page2.debut_contrat': 'Panda', + 'page2.fin_contrat': 'Panda', + 'page2.telephone': 'Panda', + 'page2.email': 'Panda', + 'page2.compostant_formation': 'Panda', + 'page2.responsable_pedagogique_formation': 'Panda', + 'page2.tel_responsable_pedagogique_formation': 'Panda', + 'page2.mail_responsable_pedagogique_formation': 'Panda', + 'page2.tel_tuteur_pedagogique': 'Panda', + 'page2.mail_tuteur_pedagogique': 'Panda', + 'page2.tuteur_entreprise': 'Panda', + 'page2.entreprise': 'Panda', + 'page2.adresse_entreprise': 'Panda', + 'page2.tel_tuteur_entreprise': 'Panda', + 'page2.mail_tuteur_entreprise': 'Panda', + 'page4.poste_occupe': 'Panda', + 'page4.poste_occupe_2': 'Panda', + 'PEntreprise.n_periode': 'Panda', + 'PEntreprise.debut_periode': 'Panda', + 'PEntreprise.fin_periode': 'Panda', + 'PEntreprise.travaux_entreprise': 'Panda', + 'PEntreprise.remarque_tuteur': 'Panda', + 'pagePFormation.n_periode': 'Panda', + 'pagePFormation.bilan_periode': 'Panda', + + } + + pdf_fusion = ["/page1.pdf", "/page2.pdf"] + chemin_pdf = "/tmp" + + nom_pdf = "Livret_Alternant_BOB_Armandeau.pdf" + + fusion_fichiers(chemin_pdf, nom_pdf, pdf_fusion) + + # Prenom NOM + # remplir_template() diff --git a/backend/app/tools/LibPdf.py b/backend/app/tools/LibPdf.py index da93aa5..985594a 100644 --- a/backend/app/tools/LibPdf.py +++ b/backend/app/tools/LibPdf.py @@ -55,15 +55,14 @@ def allowed_file(filename): def upload_file(file_to_upload, upload_folder): """ - rep de l'etu avec id - :param file: + televersement d'un fichier + :param file_to_upload: :param upload_folder: :return: """ file_to_upload.save(os.path.join(upload_folder, secure_filename(file_to_upload.filename))) - def delete_file(pdf_path): if os.path.exists(pdf_path): os.remove(pdf_path) diff --git a/backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py b/backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py index 45e1940..4604a9e 100644 --- a/backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py +++ b/backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py @@ -1,20 +1,25 @@ +import filecmp import os import unittest +from pathlib import Path from werkzeug.datastructures import FileStorage -from app.tools.LibPdf import upload_file +from app.tools.LibPdf import upload_file, allowed_file class TestFusionTestCase(unittest.TestCase): def setUp(self): - self.datadir = os.path.join(os.path.dirname(__file__)) + self.datadir = "upload/page1.pdf" + self.file_name = "page1.pdf" def test_fusion(self): - with open("page1.pdf", 'rb') as fp: + with open(self.file_name, 'rb') as fp: file = FileStorage(fp) upload_file(file, "upload") - # self.assertTrue(Path(self.datadir + "/testFusion.pdf").is_file(), "Pdf fusionne inexistant") - # self.assertTrue(len(get_pdf_from_directory(self.datadir)) > 0, "pdf non trouve") - # os.remove(self.datadir + "/testFusion.pdf") + self.assertTrue(Path(self.datadir).is_file(), "Pdf upload inexistant") + self.assertTrue(filecmp.cmp(self.datadir, self.file_name), "fichiers non identique") + self.assertTrue(allowed_file(self.file_name), "format non conforme") + + os.remove(self.datadir) From cf95abbf17316296d52fb35cc26a884b9179bfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20ARNAUDEAU?= Date: Fri, 31 Mar 2017 12:35:29 +0200 Subject: [PATCH 3/7] =?UTF-8?q?TG-36=20:=20LivretAPI=20->=20cr=C3=A9ation?= =?UTF-8?q?=20de=20livret=20+=20s=C3=A9curisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/OLA.mysql | 3 ++ backend/app/OLA_DATA.mysql | 18 ------- backend/app/api/GroupAPI.py | 26 +++++---- backend/app/api/LivretAPI.py | 98 ++++++++++++++++++---------------- backend/app/api/UserAPI.py | 24 ++++++--- backend/app/api/UserInfoAPI.py | 4 +- backend/app/api/mailsModels.py | 19 ++++++- 7 files changed, 104 insertions(+), 88 deletions(-) delete mode 100644 backend/app/OLA_DATA.mysql diff --git a/backend/OLA.mysql b/backend/OLA.mysql index 79529a8..80d8b2c 100644 --- a/backend/OLA.mysql +++ b/backend/OLA.mysql @@ -66,6 +66,7 @@ CREATE TABLE IF NOT EXISTS LIVRET contract_type INT NOT NULL, contract_start DATE NOT NULL, contract_end DATE NOT NULL, + description TEXT NOT NULL, ressources_dir VARCHAR(512), opened TINYINT(1) NOT NULL, expire DATE NOT NULL, @@ -143,3 +144,5 @@ CREATE UNIQUE INDEX user_email ON `USER` (`email`); CREATE UNIQUE INDEX user_hash ON `USER` (`hash`); +CREATE UNIQUE INDEX tutorship_unique_bygroup + ON `TUTORSHIP` (`group_id`, `student_id`); diff --git a/backend/app/OLA_DATA.mysql b/backend/app/OLA_DATA.mysql deleted file mode 100644 index 1daf4d5..0000000 --- a/backend/app/OLA_DATA.mysql +++ /dev/null @@ -1,18 +0,0 @@ -USE OLA; -INSERT INTO SETTINGS VALUES ('URL_BASE_DIRECTORY', '/OLA_RESSOURCES/', 'Répertoire base pour le dépot des fichiers'); -INSERT INTO SETTINGS VALUES ('OLA_URL', 'ola.univ-tlse2.fr/', 'URL de l application'); - -INSERT INTO `USER` VALUES (1, 'sec', '1', 'sec@univ-tlse2.fr', '01.23.45.67.89'); -INSERT INTO `USER` VALUES (2, 'etu1', '4', 'etu1@univ-tlse2.fr', '01.23.45.67.89'); -INSERT INTO `USER` VALUES (3, 'etu2', '4', 'etu2@univ-tlse2.fr', '01.23.45.67.89'); -INSERT INTO `USER` VALUES (4, 'etu3', '4', 'etu3@univ-tlse2.fr', '01.23.45.67.89'); -INSERT INTO `USER` VALUES (5, 'resp', '2-3', 'resp@univ-tlse2.fr', '01.23.45.67.89'); -INSERT INTO `USER` VALUES (6, 'tut', '3', 'tut@univ-tlse2.fr', '01.23.45.67.89'); - -INSERT INTO `GROUP` VALUES (1, 'M2_ICE_2016-2017_TEST', '2017', 'Master2 ICE', 'Master 2 Informatique Collaborative en Entreprise', 'Sciences du chômage proffessionnel', 5, 1, '/home/dan/PycharmProjects/OLA/backend/app/OLA_RESSOURCES/M2_ICE_2016-2017_TEST'); -INSERT INTO `GROUP` VALUES (2, 'M1_ICE_2016-2017_TEST', '2017', 'Master1 ICE', 'Master 1 Informatique Collaborative en Entreprise', 'Sciences du chômage proffessionnel', 5, 1, '/home/dan/PycharmProjects/OLA/backend/app/OLA_RESSOURCES/M1_ICE_2016-2017_TEST'); - -INSERT INTO TUTORSHIP VALUES (DEFAULT, 1, 2, 5); -INSERT INTO TUTORSHIP VALUES (DEFAULT, 2, 4, 5); -INSERT INTO TUTORSHIP VALUES (DEFAULT, 1, 3, 6); - diff --git a/backend/app/api/GroupAPI.py b/backend/app/api/GroupAPI.py index deaa810..781b566 100644 --- a/backend/app/api/GroupAPI.py +++ b/backend/app/api/GroupAPI.py @@ -12,6 +12,7 @@ class GroupAPI(Resource): """ Group Api Resource """ + @login_required(roles=[Roles.resp_formation]) def post(self): args = request.get_json(cache=False, force=True) @@ -48,8 +49,8 @@ class GroupAPI(Resource): "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "2" not in user['role'].split('-'): - role = user['role'] + "-2" + if str(Roles.resp_formation) not in user['role'].split('-'): + role = user['role'] + "-" + str(Roles.resp_formation) query = USER.update().values(role=role).where(USER.c.id == resp_id) query.execute() @@ -69,8 +70,8 @@ class GroupAPI(Resource): "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "1" not in user['role'].split('-'): - role = user['role'] + "-1" + if str(Roles.secretaire) not in user['role'].split('-'): + role = user['role'] + "-" + str(Roles.secretaire) query = USER.update().values(role=role).where(USER.c.id == sec_id) query.execute() @@ -86,6 +87,7 @@ class GroupAPI(Resource): return {"GID": res.lastrowid}, 201 + @login_required(roles=Roles.resp_formation) def put(self, gid): args = request.get_json(cache=False, force=True) if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): @@ -125,8 +127,8 @@ class GroupAPI(Resource): "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "2" not in user['role'].split('-'): - role = user['role'] + "-2" + if str(Roles.resp_formation) not in user['role'].split('-'): + role = user['role'] + "-" + str(Roles.resp_formation) query = USER.update().values(role=role).where(USER.c.id == resp_id) query.execute() @@ -146,8 +148,8 @@ class GroupAPI(Resource): "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "1" not in user['role'].split('-'): - role = user['role'] + "-1" + if str(Roles.secretaire) not in user['role'].split('-'): + role = user['role'] + "-" + str(Roles.secretaire) query = USER.update().values(role=role).where(USER.c.id == sec_id) query.execute() @@ -166,12 +168,14 @@ class GroupAPI(Resource): return {"GID": gid}, 200 + @login_required() def get(self, gid=0, name=""): if gid > 0: return {'GROUP': getGroup(gid=gid)}, 200 elif name != "": return {'GROUP': getGroup(name=name)}, 200 + @login_required(roles=Roles.resp_formation) def options(self, gid): args = request.get_json(cache=False, force=True) if not checkParams(['pairs'], args): @@ -188,16 +192,16 @@ class GroupAPI(Resource): stud = getUser(uid=p[0]) if stud is None: return {"ERROR": "The user with id " + str(p[0]) + " does not exists !"}, 400 - elif stud['role'] != "4": + elif stud['role'] != str(Roles.etudiant): return {"ERROR": "A student must have the 'student' role !"}, 400 tutor = getUser(uid=p[1]) if tutor is None: return {"ERROR": "The user with id " + str(p[1]) + " does not exists !"}, 400 - elif tutor['role'] == "4": + elif tutor['role'] == str(Roles.etudiant): return {"ERROR": "A student can't be a tutor !"}, 400 elif "3" not in tutor['role'].split('-'): - role = tutor['role'] + "-3" + role = tutor['role'] + "-" + str(Roles.tuteur_univ) query = USER.update().values(role=role).where(USER.c.id == p[1]) query.execute() except IndexError: diff --git a/backend/app/api/LivretAPI.py b/backend/app/api/LivretAPI.py index 0667f1c..596a054 100644 --- a/backend/app/api/LivretAPI.py +++ b/backend/app/api/LivretAPI.py @@ -1,80 +1,83 @@ import os +from datetime import datetime +from dateutil.relativedelta import relativedelta +from flask import session from flask_restful import Resource, request +from sqlalchemy import and_ from app.api import mailsModels -from app.model import Roles, getParam, getGroup, getUser, USER, GROUP, TUTORSHIP -from app.utils import send_mail, checkParams from app.api.LoginAPI import login_required +from app.model import Roles, getParam, getGroup, getUser, USER, GROUP, TUTORSHIP, LIVRET +from app.utils import send_mail, checkParams + class LivretAPI(Resource): """ Livret Api Resource """ + @login_required(roles=[Roles.etudiant]) def post(self): args = request.get_json(cache=False, force=True) - if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): + if not checkParams(['student_id', 'group_id', 'etutor_id', 'company_name', 'company_address', 'contract_type', + 'contract_start', 'contract_end', 'description'], args): return {"ERROR": "One or more parameters are missing !"}, 400 - name = args['name'] - year = args['year'] - class_short = args['class_short'] - class_long = args['class_long'] - department = args['department'] - resp_id = args['resp_id'] - sec_id = args['sec_id'] - res_dir = getParam('BASE_DIRECTORY') + name + "/" + user = session.get("user") + group_id = args['group_id'] + etutor_id = args['etutor_id'] + company_name = args['company_name'] + company_address = args['company_address'] + contract_type = int(args['contract_type']) + contract_start = datetime.strptime(args['contract_start'], "%d-%m-%Y") + contract_end = datetime.strptime(args['contract_end'], "%d-%m-%Y") + description = args['description'] mails = [] - group = getGroup(name=name) - if group is not None: - return {"GID": group["id"]}, 200 + group = getGroup(gid=group_id) + if group is None: + return {"ERROR": "This group does not exists !"}, 405 - user = getUser(uid=resp_id) + query = TUTORSHIP.select(and_(TUTORSHIP.c.group_id == group_id, TUTORSHIP.c.student_id == user["id"])) + res = query.execute() + tutorship = res.first() + + if tutorship is None: + return {"ERROR": "This student is not in this group !"}, 405 + + tutorship_id = tutorship.id + + user = getUser(uid=etutor_id) if user is None: - return {"ERROR": "The user with id " + str(resp_id) + " does not exists !"}, 400 + return {"ERROR": "The user with id " + str(etutor_id) + " does not exists !"}, 400 else: query = USER.select(USER.c.id == user["id"]) rows = query.execute() res = rows.first() if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_RESP_OF_GROUP", {"GROUP": name, - "URL": getParam('OLA_URL') + "registration/" - + res.hash}) - else: - mail = mailsModels.getMailContent("RESP_OF_GROUP", {"GROUP": name, - "URL": getParam('OLA_URL')}) - - mails.append((user["email"], mail)) - if "2" not in user['role'].split('-'): - role = user['role'] + "-2" - query = USER.update().values(role=role).where(USER.c.id == resp_id) - query.execute() - - user = getUser(uid=sec_id) - if user is None: - return {"ERROR": "The user with id " + str(sec_id) + " does not exists !"}, 400 - else: - query = USER.select(USER.c.id == user["id"]) - rows = query.execute() - res = rows.first() - if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_SEC_OF_GROUP", {"GROUP": name, + mail = mailsModels.getMailContent("NEW_ETUTOR_ADDED", {"GROUP": group["name"], "URL": getParam('OLA_URL') + "registration/" + res.hash}) else: - mail = mailsModels.getMailContent("SEC_OF_GROUP", {"GROUP": name, + mail = mailsModels.getMailContent("ETUTOR_ADDED", {"GROUP": group["name"], "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "1" not in user['role'].split('-'): - role = user['role'] + "-1" - query = USER.update().values(role=role).where(USER.c.id == sec_id) - query.execute() + if str(Roles.tuteur_entreprise) not in user['role'].split('-'): + return {"ERROR": "The user with id " + str(etutor_id) + + " doesn't have the 'etutor' role (" + str(Roles.tuteur_entreprise) + ") !"}, 400 - query = GROUP.insert().values(name=name, year=year, class_short=class_short, class_long=class_long, - department=department, resp_id=resp_id, sec_id=sec_id, ressources_dir=res_dir) + if contract_start > contract_end: + return {"ERROR": "The contract start can't be after its end !"}, 400 + + res_dir = group["ressources_dir"] + user['id'] + "/" + expire = datetime.now() + relativedelta(year=1) + + query = LIVRET.insert().values(tutorship_id=tutorship_id, etutor_id=etutor_id, company_name=company_name, + company_address=company_address, contract_type=contract_type, + contract_start=contract_start, contract_end=contract_end, + description=description, ressources_dir=res_dir, opened='1', expire=expire) res = query.execute() os.mkdir(res_dir) @@ -83,8 +86,9 @@ class LivretAPI(Resource): mail = m[1] send_mail(mail[0], addr, mail[1]) - return {"GID": res.lastrowid}, 201 + return {"LID": res.lastrowid}, 201 + @login_required(roles=[Roles.etudiant]) def put(self, gid): args = request.get_json(cache=False, force=True) if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): @@ -165,12 +169,14 @@ class LivretAPI(Resource): return {"GID": gid}, 200 + @login_required() def get(self, gid=0, name=""): if gid > 0: return {'GROUP': getGroup(gid=gid)}, 200 elif name != "": return {'GROUP': getGroup(name=name)}, 200 + @login_required(roles=Roles.etudiant) def options(self, gid): args = request.get_json(cache=False, force=True) if not checkParams(['pairs'], args): diff --git a/backend/app/api/UserAPI.py b/backend/app/api/UserAPI.py index 5b61425..3c2194d 100644 --- a/backend/app/api/UserAPI.py +++ b/backend/app/api/UserAPI.py @@ -1,10 +1,11 @@ from hashlib import sha256 +from flask import session from flask_restful import Resource, request +from app.api.LoginAPI import login_required from app.model import Roles, getUser, hashExists, USER from app.utils import checkParams, get_random_string -from app.api.LoginAPI import login_required class UserAPI(Resource): """ @@ -52,9 +53,14 @@ class UserAPI(Resource): password = sha256(psw.encode('utf-8')).hexdigest() - if getUser(uid=uid) is None: + user = getUser(uid=uid) + if user is None: return {"ERROR": "This user doesn't exists !"}, 405 + # On n'autorise pas de modifcation anonyme d'un profil s'il est déjà activé (si il a un mdp) + if user["password"] is not None and user["password"] != "" and session.get("user", None) is None: + return {"msg": "UNAUTHORIZED"}, 401 + if getUser(email=email) is not None: return {"ERROR": "A user with this email already exists !"}, 405 @@ -64,9 +70,11 @@ class UserAPI(Resource): return {"UID": uid}, 200 def get(self, uid=0, email="", hashcode=""): - if uid > 0: - return {'USER': getUser(uid=uid)}, 200 - elif email != "": - return {'USER': getUser(email=email)}, 200 - elif hashcode != "": - return {'USER': getUser(hashcode=hashcode)}, 200 + if session.get('user', None) is not None: + if uid > 0: + return {'USER': getUser(uid=uid)}, 200 + elif email != "": + return {'USER': getUser(email=email)}, 200 + + if hashcode != "": + return {'USER': getUser(hashcode=hashcode)}, 200 \ No newline at end of file diff --git a/backend/app/api/UserInfoAPI.py b/backend/app/api/UserInfoAPI.py index 5e2f6cc..eb46881 100644 --- a/backend/app/api/UserInfoAPI.py +++ b/backend/app/api/UserInfoAPI.py @@ -1,6 +1,6 @@ from flask import session from flask_restful import Resource -from app.api.LoginAPI import login_required + from app.model import LIVRET, TUTORSHIP, and_ @@ -9,7 +9,6 @@ class UserInfoAPI(Resource): UserInfo Api Resource """ - @login_required() def get(self): user = session.get("user", None) return {'USER': user}, 200 @@ -19,7 +18,6 @@ class UserGroupsAPI(Resource): """ UserGroups Api Resource """ - @login_required() def get(self): user = session.get("user", None) if user is not None: diff --git a/backend/app/api/mailsModels.py b/backend/app/api/mailsModels.py index e928d94..b92ee26 100644 --- a/backend/app/api/mailsModels.py +++ b/backend/app/api/mailsModels.py @@ -11,7 +11,7 @@ _STUD_OF_GROUP = ( _NEW_RESP_OF_GROUP = ("Votre compte OLA a été créé !", "Bonjour,

Votre compte vient d'être créé dans l'Outil du " "Livret de l'Alternant en tant que responsable du groupe #GROUPE. Vous pouvez dès " - "maintenant l'activer, en vous rendant à l'adresse :
" + "maintenant l'activer en vous rendant à l'adresse :
" "#URL

Bonne journée !

") _RESP_OF_GROUP = ( @@ -22,7 +22,7 @@ _RESP_OF_GROUP = ( _NEW_SEC_OF_GROUP = ("Votre compte OLA a été créé !", "Bonjour,

Votre compte vient d'être créé dans l'Outil du " "Livret de l'Alternant en tant que secrétaire du groupe #GROUPE. Vous pouvez dès " - "maintenant l'activer, en vous rendant à l'adresse :
" + "maintenant l'activer en vous rendant à l'adresse :
" "#URL

Bonne journée !

") _SEC_OF_GROUP = ( @@ -31,6 +31,17 @@ _SEC_OF_GROUP = ( "maintenant y accéder en vous rendant à l'adresse :
" "#URL

Bonne journée !

") +_NEW_ETUTOR_ADDED = ("Votre compte OLA a été créé !", "Bonjour,

Votre compte vient d'être créé dans l'Outil du " + "Livret de l'Alternant de l'Université Toulouse Jean-Jaurès en tant que tuteur dans le groupe #GROUPE. Vous pouvez dès " + "maintenant l'activer en vous rendant à l'adresse :
" + "#URL

Bonne journée !

") + +_ETUTOR_ADDED = ( + "Vous avez été déclaré comme tuteur dans OLA !", "Bonjour,

Votre compte vient d'être ajouté dans l'Outil du " + "Livret de l'Alternant de l'Université Toulouse Jean-Jaurès en tant que tuteur dans le groupe #GROUPE. Vous pouvez dès " + "maintenant accéder à votre compte en vous rendant à l'adresse :
" + "#URL

Bonne journée !

") + def getMailContent(mail_type, args): if mail_type == "NEW_STUD_OF_GROUP": @@ -45,6 +56,10 @@ def getMailContent(mail_type, args): mail = _NEW_SEC_OF_GROUP elif mail_type == "SEC_OF_GROUP": mail = _SEC_OF_GROUP + elif mail_type == "NEW_ETUTOR_ADDED": + mail = _NEW_ETUTOR_ADDED + elif mail_type == "ETUTOR_ADDED": + mail = _ETUTOR_ADDED else: raise Exception("Unknown mail type !") From 0891214b30a06a52744420c941e9d7bdfce697a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20ARNAUDEAU?= Date: Sat, 6 May 2017 18:03:15 +0200 Subject: [PATCH 4/7] TG-40 : LivretAPI -> finalisation de l'API des livrets + correctifs --- API_Interfaces.txt | 3 +- backend/OLA_DATA.mysql | 2 +- backend/app/api/LivretAPI.py | 173 ++++++++++----------------------- backend/app/api/LoginAPI.py | 7 +- backend/app/api/UserAPI.py | 4 +- backend/app/api/mailsModels.py | 6 +- backend/app/model.py | 60 ++++++++++-- backend/app/urls.py | 3 +- 8 files changed, 117 insertions(+), 141 deletions(-) diff --git a/API_Interfaces.txt b/API_Interfaces.txt index 9a73149..745aba2 100644 --- a/API_Interfaces.txt +++ b/API_Interfaces.txt @@ -1,7 +1,7 @@ ####################### LoginAPI (api/login) ####################### -GET -> Authentication method +POST -> Authentication method Out: 200 -> AUTH_RESULT = "OK" : Authentication sucessful 401 -> AUTH_RESULT = "AUTHENTICATION_FAILURE" : Wrong login/password @@ -27,6 +27,7 @@ POST -> Create a user if it not already exists In: email = Email and login of the user (must be unique) role = Role of the user (can be concatenated with -) 1=secrétaire, 2=resp_formation, 3=tuteur_univ, 4=étudiant, 5=tuteur_ent + name = Display name of the user Out: 200 -> UID = : The user already exists with the id USER_ID 201 -> UID = : The user has been successfully created with the id USER_ID diff --git a/backend/OLA_DATA.mysql b/backend/OLA_DATA.mysql index 7c8db3a..ed16388 100644 --- a/backend/OLA_DATA.mysql +++ b/backend/OLA_DATA.mysql @@ -1,7 +1,7 @@ USE OLA; INSERT INTO SETTINGS VALUES ('BASE_DIRECTORY', '/OLA_RESSOURCES/', 'Répertoire base pour le dépot des fichiers'); INSERT INTO SETTINGS VALUES ('TEMPLATES_DIRECTORY', '/OLA_TEMPLATES/', 'Répertoire base pour le dépot des fichiers'); -INSERT INTO SETTINGS VALUES ('OLA_URL', 'ola.univ-tlse2.fr/', 'URL de l application'); +INSERT INTO SETTINGS VALUES ('OLA_URL', 'http://ola.univ-tlse2.fr/', 'URL de l application'); INSERT INTO `USER` VALUES (1, '1', 'sec@univ-tlse2.fr', 'Secrétaire', DEFAULT, 'aZeRtYuIoP', '01.23.45.67.89'); INSERT INTO `USER` VALUES (2, '4', 'etu1@univ-tlse2.fr', 'Etudiant 1', DEFAULT, 'qSdFgHjKlM', '01.23.45.67.89'); diff --git a/backend/app/api/LivretAPI.py b/backend/app/api/LivretAPI.py index 596a054..0898901 100644 --- a/backend/app/api/LivretAPI.py +++ b/backend/app/api/LivretAPI.py @@ -1,14 +1,12 @@ import os -from datetime import datetime +from datetime import datetime, timedelta -from dateutil.relativedelta import relativedelta from flask import session from flask_restful import Resource, request -from sqlalchemy import and_ from app.api import mailsModels from app.api.LoginAPI import login_required -from app.model import Roles, getParam, getGroup, getUser, USER, GROUP, TUTORSHIP, LIVRET +from app.model import Roles, getParam, getGroup, getUser, USER, LIVRET, getLivret, getTutorship from app.utils import send_mail, checkParams @@ -20,7 +18,7 @@ class LivretAPI(Resource): @login_required(roles=[Roles.etudiant]) def post(self): args = request.get_json(cache=False, force=True) - if not checkParams(['student_id', 'group_id', 'etutor_id', 'company_name', 'company_address', 'contract_type', + if not checkParams(['group_id', 'etutor_id', 'company_name', 'company_address', 'contract_type', 'contract_start', 'contract_end', 'description'], args): return {"ERROR": "One or more parameters are missing !"}, 400 @@ -39,14 +37,16 @@ class LivretAPI(Resource): if group is None: return {"ERROR": "This group does not exists !"}, 405 - query = TUTORSHIP.select(and_(TUTORSHIP.c.group_id == group_id, TUTORSHIP.c.student_id == user["id"])) - res = query.execute() - tutorship = res.first() + tutorship = getTutorship(gid=group_id, student=user["id"]) if tutorship is None: return {"ERROR": "This student is not in this group !"}, 405 - tutorship_id = tutorship.id + tutorship_id = tutorship["id"] + + livret = getLivret(group_id=group_id, student_id=user["id"]) + if livret is not None: + return {"ERROR": "This livret already exists !"}, 405 user = getUser(uid=etutor_id) if user is None: @@ -56,11 +56,11 @@ class LivretAPI(Resource): rows = query.execute() res = rows.first() if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_ETUTOR_ADDED", {"GROUP": group["name"], + mail = mailsModels.getMailContent("NEW_ETUTOR_ADDED", {"GROUPE": group["name"], "URL": getParam('OLA_URL') + "registration/" + res.hash}) else: - mail = mailsModels.getMailContent("ETUTOR_ADDED", {"GROUP": group["name"], + mail = mailsModels.getMailContent("ETUTOR_ADDED", {"GROUPE": group["name"], "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) @@ -71,8 +71,8 @@ class LivretAPI(Resource): if contract_start > contract_end: return {"ERROR": "The contract start can't be after its end !"}, 400 - res_dir = group["ressources_dir"] + user['id'] + "/" - expire = datetime.now() + relativedelta(year=1) + res_dir = group["ressources_dir"] + "/" + str(user['id']) + "/" + expire = datetime.now() + timedelta(days=365) query = LIVRET.insert().values(tutorship_id=tutorship_id, etutor_id=etutor_id, company_name=company_name, company_address=company_address, contract_type=contract_type, @@ -89,139 +89,66 @@ class LivretAPI(Resource): return {"LID": res.lastrowid}, 201 @login_required(roles=[Roles.etudiant]) - def put(self, gid): + def put(self, lid): args = request.get_json(cache=False, force=True) - if not checkParams(['name', 'year', 'class_short', 'class_long', 'department', 'resp_id', 'sec_id'], args): + if not checkParams(['etutor_id', 'company_name', 'company_address', 'contract_type', + 'contract_start', 'contract_end', 'description'], args): return {"ERROR": "One or more parameters are missing !"}, 400 - name = args['name'].replace(" ", "_").replace("/", "-") - year = args['year'] - class_short = args['class_short'] - class_long = args['class_long'] - department = args['department'] - resp_id = args['resp_id'] - sec_id = args['sec_id'] - res_dir = getParam('BASE_DIRECTORY') + name + "/" + etutor_id = args['etutor_id'] + company_name = args['company_name'] + company_address = args['company_address'] + contract_type = int(args['contract_type']) + contract_start = datetime.strptime(args['contract_start'], "%d-%m-%Y") + contract_end = datetime.strptime(args['contract_end'], "%d-%m-%Y") + description = args['description'] mails = [] - group = getGroup(gid=gid) - if group is None: - return {"ERROR": "This group does not exists !"}, 405 + livret = getLivret(lid=lid) + if livret is None: + return {"ERROR": "This livret does not exists !"}, 405 - group2 = getGroup(name=name) - if group2 is not None: - return {"ERROR": "A group with this name already exists !"}, 405 - - user = getUser(uid=resp_id) + user = getUser(uid=etutor_id) if user is None: - return {"ERROR": "The user with id " + str(resp_id) + " does not exists !"}, 400 + return {"ERROR": "The user with id " + str(etutor_id) + " does not exists !"}, 400 else: query = USER.select(USER.c.id == user["id"]) rows = query.execute() res = rows.first() if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_RESP_OF_GROUP", {"GROUP": group["name"], - "URL": getParam('OLA_URL') + "registration/" - + res.hash}) + mail = mailsModels.getMailContent("NEW_ETUTOR_ADDED", + {"GROUPE": livret["tutorship_id"]["group_id"]["name"], + "URL": getParam('OLA_URL') + "registration/" + + res.hash}) else: - mail = mailsModels.getMailContent("RESP_OF_GROUP", {"GROUP": group["name"], - "URL": getParam('OLA_URL')}) - - mails.append((user["email"], mail)) - if "2" not in user['role'].split('-'): - role = user['role'] + "-2" - query = USER.update().values(role=role).where(USER.c.id == resp_id) - query.execute() - - user = getUser(uid=sec_id) - if user is None: - return {"ERROR": "The user with id " + str(sec_id) + " does not exists !"}, 400 - else: - query = USER.select(USER.c.id == user["id"]) - rows = query.execute() - res = rows.first() - if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_SEC_OF_GROUP", {"GROUP": group["name"], - "URL": getParam('OLA_URL') + "registration/" - + res.hash}) - else: - mail = mailsModels.getMailContent("SEC_OF_GROUP", {"GROUP": group["name"], + mail = mailsModels.getMailContent("ETUTOR_ADDED", {"GROUPE": livret["tutorship_id"]["group_id"]["name"], "URL": getParam('OLA_URL')}) mails.append((user["email"], mail)) - if "1" not in user['role'].split('-'): - role = user['role'] + "-1" - query = USER.update().values(role=role).where(USER.c.id == sec_id) - query.execute() + if str(Roles.tuteur_entreprise) not in user['role'].split('-'): + return {"ERROR": "The user with id " + str(etutor_id) + + " doesn't have the 'etutor' role (" + str(Roles.tuteur_entreprise) + ") !"}, 400 - query = GROUP.update().values(name=name, year=year, class_short=class_short, class_long=class_long, - department=department, resp_id=resp_id, sec_id=sec_id, ressources_dir=res_dir) \ - .where(GROUP.c.id == gid) + if contract_start > contract_end: + return {"ERROR": "The contract start can't be after its end !"}, 400 + + query = LIVRET.update().values(etutor_id=etutor_id, company_name=company_name, + company_address=company_address, contract_type=contract_type, + contract_start=contract_start, contract_end=contract_end, + description=description) \ + .where(LIVRET.c.id == lid) query.execute() - if group["ressources_dir"] != res_dir: - os.rename(group["ressources_dir"], res_dir) - for m in mails: addr = m[0] mail = m[1] send_mail(mail[0], addr, mail[1]) - return {"GID": gid}, 200 + return {"LID": lid}, 200 @login_required() - def get(self, gid=0, name=""): - if gid > 0: - return {'GROUP': getGroup(gid=gid)}, 200 - elif name != "": - return {'GROUP': getGroup(name=name)}, 200 - - @login_required(roles=Roles.etudiant) - def options(self, gid): - args = request.get_json(cache=False, force=True) - if not checkParams(['pairs'], args): - return {"ERROR": "One or more parameters are missing !"}, 400 - - pairs = args["pairs"] - - group = getGroup(gid=gid) - if group is None: - return {"ERROR": "This group does not exists !"}, 405 - - for p in pairs: - try: - stud = getUser(uid=p[0]) - if stud is None: - return {"ERROR": "The user with id " + str(p[0]) + " does not exists !"}, 400 - elif stud['role'] != "4": - return {"ERROR": "A student must have the 'student' role !"}, 400 - - tutor = getUser(uid=p[1]) - if tutor is None: - return {"ERROR": "The user with id " + str(p[1]) + " does not exists !"}, 400 - elif tutor['role'] == "4": - return {"ERROR": "A student can't be a tutor !"}, 400 - elif "3" not in tutor['role'].split('-'): - role = tutor['role'] + "-3" - query = USER.update().values(role=role).where(USER.c.id == p[1]) - query.execute() - except IndexError: - return {"ERROR": "Pairs are incorrectly formed !"}, 409 - - query = TUTORSHIP.insert().values(group_id=gid, student_id=p[0], ptutor_id=p[1]) - query.execute() - - query = USER.select(USER.c.id == stud["id"]) - rows = query.execute() - res = rows.first() - if res.hash is not None and len(res.hash) > 0: - mail = mailsModels.getMailContent("NEW_STUD_OF_GROUP", {"GROUP": group["name"], - "URL": getParam('OLA_URL') + "registration/" - + res.hash}) - else: - mail = mailsModels.getMailContent("STUD_OF_GROUP", {"GROUP": group["name"], - "URL": getParam('OLA_URL')}) - - send_mail(mail[0], stud["email"], mail[1]) - - return {"RESULT": "Pairs added successfully"}, 200 + def get(self, lid=0, group_id=0, student_id=0): + if lid > 0: + return {'LIVRET': getLivret(lid=lid)}, 200 + elif group_id > 0 and student_id > 0: + return {'LIVRET': getLivret(group_id=group_id, student_id=student_id)}, 200 diff --git a/backend/app/api/LoginAPI.py b/backend/app/api/LoginAPI.py index b388529..e99686a 100644 --- a/backend/app/api/LoginAPI.py +++ b/backend/app/api/LoginAPI.py @@ -50,14 +50,13 @@ class LoginAPI(Resource): return {'AUTH_RESULT': 'OK'}, 200 - def login_required(roles=[]): def my_login_required(func): - def wrapper(*args): + def wrapper(*args, **kvargs): current_user = session.get('user', None) if current_user is None or (len(roles) != 0 and not sum([1 for x in current_user['role'].split("-") if int(x) in roles]) > 0): - return {"msg": "UNAUTHORIZED"}, 401 - return func(*args) + return {"ERROR": "UNAUTHORIZED"}, 401 + return func(*args, **kvargs) return wrapper return my_login_required diff --git a/backend/app/api/UserAPI.py b/backend/app/api/UserAPI.py index 3c2194d..9b14a41 100644 --- a/backend/app/api/UserAPI.py +++ b/backend/app/api/UserAPI.py @@ -7,11 +7,13 @@ from app.api.LoginAPI import login_required from app.model import Roles, getUser, hashExists, USER from app.utils import checkParams, get_random_string + class UserAPI(Resource): """ User Api Resource """ - @login_required(roles=[Roles.resp_formation]) + + @login_required(roles=[Roles.resp_formation, Roles.etudiant]) def post(self): args = request.get_json(cache=False, force=True) if not checkParams(['role', 'email', 'name'], args): diff --git a/backend/app/api/mailsModels.py b/backend/app/api/mailsModels.py index b92ee26..c64d0dd 100644 --- a/backend/app/api/mailsModels.py +++ b/backend/app/api/mailsModels.py @@ -63,6 +63,8 @@ def getMailContent(mail_type, args): else: raise Exception("Unknown mail type !") + obj = mail[0] + content = str(mail[1]) for key, value in args.items(): - mail[1].replace("#" + key, value) - return mail + content = content.replace("#" + key, value) + return (obj, content) diff --git a/backend/app/model.py b/backend/app/model.py index 6179978..5861f3b 100644 --- a/backend/app/model.py +++ b/backend/app/model.py @@ -75,15 +75,59 @@ def getGroup(gid=0, name=""): return None -def getTutorshipForStudent(gid, student): - query = TUTORSHIP.select(and_(TUTORSHIP.c.group_id == gid, TUTORSHIP.c.student_id == student)) - rows = query.execute() - res = rows.first() - if res is not None: - return {"id": res.id, "group_id": getGroup(gid=res.group_id), "student_id": getUser(uid=res.student_id), - "ptutor_id": getUser(uid=res.ptutor_id)} +def getTutorship(tid=0, gid=0, student=0): + if tid == 0 and gid == 0 and student == 0: + raise Exception("getGroup must be called with at least one argument !") else: - return None + if gid != 0: + query = TUTORSHIP.select(and_(TUTORSHIP.c.group_id == gid, TUTORSHIP.c.student_id == student)) + rows = query.execute() + res = rows.first() + elif tid != 0: + query = TUTORSHIP.select(TUTORSHIP.c.id == tid) + rows = query.execute() + res = rows.first() + else: + raise Exception("getTutorship must be called with two parameter for group+student search !") + + if res is not None: + return {"id": res.id, "group_id": getGroup(gid=res.group_id), "student_id": getUser(uid=res.student_id), + "ptutor_id": getUser(uid=res.ptutor_id)} + else: + return None + + +def getLivret(lid=0, group_id=0, student_id=0): + res = None + + if lid == 0 and student_id == "": + raise Exception("getLivret must be called with at least one argument !") + else: + if lid != 0: + query = LIVRET.select(LIVRET.c.id == lid) + rows = query.execute() + res = rows.first() + + elif student_id != 0 and group_id != 0: + tutorship = getTutorship(gid=group_id, student=student_id) + if tutorship is None: + return None + query = LIVRET.select(LIVRET.c.tutorship_id == tutorship["id"]) + rows = query.execute() + res = rows.first() + else: + raise Exception("getLivret must be called with two parameter for group+student search !") + + if res is not None: + return {"id": res.id, "tutorship_id": getTutorship(tid=res.tutorship_id), + "etutor_id": getUser(uid=res.etutor_id), "company_name": res.company_name, + "company_address": res.company_address, "contract_type": res.contract_type, + "contract_start": res.contract_start.strftime('%d-%m-%Y'), + "contract_end": res.contract_end.strftime('%d-%m-%Y'), + "ressources_dir": res.ressources_dir, "opened": res.opened, + "expire": res.expire.strftime('%d-%m-%Y')} + else: + return None def hashExists(test): diff --git a/backend/app/urls.py b/backend/app/urls.py index 5ccd60b..2a0027d 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -14,4 +14,5 @@ api.add_resource(UserGroupsAPI, '/api/userGroups') api.add_resource(UserAPI, '/api/user', '/api/user/byuid/', '/api/user/byemail/', '/api/user/byhash/') api.add_resource(GroupAPI, '/api/group', '/api/group/bygid/', '/api/group/byname/') -api.add_resource(LivretAPI, '/api/livret', '/api/livret/byuid/') +api.add_resource(LivretAPI, '/api/livret', '/api/livret/bylid/', + '/api/livret/bytutorship//') From 5641f8ebd98f4506699e44d37d1f899e6b211690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20ARNAUDEAU?= Date: Mon, 8 May 2017 15:48:12 +0200 Subject: [PATCH 5/7] TG-40 : LivretAPI -> MAJ de l'interface --- API_Interfaces.txt | 48 ++++++++++++++++++++++++++++++++++++ backend/app/api/LivretAPI.py | 6 ++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/API_Interfaces.txt b/API_Interfaces.txt index 745aba2..b1c2634 100644 --- a/API_Interfaces.txt +++ b/API_Interfaces.txt @@ -2,6 +2,9 @@ LoginAPI (api/login) ####################### POST -> Authentication method + In: + email = Email and login of the user (must be unique) + password = Password of the user (secured by HTTPS) Out: 200 -> AUTH_RESULT = "OK" : Authentication sucessful 401 -> AUTH_RESULT = "AUTHENTICATION_FAILURE" : Wrong login/password @@ -99,3 +102,48 @@ OPTIONS -> Add pairs of users (student/tutor) to the group 400 -> ERROR = "A student can't be a tutor !" : The given USER_ID for tutor have the "student" role (4) and so can't be a tutor 405 -> ERROR = "This group doesn't exists !" : Bad GROUP_ID provided 409 -> ERROR = "Pairs are incorrectly formed !" : Bad syntax in pairs table + +######################## +LivretAPI (api/livret) +######################## +POST -> Create a livret if it not already exists + In: + group_id = Id of the group where this livret should be inserted in (we must have only one livret per student in a single group) + etutor_id = UID of the company tutor + company_name = Name of the company + company_address = Mail address of the company + contract_type = Type of the internship contract (1 = contrat d'alternance, 2 = contrat de professionnalisation, 3 = stage) + contract_start = Date of the contract's beginning (format : dd-mm-yyyy) + contract_end = Date of the contract's end (format : dd-mm-yyyy) + description = Description of the internship missions and activities overview + Out: + 200 -> LID = : The livret already exists with the id LIVRET_ID + 201 -> LID = : The livret has been successfully created with the id LIVRET_ID + 400 -> ERROR = "One or more parameters are missing" : Bad request + 400 -> ERROR = "The user with id doesn't exists !" : The given USER_ID for etutor is not found + 400 -> ERROR = "An etutor must have the 'etutor' role !" : The given USER_ID for etutor doesn't have the "etutor" role (5) + 400 -> ERROR = "The contract start can't be after its end !" : The given contract's end date is anterior to it's beginning + 405 -> ERROR = "The group with id GROUP_ID doesn't exists !" : The given GROUP_ID is not found + 405 -> ERROR = "The The current student is not registered in the group !" : The currently logged student is not affected to the specified GROUP_ID + +PUT -> Modify an existing livret + In: (Suffix = /bylid/) + etutor_id = UID of the company tutor + company_name = Name of the company + company_address = Mail address of the company + contract_type = Type of the internship contract (1 = contrat d'alternance, 2 = contrat de professionnalisation, 3 = stage) + contract_start = Date of the contract's beginning (format : dd-mm-yyyy) + contract_end = Date of the contract's end (format : dd-mm-yyyy) + description = Description of the internship missions and activities overview + Out: + 200 -> LID = : The livret has been modified successfully with the id LIVRET_ID + 400 -> ERROR = "One or more parameters are missing !" : Bad request + 400 -> ERROR = "The user with id doesn't exists !" : The given USER_ID for etutor is not found + 400 -> ERROR = "An etutor must have the 'etutor' role !" : The given USER_ID for etutor doesn't have the "etutor" role (5) + 400 -> ERROR = "The contract start can't be after its end !" : The given contract's end date is anterior to it's beginning + 405 -> ERROR = "This group doesn't exists !" : Bad LIVRET_ID provided + +GET -> Getting specified livret infos + In: (Suffixes = /bylid/ | /bytutorship// ) + Out: + 200 -> LIVRET = |null : Dictionary containing livret infos or null \ No newline at end of file diff --git a/backend/app/api/LivretAPI.py b/backend/app/api/LivretAPI.py index 0898901..02f6f9d 100644 --- a/backend/app/api/LivretAPI.py +++ b/backend/app/api/LivretAPI.py @@ -35,18 +35,18 @@ class LivretAPI(Resource): group = getGroup(gid=group_id) if group is None: - return {"ERROR": "This group does not exists !"}, 405 + return {"ERROR": "This group with id " + str(group_id) + "does not exists !"}, 405 tutorship = getTutorship(gid=group_id, student=user["id"]) if tutorship is None: - return {"ERROR": "This student is not in this group !"}, 405 + return {"ERROR": "The current student is not registered in the group" + str(group_id) + " !"}, 405 tutorship_id = tutorship["id"] livret = getLivret(group_id=group_id, student_id=user["id"]) if livret is not None: - return {"ERROR": "This livret already exists !"}, 405 + return {"LID": livret["id"]}, 200 user = getUser(uid=etutor_id) if user is None: From 335bd04803cb1890c966f69ca22e424b3ea6d75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20ARNAUDEAU?= Date: Mon, 8 May 2017 19:51:28 +0200 Subject: [PATCH 6/7] =?UTF-8?q?TG-40=20:=20PeriodAPI=20+=20S=C3=A9curisati?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/GetAllAPI.py | 31 +++++++++ backend/app/api/LivretAPI.py | 19 ++++-- backend/app/api/PeriodAPI.py | 116 +++++++++++++++++++++++++++++++++ backend/app/api/mailsModels.py | 27 +++++++- backend/app/core.py | 6 ++ backend/app/model.py | 18 +++++ backend/app/urls.py | 4 ++ 7 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 backend/app/api/GetAllAPI.py create mode 100644 backend/app/api/PeriodAPI.py diff --git a/backend/app/api/GetAllAPI.py b/backend/app/api/GetAllAPI.py new file mode 100644 index 0000000..9811d6d --- /dev/null +++ b/backend/app/api/GetAllAPI.py @@ -0,0 +1,31 @@ +from flask import session +from flask_restful import Resource + +from app.api.LoginAPI import login_required +from app.model import getLivret, PERIOD, getPeriod + + +class GetAllAPI(Resource): + """ + GetAll Api Resource + Renvoie toutes les occurences correspondant à un critère + """ + + @login_required() + def get(self, what, value): + user = session.get("user") + result = [] + + if what == "periodsOfLivret": # Toutes les périodes associées à un livret + if value > 0: + livret = getLivret(lid=value) + if livret is None: + return {"ERROR": "This livret doesn't exists !"}, 405 + query = PERIOD.select(PERIOD.c.livret_id == value) + res = query.execute() + for row in res: + result.append(getPeriod(pid=row.id)) + else: + return {'ERROR': 'Unkown parameter :' + str(what)}, 200 + + return {'RESULT': result}, 200 diff --git a/backend/app/api/LivretAPI.py b/backend/app/api/LivretAPI.py index 02f6f9d..4ce7d3a 100644 --- a/backend/app/api/LivretAPI.py +++ b/backend/app/api/LivretAPI.py @@ -48,11 +48,15 @@ class LivretAPI(Resource): if livret is not None: return {"LID": livret["id"]}, 200 - user = getUser(uid=etutor_id) - if user is None: + # On vérifie que l'utilisateur actuel a le droit de modifier ce livret + if user["id"] != livret["tutorship_id"]["student_id"]: + return {"ERROR": "UNAUTHORIZED"}, 401 + + user2 = getUser(uid=etutor_id) + if user2 is None: return {"ERROR": "The user with id " + str(etutor_id) + " does not exists !"}, 400 else: - query = USER.select(USER.c.id == user["id"]) + query = USER.select(USER.c.id == user2["id"]) rows = query.execute() res = rows.first() if res.hash is not None and len(res.hash) > 0: @@ -63,8 +67,8 @@ class LivretAPI(Resource): mail = mailsModels.getMailContent("ETUTOR_ADDED", {"GROUPE": group["name"], "URL": getParam('OLA_URL')}) - mails.append((user["email"], mail)) - if str(Roles.tuteur_entreprise) not in user['role'].split('-'): + mails.append((user2["email"], mail)) + if str(Roles.tuteur_entreprise) not in user2['role'].split('-'): return {"ERROR": "The user with id " + str(etutor_id) + " doesn't have the 'etutor' role (" + str(Roles.tuteur_entreprise) + ") !"}, 400 @@ -108,6 +112,11 @@ class LivretAPI(Resource): if livret is None: return {"ERROR": "This livret does not exists !"}, 405 + # On vérifie que l'utilisateur actuel a le droit de modifier ce livret + user = session.get("user") + if user["id"] != livret["tutorship_id"]["student_id"]: + return {"ERROR": "UNAUTHORIZED"}, 401 + user = getUser(uid=etutor_id) if user is None: return {"ERROR": "The user with id " + str(etutor_id) + " does not exists !"}, 400 diff --git a/backend/app/api/PeriodAPI.py b/backend/app/api/PeriodAPI.py new file mode 100644 index 0000000..1cd752d --- /dev/null +++ b/backend/app/api/PeriodAPI.py @@ -0,0 +1,116 @@ +import os +from datetime import datetime + +from flask import session +from flask_restful import Resource, request +from sqlalchemy import select, and_ + +from app.api import mailsModels +from app.api.LoginAPI import login_required +from app.model import Roles, getParam, getGroup, getUser, LIVRET, getLivret, TUTORSHIP, PERIOD, getPeriod, \ + TypesPeriode +from app.utils import send_mail, checkParams, get_random_string + + +class PeriodAPI(Resource): + """ + Period Api Resource + """ + + @login_required(roles=[Roles.resp_formation]) + def post(self): + args = request.get_json(cache=False, force=True) + if not checkParams(['group_id', 'period_type', 'start', 'end'], args): + return {"ERROR": "One or more parameters are missing !"}, 400 + + group_id = args['group_id'] + period_type = args['period_type'] + start = datetime.strptime(args['start'], "%d-%m-%Y") + end = datetime.strptime(args['end'], "%d-%m-%Y") + + # On vérifie que le groupe existe + group = getGroup(gid=group_id) + if group is None: + return {"ERROR": "This group with id " + str(group_id) + "does not exists !"}, 405 + + if start > end: + return {"ERROR": "The period's start can't be after its end !"}, 400 + + # On vérifie que l'utilisateur actuel a le droit de modifier ce groupe + user = session.get("user") + if user["id"] != group["resp_id"]: + return {"ERROR": "UNAUTHORIZED"}, 401 + + # On récupère tous les livrets de ce groupe + query = select([LIVRET.c.id, TUTORSHIP.c.student_id]).where( + and_(TUTORSHIP.c.id == LIVRET.c.tutorship_id, TUTORSHIP.c.group_id == group_id)) + res = query.execute() + + # Pour chaque livret du groupe on ajoute la période et on prévient l'étudiant + for row in res: + # On crée un répertoire avec un nom aléatoire + res_dir = group["ressources_dir"] + "/" + str(row.student_id) + "/" + get_random_string() + "/" + while os.path.exists(res_dir): + res_dir = group["ressources_dir"] + "/" + str(row.student_id) + "/" + get_random_string() + "/" + + # Enregistrement des infos en base + query = PERIOD.insert().values(livret_id=row.id, type=period_type, start=start, end=end, + ressources_dir=res_dir) + query.execute() + os.mkdir(res_dir) + + # Envoi d'un mail + mail = mailsModels.getMailContent("NEW_PERIOD", {"GROUPE": group["name"], + "URL": getParam('OLA_URL') + "mon_livret"}) + send_mail(mail[0], getUser(row.student_id)["email"], mail[1]) + + return {"RESULT": "OK"}, 201 + + @login_required(roles=[Roles.etudiant, Roles.tuteur_entreprise]) + def put(self, pid): + args = request.get_json(cache=False, force=True) + if not checkParams(['text'], args): + return {"ERROR": "One or more parameters are missing !"}, 400 + + text = args['text'] + user = session.get("user") + mails = [] + + # On vérifie que la période existe + period = getPeriod(pid) + if period is None: + return {"ERROR": "This period does not exists !"}, 405 + + # On vérifie que l'utilisateur actuel a le droit de modifier ce livret (étudiant ou tuteur) + livret = getLivret(lid=period["livret_id"]) + if user["id"] != livret["etutor_id"]["id"] and user["id"] != livret["tutorship_id"]["student_id"]["id"]: + return {"ERROR": "UNAUTHORIZED"}, 401 + + # Si c'est le commentaire de l'étudiant, on prévient le tuteur + if user["role"] == str(Roles.etudiant): + mail = mailsModels.getMailContent("STUD_COMMENT_ADDED", {"ETUDIANT": user["name"], + "URL": getParam('OLA_URL')}) + mails.append((user["email"], mail)) + query = PERIOD.update().values(student_desc=text).where(PERIOD.c.id == pid) + else: # Sinon on vérifie que c'est une période d'entreprise + if period["type"] == TypesPeriode.universitaire: + return {"ERROR": "A tutor can't modify a university period !"} + + mail = mailsModels.getMailContent("ETUTOR_COMMENT_ADDED", {"TUTEUR": user["name"], + "URL": getParam('OLA_URL')}) + mails.append((user["email"], mail)) + query = PERIOD.update().values(etutor_desc=text).where(PERIOD.c.id == pid) + + query.execute() + + for m in mails: + addr = m[0] + mail = m[1] + send_mail(mail[0], addr, mail[1]) + + return {"PID": pid}, 200 + + @login_required() + def get(self, pid): + if pid > 0: + return {'PERIOD': getPeriod(pid=pid)}, 200 diff --git a/backend/app/api/mailsModels.py b/backend/app/api/mailsModels.py index c64d0dd..4fd4cb5 100644 --- a/backend/app/api/mailsModels.py +++ b/backend/app/api/mailsModels.py @@ -42,6 +42,25 @@ _ETUTOR_ADDED = ( "maintenant accéder à votre compte en vous rendant à l'adresse :
" "#URL

Bonne journée !

") +_NEW_PERIOD = ( + "Nouvelle période ouverte dans OLA !", "Bonjour,

Une nouvelle période vient d'être crée sur l'Outil du " + "Livret de l'Alternant dans le groupe #GROUPE. Vous pouvez dès " + "maintenant entrer vos commentaires en vous rendant à l'adresse :
" + "#URL

Bonne journée !

") + +_STUD_COMMENT_ADDED = ( + "Livret de l'alternant mis à jour !", "Bonjour,

#ETUDIANT vient de mettre à jour son livret sur l'Outil du " + "Livret de l'Alternant. Vous pouvez dès " + "maintenant entrer à votre tour vos commentaires en vous rendant à l'adresse :
" + "#URL

Bonne journée !

") + +_ETUTOR_COMMENT_ADDED = ( + "Livret de l'alternant mis à jour !", "Bonjour,

#TUTEUR vient de mettre à jour son livret sur l'Outil du " + "Livret de l'Alternant. Vous pouvez visualiser ces modifcations" + " en vous rendant à l'adresse :
" + "#URL

Bonne journée !

") + + def getMailContent(mail_type, args): if mail_type == "NEW_STUD_OF_GROUP": @@ -60,8 +79,14 @@ def getMailContent(mail_type, args): mail = _NEW_ETUTOR_ADDED elif mail_type == "ETUTOR_ADDED": mail = _ETUTOR_ADDED + elif mail_type == "NEW_PERIOD": + mail = _NEW_PERIOD + elif mail_type == "STUD_COMMENT_ADDED": + mail = _STUD_COMMENT_ADDED + elif mail_type == "ETUTOR_COMMENT_ADDED": + mail = _ETUTOR_COMMENT_ADDED else: - raise Exception("Unknown mail type !") + raise Exception("Unknown mail type : " + str(mail_type)) obj = mail[0] content = str(mail[1]) diff --git a/backend/app/core.py b/backend/app/core.py index 2b1ac28..0c59ffe 100644 --- a/backend/app/core.py +++ b/backend/app/core.py @@ -45,3 +45,9 @@ def after_login(): # import api resources importlib.import_module("app.urls") + + +@app.teardown_request +def shutdown_session(exception=None): + engine.dispose() + db.session.remove() diff --git a/backend/app/model.py b/backend/app/model.py index 5861f3b..495cddc 100644 --- a/backend/app/model.py +++ b/backend/app/model.py @@ -130,6 +130,19 @@ def getLivret(lid=0, group_id=0, student_id=0): return None +def getPeriod(pid): + query = PERIOD.select(PERIOD.c.id == pid) + rows = query.execute() + res = rows.first() + + if res is not None: + return {"id": res.id, "livret_id": res.livret_id, "type": res.type, "start": res.start.strftime('%d-%m-%Y'), + "end": res.end.strftime('%d-%m-%Y'), "student_desc": res.student_desc, "etutor_desc": res.etutor_desc, + "ressources_dir": res.ressources_dir} + else: + return None + + def hashExists(test): query = USER.select(USER.c.hash == test) rows = query.execute() @@ -143,3 +156,8 @@ class Roles: tuteur_univ = 3 etudiant = 4 tuteur_entreprise = 5 + + +class TypesPeriode: + universitaire = 1 + entreprise = 2 diff --git a/backend/app/urls.py b/backend/app/urls.py index 2a0027d..6c3df66 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -1,6 +1,8 @@ +from app.api.GetAllAPI import GetAllAPI from app.api.GroupAPI import GroupAPI from app.api.LivretAPI import LivretAPI from app.api.LoginAPI import LoginAPI +from app.api.PeriodAPI import PeriodAPI from app.api.UserAPI import UserAPI from app.api.UserInfoAPI import UserInfoAPI, UserGroupsAPI from app.api.exampleapi import SomeApi @@ -16,3 +18,5 @@ api.add_resource(UserAPI, '/api/user', '/api/user/byuid/', '/api/user/b api.add_resource(GroupAPI, '/api/group', '/api/group/bygid/', '/api/group/byname/') api.add_resource(LivretAPI, '/api/livret', '/api/livret/bylid/', '/api/livret/bytutorship//') +api.add_resource(PeriodAPI, '/api/period', '/api/period/bypid/') +api.add_resource(GetAllAPI, '/api/getAll//') From 2e8075b25b7257b38a62b25fa817468e20306a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20ARNAUDEAU?= Date: Tue, 9 May 2017 13:32:01 +0200 Subject: [PATCH 7/7] TG-40 : MAJ de l'interface --- API_Interfaces.txt | 48 +++++++++++++++++++++++++++++++++++- backend/app/api/PeriodAPI.py | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/API_Interfaces.txt b/API_Interfaces.txt index b1c2634..b5a9ad7 100644 --- a/API_Interfaces.txt +++ b/API_Interfaces.txt @@ -23,6 +23,7 @@ GET -> Get the current logged user, return None if no one is connected Out: 200 -> USER = |null : Dictionary containing user infos or null + ######################## UserAPI (api/user) ######################## @@ -52,6 +53,7 @@ GET -> Getting specified user infos Out: 200 -> USER = |null : Dictionary containing user infos or null + ######################## GroupAPI (api/group) ######################## @@ -103,6 +105,7 @@ OPTIONS -> Add pairs of users (student/tutor) to the group 405 -> ERROR = "This group doesn't exists !" : Bad GROUP_ID provided 409 -> ERROR = "Pairs are incorrectly formed !" : Bad syntax in pairs table + ######################## LivretAPI (api/livret) ######################## @@ -146,4 +149,47 @@ PUT -> Modify an existing livret GET -> Getting specified livret infos In: (Suffixes = /bylid/ | /bytutorship// ) Out: - 200 -> LIVRET = |null : Dictionary containing livret infos or null \ No newline at end of file + 200 -> LIVRET = |null : Dictionary containing livret infos or null + + +######################## +PeriodAPI (api/period) +######################## +POST -> Create a period for all livrets in a group + In: + group_id = Id of the group where this period should be inserted in + period_type = Type of the period (1 = universitaire, 2 = entreprise) + start = Date of the period's beginning (format : dd-mm-yyyy) + end = Date of the period's end (format : dd-mm-yyyy) + Out: + 200 -> RESULT = OK : The period has been successfully created in all the livrets in the given group + 400 -> ERROR = "One or more parameters are missing" : Bad request + 401 -> ERROR = "UNAUTHORIZED" : The current user is not allowed to modify this group (only the group's resp can do it) + 400 -> ERROR = "The period's start can't be after its end !" : The given period's end date is anterior to it's beginning + 405 -> ERROR = "The group with id GROUP_ID doesn't exists !" : The given GROUP_ID is not found + +PUT -> Add the comments of a user in an existing period + In: (Suffix = /bypid/) + text = Comment added by the user about the period (student or etutor) + Out: + 200 -> PID = : The period has been modified successfully with the id PERIOD_ID + 400 -> ERROR = "One or more parameters are missing !" : Bad request + 400 -> ERROR = "This period doesn't exists !" : Bad PERIOD_ID provided + 401 -> ERROR = "UNAUTHORIZED" : The current user is not allowed to modify this group (only the student and his etutor can do it) + 405 -> ERROR = "A tutor can't modify a university period !" : A tutor can't modify a university period :) + +GET -> Getting specified period infos + In: (Suffix = /bypid/) + Out: + 200 -> PERIOD = |null : Dictionary containing period infos or null + + +######################## +GetAllAPI (api/getAll) +######################## +GET -> Getting specified period infos + In: (Suffix = //) + Parameters for / : + periodsOfLivret/ : Returns all the periods associated to the given + Out: + 200 -> RESULT = \ No newline at end of file diff --git a/backend/app/api/PeriodAPI.py b/backend/app/api/PeriodAPI.py index 1cd752d..975597f 100644 --- a/backend/app/api/PeriodAPI.py +++ b/backend/app/api/PeriodAPI.py @@ -94,7 +94,7 @@ class PeriodAPI(Resource): query = PERIOD.update().values(student_desc=text).where(PERIOD.c.id == pid) else: # Sinon on vérifie que c'est une période d'entreprise if period["type"] == TypesPeriode.universitaire: - return {"ERROR": "A tutor can't modify a university period !"} + return {"ERROR": "A tutor can't modify a university period !"}, 405 mail = mailsModels.getMailContent("ETUTOR_COMMENT_ADDED", {"TUTEUR": user["name"], "URL": getParam('OLA_URL')})