Merge branch 'master' of https://github.com/ariasia/OLA_Mirror
This commit is contained in:
commit
8c97b49a67
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
.idea
|
||||
|
||||
.directory
|
||||
|
@ -20,20 +20,13 @@ GET -> Get the current logged user, return None if no one is connected
|
||||
Out:
|
||||
200 -> USER = <USER_OBJECT>|null : Dictionary containing user infos or null
|
||||
|
||||
|
||||
########################
|
||||
Redirect to cas auth (/login)
|
||||
########################
|
||||
Redirect to cas auth
|
||||
|
||||
|
||||
########################
|
||||
UserAPI (api/user)
|
||||
########################
|
||||
POST -> Create a user if it not already exists
|
||||
In:
|
||||
CASid = Login of the user caught from the CAS authentication
|
||||
role = Role of the user (can be concatenated with -) 1=secrétaire, 2=resp_formation, 3=tuteur_univ, 4=étudiant
|
||||
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
|
||||
Out:
|
||||
200 -> UID = <USER_ID> : The user already exists with the id USER_ID
|
||||
201 -> UID = <USER_ID> : The user has been successfully created with the id USER_ID
|
||||
@ -41,17 +34,67 @@ POST -> Create a user if it not already exists
|
||||
|
||||
PUT -> Modify an existing user
|
||||
In: (Suffix = /byuid/<USER_ID>)
|
||||
CASid = Login of the user caught from the CAS authentication
|
||||
role = Role of the user (can be concatenated with -) 1=secrétaire, 2=resp_formation, 3=tuteur_univ, 4=étudiant
|
||||
role = Role of the user (can be concatenated with -) 1=secrétaire, 2=resp_formation, 3=tuteur_univ, 4=étudiant, 5=tuteur_ent
|
||||
phone = Phone number of the user (00.00.00.00.00)
|
||||
email = Email of the user
|
||||
email = Email of the user (must be unique)
|
||||
Out:
|
||||
200 -> UID = <USER_ID> : The user has been modified sucessfully with the id USER_ID
|
||||
400 -> ERROR = "One or more parameters are missing !" : Bad request
|
||||
405 -> ERROR = "This user doesn't exists !" : Bad USER_ID provided
|
||||
405 -> ERROR = "A user with this email already exists !" : A user with this email already exists
|
||||
|
||||
GET -> Getting specified user infos
|
||||
In: (Suffixes = /byuid/<USER_ID> | /bylogin/<USER_LOGIN> | /byemail/<USER_EMAIL>)
|
||||
In: (Suffixes = /byuid/<USER_ID> | /byemail/<USER_EMAIL>)
|
||||
Out:
|
||||
200 -> USER = <USER_OBJECT>|null : Dictionary containing user infos or null
|
||||
|
||||
########################
|
||||
GroupAPI (api/group)
|
||||
########################
|
||||
POST -> Create a group if it not already exists
|
||||
In:
|
||||
name = Name of the group (must be unique)
|
||||
year = Parameter setting the year
|
||||
class_short = Parameter setting the short name of the class
|
||||
class_long = Parameter setting the full name of the class
|
||||
department = Parameter setting the name of the class's department
|
||||
resp_id = UID of the group's responsible
|
||||
sec_id = UID of the group's secretary
|
||||
Out:
|
||||
200 -> GID = <GROUP_ID> : The group already exists with the id GROUP_ID
|
||||
201 -> GID = <GROUP_ID> : The group has been successfully created with the id GROUP_ID
|
||||
400 -> ERROR = "One or more parameters are missing" : Bad request
|
||||
400 -> ERROR = "The user with id <USER_ID> doesn't exists !" : The given USER_ID for resp_id or sec_id is not found
|
||||
|
||||
PUT -> Modify an existing group
|
||||
In: (Suffix = /bygid/<GROUP_ID>)
|
||||
name = Name of the group (must be unique)
|
||||
year = Parameter setting the year
|
||||
class_short = Parameter setting the short name of the class
|
||||
class_long = Parameter setting the full name of the class
|
||||
department = Parameter setting the name of the class's department
|
||||
resp_id = UID of the group's responsible
|
||||
sec_id = UID of the group's secretary
|
||||
Out:
|
||||
200 -> GID = <GROUP_ID> : The group has been modified sucessfully with the id GROUP_ID
|
||||
400 -> ERROR = "One or more parameters are missing !" : Bad request
|
||||
400 -> ERROR = "The user with id <USER_ID> doesn't exists !" : The given USER_ID for resp_id or sec_id is not found
|
||||
405 -> ERROR = "This group doesn't exists !" : Bad GROUP_ID provided
|
||||
405 -> ERROR = "A group with this name already exists !" : A group with this name already exists
|
||||
|
||||
GET -> Getting specified group infos
|
||||
In: (Suffixes = /bygid/<GROUP_ID> | /byname/<GROUP_NAME> )
|
||||
Out:
|
||||
200 -> GROUP = <GROUP_OBJECT>|null : Dictionary containing group infos or null
|
||||
|
||||
OPTIONS -> Add pairs of users (student/tutor) to the group
|
||||
In:
|
||||
pairs -> Table of pairs student's uid/tutor's uid (ex: [[1,2],[3,2]])
|
||||
Out:
|
||||
200 -> RESULT = "Pairs added successfully"
|
||||
400 -> ERROR = "One or more parameters are missing !" : Bad request
|
||||
400 -> ERROR = "The user with id <USER_ID> doesn't exists !" : The given USER_ID for student or tutor is not found
|
||||
400 -> ERROR = "A student must have the 'student' role !" : The given USER_ID for student doesn't have the "student" role (4)
|
||||
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
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Template_PDF/LivretAlternant_PEntreprise_Bilan_NOM_Prenom.pdf
Normal file
BIN
Template_PDF/LivretAlternant_PEntreprise_Bilan_NOM_Prenom.pdf
Normal file
Binary file not shown.
BIN
Template_PDF/LivretAlternant_PFormation_Bis_Bilan_NOM_Prenom.pdf
Normal file
BIN
Template_PDF/LivretAlternant_PFormation_Bis_Bilan_NOM_Prenom.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
67
Template_PDF/specification_template.txt
Normal file
67
Template_PDF/specification_template.txt
Normal file
@ -0,0 +1,67 @@
|
||||
Page 1 :
|
||||
|
||||
MASTER : {{ nom_master }}
|
||||
|
||||
{{ nom_complet_master }}
|
||||
|
||||
Année universitaire :
|
||||
|
||||
{{ annee_1 }}
|
||||
-
|
||||
{{ annee_2 }}
|
||||
|
||||
De : {{ nom_prenom }}
|
||||
|
||||
mail : {{ email }}
|
||||
|
||||
tel : {{ telephone }}
|
||||
|
||||
Alternant à : {{ entreprise }}
|
||||
|
||||
tuteur/tutrice pédagogique : {{ tuteur_pedagogique }}
|
||||
|
||||
Tuteur / Tutrice entreprise : {{ tuteur_entreprise }}
|
||||
|
||||
|
||||
Page 2 :
|
||||
Alternant
|
||||
Type de contrat :
|
||||
{{ type_contrat_apprentissage | X }}
|
||||
{{ type_contrat_professionnalisation | X }}
|
||||
{{ type_contrat_stage | X }}
|
||||
|
||||
Début de contrat : {{ debut_contrat }}
|
||||
Fin de contrat : {{ fin_contrat }}
|
||||
tel : {{ telephone }}
|
||||
mail : {{ email }}
|
||||
compostant de formation : {{ compostant_formation }}
|
||||
responsable tuteur_pedagogique de la formation : {{ responsable_pedagogique_formation }}
|
||||
tel : {{ tel_responsable_pedagogique_formation }}
|
||||
mail : {{ mail_responsable_pedagogique_formation }}
|
||||
|
||||
tuteur pédagogique :
|
||||
{{ tel_tuteur_pedagogique }}
|
||||
{{ mail_tuteur_pedagogique }}
|
||||
|
||||
tuteur entreprise :
|
||||
{{ tuteur_entreprise }}
|
||||
entreprise : {{ entreprise }}
|
||||
adresse lieu Alternance : {{ adresse_entreprise }}
|
||||
tel : {{ tel_tuteur_entreprise }}
|
||||
mail : {{ mail_tuteur_entreprise }}
|
||||
|
||||
Page 4 :
|
||||
poste occupé : {{ poste_occupe }}
|
||||
{{ poste_occupe_2 }}
|
||||
|
||||
Pentreprise :
|
||||
{{ n_periode }}
|
||||
{{ debut_periode }}
|
||||
{{ fin_periode }}
|
||||
{{ travaux_entreprise }}
|
||||
{{ remarque_tuteur }}
|
||||
|
||||
bilan_periode :
|
||||
|
||||
{{ n_periode }}
|
||||
{{ bilan_periode }}
|
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -31,3 +31,4 @@ coverage.xml
|
||||
|
||||
#Config files
|
||||
config.py
|
||||
/app/OLA_RESSOURCES/
|
||||
|
@ -38,9 +38,11 @@ CREATE TABLE IF NOT EXISTS `GROUP`
|
||||
CREATE TABLE IF NOT EXISTS `USER`
|
||||
(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`login` VARCHAR(128) NOT NULL,
|
||||
`role` VARCHAR(10) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
email VARCHAR(128) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
psw VARCHAR(256) DEFAULT NULL,
|
||||
hash VARCHAR(128),
|
||||
phone VARCHAR(15),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = INNODB;
|
||||
@ -57,10 +59,8 @@ CREATE TABLE IF NOT EXISTS TUTORSHIP
|
||||
CREATE TABLE IF NOT EXISTS LIVRET
|
||||
(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
tutorship_id BIGINT,
|
||||
etutor_name VARCHAR(128) NOT NULL,
|
||||
etutor_email VARCHAR(256) NOT NULL,
|
||||
etutor_phone VARCHAR(15) NOT NULL,
|
||||
tutorship_id BIGINT NOT NULL,
|
||||
etutor_id BIGINT NOT NULL,
|
||||
company_name VARCHAR(256) NOT NULL,
|
||||
company_address VARCHAR(512) NOT NULL,
|
||||
contract_type INT NOT NULL,
|
||||
@ -85,14 +85,6 @@ CREATE TABLE IF NOT EXISTS PERIOD
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE = INNODB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS HASHTABLE
|
||||
(
|
||||
token VARCHAR(255) NOT NULL,
|
||||
exipre DATE NOT NULL,
|
||||
period_id BIGINT NOT NULL,
|
||||
PRIMARY KEY(token)
|
||||
) ENGINE = INNODB;
|
||||
|
||||
|
||||
# Create FKs
|
||||
ALTER TABLE `GROUP`
|
||||
@ -128,6 +120,10 @@ REFERENCES TUTORSHIP (id)
|
||||
ON UPDATE CASCADE
|
||||
;
|
||||
|
||||
ALTER TABLE LIVRET
|
||||
ADD FOREIGN KEY (etutor_id)
|
||||
REFERENCES `USER` (id);
|
||||
|
||||
ALTER TABLE LIVRET
|
||||
ADD FOREIGN KEY (tutorship_id)
|
||||
REFERENCES TUTORSHIP (id)
|
||||
@ -141,13 +137,9 @@ REFERENCES LIVRET (id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE HASHTABLE
|
||||
ADD FOREIGN KEY (period_id)
|
||||
REFERENCES PERIOD (id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
;
|
||||
|
||||
# Create Indexes
|
||||
CREATE INDEX tutor_email ON LIVRET(etutor_email);
|
||||
CREATE INDEX user_login ON `USER`(`login`);
|
||||
CREATE UNIQUE INDEX user_email
|
||||
ON `USER` (`email`);
|
||||
CREATE UNIQUE INDEX user_hash
|
||||
ON `USER` (`hash`);
|
||||
|
25
backend/OLA_DATA.mysql
Normal file
25
backend/OLA_DATA.mysql
Normal file
@ -0,0 +1,25 @@
|
||||
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 `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');
|
||||
INSERT INTO `USER` VALUES (3, '4', 'etu2@univ-tlse2.fr', 'Etudiant 2', DEFAULT, 'wXcVbN', '01.23.45.67.89');
|
||||
INSERT INTO `USER` VALUES (4, '4', 'etu3@univ-tlse2.fr', 'Etudiant 3', DEFAULT, 'pOiUyTrEzA', '01.23.45.67.89');
|
||||
INSERT INTO `USER` VALUES (5, '2-3', 'resp@univ-tlse2.fr', 'Responsable', DEFAULT, 'mLkJhGfDsQ', '01.23.45.67.89');
|
||||
INSERT INTO `USER` VALUES (6, '3', 'tut@univ-tlse2.fr', 'Tuteur Pédagogique', DEFAULT, 'nBvCxW', '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, 5, 2);
|
||||
INSERT INTO TUTORSHIP VALUES (DEFAULT, 2, 5, 4);
|
||||
INSERT INTO TUTORSHIP VALUES (DEFAULT, 1, 6, 3);
|
||||
|
18
backend/app/OLA_DATA.mysql
Normal file
18
backend/app/OLA_DATA.mysql
Normal file
@ -0,0 +1,18 @@
|
||||
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);
|
||||
|
221
backend/app/api/GroupAPI.py
Normal file
221
backend/app/api/GroupAPI.py
Normal file
@ -0,0 +1,221 @@
|
||||
import os
|
||||
|
||||
from flask_restful import Resource, request
|
||||
|
||||
from app.api import mailsModels
|
||||
from app.model import *
|
||||
from app.utils import *
|
||||
|
||||
|
||||
class GroupAPI(Resource):
|
||||
"""
|
||||
Group Api Resource
|
||||
"""
|
||||
|
||||
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):
|
||||
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 + "/"
|
||||
mails = []
|
||||
|
||||
group = getGroup(name=name)
|
||||
if group is not None:
|
||||
return {"GID": group["id"]}, 200
|
||||
|
||||
user = getUser(uid=resp_id)
|
||||
if user is None:
|
||||
return {"ERROR": "The user with id " + str(resp_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,
|
||||
"URL": getParam('OLA_URL') + "registration/"
|
||||
+ res.hash})
|
||||
else:
|
||||
mail = mailsModels.getMailContent("SEC_OF_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()
|
||||
|
||||
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)
|
||||
res = query.execute()
|
||||
os.mkdir(res_dir)
|
||||
|
||||
for m in mails:
|
||||
addr = m[0]
|
||||
mail = m[1]
|
||||
send_mail(mail[0], addr, mail[1])
|
||||
|
||||
return {"GID": res.lastrowid}, 201
|
||||
|
||||
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):
|
||||
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 + "/"
|
||||
mails = []
|
||||
|
||||
group = getGroup(gid=gid)
|
||||
if group is None:
|
||||
return {"ERROR": "This group 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)
|
||||
if user is None:
|
||||
return {"ERROR": "The user with id " + str(resp_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})
|
||||
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"],
|
||||
"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()
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
def get(self, gid=0, name=""):
|
||||
if gid > 0:
|
||||
return {'GROUP': getGroup(gid=gid)}, 200
|
||||
elif name != "":
|
||||
return {'GROUP': getGroup(name=name)}, 200
|
||||
|
||||
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
|
221
backend/app/api/LivretAPI.py
Normal file
221
backend/app/api/LivretAPI.py
Normal file
@ -0,0 +1,221 @@
|
||||
import os
|
||||
|
||||
from flask_restful import Resource, request
|
||||
|
||||
from app.api import mailsModels
|
||||
from app.model import *
|
||||
from app.utils import *
|
||||
|
||||
|
||||
class LivretAPI(Resource):
|
||||
"""
|
||||
Livret Api Resource
|
||||
"""
|
||||
|
||||
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):
|
||||
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 + "/"
|
||||
mails = []
|
||||
|
||||
group = getGroup(name=name)
|
||||
if group is not None:
|
||||
return {"GID": group["id"]}, 200
|
||||
|
||||
user = getUser(uid=resp_id)
|
||||
if user is None:
|
||||
return {"ERROR": "The user with id " + str(resp_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,
|
||||
"URL": getParam('OLA_URL') + "registration/"
|
||||
+ res.hash})
|
||||
else:
|
||||
mail = mailsModels.getMailContent("SEC_OF_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()
|
||||
|
||||
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)
|
||||
res = query.execute()
|
||||
os.mkdir(res_dir)
|
||||
|
||||
for m in mails:
|
||||
addr = m[0]
|
||||
mail = m[1]
|
||||
send_mail(mail[0], addr, mail[1])
|
||||
|
||||
return {"GID": res.lastrowid}, 201
|
||||
|
||||
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):
|
||||
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 + "/"
|
||||
mails = []
|
||||
|
||||
group = getGroup(gid=gid)
|
||||
if group is None:
|
||||
return {"ERROR": "This group 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)
|
||||
if user is None:
|
||||
return {"ERROR": "The user with id " + str(resp_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})
|
||||
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"],
|
||||
"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()
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
def get(self, gid=0, name=""):
|
||||
if gid > 0:
|
||||
return {'GROUP': getGroup(gid=gid)}, 200
|
||||
elif name != "":
|
||||
return {'GROUP': getGroup(name=name)}, 200
|
||||
|
||||
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
|
51
backend/app/api/LoginAPI.py
Normal file
51
backend/app/api/LoginAPI.py
Normal file
@ -0,0 +1,51 @@
|
||||
from hashlib import sha256
|
||||
|
||||
from flask import session, request
|
||||
from flask_restful import Resource
|
||||
|
||||
from app.core import app
|
||||
from app.model import USER, getUser
|
||||
from app.utils import checkParams
|
||||
|
||||
|
||||
class LoginAPI(Resource):
|
||||
"""
|
||||
Login Api Resource
|
||||
"""
|
||||
|
||||
def post(self):
|
||||
args = request.get_json(cache=False, force=True)
|
||||
if not checkParams(['email', 'password'], args):
|
||||
return {"ERROR": "One or more parameters are missing !"}, 400
|
||||
email = args['email']
|
||||
psw = args['password']
|
||||
password = sha256(psw.encode('utf-8')).hexdigest()
|
||||
|
||||
if "user" in session and session["user"] is not None:
|
||||
return {'AUTH_RESULT': 'ALREADY_LOGGED'}, 201
|
||||
|
||||
query = USER.select(USER.c.email == email)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
if app.config['TESTING']:
|
||||
if res is not None and psw == email:
|
||||
user = getUser(uid=res.id)
|
||||
session['user'] = user
|
||||
return {'AUTH_RESULT': 'OK'}, 200
|
||||
else:
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'AUTHENTICATION_FAILED'}, 401
|
||||
else:
|
||||
if res is not None and password != "" and password == res.psw:
|
||||
user = getUser(uid=res.id)
|
||||
session['user'] = user
|
||||
return {'AUTH_RESULT': 'OK'}, 200
|
||||
else:
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'AUTHENTICATION_FAILED'}, 401
|
||||
|
||||
def delete(self):
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'OK'}, 200
|
||||
|
43
backend/app/api/PdfAPI.py
Normal file
43
backend/app/api/PdfAPI.py
Normal file
@ -0,0 +1,43 @@
|
||||
import os
|
||||
|
||||
from flask import request
|
||||
from flask_restful import Resource
|
||||
from flask_restful.reqparse import RequestParser
|
||||
from model import getParam
|
||||
|
||||
from app.model import getGroup
|
||||
from app.tools.LibPdf import delete_file, upload_file, allowed_file
|
||||
|
||||
|
||||
class PdfAPI(Resource):
|
||||
"""
|
||||
Pdf Api Resource
|
||||
"""
|
||||
|
||||
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']))
|
||||
|
||||
def post(self):
|
||||
"""
|
||||
Upload d'un template
|
||||
:return:
|
||||
"""
|
||||
parser = RequestParser()
|
||||
parser.add_argument('groupeName', required=True, help="id/name groupe cannot be blank!")
|
||||
args = parser.parse_args()
|
||||
|
||||
group = getGroup(args['groupe'])
|
||||
file = request.files['file']
|
||||
|
||||
if file.filename == '':
|
||||
return {"message": "Fichier non trouve"}, 400
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
upload_file(file, group["ressources_dir"])
|
@ -1,7 +1,9 @@
|
||||
from hashlib import sha256
|
||||
|
||||
from flask_restful import Resource, request
|
||||
|
||||
from app.model import *
|
||||
from app.utils import checkParams
|
||||
from app.utils import checkParams, get_random_string
|
||||
|
||||
|
||||
class UserAPI(Resource):
|
||||
@ -11,45 +13,60 @@ class UserAPI(Resource):
|
||||
|
||||
def post(self):
|
||||
args = request.get_json(cache=False, force=True)
|
||||
if not checkParams(['CASid', 'role'], args):
|
||||
if not checkParams(['role', 'email', 'name'], args):
|
||||
return {"ERROR": "One or more parameters are missing !"}, 400
|
||||
|
||||
CASid = args['CASid']
|
||||
role = args['role']
|
||||
email = self.getEmailFromCAS(CASid)
|
||||
email = args['email']
|
||||
name = args['name']
|
||||
phone = None
|
||||
user = getUser(login=CASid)
|
||||
user = getUser(email=email)
|
||||
hashpass = get_random_string()
|
||||
while hashExists(hashpass):
|
||||
hashpass = get_random_string()
|
||||
|
||||
if user is not None:
|
||||
return {"UID": user["id"]}, 200
|
||||
|
||||
query = USER.insert().values(login=CASid, email=email, role=role, phone=phone)
|
||||
query = USER.insert().values(email=email, role=role, phone=phone, name=name, hash=hashpass)
|
||||
res = query.execute()
|
||||
return {"UID": res.lastrowid}, 201
|
||||
|
||||
def put(self, uid):
|
||||
args = request.get_json(cache=False, force=True)
|
||||
if not checkParams(['CASid', 'role', 'email', 'phone'], args):
|
||||
if not checkParams(['role', 'email', 'phone', 'name', 'password', 'firstname'], args):
|
||||
return {"ERROR": "One or more parameters are missing !"}, 400
|
||||
|
||||
role = args['role']
|
||||
email = args['email']
|
||||
phone = args['phone']
|
||||
firstname = args['firstname']
|
||||
name = args['name']
|
||||
psw = args['password']
|
||||
|
||||
name = firstname.title() + " " + name.upper()
|
||||
# TODO : Lors de l'ajout des fiches d'absence ca sera ça le critère de recherche + le groupe
|
||||
|
||||
if psw is None or len(psw) < 8:
|
||||
return {"ERROR": "Password can't be empty or less than 8 characters !"}, 400
|
||||
|
||||
password = sha256(psw.encode('utf-8')).hexdigest()
|
||||
|
||||
if getUser(uid=uid) is None:
|
||||
return {"ERROR": "This user doesn't exists !"}, 405
|
||||
|
||||
CASid = args['CASid']
|
||||
role = args['role']
|
||||
email = args['email']
|
||||
phone = args['phone']
|
||||
query = USER.update().values(login=CASid, email=email, role=role, phone=phone).where(USER.c.id == uid)
|
||||
if getUser(email=email) is not None:
|
||||
return {"ERROR": "A user with this email already exists !"}, 405
|
||||
|
||||
query = USER.update().values(email=email, role=role, phone=phone, name=name, psw=password, hash=None) \
|
||||
.where(USER.c.id == uid)
|
||||
query.execute()
|
||||
return {"UID": uid}, 200
|
||||
|
||||
def get(self, uid=0, login="", email=""):
|
||||
def get(self, uid=0, email="", hashcode=""):
|
||||
if uid > 0:
|
||||
return {'USER': getUser(uid=uid)}, 200
|
||||
elif login != "":
|
||||
return {'USER': getUser(login=login)}, 200
|
||||
elif email != "":
|
||||
return {'USER': getUser(email=email)}, 200
|
||||
|
||||
@staticmethod
|
||||
def getEmailFromCAS(CASid):
|
||||
return ""
|
||||
elif hashcode != "":
|
||||
return {'USER': getUser(hashcode=hashcode)}, 200
|
||||
|
@ -1,6 +1,8 @@
|
||||
from flask import session
|
||||
from flask_restful import Resource
|
||||
|
||||
from app.model import *
|
||||
|
||||
|
||||
class UserInfoAPI(Resource):
|
||||
"""
|
||||
@ -8,5 +10,24 @@ class UserInfoAPI(Resource):
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
user = session["user"]
|
||||
user = session.get("user", None)
|
||||
return {'USER': user}, 200
|
||||
|
||||
|
||||
class UserGroupsAPI(Resource):
|
||||
"""
|
||||
UserGroups Api Resource
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
user = session.get("user", None)
|
||||
if user is not None:
|
||||
subquery = LIVRET.select().distinct().with_only_columns([LIVRET.c.tutorship_id])
|
||||
query = TUTORSHIP.select(
|
||||
and_(TUTORSHIP.c.student_id == user["id"], TUTORSHIP.c.id.notin_(subquery))).distinct()
|
||||
res = query.execute()
|
||||
liste = []
|
||||
for r in res:
|
||||
liste.append(r.group_id)
|
||||
|
||||
return {'GROUP_LIST': liste}, 200
|
||||
|
@ -1,38 +0,0 @@
|
||||
from flask import session
|
||||
from flask_restful import Resource
|
||||
from flask_restful.reqparse import RequestParser
|
||||
|
||||
from app.model import *
|
||||
from app.core import cas
|
||||
|
||||
class LoginAPI(Resource):
|
||||
"""
|
||||
Login Api Resource
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
if "user" in session and session["user"] is not None:
|
||||
return {'AUTH_RESULT': 'ALREADY_LOGGED'}, 201
|
||||
userInfo = self.getUserInfoFromCAS()
|
||||
|
||||
if userInfo is not None:
|
||||
user = getUser(login=userInfo['login'])
|
||||
if user is not None and isUserAllowed(user["id"]):
|
||||
session['user'] = user
|
||||
return {'AUTH_RESULT': 'OK'}, 200
|
||||
else:
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'NOT_ALLOWED'}, 403
|
||||
else:
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'AUTHENTICATION_FAILED'}, 401
|
||||
|
||||
def delete(self):
|
||||
session['user'] = None
|
||||
return {'AUTH_RESULT': 'OK'}, 200
|
||||
|
||||
def getUserInfoFromCAS(self):
|
||||
if cas.username is not None:
|
||||
return {"login": cas.username}
|
||||
else:
|
||||
return None
|
53
backend/app/api/mailsModels.py
Normal file
53
backend/app/api/mailsModels.py
Normal file
@ -0,0 +1,53 @@
|
||||
_NEW_STUD_OF_GROUP = ("Votre compte OLA a été créé !", "Bonjour,<br/><p>Votre compte vient d'être créé dans l'Outil du "
|
||||
"Livret de l'Alternant dans le groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant l'activer, puis créer un livret en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
_STUD_OF_GROUP = (
|
||||
"Vous avez été ajouté à un groupe OLA !", "Bonjour,<br/><p>Votre compte vient d'être ajouté dans l'Outil du "
|
||||
"Livret de l'Alternant au groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant créer un livret en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
_NEW_RESP_OF_GROUP = ("Votre compte OLA a été créé !", "Bonjour,<br/><p>Votre compte vient d'être créé dans l'Outil du "
|
||||
"Livret de l'Alternant en tant que responsable du groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant l'activer, en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
_RESP_OF_GROUP = (
|
||||
"Vous avez été ajouté à un groupe OLA !", "Bonjour,<br/><p>Votre compte vient d'être ajouté dans l'Outil du "
|
||||
"Livret de l'Alternant en tant que responsable du groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant y accéder en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
_NEW_SEC_OF_GROUP = ("Votre compte OLA a été créé !", "Bonjour,<br/><p>Votre compte vient d'être créé dans l'Outil du "
|
||||
"Livret de l'Alternant en tant que secrétaire du groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant l'activer, en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
_SEC_OF_GROUP = (
|
||||
"Vous avez été ajouté à un groupe OLA !", "Bonjour,<br/><p>Votre compte vient d'être ajouté dans l'Outil du "
|
||||
"Livret de l'Alternant en tant que secrétaire du groupe <b>#GROUPE</b>. Vous pouvez dès "
|
||||
"maintenant y accéder en vous rendant à l'adresse : <br/>"
|
||||
"<a href='#URL'>#URL</a></p><p>Bonne journée !</p>")
|
||||
|
||||
|
||||
def getMailContent(mail_type, args):
|
||||
if mail_type == "NEW_STUD_OF_GROUP":
|
||||
mail = _NEW_STUD_OF_GROUP
|
||||
elif mail_type == "STUD_OF_GROUP":
|
||||
mail = _STUD_OF_GROUP
|
||||
elif mail_type == "NEW_RESP_OF_GROUP":
|
||||
mail = _NEW_RESP_OF_GROUP
|
||||
elif mail_type == "RESP_OF_GROUP":
|
||||
mail = _RESP_OF_GROUP
|
||||
elif mail_type == "NEW_SEC_OF_GROUP":
|
||||
mail = _NEW_SEC_OF_GROUP
|
||||
elif mail_type == "SEC_OF_GROUP":
|
||||
mail = _SEC_OF_GROUP
|
||||
else:
|
||||
raise Exception("Unknown mail type !")
|
||||
|
||||
for key, value in args.items():
|
||||
mail[1].replace("#" + key, value)
|
||||
return mail
|
@ -31,6 +31,7 @@ class Config:
|
||||
CAS_LOGIN_ROUTE = "/login"
|
||||
CAS_LOGOUT_ROUTE = "/logout"
|
||||
CAS_VALIDATE_ROUTE = "/serviceValidate"
|
||||
MAILER = True
|
||||
|
||||
|
||||
|
||||
@ -49,3 +50,4 @@ class Test(Config):
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
SQLALCHEMY_DATABASE_URI = \
|
||||
'sqlite:///' + os.path.join(BASE_DIR, '../test.db')
|
||||
MAILER = False
|
||||
|
@ -1,16 +1,16 @@
|
||||
import importlib
|
||||
from datetime import timedelta
|
||||
|
||||
from flask_cas import CAS
|
||||
|
||||
from app.config import Config
|
||||
from flask import Flask, session, redirect
|
||||
from flask_cas import CAS
|
||||
from flask_restful import Api
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
|
||||
from app.config import Config
|
||||
|
||||
# initialization Flask
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config.ACTIVE_CONFIG)
|
||||
@ -20,6 +20,7 @@ app.permanent_session_lifetime = \
|
||||
minutes=app.config['SESSION_VALIDITY_DURATION_WITHOUT_ACTIVITY_MIN']
|
||||
)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
session.modified = True
|
||||
@ -37,6 +38,7 @@ api = Api(app)
|
||||
# Cas Flask
|
||||
cas = CAS(app)
|
||||
|
||||
|
||||
@app.route('/redirect')
|
||||
def after_login():
|
||||
return redirect("/api/login")
|
||||
|
@ -1,11 +1,10 @@
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import and_
|
||||
|
||||
from app.core import meta, db, Base
|
||||
from app.core import meta, Base
|
||||
|
||||
USER = Table('USER', meta, autoload=False)
|
||||
SETTINGS = Table('SETTINGS', meta, autoload=False)
|
||||
HASHTABLE = Table('HASHTABLE', meta, autoload=False)
|
||||
GROUP = Table('GROUP', meta, autoload=False)
|
||||
TUTORSHIP = Table('TUTORSHIP', meta, autoload=False)
|
||||
PERIOD = Table('PERIOD', meta, autoload=False)
|
||||
@ -13,24 +12,26 @@ LIVRET = Table('LIVRET', meta, autoload=False)
|
||||
|
||||
user_class = Base.classes.USER
|
||||
settings_class = Base.classes.SETTINGS
|
||||
hashtable_class = Base.classes.HASHTABLE
|
||||
group_class = Base.classes.GROUP
|
||||
tutorship_class = Base.classes.TUTORSHIP
|
||||
period_class = Base.classes.PERIOD
|
||||
livret_class = Base.classes.LIVRET
|
||||
|
||||
|
||||
def getUser(uid=0, login="", email=""):
|
||||
def getParam(key):
|
||||
query = SETTINGS.select(SETTINGS.c.key == key)
|
||||
rows = query.execute()
|
||||
return rows.first().value
|
||||
|
||||
|
||||
def getUser(uid=0, email="", hashcode=""):
|
||||
res = None
|
||||
|
||||
if uid == 0 and login == "" and email == "":
|
||||
if uid == 0 and email == "" and hashcode == "":
|
||||
raise Exception("getUser must be called with one argument !")
|
||||
else:
|
||||
if uid != 0:
|
||||
res = db.session.query(user_class).get(uid)
|
||||
|
||||
elif login != "":
|
||||
query = USER.select(USER.c.login == login)
|
||||
query = USER.select(USER.c.id == uid)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
@ -39,14 +40,54 @@ def getUser(uid=0, login="", email=""):
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
elif hashcode != "":
|
||||
query = USER.select(USER.c.hash == hashcode)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
if res is not None:
|
||||
return {"id": res.id, "login": res.login, "email": res.email, "role": res.role, "phone": res.phone}
|
||||
return {"id": res.id, "email": res.email, "role": res.role, "phone": res.phone, "name": res.name}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def isUserAllowed(uid):
|
||||
query = db.session.query(group_class, tutorship_class).join(tutorship_class) \
|
||||
.filter(or_(tutorship_class.student_id == uid, group_class.resp_id == uid))
|
||||
res = query.all()
|
||||
return res is not None and len(res) > 0
|
||||
def getGroup(gid=0, name=""):
|
||||
res = None
|
||||
|
||||
if gid == 0 and name == "":
|
||||
raise Exception("getGroup must be called with one argument !")
|
||||
else:
|
||||
if gid != 0:
|
||||
query = GROUP.select(GROUP.c.id == gid)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
elif name != "":
|
||||
query = GROUP.select(GROUP.c.name == name)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
|
||||
if res is not None:
|
||||
return {"id": res.id, "name": res.name, "year": res.year, "class_short": res.class_short,
|
||||
"class_long": res.class_long, "department": res.department, "resp_id": getUser(uid=res.resp_id),
|
||||
"sec_id": getUser(uid=res.sec_id), "ressources_dir": res.ressources_dir}
|
||||
else:
|
||||
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)}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def hashExists(test):
|
||||
query = USER.select(USER.c.hash == test)
|
||||
rows = query.execute()
|
||||
res = rows.first()
|
||||
return res is not None
|
||||
|
@ -1,25 +0,0 @@
|
||||
import os
|
||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||
|
||||
|
||||
def fusion_fichiers(chemin_merge_pdf, nom_merge_pdf, liste_de_pdf):
|
||||
"""
|
||||
Permet de fusionner un ensemble de pdf
|
||||
:param chemin_merge_pdf: chemin ou l'on souhaite fusioner l'ensemble des pdf
|
||||
:param nom_merge_pdf: nom que l'on souhaite donner au fichier pdf final
|
||||
:param liste_de_pdf: liste de pdf avec leur chemin inclu
|
||||
:return:
|
||||
"""
|
||||
merger = PdfFileMerger()
|
||||
for filename in liste_de_pdf:
|
||||
merger.append(PdfFileReader(os.path.join(filename), "rb"))
|
||||
merger.write(os.path.join(chemin_merge_pdf, nom_merge_pdf))
|
||||
|
||||
|
||||
def get_pdf_from_directory(chemin_des_pdf):
|
||||
"""
|
||||
Permet de récuperer l'ensemble des pdf d'un chemin
|
||||
:param chemin_des_pdf:
|
||||
:return:
|
||||
"""
|
||||
return [f for f in os.listdir(chemin_des_pdf) if f.endswith("pdf")]
|
@ -1,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Script python qui remplie les pdf basés sur un template jinja. """
|
||||
|
||||
import os
|
||||
|
||||
from pdfjinja import PdfJinja
|
||||
|
||||
|
||||
def remplir_template(dirname_template, pdf_template, dirname_output_file, pdf_output, dictionnaire):
|
||||
"""
|
||||
Fonction qui permet de remplir un pdf template
|
||||
:param dirname_template: chemin du fichier de template
|
||||
:param pdf_template: nom du fichier de template
|
||||
:param dirname_output_file: chemin des pdf généré
|
||||
:param pdf_output: nom du fichier pdf à générer
|
||||
:param dictionnaire: dictionnaire contenant le nom des textfields des pdf ainsi que leurs valeurs
|
||||
:return:
|
||||
"""
|
||||
template_pdf_file = os.path.join(dirname_template, pdf_template)
|
||||
template_pdf = PdfJinja(template_pdf_file)
|
||||
|
||||
rendered_pdf = template_pdf(dictionnaire)
|
||||
|
||||
output_file = os.path.join(dirname_output_file, pdf_output)
|
||||
rendered_pdf.write(open(output_file, 'wb'))
|
69
backend/app/tools/LibPdf.py
Normal file
69
backend/app/tools/LibPdf.py
Normal file
@ -0,0 +1,69 @@
|
||||
import os
|
||||
|
||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||
from pdfjinja import PdfJinja
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
||||
def fusion_fichiers(chemin_merge_pdf, nom_merge_pdf, liste_de_pdf):
|
||||
"""
|
||||
Permet de fusionner un ensemble de pdf
|
||||
:param chemin_merge_pdf: chemin ou l'on souhaite fusioner l'ensemble des pdf
|
||||
:param nom_merge_pdf: nom que l'on souhaite donner au fichier pdf final
|
||||
:param liste_de_pdf: liste de pdf avec leur chemin inclu
|
||||
:return:
|
||||
"""
|
||||
merger = PdfFileMerger()
|
||||
for filename in liste_de_pdf:
|
||||
merger.append(PdfFileReader(os.path.join(filename), "rb"))
|
||||
merger.write(os.path.join(chemin_merge_pdf, nom_merge_pdf))
|
||||
|
||||
|
||||
def get_pdf_from_directory(chemin_des_pdf):
|
||||
"""
|
||||
Permet de récuperer l'ensemble des pdf d'un chemin
|
||||
:param chemin_des_pdf:
|
||||
:return:
|
||||
"""
|
||||
return [f for f in os.listdir(chemin_des_pdf) if f.endswith("pdf")]
|
||||
|
||||
|
||||
def remplir_template(dirname_template, pdf_template, dirname_output_file, pdf_output, dictionnaire):
|
||||
"""
|
||||
Fonction qui permet de remplir un pdf template
|
||||
:param dirname_template: chemin du fichier de template
|
||||
:param pdf_template: nom du fichier de template
|
||||
:param dirname_output_file: chemin des pdf généré
|
||||
:param pdf_output: nom du fichier pdf à générer
|
||||
:param dictionnaire: dictionnaire contenant le nom des textfields des pdf ainsi que leurs valeurs
|
||||
:return:
|
||||
"""
|
||||
template_pdf_file = os.path.join(dirname_template, pdf_template)
|
||||
template_pdf = PdfJinja(template_pdf_file)
|
||||
|
||||
rendered_pdf = template_pdf(dictionnaire)
|
||||
|
||||
output_file = os.path.join(dirname_output_file, pdf_output)
|
||||
rendered_pdf.write(open(output_file, 'wb'))
|
||||
|
||||
|
||||
def allowed_file(filename):
|
||||
allowed_extensions = {'pdf'}
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||
|
||||
|
||||
def upload_file(file_to_upload, upload_folder):
|
||||
"""
|
||||
rep de l'etu avec id
|
||||
:param file:
|
||||
: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)
|
@ -1,12 +1,17 @@
|
||||
from app.api.GroupAPI import GroupAPI
|
||||
from app.api.LivretAPI import LivretAPI
|
||||
from app.api.LoginAPI import LoginAPI
|
||||
from app.api.UserAPI import UserAPI
|
||||
from app.api.UserInfoAPI import UserInfoAPI
|
||||
from app.api.UserInfoAPI import UserInfoAPI, UserGroupsAPI
|
||||
from app.api.exampleapi import SomeApi
|
||||
from app.api.loginAPI import LoginAPI
|
||||
from app.core import api
|
||||
|
||||
# Some Api resource
|
||||
api.add_resource(SomeApi, '/api/someapi', '/api/someapi/<int:id>')
|
||||
api.add_resource(LoginAPI, '/api/login')
|
||||
api.add_resource(UserAPI, '/api/user', '/api/user/byuid/<int:uid>', '/api/user/bylogin/<string:login>',
|
||||
'/api/user/byemail/<string:email>')
|
||||
api.add_resource(UserInfoAPI, '/api/userInfo')
|
||||
api.add_resource(UserGroupsAPI, '/api/userGroups')
|
||||
api.add_resource(UserAPI, '/api/user', '/api/user/byuid/<int:uid>', '/api/user/byemail/<string:email>',
|
||||
'/api/user/byhash/<string:hashcode>')
|
||||
api.add_resource(GroupAPI, '/api/group', '/api/group/bygid/<int:gid>', '/api/group/byname/<string:name>')
|
||||
api.add_resource(LivretAPI, '/api/livret', '/api/livret/byuid/<int:uid>')
|
||||
|
@ -3,8 +3,12 @@ import string
|
||||
from hashlib import sha512
|
||||
|
||||
from flask import json
|
||||
from mailer import Mailer
|
||||
from mailer import Message
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
|
||||
from app.core import app
|
||||
|
||||
SIMPLE_CHARS = string.ascii_letters + string.digits
|
||||
|
||||
|
||||
@ -57,3 +61,12 @@ def new_alchemy_encoder(revisit_self=False, fields_to_expand=[]):
|
||||
def checkParams(wanted, args):
|
||||
inter = [elt for elt in wanted if elt in args]
|
||||
return len(inter) == len(wanted)
|
||||
|
||||
|
||||
def send_mail(subject, to, html):
|
||||
if app.config['MAILER']:
|
||||
message = Message(From="ola.noreply@univ-tlse2.fr", To=to, charset="utf-8")
|
||||
message.Subject = subject
|
||||
message.Html = html
|
||||
sender = Mailer('localhost') # TODO: Mettre le SMTP de la fac ici
|
||||
sender.send(message)
|
||||
|
@ -3,6 +3,7 @@ flask-script < 2.1
|
||||
flask-sqlalchemy < 2.2
|
||||
flask-restful < 0.4
|
||||
flask-cas
|
||||
mailer
|
||||
mysqlclient < 1.4
|
||||
pdfjinja < 1.1
|
||||
PyPDF2 < 1.27
|
||||
|
@ -46,7 +46,7 @@ class AuthTestCase(unittest.TestCase):
|
||||
return self.app.post('/api/login',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
login=email,
|
||||
email=email,
|
||||
password=password
|
||||
)
|
||||
), content_type='application/json')
|
||||
@ -58,34 +58,30 @@ class AuthTestCase(unittest.TestCase):
|
||||
return self.app.delete('/api/login')
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login('admin', 'admin')
|
||||
rv = self.login('admin@admin.com', 'admin@admin.com')
|
||||
self.assertEqual(rv.status_code, 200, 'Login as admin Failed')
|
||||
|
||||
rv = self.login('admin', 'admin')
|
||||
rv = self.login('admin@admin.com', 'admin@admin.com')
|
||||
self.assertEqual(rv.status_code, 201, 'Login as admin succeed but should have already been done')
|
||||
|
||||
rv = self.getUserInfo()
|
||||
self.assertEqual(rv.status_code, 200, 'Getting user info failed')
|
||||
self.assertEqual({"id": getUser(login="admin")["id"], "login": "admin", "email": "admin@admin.com", "role": 4,
|
||||
self.assertEqual({"id": getUser(login="admin")["id"], "login": "admin", "email": "admin@admin.com", "role": "4",
|
||||
"phone": "00.00.00.00.00"}, json.loads(rv.data)['USER'], 'Invalid user info')
|
||||
|
||||
rv = self.logout()
|
||||
self.assertEqual(rv.status_code, 200, 'Logout Failed')
|
||||
|
||||
rv = self.login('adminx', 'admin')
|
||||
self.assertEqual(rv.status_code, 401, 'Authentication from CAS has not failed for the invalid user xadmin !')
|
||||
rv = self.login('adminx@admin.com', 'admin@admin.com')
|
||||
self.assertEqual(rv.status_code, 401, 'Authentication not failed for the invalid user!')
|
||||
|
||||
rv = self.getUserInfo()
|
||||
self.assertEqual(rv.status_code, 200, 'Getting user info failed')
|
||||
self.assertIsNone(json.loads(rv.data)['USER'], 'User info should be None')
|
||||
|
||||
rv = self.login('admin', 'adminx')
|
||||
rv = self.login('admin@admin.com', 'admin@admin.comx')
|
||||
self.assertEqual(rv.status_code, 401,
|
||||
'Authentication from CAS has not failed for the invalid password xadmin !')
|
||||
|
||||
rv = self.login('toto', 'toto')
|
||||
self.assertEqual(rv.status_code, 403, 'Authentication shouldn\'t be allowed for user toto !')
|
||||
|
||||
'Authenticationnot failed for the invalid password !')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
108
backend/tests/api/test_Group.py
Normal file
108
backend/tests/api/test_Group.py
Normal file
@ -0,0 +1,108 @@
|
||||
import unittest
|
||||
|
||||
from flask import json
|
||||
|
||||
from app.core import app
|
||||
from app.model import USER, user_class, GROUP, group_class
|
||||
|
||||
|
||||
class GroupTestCase(unittest.TestCase):
|
||||
uid = None
|
||||
uid2 = None
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
query = USER.insert().values(email="admin@admin.com", role="4", phone="00.00.00.00.00", name="admin",
|
||||
hash="toto")
|
||||
res = query.execute()
|
||||
cls.uid = res.lastrowid
|
||||
query = USER.insert().values(email="adminx@admin.com", role="3", phone="00.00.00.00.00", name="adminx",
|
||||
hash="zozo")
|
||||
res = query.execute()
|
||||
cls.uid2 = res.lastrowid
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
query = GROUP.delete().where(group_class.name == "group_test")
|
||||
query.execute()
|
||||
query = GROUP.delete().where(group_class.name == "group_test2")
|
||||
query.execute()
|
||||
query = USER.delete().where(user_class.email == "admin@admin.com")
|
||||
query.execute()
|
||||
query = USER.delete().where(user_class.email == "adminx@admin.com")
|
||||
query.execute()
|
||||
|
||||
def setUp(self):
|
||||
self.app = app.test_client()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def create_group(self, name, year, class_short, class_long, department, resp_id, sec_id):
|
||||
return self.app.post('/api/group',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
name=name,
|
||||
year=year,
|
||||
class_short=class_short,
|
||||
class_long=class_long,
|
||||
department=department,
|
||||
resp_id=resp_id,
|
||||
sec_id=sec_id
|
||||
)
|
||||
), content_type='application/json')
|
||||
|
||||
def getGroupByID(self, GID):
|
||||
return self.app.get('/api/group/bygid/' + str(GID))
|
||||
|
||||
def getGroupByName(self, name):
|
||||
return self.app.get('/api/group/byname/' + name)
|
||||
|
||||
def change_group(self, GID, name, year, class_short, class_long, department, resp_id, sec_id):
|
||||
return self.app.put('/api/group/bygid/' + str(GID),
|
||||
data=json.dumps(
|
||||
dict(
|
||||
name=name,
|
||||
year=year,
|
||||
class_short=class_short,
|
||||
class_long=class_long,
|
||||
department=department,
|
||||
resp_id=resp_id,
|
||||
sec_id=sec_id
|
||||
)
|
||||
), content_type='application/json')
|
||||
|
||||
def test_group(self):
|
||||
rv = self.create_group('group_test', '2017', 'GT', 'GROUP_TEST', 'TESTING', self.uid, self.uid2)
|
||||
self.assertEqual(rv.status_code, 201, 'Creating group Failed')
|
||||
gid = json.loads(rv.data)['GID']
|
||||
self.assertIsNotNone(gid)
|
||||
|
||||
rv = self.create_group('group_test', '2017', 'GT', 'GROUP_TEST', 'TESTING', self.uid, self.uid2)
|
||||
self.assertEqual(rv.status_code, 200, 'Group is supposed to already exist')
|
||||
gid2 = json.loads(rv.data)['GID']
|
||||
self.assertEqual(gid, gid2, "The GID must be the same !")
|
||||
|
||||
rv = self.getGroupByID(gid)
|
||||
self.assertEqual(rv.status_code, 200, 'Getting group failed by ID')
|
||||
group = json.loads(rv.data)['GROUP']
|
||||
self.assertIsNotNone(group)
|
||||
|
||||
rv = self.getGroupByName("group_test")
|
||||
self.assertEqual(rv.status_code, 200, 'Getting group failed by Name')
|
||||
group2 = json.loads(rv.data)['GROUP']
|
||||
self.assertEqual(group, group2, "Group by name must be the same !")
|
||||
|
||||
rv = self.change_group(gid, 'group_test2', '2018', 'GT2', 'GROUP_TEST2', 'TESTING2', self.uid2, self.uid)
|
||||
self.assertEqual(rv.status_code, 200, 'Group modification failed !')
|
||||
gid3 = json.loads(rv.data)['GID']
|
||||
self.assertEqual(gid, gid3, "GIDs doesn't match !")
|
||||
|
||||
rv = self.getGroupByName('group_test2')
|
||||
self.assertEqual(rv.status_code, 200, 'Getting modified group failed by Name')
|
||||
group4 = json.loads(rv.data)['GROUP']
|
||||
self.assertIsNotNone(group4, "Modified group shouldn't be None !")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
88
backend/tests/api/test_User.py
Normal file
88
backend/tests/api/test_User.py
Normal file
@ -0,0 +1,88 @@
|
||||
import unittest
|
||||
|
||||
from flask import json
|
||||
|
||||
from app.core import app
|
||||
from app.model import USER, user_class
|
||||
|
||||
|
||||
class UserTestCase(unittest.TestCase):
|
||||
uid = None
|
||||
gid = None
|
||||
tid = None
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
query = USER.delete().where(user_class.email == "admin@admin.com")
|
||||
query.execute()
|
||||
query = USER.delete().where(user_class.email == "adminx@admin.com")
|
||||
query.execute()
|
||||
|
||||
def setUp(self):
|
||||
self.app = app.test_client()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def create_user(self, email, role, name):
|
||||
return self.app.post('/api/user',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
email=email,
|
||||
role=role,
|
||||
name=name
|
||||
)
|
||||
), content_type='application/json')
|
||||
|
||||
def getUserByID(self, UID):
|
||||
return self.app.get('/api/user/byuid/' + str(UID))
|
||||
|
||||
def getUserByEmail(self, email):
|
||||
return self.app.get('/api/user/byemail/' + email)
|
||||
|
||||
def change_user(self, UID, email, role, phone, name, password):
|
||||
return self.app.put('/api/user/byuid/' + str(UID),
|
||||
data=json.dumps(
|
||||
dict(
|
||||
role=role,
|
||||
email=email,
|
||||
phone=phone,
|
||||
name=name,
|
||||
password=password
|
||||
)
|
||||
), content_type='application/json')
|
||||
|
||||
def test_user(self):
|
||||
rv = self.create_user('admin@admin.com', '4', 'Admin')
|
||||
self.assertEqual(rv.status_code, 201, 'Creating user Failed')
|
||||
uid = json.loads(rv.data)['UID']
|
||||
self.assertIsNotNone(uid)
|
||||
|
||||
rv = self.create_user('admin@admin.com', '4', 'Admin')
|
||||
self.assertEqual(rv.status_code, 200, 'User is supposed to already exist')
|
||||
uid2 = json.loads(rv.data)['UID']
|
||||
self.assertEqual(uid, uid2, "The UID must be the same !")
|
||||
|
||||
rv = self.getUserByID(uid)
|
||||
self.assertEqual(rv.status_code, 200, 'Getting user failed by ID')
|
||||
user = json.loads(rv.data)['USER']
|
||||
self.assertIsNotNone(user)
|
||||
|
||||
rv = self.getUserByEmail("admin@admin.com")
|
||||
self.assertEqual(rv.status_code, 200, 'Getting user failed by email')
|
||||
user3 = json.loads(rv.data)['USER']
|
||||
self.assertEqual(user, user3, "User by email must be the same !")
|
||||
|
||||
rv = self.change_user(uid, 'adminx@admin.com', '3', '11.11.11.11.11', 'Adminx', 'password')
|
||||
self.assertEqual(rv.status_code, 200, 'User modification failed !')
|
||||
uid3 = json.loads(rv.data)['UID']
|
||||
self.assertEqual(uid, uid3, "UIDs doesn't match !")
|
||||
|
||||
rv = self.getUserByEmail("adminx@admin.com")
|
||||
self.assertEqual(rv.status_code, 200, 'Getting modified user failed by Email')
|
||||
user4 = json.loads(rv.data)['USER']
|
||||
self.assertIsNotNone(user4, "Modified user shouldn't be None !")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,9 +1,7 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from builtins import print
|
||||
from pathlib import Path
|
||||
|
||||
from app.tools.FusionPdf import fusion_fichiers, get_pdf_from_directory
|
||||
|
BIN
backend/tests/tools/libPdf/UploadPDF/page1.pdf
Normal file
BIN
backend/tests/tools/libPdf/UploadPDF/page1.pdf
Normal file
Binary file not shown.
20
backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py
Normal file
20
backend/tests/tools/libPdf/UploadPDF/testUploadPdf.py
Normal file
@ -0,0 +1,20 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from werkzeug.datastructures import FileStorage
|
||||
|
||||
from app.tools.LibPdf import upload_file
|
||||
|
||||
|
||||
class TestFusionTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.datadir = os.path.join(os.path.dirname(__file__))
|
||||
|
||||
def test_fusion(self):
|
||||
with open("page1.pdf", '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")
|
0
backend/tests/tools/libPdf/pdfjinja/__init__.py
Normal file
0
backend/tests/tools/libPdf/pdfjinja/__init__.py
Normal file
@ -3,9 +3,8 @@ import unittest
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
from pdfjinja import PdfJinja
|
||||
|
||||
from app.tools.InsertTemplate import remplir_template
|
||||
from pdfjinja import PdfJinja
|
||||
|
||||
|
||||
class InsertTemplateTestCase(unittest.TestCase):
|
@ -46,6 +46,8 @@
|
||||
<script src="bower_components/angular-material/angular-material.js"></script>
|
||||
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
|
||||
<script src="bower_components/angular-material-icons/angular-material-icons.min.js"></script>
|
||||
<script src="bower_components/underscore/underscore.js"></script>
|
||||
<script src="bower_components/angular-file-upload/dist/angular-file-upload.min.js"></script>
|
||||
<!-- endbower -->
|
||||
<!-- endbuild -->
|
||||
|
||||
@ -54,6 +56,10 @@
|
||||
<script src="scripts/controllers/login.js"></script>
|
||||
<script src="scripts/controllers/studentSpace.js"></script>
|
||||
<script src="scripts/controllers/responsableFormationSpace.js"></script>
|
||||
<script src="scripts/controllers/administrationSpace.js"></script>
|
||||
<script src="scripts/controllers/administrationDialog.js"></script>
|
||||
|
||||
<script src="scripts/services/Filters.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
|
@ -14,7 +14,8 @@ var app = angular.module('clientApp', [
|
||||
'ngSanitize',
|
||||
'ngMaterial',
|
||||
'ui.router',
|
||||
'ngMdIcons'
|
||||
'ngMdIcons',
|
||||
'angularFileUpload'
|
||||
]);
|
||||
|
||||
app.config(function ($stateProvider, $urlRouterProvider) {
|
||||
@ -42,5 +43,11 @@ app.config(function ($stateProvider, $urlRouterProvider) {
|
||||
url: '/espace-formation',
|
||||
templateUrl: 'views/responsableFormationSpace.html',
|
||||
controller: 'ResponsableFormationSpaceCtrl'
|
||||
})
|
||||
|
||||
.state('administrationSpace', {
|
||||
url: '/espace-secretariat',
|
||||
templateUrl: 'views/administrationSpace.html',
|
||||
controller: 'AdministrationSpaceCtrl'
|
||||
});
|
||||
});
|
104
frontend/app/scripts/controllers/administrationDialog.js
Normal file
104
frontend/app/scripts/controllers/administrationDialog.js
Normal file
@ -0,0 +1,104 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name frontendApp.controller:AdministrationDialogCtrl
|
||||
* @description
|
||||
* # AdministrationDialogCtrl
|
||||
* Controller of the frontendApp
|
||||
*/
|
||||
angular.module('clientApp')
|
||||
.controller('AdministrationDialogCtrl', function ($scope, $state, FileUploader, $mdDialog, fileNameFilter, illegalFileNamesFilter, type) {
|
||||
|
||||
console.log(type);
|
||||
|
||||
// Public methods -------------------
|
||||
|
||||
$scope.hide = function () {
|
||||
$mdDialog.hide();
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$mdDialog.cancel();
|
||||
};
|
||||
|
||||
$scope.answer = function (answer) {
|
||||
$mdDialog.hide(answer);
|
||||
};
|
||||
|
||||
$scope.allDocumentsAreIllegal = function() {
|
||||
return (fileNameFilter(uploader.queue, type).length === 0);
|
||||
};
|
||||
|
||||
$scope.areThereIllegalFiles = function() {
|
||||
return (illegalFileNamesFilter(uploader.queue, type).length !== 0);
|
||||
};
|
||||
|
||||
var uploader = $scope.uploader = new FileUploader({
|
||||
url: 'upload.php'
|
||||
});
|
||||
|
||||
// Private methods ------------------
|
||||
|
||||
// FILTERS
|
||||
|
||||
// a sync filter
|
||||
uploader.filters.push({
|
||||
name: 'syncFilter',
|
||||
fn: function (item /*{File|FileLikeObject}*/, options) {
|
||||
console.log('syncFilter');
|
||||
return this.queue.length < 10;
|
||||
}
|
||||
});
|
||||
|
||||
// an async filter
|
||||
uploader.filters.push({
|
||||
name: 'asyncFilter',
|
||||
fn: function (item /*{File|FileLikeObject}*/, options, deferred) {
|
||||
console.log('asyncFilter');
|
||||
setTimeout(deferred.resolve, 1e3);
|
||||
}
|
||||
});
|
||||
|
||||
// CALLBACKS
|
||||
|
||||
uploader.onWhenAddingFileFailed = function (item /*{File|FileLikeObject}*/, filter, options) {
|
||||
console.info('onWhenAddingFileFailed', item, filter, options);
|
||||
};
|
||||
uploader.onAfterAddingFile = function (fileItem) {
|
||||
console.info('onAfterAddingFile', fileItem);
|
||||
};
|
||||
uploader.onAfterAddingAll = function (addedFileItems) {
|
||||
console.info('onAfterAddingAll', addedFileItems);
|
||||
};
|
||||
uploader.onBeforeUploadItem = function (item) {
|
||||
console.info('onBeforeUploadItem', item);
|
||||
};
|
||||
uploader.onProgressItem = function (fileItem, progress) {
|
||||
console.info('onProgressItem', fileItem, progress);
|
||||
};
|
||||
uploader.onProgressAll = function (progress) {
|
||||
console.info('onProgressAll', progress);
|
||||
};
|
||||
uploader.onSuccessItem = function (fileItem, response, status, headers) {
|
||||
console.info('onSuccessItem', fileItem, response, status, headers);
|
||||
};
|
||||
uploader.onErrorItem = function (fileItem, response, status, headers) {
|
||||
console.info('onErrorItem', fileItem, response, status, headers);
|
||||
};
|
||||
uploader.onCancelItem = function (fileItem, response, status, headers) {
|
||||
console.info('onCancelItem', fileItem, response, status, headers);
|
||||
};
|
||||
uploader.onCompleteItem = function (fileItem, response, status, headers) {
|
||||
console.info('onCompleteItem', fileItem, response, status, headers);
|
||||
};
|
||||
uploader.onCompleteAll = function () {
|
||||
console.info('onCompleteAll');
|
||||
};
|
||||
|
||||
console.info('uploader', uploader);
|
||||
|
||||
});
|
||||
|
||||
})();
|
125
frontend/app/scripts/controllers/administrationSpace.js
Normal file
125
frontend/app/scripts/controllers/administrationSpace.js
Normal file
@ -0,0 +1,125 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name frontendApp.controller:AdministrationSpaceCtrl
|
||||
* @description
|
||||
* # AdministrationSpaceCtrl
|
||||
* Controller of the frontendApp
|
||||
*/
|
||||
angular.module('clientApp')
|
||||
.controller('AdministrationSpaceCtrl', function ($scope, $state, $mdDialog, FileUploader) {
|
||||
|
||||
angular.extend($scope, {
|
||||
logout,
|
||||
deleteAbsence,
|
||||
deleteTrackingSheet,
|
||||
importAbsences,
|
||||
importVisitSheets
|
||||
})
|
||||
|
||||
init();
|
||||
|
||||
// Public methods -------------------
|
||||
|
||||
function logout() {
|
||||
$state.go('login');
|
||||
}
|
||||
|
||||
function deleteAbsence(groupIndex, periodIndex, absenceIndex) {
|
||||
$scope.formationGroups[groupIndex].formattedAbsences[periodIndex].absences.splice(absenceIndex, 1);
|
||||
}
|
||||
|
||||
function deleteTrackingSheet(groupIndex, trackingSheetIndex) {
|
||||
$scope.formationGroups[groupIndex].trackingSheets.splice(trackingSheetIndex, 1);
|
||||
}
|
||||
|
||||
function importVisitSheets(ev) {
|
||||
$mdDialog.show({
|
||||
controller: 'AdministrationDialogCtrl',
|
||||
templateUrl: 'import-fiches-visite',
|
||||
parent: angular.element(document.body),
|
||||
targetEvent: ev,
|
||||
clickOutsideToClose: true,
|
||||
fullscreen: 'false',
|
||||
locals : { type : 'visit'}
|
||||
})
|
||||
.then(function (answer) {
|
||||
$scope.status = 'You said the information was "' + answer + '".';
|
||||
}, function () {
|
||||
$scope.status = 'You cancelled the dialog.';
|
||||
});
|
||||
}
|
||||
|
||||
function importAbsences(ev) {
|
||||
$mdDialog.show({
|
||||
controller: 'AdministrationDialogCtrl',
|
||||
templateUrl: 'import-fiches-absences',
|
||||
parent: angular.element(document.body),
|
||||
targetEvent: ev,
|
||||
clickOutsideToClose: true,
|
||||
fullscreen: 'false',
|
||||
locals : { type : 'absence'}
|
||||
})
|
||||
.then(function (answer) {
|
||||
$scope.status = 'You said the information was "' + answer + '".';
|
||||
}, function () {
|
||||
$scope.status = 'You cancelled the dialog.';
|
||||
});
|
||||
}
|
||||
|
||||
// Private methods ------------------
|
||||
|
||||
function init() {
|
||||
var formationGroups = [{
|
||||
label: "Master2 ICE",
|
||||
absences: [
|
||||
{ id: 1, title: "Absence_Matthieu_Penchenat_P1" },
|
||||
{ id: 2, title: "Absence_Renan_Husson_P1" },
|
||||
{ id: 3, title: "Absence_Renan_Husson_P2" },
|
||||
{ id: 1, title: "Absence_Renan_Husson_P3" },
|
||||
{ id: 2, title: "Absence_Matthieu_Penchenat_P2" },
|
||||
{ id: 3, title: "Absence_Matthieu_Penchenat_P3" },
|
||||
{ id: 1, title: "Absence_Quentin_Rouland_P1" },
|
||||
{ id: 2, title: "Absence_Quentin_Rouland_P2" },
|
||||
{ id: 3, title: "Absence_Quentin_Rouland_P3" },
|
||||
{ id: 1, title: "Absence_Sitan_Coulibaly_P1" },
|
||||
{ id: 2, title: "Absence_Sitan_Coulibaly_P2" },
|
||||
{ id: 3, title: "Absence_Sitan_Coulibaly_P3" }
|
||||
],
|
||||
trackingSheets: [
|
||||
{ id: 3, fileName: "FicheVisite_Sitan_Coulibaly_1" },
|
||||
{ id: 2, fileName: "FicheVisite_Sitan_Coulibaly_2" },
|
||||
{ id: 1, fileName: "FicheVisite_Sitan_Coulibaly_3" }
|
||||
]
|
||||
}, {
|
||||
label: "Master1 ISMAG",
|
||||
absences: [
|
||||
{ id: 1, title: "Absence_Matthieu_Penchenat_P1" },
|
||||
{ id: 2, title: "Absence_Matthieu_Penchenat_P2" },
|
||||
{ id: 3, title: "Absence_Matthieu_Penchenat_P3" }
|
||||
],
|
||||
trackingSheets: [
|
||||
{ id: 3, fileName: "FicheVisite_Renan_Husson_1" },
|
||||
{ id: 2, fileName: "FicheVisite_Renan_Husson_2" },
|
||||
{ id: 1, fileName: "FicheVisite_Renan_Husson_3" }
|
||||
]
|
||||
}];
|
||||
|
||||
$scope.formationGroups = formationGroups.map(function (formationGroup) {
|
||||
formationGroup.formattedAbsences = reformatAbsences(formationGroup.absences);
|
||||
return formationGroup;
|
||||
});
|
||||
}
|
||||
|
||||
function reformatAbsences(absences) {
|
||||
var myObj = _.groupBy(absences, function (absence) { return absence.title.split('_').pop(); });
|
||||
|
||||
return _.map(myObj, function (value, index) {
|
||||
return { period: index, absences: value };
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
28
frontend/app/scripts/services/Filters.js
Normal file
28
frontend/app/scripts/services/Filters.js
Normal file
@ -0,0 +1,28 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
angular.module('clientApp')
|
||||
.filter('fileName', function () {
|
||||
|
||||
return function (queue, type) {
|
||||
|
||||
var reg = (type === 'absence') ?/^Absence_[A-Z][a-z]*_[A-Z][a-z]*_P\d*.pdf$/ : /^Visite_[A-Z][a-z]*_[A-Z][a-z]*_\d*.pdf$/;
|
||||
return queue.filter(function (item) {
|
||||
return reg.test(item.file.name);
|
||||
});
|
||||
};
|
||||
})
|
||||
.filter('illegalFileNames', function () {
|
||||
|
||||
return function (queue, type) {
|
||||
|
||||
var reg = (type === 'absence') ?/^Absence_[A-Z][a-z]*_[A-Z][a-z]*_P\d*.pdf$/ : /^Visite_[A-Z][a-z]*_[A-Z][a-z]*_\d*.pdf$/;
|
||||
return queue.filter(function (item) {
|
||||
return !reg.test(item.file.name);
|
||||
}).map(function(item) {
|
||||
return item.file.name;
|
||||
});
|
||||
};
|
||||
});
|
||||
})();
|
@ -75,3 +75,23 @@ md-tab-data{
|
||||
/* border: 1px solid; */
|
||||
background-color: yellowgreen;
|
||||
}
|
||||
|
||||
.well {
|
||||
padding: 15px;
|
||||
margin: 5px;
|
||||
background-color: #e0e0e0;
|
||||
border: dotted 3px lightgray;
|
||||
}
|
||||
|
||||
.note {
|
||||
padding: 15px;
|
||||
margin: 5px;
|
||||
background-color: #a5d6a7;
|
||||
}
|
||||
|
||||
|
||||
.error {
|
||||
padding: 15px;
|
||||
margin: 5px;
|
||||
background-color: #ef9a9a;
|
||||
}
|
||||
|
304
frontend/app/views/administrationSpace.html
Normal file
304
frontend/app/views/administrationSpace.html
Normal file
@ -0,0 +1,304 @@
|
||||
<div layout="column">
|
||||
<md-toolbar>
|
||||
|
||||
<div class="md-toolbar-tools">
|
||||
<h2 md-truncate flex>Bienvenue Isabelle Michu</h2>
|
||||
|
||||
<md-button ng-click="logout()">
|
||||
Se déconnecter
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
</md-toolbar>
|
||||
</div>
|
||||
|
||||
<div layout="row" layout-margin>
|
||||
|
||||
<div ng-cloak flex="100">
|
||||
<md-content>
|
||||
<md-tabs md-dynamic-height md-border-bottom>
|
||||
<md-tab ng-repeat="formationGroup in formationGroups" label="{{formationGroup.label}}">
|
||||
|
||||
<div layout="row" layout-margin>
|
||||
<md-card flex="50">
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">Fiches d'absence</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
<md-content flex layout-padding>
|
||||
<div style="height: 350px; overflow: auto">
|
||||
<div ng-repeat="formattedAbsence in formationGroup.formattedAbsences">
|
||||
<md-subheader class="md-no-sticky">Période {{formattedAbsence.period}}</md-subheader>
|
||||
<md-list-item ng-repeat="absence in formattedAbsence.absences">
|
||||
<p>{{absence.title}}</p>
|
||||
<ng-md-icon icon="delete" style="fill: grey" size="24" ng-click="deleteAbsence($parent.$parent.$index, $parent.$index, $index)"></ng-md-icon>
|
||||
</md-list-item>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<center>
|
||||
<md-button class="md-raised md-primary" ng-click="importAbsences($event)">Importer des fiches d'absences</md-button>
|
||||
</center>
|
||||
</md-content>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
<md-card flex="50">
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">Fiches de visite</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
<md-content flex layout-padding>
|
||||
<div style="height: 350px; overflow: auto">
|
||||
<md-list-item ng-repeat="sheet in formationGroup.trackingSheets">
|
||||
<p>{{sheet.fileName}}</p>
|
||||
<ng-md-icon icon="delete" style="fill: grey" size="24" ng-click="deleteTrackingSheet($parent.$index, $index)"></ng-md-icon>
|
||||
</md-list-item>
|
||||
</div>
|
||||
<br />
|
||||
<center>
|
||||
<md-button class="md-raised md-primary" ng-click="importVisitSheets($event)">Importer des fiches de visite</md-button>
|
||||
</center>
|
||||
</md-content>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</div>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</md-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="import-fiches-absences">
|
||||
|
||||
<md-dialog aria-label="Importer des fiches d'absences">
|
||||
<form ng-cloak>
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Importer des fiches d'absences</h2>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content">
|
||||
|
||||
<div layout="row">
|
||||
|
||||
<div flex>
|
||||
|
||||
<h3>Importer des fiches d'absences</h3>
|
||||
<input type="file" nv-file-select="" accept=".pdf" uploader="uploader" multiple /><br/><br/>
|
||||
|
||||
<p class="note">
|
||||
<ng-md-icon icon="warning" size="20"></ng-md-icon>
|
||||
Note d'utilisation : chaque fiche d'absence doit respecter une règle de nommage.<br/> Nom
|
||||
de fichier : Absence_Prenom_Nom_PN.pdf<br/>
|
||||
<i>Le N de PN étant un numéro - exemple : (P1, P2, P3, etc.)</i>
|
||||
</p>
|
||||
|
||||
<div ng-if="uploader.queue.length !== 0">
|
||||
<h3>Liste des documents : </h3>
|
||||
<p>Nombre de documents : {{ uploader.queue.length }}</p>
|
||||
|
||||
<div ng-if="areThereIllegalFiles()" style="background-color: #ef9a9a; padding: 2px 10px 2px 10px;margin: 10px 0 10px 0">
|
||||
<p>
|
||||
Les documents suivants ne respectent pas la règle de nommage :
|
||||
</p>
|
||||
<ul>
|
||||
<li ng-repeat="fileName in uploader.queue | illegalFileNames:'absence'">{{fileName}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div ng-if="!allDocumentsAreIllegal()">
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%">Nom</th>
|
||||
<th ng-show="uploader.isHTML5">Taille</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in uploader.queue | fileName:'absence'">
|
||||
<td><strong>{{ item.file.name }}</strong></td>
|
||||
<td ng-show="uploader.isHTML5" nowrap>{{ item.file.size/1024/1024|number:2 }} MB</td>
|
||||
<td class="text-center">
|
||||
<span ng-show="item.isSuccess">
|
||||
<ng-md-icon icon="done" size="24"></ng-md-icon>
|
||||
</span>
|
||||
<span ng-show="item.isCancel">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon>
|
||||
</span>
|
||||
<span ng-show="item.isError">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon>
|
||||
</span>
|
||||
</td>
|
||||
<td nowrap>
|
||||
<md-button type="button" class="md-raised md-primary" ng-click="item.upload()" ng-disabled="item.isReady || item.isUploading || item.isSuccess">
|
||||
<ng-md-icon icon="file_upload" size="24"></ng-md-icon> Charger
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised" ng-click="item.cancel()" ng-disabled="!item.isUploading">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon> Annuler
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised md-warn" ng-click="item.remove()">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon> Supprimer
|
||||
</md-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style='margin-top : 40px'>
|
||||
<div>
|
||||
Niveau de progression :
|
||||
<md-progress-linear md-mode="determinate" value="{{uploader.progress}}"></md-progress-linear>
|
||||
</div>
|
||||
<md-button type="button" class="md-raised md-primary" ng-click="uploader.uploadAll()" ng-disabled="!uploader.getNotUploadedItems().length">
|
||||
<ng-md-icon icon="file_upload" size="24"></ng-md-icon> Tout charger
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised" ng-click="uploader.cancelAll()" ng-disabled="!uploader.isUploading">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon> Tout annuler
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised md-warn" ng-click="uploader.clearQueue()" ng-disabled="!uploader.queue.length">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon> Tout supprimer
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="answer('useful')">
|
||||
Fermer
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</md-dialog>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="import-fiches-visite">
|
||||
|
||||
<md-dialog aria-label="Importer des fiches de visite">
|
||||
<form ng-cloak>
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Importer des fiches de visite</h2>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content">
|
||||
|
||||
<div layout="row">
|
||||
|
||||
<div flex>
|
||||
|
||||
<h3>Importer des fiches de visite</h3>
|
||||
<input type="file" nv-file-select="" accept=".pdf" uploader="uploader" multiple /><br/><br/>
|
||||
|
||||
<p class="note">
|
||||
<ng-md-icon icon="warning" size="20"></ng-md-icon>
|
||||
Note d'utilisation : chaque fiche de visite doit respecter une règle de nommage.<br/> Nom
|
||||
de fichier : Visite_Prenom_Nom_N.pdf<br/>
|
||||
<i>Le N étant un numéro.</i>
|
||||
</p>
|
||||
|
||||
<div ng-if="uploader.queue.length !== 0">
|
||||
<h3>Liste des documents : </h3>
|
||||
<p>Nombre de documents : {{ uploader.queue.length }}</p>
|
||||
|
||||
<div ng-if="areThereIllegalFiles()" style="background-color: #ef9a9a; padding: 2px 10px 2px 10px;margin: 10px 0 10px 0">
|
||||
<p>
|
||||
Les documents suivants ne respectent pas la règle de nommage :
|
||||
</p>
|
||||
<ul>
|
||||
<li ng-repeat="fileName in uploader.queue | illegalFileNames:'visit'">{{fileName}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div ng-if="!allDocumentsAreIllegal()">
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%">Nom</th>
|
||||
<th ng-show="uploader.isHTML5">Taille</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in uploader.queue | fileName:'visit'">
|
||||
<td><strong>{{ item.file.name }}</strong></td>
|
||||
<td ng-show="uploader.isHTML5" nowrap>{{ item.file.size/1024/1024|number:2 }} MB</td>
|
||||
<td class="text-center">
|
||||
<span ng-show="item.isSuccess">
|
||||
<ng-md-icon icon="done" size="24"></ng-md-icon>
|
||||
</span>
|
||||
<span ng-show="item.isCancel">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon>
|
||||
</span>
|
||||
<span ng-show="item.isError">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon>
|
||||
</span>
|
||||
</td>
|
||||
<td nowrap>
|
||||
<md-button type="button" class="md-raised md-primary" ng-click="item.upload()" ng-disabled="item.isReady || item.isUploading || item.isSuccess">
|
||||
<ng-md-icon icon="file_upload" size="24"></ng-md-icon> Charger
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised" ng-click="item.cancel()" ng-disabled="!item.isUploading">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon> Annuler
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised md-warn" ng-click="item.remove()">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon> Supprimer
|
||||
</md-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style='margin-top : 40px'>
|
||||
<div>
|
||||
Niveau de progression :
|
||||
<md-progress-linear md-mode="determinate" value="{{uploader.progress}}"></md-progress-linear>
|
||||
</div>
|
||||
<md-button type="button" class="md-raised md-primary" ng-click="uploader.uploadAll()" ng-disabled="!uploader.getNotUploadedItems().length">
|
||||
<ng-md-icon icon="file_upload" size="24"></ng-md-icon> Tout charger
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised" ng-click="uploader.cancelAll()" ng-disabled="!uploader.isUploading">
|
||||
<ng-md-icon icon="cancel" size="24"></ng-md-icon> Tout annuler
|
||||
</md-button>
|
||||
<md-button type="button" class="md-raised md-warn" ng-click="uploader.clearQueue()" ng-disabled="!uploader.queue.length">
|
||||
<ng-md-icon icon="remove_circle_outline" size="24"></ng-md-icon> Tout supprimer
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="answer('useful')">
|
||||
Fermer
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</md-dialog>
|
||||
</script>
|
@ -8,6 +8,8 @@
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
|
||||
<md-button ui-sref="administrationSpace">Log to administration</md-button>
|
||||
<p class="p-home">
|
||||
The titles of Washed Out's breakthrough song and the first single from Paracosm share the two most important words in Ernest
|
||||
Greene's musical language: feel it. It's a simple request, as well...
|
||||
|
@ -13,37 +13,209 @@
|
||||
</div>
|
||||
|
||||
<div layout="row" layout-align="center none">
|
||||
<div ng-controller="AppCtrl" class="sample" layout="column" ng-cloak>
|
||||
<md-content class="md-padding">
|
||||
<md-tabs md-selected="selectedIndex" md-border-bottom md-autoselect>
|
||||
<md-tab ng-repeat="tab in tabs"
|
||||
ng-disabled="tab.disabled"
|
||||
label="{{tab.title}}">
|
||||
<div class="demo-tab tab{{$index%4}}" style="padding: 25px; text-align: center;">
|
||||
<div ng-bind="tab.content"></div>
|
||||
<br/>
|
||||
<md-button class="md-primary md-raised" ng-click="removeTab( tab )" ng-disabled="tabs.length <= 1">Remove Tab</md-button>
|
||||
</div>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</md-content>
|
||||
<md-content flex="80">
|
||||
|
||||
<form ng-submit="addTab(tTitle,tContent)" layout="column" class="md-padding" style="padding-top: 0;">
|
||||
<div layout="row" layout-sm="column">
|
||||
<div flex style="position: relative;">
|
||||
<h2 class="md-subhead" style="position: absolute; bottom: 0; left: 0; margin: 0; font-weight: 500; text-transform: uppercase; line-height: 35px; white-space: nowrap;">Add a new Tab:</h2>
|
||||
<div ng-cloak>
|
||||
<md-content>
|
||||
<md-tabs md-dynamic-height md-border-bottom>
|
||||
<md-tab label="périodes">
|
||||
<md-content class="md-padding">
|
||||
|
||||
|
||||
<div layout="row" layout-align="end">
|
||||
<md-button class="md-raised" ng-click="exportBooklet()">Exporter le livret</md-button>
|
||||
</div>
|
||||
<md-input-container>
|
||||
<label for="label">Label</label>
|
||||
<input type="text" id="label" ng-model="tTitle">
|
||||
|
||||
<md-card ng-repeat="period in periods">
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<h2 class="md-display-1">Période {{period.number}}</h2>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
|
||||
|
||||
<div layout="column" layout-fill>
|
||||
<div flex class="green darken-2">
|
||||
<h3 class="md-headline" style="padding-left : 10px">
|
||||
Université
|
||||
<md-button class="md-icon-button" ng-click="toggleAccordion(period.university.icon, 'false', $index)" aria-label="call">
|
||||
<ng-md-icon icon="{{period.university.icon}}" style="fill: blue" size="24"></ng-md-icon>
|
||||
</md-button>
|
||||
</h3>
|
||||
|
||||
<md-content flex layout-padding ng-if="isOpenAccordion(period.university.icon)">
|
||||
<p>Lorem ipsum dolor sit amet, ne quod novum mei. Sea omnium invenire mediocrem at, in lobortis conclusionemque
|
||||
nam. Ne deleniti appetere reprimique pro, inani labitur disputationi te sed. At vix sale omnesque,
|
||||
id pro labitur reformidans accommodare, cum labores honestatis eu. Nec quem lucilius in, eam praesent
|
||||
reformidans no. Sed laudem aliquam ne.</p><br/>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label>Commentaire de période</label>
|
||||
<textarea ng-model="period.university.comment" md-maxlength="150" rows="5" md-select-on-focus></textarea>
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label for="content">Content</label>
|
||||
<input type="text" id="content" ng-model="tContent">
|
||||
|
||||
<div layout="row" layout-align="end">
|
||||
<md-button class="md-raised">Enregistrer</md-button>
|
||||
</div>
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
<div flex class="green darken-3">
|
||||
<h3 class="md-headline" style="padding-left : 10px">
|
||||
Entreprise
|
||||
<md-button class="md-icon-button" ng-click="toggleAccordion(period.company.icon, 'true', $index)" aria-label="call">
|
||||
<ng-md-icon icon="{{period.company.icon}}" style="fill: blue" size="24"></ng-md-icon>
|
||||
</md-button>
|
||||
</h3>
|
||||
|
||||
<md-content flex layout-padding ng-if="isOpenAccordion(period.company.icon)">
|
||||
<p>Lorem ipsum dolor sit amet, ne quod novum mei. Sea omnium invenire mediocrem at, in lobortis conclusionemque
|
||||
nam. Ne deleniti appetere reprimique pro, inani labitur disputationi te sed. At vix sale omnesque,
|
||||
id pro labitur reformidans accommodare, cum labores honestatis eu. Nec quem lucilius in, eam praesent
|
||||
reformidans no. Sed laudem aliquam ne.</p>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label>Commentaire de période</label>
|
||||
<textarea ng-model="period.company.comment" md-maxlength="150" rows="5" md-select-on-focus></textarea>
|
||||
</md-input-container>
|
||||
<md-button class="add-tab md-primary md-raised" ng-disabled="!tTitle || !tContent" type="submit" style="margin-right: 0;">Add Tab</md-button>
|
||||
|
||||
<div layout="row" layout-align="end">
|
||||
<md-button class="md-raised">Enregistrer</md-button>
|
||||
</div>
|
||||
</md-content>
|
||||
</div>
|
||||
</div>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
</md-content>
|
||||
</md-tab>
|
||||
<md-tab label="données administratives">
|
||||
<md-content class="md-padding">
|
||||
<md-content layout-padding>
|
||||
<form name="projectForm">
|
||||
<md-card>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<h3 class="md-headline">Informations personnelles</h3>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50">
|
||||
<label>Prénom</label>
|
||||
<input required name="studentFirstName" ng-model="studentFirstName">
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50">
|
||||
<label>Nom</label>
|
||||
<input required name="studentLastName" ng-model="studentLastName">
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50">
|
||||
<label>Type de contrat / d'engagement</label>
|
||||
<md-select name="type" ng-model="contractType" required>
|
||||
<md-option value="Apprentissage">Apprentissage</md-option>
|
||||
<md-option value="Professionnalisation">Professionnalisation</md-option>
|
||||
<md-option value="Stage">Stage</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="25">
|
||||
<label>Début de contrat</label>
|
||||
<md-datepicker ng-model="beginContractDate"></md-datepicker>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="25">
|
||||
<label>Fin de contrat</label>
|
||||
<md-datepicker ng-model="endContractDate"></md-datepicker>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50">
|
||||
<label>Email</label>
|
||||
<input required type="email" name="studentEmail" ng-model="studentEmail" />
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50">
|
||||
<label>Téléphone</label>
|
||||
<input required type="text" name="studentPhoneNumber" ng-model="studentPhoneNumber" />
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
<md-card>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<h3 class="md-headline">Tuteur pédagogique</h3>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label>Email</label>
|
||||
<input required type="email" name="universityEmail" ng-model="universityEmail" />
|
||||
</md-input-container>
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
<md-card>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<h3 class="md-headline">Tuteur entreprise</h3>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50">
|
||||
<label>Prénom</label>
|
||||
<input required name="companyFirstName" ng-model="companyFirstName">
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50">
|
||||
<label>Nom</label>
|
||||
<input required name="companyLastName" ng-model="companyLastName">
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50">
|
||||
<label>Nom de l'entreprise</label>
|
||||
<input name="companyName" ng-model="companyName">
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50">
|
||||
<label>Lieu de l'alternance</label>
|
||||
<input name="companyAddress" ng-model="companyAddress">
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label>Email</label>
|
||||
<input required type="email" name="companyEmail" ng-model="companyEmail" />
|
||||
</md-input-container>
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
<div layout="row" layout-align="end">
|
||||
<md-button class="md-raised">Enregistrer</md-button>
|
||||
</div>
|
||||
</form>
|
||||
</md-content>
|
||||
</md-tab>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</md-tabs>
|
||||
</md-content>
|
||||
</div>
|
||||
</md-content>
|
||||
</div>
|
@ -8,7 +8,9 @@
|
||||
"angular-sanitize": "^1.4.0",
|
||||
"angular-material": "^1.1.3",
|
||||
"angular-ui-router": "^0.4.2",
|
||||
"angular-material-icons": "^0.7.1"
|
||||
"angular-material-icons": "^0.7.1",
|
||||
"underscore": "^1.8.3",
|
||||
"angular-file-upload": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.4.0"
|
||||
|
@ -29,6 +29,8 @@ module.exports = function(config) {
|
||||
'bower_components/angular-material/angular-material.js',
|
||||
'bower_components/angular-ui-router/release/angular-ui-router.js',
|
||||
'bower_components/angular-material-icons/angular-material-icons.min.js',
|
||||
'bower_components/underscore/underscore.js',
|
||||
'bower_components/angular-file-upload/dist/angular-file-upload.min.js',
|
||||
'bower_components/angular-mocks/angular-mocks.js',
|
||||
// endbower
|
||||
'app/scripts/**/*.js',
|
||||
|
Reference in New Issue
Block a user