Commit ecd54252f437cc431dd46a3b4cb41d59e1d854d8

Authored by hitier
2 parents fffa70b6 292dbb6f

Add edit forms

with data models update
CHANGELOG.md
... ... @@ -23,6 +23,16 @@ Should be noted only functionnal changes,
23 23 or major refactoring improvments.
24 24  
25 25 ## Unreleased
  26 +## [0.3.pre-9] - 2021-05-14 - Edit Forms
  27 +### New
  28 +Charge, Agent, Project Edit
  29 +Bootstrap form display
  30 +### Changed
  31 +Agent and Project descriptions
  32 +Category/Label data model
  33 +
  34 +
  35 +## Unreleased
26 36 ## [0.3.pre-8] - 2021-05-06 - Front Fixes
27 37 ### Changed
28 38 Sidebar menu enhanced
... ...
VERSION.txt
1   -0.3.pre-8
  1 +0.3.pre-9
... ...
app/commands/commands.py
... ... @@ -10,7 +10,7 @@ from sqlalchemy.exc import IntegrityError
10 10 from sqlalchemy.sql import func
11 11  
12 12 from app.models import db, Agent, Service, Project, Capacity, Period, Charge, AgentStatus, Company, AgentBap, \
13   - AgentGrade, Category, Label, ProjectLabel
  13 + AgentGrade, Category, Label, ProjectLabel, CategoryLabel
14 14 # TODO: rename to methods and add get_roles()
15 15 from app.auth.models import User, _nameToRole, _roleToName
16 16  
... ... @@ -79,15 +79,22 @@ def feed_from_irap(csv_file_name):
79 79 grades = []
80 80 companies = []
81 81 statuses = []
  82 + labels = []
82 83  
83   - # build a category dict of lists
  84 + # Build a category dict of lists
84 85 # key being the category name,
85 86 # list being filled with corresponding labels
86   - categories = {k: [] for k in categorie_keys}
  87 + categorie_labels = {k: [] for k in categorie_keys}
87 88  
  89 + # Projects' labels is a dict of lists
  90 + # indexed by project name
  91 + # containing labels for that project
88 92 #
89   - categorized_projects = {}
  93 + project_labels = {}
90 94  
  95 + #
  96 + # Parse the rows and fill in various lists
  97 + #
91 98 for r in rows:
92 99 services.append(r[service_key])
93 100 baps.append(r[bap_key])
... ... @@ -95,16 +102,20 @@ def feed_from_irap(csv_file_name):
95 102 companies.append(r[company_key])
96 103 statuses.append(r[status_key])
97 104  
98   - # categorized_projects
99   - project = r[project_key]
100   - categorized_projects[project] = []
  105 + # the projet and its labels
  106 + project_name = r[project_key]
  107 + project_labels[project_name] = []
101 108  
102   - # fill in the category-labels dict
  109 + # now fill in both
  110 + # the labels list,
  111 + # the category-labels dict,
  112 + # and the project-labels dict
103 113 for k in categorie_keys:
104   - categories[k].append(r[k])
105   - categorized_projects[project].append(r[k])
  114 + labels.append(r[k])
  115 + categorie_labels[k].append(r[k])
  116 + project_labels[project_name].append(r[k])
106 117  
107   - # build agents list of dicts
  118 + # create the agents list of dicts
108 119 agents.append({
109 120 'firstname': r[firstname_key],
110 121 'secondname': r[secondname_key],
... ... @@ -124,25 +135,25 @@ def feed_from_irap(csv_file_name):
124 135 #
125 136 # 1- first remove empty string with filter()
126 137 # 2- then keep only uniq item with set()
127   - # 3- at last alpha sort with sorted()
  138 + # 3- at last alpha sort with sorted()
128 139 #
129 140 services = sorted(set(filter(None, services)))
130 141 baps = sorted(set(filter(None, baps)))
131 142 grades = sorted(set(filter(None, grades)))
132 143 companies = sorted(set(filter(None, companies)))
133 144 statuses = sorted(set(filter(None, statuses)))
  145 + labels = sorted(set(filter(None, labels)))
134 146  
135   - # Do the same for the projects, that are keys of categorized_projects
  147 + # Do the same for the projects, that are keys of project_labels
136 148 #
137   - projects = sorted(set(categorized_projects.keys()))
  149 + projects = sorted(set(project_labels.keys()))
138 150  
139   - # Do the same for the category labels stored as a dict
  151 + # Do the same for the labels inside each category
  152 + # c is the category name containing the labels list
140 153 #
141   - # k is category name
142   - # v is labels list for that category
143   - for k in categorie_keys:
144   - labels = sorted(set(filter(None, categories[k])))
145   - categories[k] = labels
  154 + for c in categorie_keys:
  155 + c_labels = sorted(set(filter(None, categorie_labels[c])))
  156 + categorie_labels[c] = c_labels
146 157  
147 158 # At least, as agents is a list of dicts, sorting is a bit tricky
148 159 #
... ... @@ -153,6 +164,12 @@ def feed_from_irap(csv_file_name):
153 164 agents = list({(a['firstname'], a['secondname']): a for a in agents}.values())
154 165 agents = sorted(agents, key=lambda a: (a['firstname'], a['secondname']))
155 166  
  167 + #
  168 + # We are done with collecting data
  169 + #
  170 + # Now we write to database
  171 + #
  172 +
156 173 # Feed baps from column
157 174 #
158 175 for b in baps:
... ... @@ -188,6 +205,20 @@ def feed_from_irap(csv_file_name):
188 205 db.session.add(n_p)
189 206 db.session.commit()
190 207  
  208 + # Feed labels from column
  209 + #
  210 + for _l in labels:
  211 + n_l = Label(name=_l)
  212 + db.session.add(n_l)
  213 + db.session.commit()
  214 +
  215 + # Feed categories from initial list
  216 + #
  217 + for _c in categorie_keys:
  218 + n_c = Category(name=_c)
  219 + db.session.add(n_c)
  220 + db.session.commit()
  221 +
191 222 # Feed services from column
192 223 #
193 224 for s in services:
... ... @@ -212,25 +243,25 @@ def feed_from_irap(csv_file_name):
212 243  
213 244 # Feed categories and labels
214 245 #
215   - for c, l in categories.items():
216   - current_app.logger.debug("Adding category " + c)
217   - n_c = Category(name=c)
218   - db.session.add(n_c)
219   - # db.session.commit()
220   - for label in l:
221   - current_app.logger.debug("Adding label {} for id {}".format(label, n_c.id))
222   - n_l = Label(name=label, category=n_c)
223   - db.session.add(n_l)
  246 + for category, labels in categorie_labels.items():
  247 + print(category)
  248 + n_c = Category.query.filter_by(name=category).one()
  249 + for label in labels:
  250 + print(label)
  251 + n_l = Label.query.filter(Label.name == label).one()
  252 + current_app.logger.debug(f"Adding label {label} to category {category}")
  253 + n_cl = CategoryLabel(label=n_l, category=n_c)
  254 + db.session.add(n_cl)
224 255 db.session.commit()
225 256  
226 257 # Feed project's labels
227 258 #
228   - for p, labels in categorized_projects.items():
229   - n_p = Project.query.filter(Project.name == p).one()
230   - for l in labels:
231   - n_l = Label.query.filter(Label.name == l).one()
232   - n_c = n_l.category
233   - n_pl = ProjectLabel(project=n_p, category=n_c, label=n_l)
  259 + for project, labels in project_labels.items():
  260 + print(f"Project {project}")
  261 + n_p = Project.query.filter(Project.name == project).one()
  262 + for label in labels:
  263 + n_l = Label.query.filter(Label.name == label).one()
  264 + n_pl = ProjectLabel(project=n_p, label=n_l)
234 265 db.session.add(n_pl)
235 266 db.session.commit()
236 267  
... ... @@ -305,7 +336,65 @@ def feed_from_lesia():
305 336 Remember to configure the proper database uri in the db_config.py file.
306 337 """
307 338 from .lesia_db import lesia_agent, lesia_session, lesia_service, lesia_project, \
308   - lesia_fonction, lesia_periods, lesia_affectation
  339 + lesia_fonction, lesia_periods, lesia_affectation, lesia_domains, lesia_poles, \
  340 + lesia_domainprojects
  341 +
  342 + # Feed all lesia 'domaine' names and link to new category "Domaine"
  343 + #
  344 + domain_category = Category(name="Domaine")
  345 + domains = lesia_session.query(lesia_domains)
  346 + for d in domains:
  347 + n_l = Label(name=d.nom)
  348 + n_cl = CategoryLabel(category=domain_category, label=n_l)
  349 + db.session.add(n_cl)
  350 + db.session.commit()
  351 +
  352 + # Feed all lesia 'pรดle' names and link to new category "Pรดle"
  353 + #
  354 + pole_category = Category(name="Pรดle")
  355 + poles = lesia_session.query(lesia_poles)
  356 + for p in poles:
  357 + n_l = Label(name=p.nom)
  358 + n_cl = CategoryLabel(category=pole_category, label=n_l)
  359 + db.session.add(n_cl)
  360 + db.session.commit()
  361 +
  362 + # Feed lesia project with proper "pรดle"
  363 + # (as this information is stored in gestit_projets)
  364 + #
  365 + projects = lesia_session.query(lesia_project).all()
  366 + for p in projects:
  367 + # add project
  368 + n_p = Project(id=p.id, name=p.nom)
  369 + db.session.add(n_p)
  370 + # get corresponding lesia pole name
  371 + pole_name = lesia_session.query(lesia_poles).filter(lesia_poles.id == p.pole_id).one().nom
  372 + # search corresponding Label and store in ProjectLabel table
  373 + n_l = Label.query.filter(Label.name == pole_name).one()
  374 + n_pl = ProjectLabel(project=n_p, label=n_l)
  375 + db.session.add(n_pl)
  376 + db.session.commit()
  377 +
  378 + # Get projects domain information and store in ProjectLabel
  379 + #
  380 + domain_projects = lesia_session.query(lesia_domainprojects)
  381 + for dp in domain_projects:
  382 + project_name = lesia_session.query(lesia_project).filter(lesia_project.id == dp.projet_id).one().nom
  383 + domain_name = lesia_session.query(lesia_domains).filter(lesia_domains.id == dp.domaine_id).one().nom
  384 + n_p = Project.query.filter(Project.name == project_name).one()
  385 + n_l = Label.query.filter(Label.name == domain_name).one()
  386 + n_pl = ProjectLabel(project=n_p, label=n_l)
  387 + db.session.add(n_pl)
  388 + # Some projects have 2 domain labels in lesia db
  389 + # That is not allowed any more in the new model.
  390 + #
  391 + try:
  392 + db.session.commit()
  393 + except IntegrityError:
  394 + db.session.rollback()
  395 + current_app.logger.error(
  396 + "Error adding project to category/label: {} {} {}".format(n_p.name, n_l.category.name, n_l.name))
  397 + continue
309 398  
310 399 agents = lesia_session.query(lesia_agent).all()
311 400 for a in agents:
... ... @@ -319,12 +408,6 @@ def feed_from_lesia():
319 408 db.session.add(n_s)
320 409 db.session.commit()
321 410  
322   - projects = lesia_session.query(lesia_project).all()
323   - for p in projects:
324   - n_p = Project(id=p.id, name=p.nom)
325   - db.session.add(n_p)
326   - db.session.commit()
327   -
328 411 fonctions = lesia_session.query(lesia_fonction).all()
329 412 for f in fonctions:
330 413 n_c = Capacity(id=f.id, name=f.nom)
... ...
app/commands/lesia_db.py
... ... @@ -20,7 +20,6 @@ except OperationalError as oe:
20 20  
21 21 lesia_session = Session(engine)
22 22  
23   -
24 23 # mapped classes are now created with names by default
25 24 # matching that of the table name.
26 25 lesia_agent = lesia_base.classes.agent
... ... @@ -29,4 +28,6 @@ lesia_project = lesia_base.classes.gestit_projets
29 28 lesia_fonction = lesia_base.classes.gestit_fonctions
30 29 lesia_affectation = lesia_base.classes.gestit_affectations
31 30 lesia_periods = lesia_base.classes.gestit_semestres
32   -
  31 +lesia_domains = lesia_base.classes.gestit_domaines
  32 +lesia_poles = lesia_base.classes.poles
  33 +lesia_domainprojects = lesia_base.classes.gestit_domaine_projets
... ...
app/db_mgr.py
1   -from app.models import db, Period
  1 +from app.models import db, Period, Project, Category
2 2  
3 3 # TODO: make this configurable, and choose another place to use it,
4 4 # in 'routes.py' maybe
... ... @@ -8,14 +8,53 @@ from app.models import db, Period
8 8 charge_unit = 100
9 9  
10 10  
  11 +def category_labels():
  12 + """
  13 + Build a list of dicts of the categorized labels for form display:
  14 +
  15 + [{'name': 'Domaine',
  16 + 'labels': [{'id': 1, 'name': 'R&T'},
  17 + {'id': 3, 'name': 'Autres 2'},
  18 + .
  19 + .
  20 + .
  21 + {'id': 11, 'name': 'Instrumentation Sol'}],
  22 + },
  23 + {'name': 'Pรดle',
  24 + 'labels': [{'id': 12, 'name': 'Astronomie'},
  25 + {'id': 14, 'name': 'Solaire'},
  26 + .
  27 + .
  28 + .
  29 + {'id': 21, 'name': 'Administration'}]
  30 + }]
  31 +
  32 +
  33 + :return:
  34 + """
  35 + _categories = []
  36 + for _c in Category.query.all():
  37 + _category = {'name': _c.name, 'labels': []}
  38 + for _l in _c.labels:
  39 + _category['labels'].append({'id': _l.label.id, 'name': _l.label.name})
  40 + _categories.append(_category)
  41 + return _categories
  42 +
  43 +
11 44 def projects():
12 45 """
13   - Build the list of all agents, with their charges for the current period
  46 + Build the list of all projects, with their charges for the current period
  47 +
  48 + Returns a table with headers in the first line:
  49 + Id, Project name, Category_1, ... , Category_n, Total_Charge_for_the_period
  50 +
  51 + The category cells embed the list of the labels of the project for that category.
  52 +
14 53 :return:
15 54 """
16 55 current_period_id = get_current_period()
17 56 sql_txt = """
18   - select p.id, p.name, IFNULL(sum(tc.charge_rate), 0) as total_charge
  57 + select p.id, IFNULL(sum(tc.charge_rate), 0) as total_charge
19 58 from project as p left join
20 59 ( select c.project_id, c.charge_rate from charge c where c.period_id = {})
21 60 tc
... ... @@ -23,7 +62,37 @@ def projects():
23 62 group by p.id
24 63 order by total_charge desc;
25 64 """.format(current_period_id)
26   - all_projects = db.session.execute(sql_txt).fetchall()
  65 + projects_charges = db.session.execute(sql_txt).fetchall()
  66 + # First row is table titles
  67 + all_projects = [["Id", "Projet"]]
  68 + # Add all categories as next table titles
  69 + # headers will then look like [["Id", "Projet", "Domaine", "Pรดle"]]
  70 + categories = Category.query.all()
  71 + for c in categories:
  72 + all_projects[0].append(c.name)
  73 + # then add charge title
  74 + # headers become [["Id", "Projet", "Domaine", "Pรดle", "Charge"]]
  75 + all_projects[0].append("Charge")
  76 + # Build the table row by row
  77 + for _pc in projects_charges:
  78 + p_id = _pc[0]
  79 + project = Project.query.get(int(p_id))
  80 + # adding 2 first columns: id, name
  81 + project_row = [project.id, project.name]
  82 + # then labels, many for each category
  83 + for category in categories:
  84 + # we build the labels.name list of the intersection of current category's labels with project's labels
  85 + # Comprehensive shortcut is:
  86 + # labels = [_cl.label.name for _cl in category.labels if _cl.label in [_pl.label for _pl in project.labels]]
  87 + #
  88 + category_labels = [_cl.label for _cl in category.labels]
  89 + project_labels = [_pl.label for _pl in project.labels]
  90 + intersection_labels = list(set.intersection(set(project_labels), set(category_labels)))
  91 + labels = [label.name for label in intersection_labels]
  92 + project_row.append(labels)
  93 + # then add total charge
  94 + project_row.append(_pc[1])
  95 + all_projects.append(project_row)
27 96 return all_projects
28 97  
29 98  
... ...
app/main/routes.py
  1 +from pprint import pprint
1 2  
2   -from flask import render_template, make_response, current_app, redirect, url_for, request
  3 +from flask import render_template, make_response, current_app, redirect, url_for, request, flash
3 4 from flask_login import login_required, current_user
4 5  
5 6 from . import bp
6 7  
7   -from app.models import Agent, Project, Service, Capacity, Period
  8 +from app.models import Agent, Project, Service, Capacity, Period, db, Company, AgentGrade, AgentStatus, AgentBap, Charge
8 9 from app import db_mgr
9 10 from app.auth.routes import role_required
10 11  
... ... @@ -85,12 +86,6 @@ def periods():
85 86 periods=all_periods)
86 87  
87 88  
88   -@bp.route('/charge/add')
89   -@role_required('service')
90   -def charge_add():
91   - return render_template('charge.html', subtitle="Affecter un agent")
92   -
93   -
94 89 @bp.route('/project/<project_id>')
95 90 @role_required('project')
96 91 def project(project_id):
... ... @@ -98,7 +93,7 @@ def project(project_id):
98 93 this_project = Project.query.get(int(project_id))
99 94 charges_table = db_mgr.charges_by_project(project_id)
100 95 return render_template('project.html',
101   - project=this_project,
  96 + project=this_project.to_struct(),
102 97 charges=charges_table,
103 98 subtitle="{}".format(this_project.name))
104 99  
... ... @@ -112,7 +107,125 @@ def agent(agent_id):
112 107 return render_template('agent.html',
113 108 agent=this_agent,
114 109 charges=charges_table,
115   - subtitle="{} {}".format(this_agent.firstname, this_agent.secondname))
  110 + subtitle=f"{this_agent.fullname}")
  111 +
  112 +
  113 +# - - - - - - - - - - - - - - - - - - - - FORMS - - - - - - - - - - - - - - - - - - - - #
  114 +
  115 +@bp.route('/project/create', methods=('POST', 'GET'))
  116 +@bp.route('/project/<project_id>/edit', methods=('POST', 'GET'))
  117 +@role_required('service')
  118 +def project_edit(project_id=None):
  119 + if request.method == 'GET':
  120 + if project_id:
  121 + this_project = Project.query.get(int(project_id))
  122 + else:
  123 + this_project = Project()
  124 + project_struct = this_project.to_struct()
  125 + categories = db_mgr.category_labels()
  126 + return render_template('project_form.html', project=project_struct,
  127 + categories=categories)
  128 + elif request.method == 'POST':
  129 + try:
  130 + project_id = request.form['project_id']
  131 + except KeyError:
  132 + project_id = None
  133 + if project_id:
  134 + # then update existing
  135 + this_project = Project.query.get(int(project_id))
  136 + done_string = "mis ร  jour."
  137 + else:
  138 + # or create from scratch
  139 + this_project = Project()
  140 + done_string = "ajoutรฉ."
  141 + # fill orm with form and write to db
  142 + this_project.from_request(request)
  143 + db.session.add(this_project)
  144 + db.session.commit()
  145 + # we're done
  146 + flash(f"Project {this_project.name} (#{this_project.id}) " + done_string)
  147 + return redirect(url_for('main.project', project_id=this_project.id))
  148 +
  149 +
  150 +@bp.route('/agent/create', methods=('POST', 'GET'))
  151 +@bp.route('/agent/<agent_id>/edit', methods=('POST', 'GET'))
  152 +@role_required('service')
  153 +def agent_edit(agent_id=None):
  154 + # Make the form, filled with existing agent if updating
  155 + #
  156 + if request.method == 'GET':
  157 + companies = Company.query.all()
  158 + grades = AgentGrade.query.all()
  159 + statuses = AgentStatus.query.all()
  160 + baps = AgentBap.query.all()
  161 + if agent_id:
  162 + this_agent = Agent.query.get(int(agent_id))
  163 + else:
  164 + this_agent = Agent()
  165 + # export to structure for jinja display
  166 + agent_struct = this_agent.to_struct()
  167 + return render_template('agent_form.html', agent=agent_struct,
  168 + companies=companies,
  169 + statuses=statuses,
  170 + baps=baps,
  171 + grades=grades)
  172 + # Or submit for db writing
  173 + #
  174 + elif request.method == 'POST':
  175 + try:
  176 + agent_id = request.form['agent_id']
  177 + except KeyError:
  178 + agent_id = None
  179 + if agent_id:
  180 + # then update existing
  181 + this_agent = Agent.query.get(int(agent_id))
  182 + done_string = "mis ร  jour."
  183 + else:
  184 + # or create from scratch
  185 + this_agent = Agent()
  186 + done_string = "ajoutรฉ."
  187 + # fill orm with form and write to db
  188 + this_agent.from_request(request)
  189 + db.session.add(this_agent)
  190 + db.session.commit()
  191 + # we're done
  192 + flash(f"Agent {this_agent.fullname} (#{this_agent.id}) " + done_string)
  193 + return redirect(url_for('main.agent', agent_id=this_agent.id))
  194 +
  195 +
  196 +@bp.route('/charge/add', methods=('POST', 'GET'))
  197 +@role_required('service')
  198 +def charge_add():
  199 + if request.method == 'GET':
  200 + try:
  201 + this_agent = Agent.query.get(int(request.args['agent_id']))
  202 + except KeyError:
  203 + this_agent = None
  204 + try:
  205 + this_project = Project.query.get(int(request.args['project_id']))
  206 + except KeyError:
  207 + this_project = None
  208 + this_agents = Agent.query.order_by(Agent.firstname).all()
  209 + this_projects = Project.query.order_by(Project.name).all()
  210 + this_services = Service.query.order_by(Service.name).all()
  211 + this_periods = Period.query.order_by(Period.id).all()
  212 + this_capacities = Capacity.query.order_by(Capacity.name).all()
  213 + return render_template('charge_form.html', subtitle="Affecter un agent",
  214 + agent=this_agent,
  215 + project=this_project,
  216 + projects=this_projects,
  217 + services=this_services,
  218 + periods=this_periods,
  219 + capacities=this_capacities,
  220 + agents=this_agents)
  221 + elif request.method == 'POST':
  222 + this_agent = Agent.query.get(request.form.get('agent_id'))
  223 + this_charge = Charge()
  224 + this_charge.from_request(request)
  225 + db.session.add(this_charge)
  226 + db.session.commit()
  227 + flash(f"Nouvelle charge pour l'agent {this_agent.fullname}")
  228 + return redirect(url_for('main.agent', agent_id=this_agent.id))
116 229  
117 230  
118 231 # - - - - - - - - - - - - - - - - - - - - REST API - - - - - - - - - - - - - - - - - - - -
... ... @@ -141,4 +254,3 @@ def charge_agent_csv(agent_id):
141 254 resp = make_response("\n".join(csv_table))
142 255 resp.headers['Content-Type'] = 'text/plain;charset=utf8'
143 256 return resp
144   -
... ...
app/main/templates/agent.html
1 1 {% extends "base_page.html" %}
2 2  
3 3 {% block more_heads %}
4   -<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet" type="text/css"/>
  4 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  5 + type="text/css"/>
5 6 {% endblock %}
6 7  
7 8 {% block content %}
... ... @@ -9,24 +10,84 @@
9 10 <!-- Invisible span to definte wich ul and a in the navbar are actived -->
10 11 <span id="nav_actived" style="display: none">agent,agents</span>
11 12  
12   -<div id="projects_chart" class="charge_chart"></div>
  13 +<div class="card">
  14 + <div class="card-header">
  15 + Fiche Agent
  16 + </div>
  17 + <div class="card-body">
  18 + <dl class="row agent">
  19 + <dt class="col-sm-2 text-right">ID :</dt>
  20 + <dd class="col-sm-10 text-left">{{agent.id}}</dd>
  21 + <dt class="col-sm-2 text-right">Genre :</dt>
  22 + <dd class="col-sm-10 text-left"></dd>
  23 + <dt class="col-sm-2 text-right">Date de naissance :</dt>
  24 + <dd class="col-sm-10 text-left"></dd>
  25 + <dt class="col-sm-2 text-right">E-mail :</dt>
  26 + <dd class="col-sm-10 text-left"></dd>
  27 + {# TODO: put different spacing #}
  28 + <dt class="col-sm-2 text-right"></dt>
  29 + <dd class="col-sm-10 text-left"></dd>
  30 + <dt class="col-sm-2 text-right">Statut :</dt>
  31 + <dd class="col-sm-10 text-left">{{agent.status.name}}</dd>
  32 + <dt class="col-sm-2 text-right">Organisme :</dt>
  33 + <dd class="col-sm-10 text-left">{{agent.company.name}}</dd>
  34 + <dt class="col-sm-2 text-right">Corps :</dt>
  35 + <dd class="col-sm-10 text-left"></dd>
  36 + <dt class="col-sm-2 text-right">Grade :</dt>
  37 + <dd class="col-sm-10 text-left">{{agent.grade.name}}</dd>
  38 + <dt class="col-sm-2 text-right">BAP :</dt>
  39 + <dd class="col-sm-10 text-left">{{agent.bap.name}}</dd>
  40 + {# TODO: puth different spacing #}
  41 + <dt class="col-sm-2 text-right"></dt>
  42 + <dd class="col-sm-10 text-left"></dd>
  43 + <dt class="col-sm-2 text-right">Quotitรฉ :</dt>
  44 + <dd class="col-sm-10 text-left"></dd>
  45 + <dt class="col-sm-2 text-right">Date arrivรฉe :</dt>
  46 + <dd class="col-sm-10 text-left"></dd>
  47 + <dt class="col-sm-2 text-right">Date dรฉpart :</dt>
  48 + <dd class="col-sm-10 text-left"></dd>
  49 + {# TODO: put different spacing #}
  50 + <dt class="col-sm-2 text-right"></dt>
  51 + <dd class="col-sm-10 text-left"></dd>
  52 + <dt class="col-sm-2 text-right">Commentaires :</dt>
  53 + <dd class="col-sm-10 text-left"></dd>
  54 + {# TODO: put different spacing #}
  55 + <dt class="col-sm-2 text-right"></dt>
  56 + <dd class="col-sm-10 text-left"></dd>
  57 + <dt class="col-sm-2 text-right">Fiche de poste :</dt>
  58 + <dd class="col-sm-10 text-left"></dd>
  59 + {# TODO: put different spacing #}
  60 + <dt class="col-sm-2 text-right"></dt>
  61 + <dd class="col-sm-10 text-left"></dd>
  62 + <dt class="col-sm-2 text-right">Compรฉtences :</dt>
  63 + <dd class="col-sm-10 text-left"></dd>
  64 + <dt class="col-sm-2 text-right">Besoin en formation :</dt>
  65 + <dd class="col-sm-10 text-left"></dd>
  66 + </dl>
  67 + <a class="card-link" href="{{url_for('main.agent_edit', agent_id=agent.id)}}">Modifier</a>
  68 + <a class="card-link" href="{{url_for('main.charge_add', agent_id=agent.id)}}">Charger</a>
  69 + </div>
  70 +</div>
  71 +
  72 +<div class="charge_chart" id="projects_chart"></div>
13 73 <table id="charge_table">
14 74 <thead>
15 75 <tr>
16   - {% for header in charges[0] %}
  76 + {% for header in charges[0] %}
17 77 <th>{{header}}</th>
18   - {% endfor %}
  78 + {% endfor %}
19 79 </tr>
20 80 </thead>
21 81 <tbody>
22 82 {% for line in charges[1:] %}
23 83 <tr>
24 84 {% for i in range(line|length) %}
25   - {%if 'Charge' in charges[0][i] %}
26   - <td>{{line[i]}} %</td>
27   - {%else%}
28   - <td>{{line[i]}}</td>
29   - {%endif%}
  85 + {%if 'Charge' in charges[0][i] %}
  86 + {% set charge = line[i] | int %}
  87 + <td>{{charge / 100}}</td>
  88 + {%else%}
  89 + <td>{{line[i]}}</td>
  90 + {%endif%}
30 91 {% endfor %}
31 92 </tr>
32 93 {% endfor %}
... ... @@ -42,7 +103,7 @@
42 103 <script>
43 104 build_chart("#projects_chart",
44 105 "{{url_for('main.charge_agent_csv', agent_id=agent.id)}}",
45   - "{{agent.secondname}}"+ " {{agent.firstname}}",
  106 + "{{agent.fullname}}",
46 107 "project");
47 108 </script>
48 109 {% endblock %}
... ...
app/main/templates/agent_form.html 0 โ†’ 100644
... ... @@ -0,0 +1,75 @@
  1 +{% extends "base_page.html" %}
  2 +
  3 +{# Set the title that will be used in base_page #}
  4 +{% if agent and agent['id'] not in ['', None] %}
  5 + {% set subtitle = "Modifier l'agent "+ agent['fullname'] %}
  6 +{% else %}
  7 + {% set subtitle = "Ajouter un nouvel agent" %}
  8 +{% endif %}
  9 +
  10 +{% block content %}
  11 +
  12 + <!-- Invisible span to define wich ul and a in the navbar are actived -->
  13 + <span id="nav_actived" style="display: none">cds,agent/create</span>
  14 +
  15 + <form id="agent_form" class="pdc-form" action="{{ url_for('main.agent_edit') }}" method="post">
  16 + {% if agent and agent['id'] not in ['', None] %}
  17 + <input class="form-control" id="agent_id" name="agent_id" type="hidden" value="{{ agent['id'] }}">
  18 + {% endif %}
  19 + <div class="form-group">
  20 + <label for="firstname">Nom</label>
  21 + <input class="form-control" id="firstname" name="firstname" type="text" value="{{ agent['firstname'] }}">
  22 + </div>
  23 + <div class="form-group">
  24 + <label for="secondname">Prรฉnom</label>
  25 + <input class="form-control" id="secondname" name="secondname" type="text" value="{{ agent['secondname'] }}">
  26 + </div>
  27 + <div class="form-group form-check form-check-inline col-sm-5 col-xl-3">
  28 + <input class="form-check-input " type="checkbox" id="virtual" name="virtual"
  29 + {{ 'checked' if agent['virtual'] == 1 }} value="1">
  30 + <label class="form-check-label " for="virtual">Virtuel</label>
  31 + </div>
  32 + <div class="form-group form-check form-check-inline col-sm-5 col-xl-3">
  33 + <input class="form-check-input " type="checkbox" id="permanent" name="permanent"
  34 + {{ 'checked' if agent['permanent'] == 1 }} value="1">
  35 + <label class="form-check-label " for="permanent">Permanent</label>
  36 + </div>
  37 + <div class="form-group">
  38 + <label for="company_id">Structure</label>
  39 + <select id="company_id" name="company_id" class="form-control">
  40 + <option selected>---</option>
  41 + {% for c in companies %}
  42 + <option value="{{ c.id }}" {{ "selected" if c.id == agent['company_id'] }}>{{ c.name }}</option>
  43 + {% endfor %}
  44 + </select>
  45 + </div>
  46 + <div class="form-group">
  47 + <label for="grade_id">Grade</label>
  48 + <select id="grade_id" name="grade_id" class="form-control">
  49 + <option selected>---</option>
  50 + {% for g in grades %}
  51 + <option value="{{ g.id }}" {{ "selected" if g.id == agent['grade_id'] }}>{{ g.name }}</option>
  52 + {% endfor %}
  53 + </select>
  54 + </div>
  55 + <div class="form-group">
  56 + <label for="status_id">Statut</label>
  57 + <select id="status_id" name="status_id" class="form-control">
  58 + <option selected>---</option>
  59 + {% for s in statuses %}
  60 + <option value="{{ s.id }}" {{ "selected" if s.id == agent['status_id'] }}>{{ s.name }}</option>
  61 + {% endfor %}
  62 + </select>
  63 + </div>
  64 + <div class="form-group">
  65 + <label for="bap_id">Bap</label>
  66 + <select id="bap_id" name="bap_id" class="form-control">
  67 + <option selected>---</option>
  68 + {% for b in baps %}
  69 + <option value="{{ b.id }}" {{ "selected" if b.id == agent['bap_id'] }}>{{ b.name }}</option>
  70 + {% endfor %}
  71 + </select>
  72 + </div>
  73 + <input class="pdc-form-submit" type="submit" value="Valider">
  74 + </form>
  75 +{% endblock %}
... ...
app/main/templates/agents.html
... ... @@ -19,7 +19,7 @@
19 19 {{ agent.firstname }}
20 20 {{ agent.secondname }}
21 21 </a></td>
22   - <td>{{agent.total_charge}} %</td>
  22 + <td>{{agent.total_charge / 100}}</td>
23 23 <td>{{agent.num_projects}}</td>
24 24 </tr>
25 25 {% endfor %}
... ...
app/main/templates/charge.html deleted
... ... @@ -1,1099 +0,0 @@
1   -{% extends "base_page.html" %}
2   -{% block content %}
3   -
4   -<!-- Invisible span to definte wich ul and a in the navbar are actived -->
5   -<span id="nav_actived" style="display: none">cds,charge/add</span>
6   -
7   - <form class="form-horizontal" id="new_affectation" action="#" method="post">
8   -<input name="utf8" type="hidden" value="โœ“">
9   -<input type="hidden" name="authenticity_token" value="NPVU9shCwiRt07EXMAroxTdsP61Et00VQjkBiit3MWeqQt3Eez9UdGK68RzZ6npLCFyg0gL+M1MR6VjK9WOCYw==">
10   -
11   - <!-- Debut message d'erreur -->
12   - <!-- Fin message d'erreur -->
13   -
14   -
15   - <div class="form-group">
16   - <label class="col-sm-2 control-label" for="affectation_agent_id">Nom de l'agent *</label>
17   - <div class="col-sm-10">
18   - <select class="form-control" name="affectation[agent_id]" id="affectation_agent_id">
19   - <option value="">Choisir un agent</option>
20   - <optgroup label="Agent Rรฉel">
21   - <option value="514">ABADIE Paul</option>
22   -<option value="351">ABDALLAH Juliette</option>
23   -<option value="132">ADAM Charles</option>
24   -<option value="339">ADRIEN Coralie</option>
25   -<option value="484">AGNES Acace</option>
26   -<option value="529">AGOSTINI Matthieu</option>
27   -<option value="673">AHMED Albine</option>
28   -<option value="340">AISSAOUI Daphnรฉe</option>
29   -<option value="466">AISSAOUI Pulchรฉrie</option>
30   -<option value="665">ALLOUCHE Adriana</option>
31   -<option value="389">AMINE Cรฉlia</option>
32   -<option value="530">AMIOT Coralie</option>
33   -<option value="452">ANDRE Agรฉnor</option>
34   -<option value="553">ANGELIQUE Aurรฉlien</option>
35   -<option value="620">ARAUJO Cyprien</option>
36   -<option value="623">ARNAL Philibert</option>
37   -<option value="204">ARNAUD Sarah</option>
38   -<option value="110">ARNOLD Fiona</option>
39   -<option value="611">ARNOLD Bernard</option>
40   -<option value="652">ATLAN Aldegonde</option>
41   -<option value="291">ATTIA France</option>
42   -<option value="660">AUGER Jules</option>
43   -<option value="297">AUGUSTE Serge</option>
44   -<option value="397">AUGUSTE Rolande</option>
45   -<option value="338">AUGUSTIN Antonine</option>
46   -<option value="250">BACHELIER Annie</option>
47   -<option value="346">BAIL Yann</option>
48   -<option value="345">BARDIN Innocent</option>
49   -<option value="74">BARRAL Claude</option>
50   -<option value="688">BARRAL Xavier</option>
51   -<option value="552">BARRIER Gautier</option>
52   -<option value="448">BAUDET Baptiste</option>
53   -<option value="538">BAUDOUIN Magali</option>
54   -<option value="326">BEAULIEU Cรฉlia</option>
55   -<option value="567">BEAUVOIS Fernande</option>
56   -<option value="558">BECK Corentin</option>
57   -<option value="376">BEDU Lucile</option>
58   -<option value="563">BELTRAN Dorothรฉe</option>
59   -<option value="451">BENJAMIN Eudes</option>
60   -<option value="108">BERAUD Fernande</option>
61   -<option value="639">BERNADETTE Michel</option>
62   -<option value="344">BERNARD Honorรฉ</option>
63   -<option value="363">BERNARD Franck</option>
64   -<option value="415">BERNARD Henriette</option>
65   -<option value="435">BERNARD Maud</option>
66   -<option value="555">BERNARD Marc</option>
67   -<option value="365">BERNARDO Martine</option>
68   -<option value="16">BESSE Alcime</option>
69   -<option value="542">BETTY Aleth</option>
70   -<option value="587">BETTY Gaรซl</option>
71   -<option value="12">BIDAULT Agathange</option>
72   -<option value="513">BIDAULT Prosper</option>
73   -<option value="554">BIDAULT Nadรจge</option>
74   -<option value="506">BLAISE Agilbert</option>
75   -<option value="470">BLANC Marceau</option>
76   -<option value="604">BLANC Cyrille</option>
77   -<option value="562">BLANCHARD Solange</option>
78   -<option value="197">BLANCHET Roger</option>
79   -<option value="482">BLANCHET Ferdinand</option>
80   -<option value="459">BLANDIN Aimรฉe</option>
81   -<option value="49">BLIN Arsรจne</option>
82   -<option value="171">BLOUIN Micheline</option>
83   -<option value="669">BLUE Ghislain</option>
84   -<option value="136">BOISSIER Jean</option>
85   -<option value="181">BONNEFOY Pamela</option>
86   -<option value="424">BORDE Rรฉmi</option>
87   -<option value="342">BOS Corentin</option>
88   -<option value="642">BOUAZIZ Archange</option>
89   -<option value="226">BOUCHARD Thibault</option>
90   -<option value="422">BOUCHET Sรฉgolรจne</option>
91   -<option value="637">BOUR Matthieu</option>
92   -<option value="251">BOURGEOIS Isaline</option>
93   -<option value="230">BOURGEOIS Mathilde</option>
94   -<option value="680">BOURGES Alban</option>
95   -<option value="98">BOURGOIN Emmanuel</option>
96   -<option value="536">BOURGOIN Ernest</option>
97   -<option value="492">BOURREAU Constance</option>
98   -<option value="24">BOUTET Aloรฏs</option>
99   -<option value="79">BOUTIN Clรฉmentine</option>
100   -<option value="486">BOUVARD Alcime</option>
101   -<option value="311">BOUZIDI Emmanuel</option>
102   -<option value="387">BOYER Hugo</option>
103   -<option value="471">BRAVO Amรฉdรฉe</option>
104   -<option value="117">BRESSON Gaston</option>
105   -<option value="120">BRETON Stรฉphanie</option>
106   -<option value="446">BRIANT Apollinaire</option>
107   -<option value="358">BRIANT Adalsinde</option>
108   -<option value="441">BROUARD Jรฉrรฉmie</option>
109   -<option value="335">BRU Joรซl</option>
110   -<option value="580">BUCHER Albรฉric</option>
111   -<option value="177">BUQUET Nicolette</option>
112   -<option value="8">CABARET Adriana</option>
113   -<option value="145">CABRERA Laurel</option>
114   -<option value="431">CAMARA Prosper</option>
115   -<option value="138">CAMPOS Joachim</option>
116   -<option value="621">CANO Dorian</option>
117   -<option value="507">CAPRON Mathis</option>
118   -<option value="485">CARPENTIER Ysoline</option>
119   -<option value="247">CARRIERE Claire</option>
120   -<option value="544">CARTIER Abelin</option>
121   -<option value="158">CARTON Lรฉa</option>
122   -<option value="690">CARVALHO Anรฉmone</option>
123   -<option value="368">CASTRO Amรฉdรฉe</option>
124   -<option value="540">CAT Yann</option>
125   -<option value="5">CATALA Adeline</option>
126   -<option value="320">CELLIER Jรฉrรฉmie</option>
127   -<option value="278">CHABANE Jรฉrรดme</option>
128   -<option value="423">CHATEL Alexis</option>
129   -<option value="41">CHATELET Ariane</option>
130   -<option value="472">CHATILLON Romuald</option>
131   -<option value="685">CHATILLON Adรจle</option>
132   -<option value="560">CHENU Wilfried</option>
133   -<option value="80">CHIRAC Colas</option>
134   -<option value="245">CHIRAC Didier</option>
135   -<option value="429">CHIRAC Edith</option>
136   -<option value="531">CHIRAC Sabine</option>
137   -<option value="455">CHLOE Mahaut</option>
138   -<option value="114">CHOU Fulbert</option>
139   -<option value="383">CHRETIEN Apollonie</option>
140   -<option value="676">CLAUSS Pacรดme</option>
141   -<option value="378">CLAVIER Adalbรฉron</option>
142   -<option value="194">COGNARD Raphaรซlle</option>
143   -<option value="325">COHEN Valรฉrie</option>
144   -<option value="479">COLLE Adalbรฉron</option>
145   -<option value="497">COLLE Aurora</option>
146   -<option value="32">COLLIGNON Angilberte</option>
147   -<option value="237">COLLIN Bertrand</option>
148   -<option value="508">COMBES Arsรจne</option>
149   -<option value="463">CONSTANTIN Romรฉo</option>
150   -<option value="38">COQUIN Aphrodite</option>
151   -<option value="576">COSTA Nadรจge</option>
152   -<option value="264">COULIBALY Eloi</option>
153   -<option value="95">COURTIN Suzette</option>
154   -<option value="490">COURTY Antonine</option>
155   -<option value="454">COUTURE Lydia</option>
156   -<option value="148">DANG Lou</option>
157   -<option value="408">DAUMAS Adalbรฉron</option>
158   -<option value="539">DEFRANCE Agrippine</option>
159   -<option value="45">DELAIRE Armand</option>
160   -<option value="510">DELANNOY Archange</option>
161   -<option value="627">DELAPORTE Hugo</option>
162   -<option value="498">DELCOURT Aurora</option>
163   -<option value="184">DELVAL Perceval</option>
164   -<option value="458">DELVAL Arlette</option>
165   -<option value="170">DEMARET Maximilien</option>
166   -<option value="249">DERRIEN Adrien</option>
167   -<option value="235">DESJARDINS Ariane</option>
168   -<option value="316">DEVIN Azalรฉe</option>
169   -<option value="369">DIARRA Sigismond</option>
170   -<option value="645">DIARRA Jonathan</option>
171   -<option value="523">DIAZ Pascal</option>
172   -<option value="561">DIOP Anaรฏs</option>
173   -<option value="211">DOE Tancrรจde</option>
174   -<option value="282">DOUBLET Marceline</option>
175   -<option value="379">DU Maxime</option>
176   -<option value="1">DUBOIS Abdon</option>
177   -<option value="659">DUBUISSON Sonia</option>
178   -<option value="99">DUBUS Emmanuelle</option>
179   -<option value="399">DUCLOS Althรฉe</option>
180   -<option value="388">DUFRESNE Huguette</option>
181   -<option value="54">DUMORTIER Aubertin</option>
182   -<option value="35">DUPOND Annabelle</option>
183   -<option value="521">DUPOND Maxime</option>
184   -<option value="583">DUPOND Mรฉlina</option>
185   -<option value="92">DUPONT Dimitri</option>
186   -<option value="480">DUPONT Faustine</option>
187   -<option value="605">DUPOUY Aldric</option>
188   -<option value="202">DURAND Sarah</option>
189   -<option value="491">DURAND Thierry</option>
190   -<option value="550">DURAND Zacharie</option>
191   -<option value="670">DURAND Esther</option>
192   -<option value="505">DUTHEIL Monique</option>
193   -<option value="487">DUTHOIT Ferdinand</option>
194   -<option value="290">DUVAL Clรฉment</option>
195   -<option value="87">ESNAULT Cรฉlia</option>
196   -<option value="608">ESTELLE Adรฉlie</option>
197   -<option value="686">ETIENNE Roch</option>
198   -<option value="573">FABRE Lorรจne</option>
199   -<option value="364">FAGOT Aleaume</option>
200   -<option value="298">FALL Philippe</option>
201   -<option value="601">FER Timothรฉe</option>
202   -<option value="359">FERRET Nathalie</option>
203   -<option value="516">FERRON Ronan</option>
204   -<option value="124">FLEUR Grรฉgoire</option>
205   -<option value="381">FLEUR Victorine</option>
206   -<option value="427">FOFANA Ambroisie</option>
207   -<option value="638">FOLLET Marguerite</option>
208   -<option value="106">FONTAINE Fantine</option>
209   -<option value="350">FONTAINE Dรฉodat</option>
210   -<option value="447">FOUCAULT Yannick</option>
211   -<option value="210">FOUQUE Sylvestre</option>
212   -<option value="416">FOURRIER Jean</option>
213   -<option value="302">FRADIN Adrien</option>
214   -<option value="667">FRADIN Aline</option>
215   -<option value="103">FRAISSE Eulalie</option>
216   -<option value="70">FRANCOIS Christophe</option>
217   -<option value="125">FRANCOIS Guy</option>
218   -<option value="178">FRED Olympe</option>
219   -<option value="426">FROMENTIN Armance</option>
220   -<option value="456">FUR Jules</option>
221   -<option value="354">GABET Sรฉverin</option>
222   -<option value="321">GAC Daniel</option>
223   -<option value="141">GARIN Kevin</option>
224   -<option value="319">GARIN Alcide</option>
225   -<option value="152">GARREAU Lucas</option>
226   -<option value="517">GASPARD Marceline</option>
227   -<option value="102">GAUCHER Esther</option>
228   -<option value="407">GAUDIN Thibert</option>
229   -<option value="413">GAUTHIER Christiane</option>
230   -<option value="655">GAUTIER Adalsinde</option>
231   -<option value="324">GAYET Alice</option>
232   -<option value="6">GENEST Adjutor</option>
233   -<option value="42">GERARD Ariel</option>
234   -<option value="75">GERARD Clotilde</option>
235   -<option value="371">GERMAIN Gatien</option>
236   -<option value="493">GERVAIS Ludivine</option>
237   -<option value="574">GERVAIS Arthaud</option>
238   -<option value="64">GESLIN Brice</option>
239   -<option value="613">GILBERT Francis</option>
240   -<option value="129">GILLES Henri</option>
241   -<option value="332">GIORDANO Cyprien</option>
242   -<option value="433">GIRARDOT Ysoline</option>
243   -<option value="545">GIRARDOT Ella</option>
244   -<option value="309">GIROD Doriane</option>
245   -<option value="57">GIROUX Audrey</option>
246   -<option value="20">GISELE Alexandre</option>
247   -<option value="205">GOMEZ Simon</option>
248   -<option value="366">GOMEZ Gaspard</option>
249   -<option value="584">GONIN Octave</option>
250   -<option value="592">GONZALES Leila</option>
251   -<option value="192">GOSSE Pรฉtronille</option>
252   -<option value="475">GOURDON Rose</option>
253   -<option value="123">GROS Georges</option>
254   -<option value="390">GROS Amalric</option>
255   -<option value="603">GUEN Aliette</option>
256   -<option value="68">GUERRERO Charles</option>
257   -<option value="19">GUEZ Alexandre</option>
258   -<option value="444">GUICHARD Clรฉmence</option>
259   -<option value="315">GUILLAUD Alexandrine</option>
260   -<option value="606">GUILLEMOT Patrice</option>
261   -<option value="84">GUILLON Cyrielle</option>
262   -<option value="468">GUILLOT Corentin</option>
263   -<option value="27">GUY Amaryllis</option>
264   -<option value="352">GUYOT Gwenael</option>
265   -<option value="175">HADDAD Mรฉdรฉric</option>
266   -<option value="333">HAMADI Marius</option>
267   -<option value="572">HAMON Henri</option>
268   -<option value="511">HASCOET Benjamin</option>
269   -<option value="522">HAVET Francis</option>
270   -<option value="220">HEBERT Vรฉronique</option>
271   -<option value="384">HEITZ Iseult</option>
272   -<option value="36">HERAUD Anselme</option>
273   -<option value="457">HERNANDEZ Arthaud</option>
274   -<option value="223">HERY Ysoie</option>
275   -<option value="216">HOANG Thibert</option>
276   -<option value="153">HONORE Lucille</option>
277   -<option value="453">HONORE Samuel</option>
278   -<option value="161">HOUDIN Marc</option>
279   -<option value="658">HOUSSIN Faustine</option>
280   -<option value="411">HUBERT Claude</option>
281   -<option value="232">HUE Mylรจne</option>
282   -<option value="126">HUET Guy</option>
283   -<option value="443">HUET Marc</option>
284   -<option value="566">HUGON Suzanne</option>
285   -<option value="142">HUGUET Kilian</option>
286   -<option value="589">HUGUET Rachel</option>
287   -<option value="310">HUMEAU Bastien</option>
288   -<option value="467">HUMEAU Argine</option>
289   -<option value="403">JACOB Aldemar</option>
290   -<option value="137">JACQ Jeannot</option>
291   -<option value="469">JACQUET Evangรฉline</option>
292   -<option value="678">JARDIN Florian</option>
293   -<option value="489">JEAN Coralie</option>
294   -<option value="82">JEGOU Colas</option>
295   -<option value="651">JEGOU Armandine</option>
296   -<option value="380">JOHN Abigaรซlle</option>
297   -<option value="214">JORGE Thibault</option>
298   -<option value="421">JOSE Audran</option>
299   -<option value="295">JOSIANE Titien</option>
300   -<option value="406">JOUBERT Gautier</option>
301   -<option value="647">JOUBERT Viviane</option>
302   -<option value="400">JOURDAN Delphine</option>
303   -<option value="461">JUHEL Gabrielle</option>
304   -<option value="586">JUIN Achille</option>
305   -<option value="633">JULLIEN Orlane</option>
306   -<option value="565">KLEIN Zรฉphir</option>
307   -<option value="179">LABROUSSE Oscar</option>
308   -<option value="656">LAFARGE Arcade</option>
309   -<option value="348">LAFON Pierre</option>
310   -<option value="37">LAHAYE Anthime</option>
311   -<option value="13">LALANNE Agilbert</option>
312   -<option value="629">LALANNE Armelle</option>
313   -<option value="398">LAMBERT Isabelle</option>
314   -<option value="450">LAMOTHE Adriana</option>
315   -<option value="25">LAMOTTE Alphonsine</option>
316   -<option value="442">LAMOTTE Roland</option>
317   -<option value="436">LANGLAIS Gatien</option>
318   -<option value="377">LAPEYRE Alaine</option>
319   -<option value="375">LAPLACE Solange</option>
320   -<option value="285">LARIVIERE Coralie</option>
321   -<option value="305">LARRIEU Patricia</option>
322   -<option value="273">LAUDE Amour</option>
323   -<option value="382">LAURENS Noรซl</option>
324   -<option value="503">LAURENT Amandine</option>
325   -<option value="564">LAVAUD Cรฉlestine</option>
326   -<option value="628">LAVIGNE Soline</option>
327   -<option value="571">LAVILLE Clotilde</option>
328   -<option value="653">LAVILLE Bertrand</option>
329   -<option value="515">LEAL Fรฉlix</option>
330   -<option value="632">LEBEL Arsรจne</option>
331   -<option value="225">LEBERT Claude</option>
332   -<option value="395">LEBERT Inรจs</option>
333   -<option value="650">LEBOEUF Huguette</option>
334   -<option value="548">LECA Albert</option>
335   -<option value="597">LECHAT Antoine</option>
336   -<option value="7">LECLERCQ Adolphe</option>
337   -<option value="275">LECLERCQ Antigone</option>
338   -<option value="590">LECLERCQ Lorraine</option>
339   -<option value="672">LECOCQ Albรฉric</option>
340   -<option value="549">LECOMTE Mรฉlanie</option>
341   -<option value="575">LECOMTE Ella</option>
342   -<option value="128">LEE Gรฉrard</option>
343   -<option value="182">LEFEVRE Pamela</option>
344   -<option value="357">LEFEVRE Antonine</option>
345   -<option value="418">LEFEVRE Eusรจbe</option>
346   -<option value="233">LEGRAND Adalsinde</option>
347   -<option value="419">LEGRAND Clรฉment</option>
348   -<option value="614">LEGRAND Alcibiade</option>
349   -<option value="428">LEGRIS Adalbert</option>
350   -<option value="502">LEGROS Sonia</option>
351   -<option value="483">LEHMANN Stรฉphanie</option>
352   -<option value="279">LELIEVRE Richard</option>
353   -<option value="439">LELONG Louis</option>
354   -<option value="464">LEM Florie</option>
355   -<option value="626">LEMAIRE Timothรฉe</option>
356   -<option value="63">LEMAITRE Boniface</option>
357   -<option value="373">LEMAITRE Angeline</option>
358   -<option value="76">LEMAY Clovis</option>
359   -<option value="89">LEMERCIER Danielle</option>
360   -<option value="199">LEMOINE Roselin</option>
361   -<option value="353">LEMOINE Blanche</option>
362   -<option value="107">LEPETIT Ferdinand</option>
363   -<option value="65">LEROUX Bรฉatrix</option>
364   -<option value="154">LEVY Lucille</option>
365   -<option value="66">LIN Caribert</option>
366   -<option value="543">LOIC Vital</option>
367   -<option value="437">LOMBARD Ernestine</option>
368   -<option value="579">LONGO Dieudonnรฉ</option>
369   -<option value="288">LOUIS Miriam</option>
370   -<option value="668">LOUP Pรฉnรฉlope</option>
371   -<option value="328">LUCAS Muriel</option>
372   -<option value="622">LUCAS Christelle</option>
373   -<option value="164">LYON Marie</option>
374   -<option value="551">LYS Jules</option>
375   -<option value="438">MADEC Adon</option>
376   -<option value="465">MAGNE Innocent</option>
377   -<option value="372">MAGNIN Yoann</option>
378   -<option value="337">MAILLARD Louis</option>
379   -<option value="664">MALET Alizรฉ</option>
380   -<option value="585">MALHERBE Bastien</option>
381   -<option value="417">MARCEL Adrienne</option>
382   -<option value="271">MARCO Mauricet</option>
383   -<option value="495">MARET Henriette</option>
384   -<option value="556">MARIANI Francis</option>
385   -<option value="308">MARQUES Matthieu</option>
386   -<option value="105">MARTEAU Evangรฉline</option>
387   -<option value="414">MARTIN Philomรจne</option>
388   -<option value="612">MARTIN Moรฏse</option>
389   -<option value="640">MARTINEAU Jรฉrรฉmie</option>
390   -<option value="341">MAS Valรฉrie</option>
391   -<option value="257">MASSON Aline</option>
392   -<option value="591">MATHIEU Aurรจle</option>
393   -<option value="386">MATHIS Adenet</option>
394   -<option value="85">MATTEI Cyrille</option>
395   -<option value="674">MAZE Marine</option>
396   -<option value="222">MAZET Yannick</option>
397   -<option value="663">MEHDI Aude</option>
398   -<option value="675">MENDES Timothรฉe</option>
399   -<option value="533">MERLE Dorothรฉe</option>
400   -<option value="60">MERTZ Azalรฉe</option>
401   -<option value="496">MESSAOUDI Gautier</option>
402   -<option value="131">METIVIER Hippolyte</option>
403   -<option value="520">METIVIER Cรฉline</option>
404   -<option value="296">MICKAEL Ronan</option>
405   -<option value="593">MILLER Aubin</option>
406   -<option value="644">MILLOT Guillaume</option>
407   -<option value="198">MIMI Rose</option>
408   -<option value="385">MIQUEL Amalthรฉe</option>
409   -<option value="598">MOHAMED Gรฉrard</option>
410   -<option value="155">MOLLARD Lucille</option>
411   -<option value="43">MONTEL Arlette</option>
412   -<option value="616">MOON Mathilde</option>
413   -<option value="193">MOREAU Raoul</option>
414   -<option value="370">MOREL Octave</option>
415   -<option value="681">MOREL Alizรฉ</option>
416   -<option value="93">MORGAN Dorian</option>
417   -<option value="636">MORGAN Lydia</option>
418   -<option value="53">MORICEAU Arthaud</option>
419   -<option value="577">MOULIN Nathalie</option>
420   -<option value="662">MOULIN Abelin</option>
421   -<option value="630">MOUNIER Aldric</option>
422   -<option value="115">MOUSSA Fรฉlix</option>
423   -<option value="147">MYLENE Lionel</option>
424   -<option value="330">NADAL Frรฉdรฉric</option>
425   -<option value="17">NEAU Alcyone</option>
426   -<option value="67">NEAU Catherine</option>
427   -<option value="18">NEGRE Aldemar</option>
428   -<option value="401">NEVEU Florent</option>
429   -<option value="671">NIANG Huguette</option>
430   -<option value="595">NICOT Cassandre</option>
431   -<option value="360">NOE Adalsinde</option>
432   -<option value="149">NOEL Lorรจne</option>
433   -<option value="349">NORMAND Cรฉlestin</option>
434   -<option value="119">NOURY Eugรฉnie</option>
435   -<option value="356">OLIVER Tristan</option>
436   -<option value="430">OLIVIER Jรฉrรฉmie</option>
437   -<option value="646">OLIVIER Rose</option>
438   -<option value="420">OLLIER Margaux</option>
439   -<option value="618">ORTEGA Sรฉverin</option>
440   -<option value="9">PAGET Adriana</option>
441   -<option value="62">PANNETIER Bastien</option>
442   -<option value="534">PATIN Huguette</option>
443   -<option value="404">PAYET Daniel</option>
444   -<option value="581">PELE Angadrรชme</option>
445   -<option value="402">PELTIER Barthรฉlemy</option>
446   -<option value="599">PENOT Hugo</option>
447   -<option value="392">PEPIN Thibault</option>
448   -<option value="512">PEREZ Lรฆtitia</option>
449   -<option value="494">PERNET Victoire</option>
450   -<option value="81">PERNIN Colas</option>
451   -<option value="641">PERREAU Baudouin</option>
452   -<option value="217">PERRET Thomas</option>
453   -<option value="537">PERRET Marcel</option>
454   -<option value="500">PERRET Olga</option>
455   -<option value="654">PERROT Mรฉlissa</option>
456   -<option value="569">PETIT Laura</option>
457   -<option value="481">PFEIFFER Daniรจle</option>
458   -<option value="609">PIAT Constance</option>
459   -<option value="615">PICARD Othon</option>
460   -<option value="289">PICAUD Baptiste</option>
461   -<option value="188">PIERRE Philothรฉe</option>
462   -<option value="281">PIERRE Cรฉdric</option>
463   -<option value="314">PINET Geoffroy</option>
464   -<option value="317">PLANCHON Arlette</option>
465   -<option value="168">POINSOT Maxence</option>
466   -<option value="274">POINSOT Angรฉlique</option>
467   -<option value="474">POINT Yolande</option>
468   -<option value="478">POINT Blanche</option>
469   -<option value="525">POINT Rรฉbecca</option>
470   -<option value="361">POIRET Gautier</option>
471   -<option value="619">POISSON Olivier</option>
472   -<option value="684">POLLET Joรซlle</option>
473   -<option value="140">PORTAL Judith</option>
474   -<option value="526">PORTE Cosette</option>
475   -<option value="29">PREVOST Anatolie</option>
476   -<option value="40">PROST Archibald</option>
477   -<option value="679">RAFFIN Raphaรซl</option>
478   -<option value="374">RAIMBAULT Adriana</option>
479   -<option value="594">RAIMBAULT Carine</option>
480   -<option value="100">RAMOS Enguerrand</option>
481   -<option value="527">RAMOS Agnรจs</option>
482   -<option value="488">REGIS Simon</option>
483   -<option value="682">REGNIER Caroline</option>
484   -<option value="322">REMY Laurent</option>
485   -<option value="412">RENAUD Danielle</option>
486   -<option value="643">REVEL Aloys</option>
487   -<option value="109">RICCI Fiacre</option>
488   -<option value="425">RICHAUD Sarah</option>
489   -<option value="323">RICHE Ernestine</option>
490   -<option value="689">RICHE Romain</option>
491   -<option value="254">ROBBE Jeannot</option>
492   -<option value="666">ROBERT Gaspard</option>
493   -<option value="625">ROBIC Eugรจne</option>
494   -<option value="528">ROBIN Auxence</option>
495   -<option value="624">ROBIN Vanessa</option>
496   -<option value="499">ROCH Christian</option>
497   -<option value="519">RODRIGUEZ Flavien</option>
498   -<option value="610">ROLAND Caribert</option>
499   -<option value="462">ROLLAND Arthur</option>
500   -<option value="113">ROSA Franck</option>
501   -<option value="677">ROUAULT Paul</option>
502   -<option value="97">ROUGE Elsa</option>
503   -<option value="393">ROUGE Philibert</option>
504   -<option value="300">ROUSSEAU Joรซlle</option>
505   -<option value="582">ROUSSEAU Renรฉe</option>
506   -<option value="394">ROUSSEAUX Sabine</option>
507   -<option value="509">ROUSSEAUX Thรฉodore</option>
508   -<option value="460">ROY Jรฉrรดme</option>
509   -<option value="28">ROYER Anastase</option>
510   -<option value="570">RUSSO Mathilde</option>
511   -<option value="44">SAADI Armance</option>
512   -<option value="432">SABINE Roselin</option>
513   -<option value="648">SALA Roch</option>
514   -<option value="504">SALAH Virginie</option>
515   -<option value="602">SALAH Angรจle</option>
516   -<option value="449">SALVADOR Guilhem</option>
517   -<option value="213">SANTOS Tanguy</option>
518   -<option value="410">SCHMITT Agรฉnor</option>
519   -<option value="634">SERGENT Antoine</option>
520   -<option value="617">SIMON Serge</option>
521   -<option value="195">SIMONET Raphaรซlle</option>
522   -<option value="683">SIMONIN Elia</option>
523   -<option value="687">SLIMANI Tristan</option>
524   -<option value="134">SOARES Iris</option>
525   -<option value="58">SOUKI Audric</option>
526   -<option value="661">SOUKI Aure</option>
527   -<option value="130">SOYER Henriette</option>
528   -<option value="532">STEF Louise</option>
529   -<option value="559">STEF Edith</option>
530   -<option value="48">STEIN Arolde</option>
531   -<option value="180">SYLLA Ozanne</option>
532   -<option value="535">SYLLA Dimitri</option>
533   -<option value="2">TARDIF Franck</option>
534   -<option value="440">TECHER Tanguy</option>
535   -<option value="635">TESSON Joseph</option>
536   -<option value="524">THEBAULT Samuel</option>
537   -<option value="588">THIBAULT Monique</option>
538   -<option value="547">THIBAUT Roch</option>
539   -<option value="477">THOMAS Jessica</option>
540   -<option value="657">TISSERAND Cรฉcile</option>
541   -<option value="186">TOURE Philippe</option>
542   -<option value="391">TRAN Adalric</option>
543   -<option value="631">TRAORE Cassandre</option>
544   -<option value="568">TRAVERS Paul</option>
545   -<option value="649">TREMBLAY Thaรฏs</option>
546   -<option value="546">VAILLANT Darius</option>
547   -<option value="409">VALET Cyprien</option>
548   -<option value="157">VALET Damien</option>
549   -<option value="600">VALLEE Hรฉlรจne</option>
550   -<option value="334">VAN Aleaume</option>
551   -<option value="14">VARIN Agilbert</option>
552   -<option value="46">VERNAY Armand</option>
553   -<option value="104">VERO Caribert</option>
554   -<option value="578">VERRIER Ange</option>
555   -<option value="329">VIAUD Alphonse</option>
556   -<option value="476">VIGNAUD Samuel</option>
557   -<option value="541">VILLAIN Baptiste</option>
558   -<option value="71">VINET Claire</option>
559   -<option value="405">VIOLETTE Lauren</option>
560   -<option value="607">VIVIER Denise</option>
561   -<option value="52">VOISIN Arsรจne</option>
562   -<option value="557">VOISIN Lรฆtitia</option>
563   -<option value="159">VUILLEMIN Magali</option>
564   -<option value="219">WALLET Ulysse</option>
565   -<option value="312">WOLF Tanguy</option>
566   -<option value="501">YANG Job</option>
567   -<option value="201">YANN Samuel</option>
568   -<option value="473">ZIDANE Alexandrine</option>
569   - </optgroup>
570   - <optgroup label="Agent Virtuel">
571   - <option value="990032"> Ingรฉnieur IE RPW/ROC chaine de traitement</option>
572   -<option value="990030"> Ingรฉnieur IE RPW/ROC Interfaces utilisateurs</option>
573   -<option value="990008">AI Production des donnรฉes</option>
574   -<option value="990024">AIT operation and Test-Bed</option>
575   -<option value="990022">Besoin C2L aprรจs 2017</option>
576   -<option value="990023">Besoin pour Miosotys</option>
577   -<option value="990020">IE BAP E Logiciels de test</option>
578   -<option value="990021">IE BAP E Logiciels embarquรฉs</option>
579   -<option value="990017">Ingรฉnieur CDD PLATO/LV (Dรฉveloppement LV)</option>
580   -<option value="990015">Ingรฉnieur CDD PLATO/LV (SGSE) #1</option>
581   -<option value="990016">Ingรฉnieur CDD PLATO/LV (SGSE) #2</option>
582   -<option value="990033">Ingรฉnieur IE RPW/TNR-HFR</option>
583   -<option value="990026">Ingรฉnieur IR MAJIS/PLATO Dรฉveloppement pipeline</option>
584   -<option value="990031">Ingรฉnieur IR RPW/ROC Operations</option>
585   -<option value="990005">Ingรฉnieur IR RPW/ROC Systรจme logiciel</option>
586   -<option value="990029">Ingรฉnieur Permanent IR SIS ROC </option>
587   -<option value="990018">Ingรฉnieur prestataire PLATO/LV (AQL)</option>
588   -<option value="990014">Ingรฉnieur prestataire PLATO/LV (Dรฉveloppement LV)</option>
589   -<option value="990012">Ingรฉnieur prestataire PLATO/LV (SGSE)</option>
590   -<option value="990011">Ingรฉnieur prestataire PLATO/LV (Spรฉcification/Validation)</option>
591   -<option value="990013">Ingรฉnieur prestataire PLATO/LV (Validation)</option>
592   -<option value="990034">xxxx</option>
593   - </optgroup>
594   - <optgroup label="Stagiaire">
595   - <option value="900031">ABED Mahfoudh</option>
596   -<option value="900018">ADDA Pierre</option>
597   -<option value="900080">AFKERIOS Orlena</option>
598   -<option value="900016">AGNAN Marco</option>
599   -<option value="900233">AHMED KAMAL Zen</option>
600   -<option value="900026">ALEXOUDI Xanthippi</option>
601   -<option value="900095">ALVADO Marie</option>
602   -<option value="900112">AMAND Cedric</option>
603   -<option value="900147">ANDRE Aymeric</option>
604   -<option value="900034">ANDRIAMANANJAONA Prisca</option>
605   -<option value="900209">AOUINA Ayoub</option>
606   -<option value="900178">ARFI Benjamin</option>
607   -<option value="900064">ARROUB Salima</option>
608   -<option value="900176">ARROUB Jawad</option>
609   -<option value="900225">BALCI Burak</option>
610   -<option value="900164">BARAUD Romain</option>
611   -<option value="900066">BARBOT Armand</option>
612   -<option value="900243">BARRAUD Oceane</option>
613   -<option value="900167">BARROU DUMONT Zacharie</option>
614   -<option value="900086">BEADONNET Florian</option>
615   -<option value="900041">BELFILALI Ibtissam</option>
616   -<option value="900181">BEN EL BARGUIA Ghila</option>
617   -<option value="900206">BEN NEJMA Saber</option>
618   -<option value="900158">BEN RAHHAL Malak</option>
619   -<option value="900089">BENHAMOU Imen</option>
620   -<option value="900082">BENMERIEM Siham</option>
621   -<option value="900268">BENSEGUANE Selma</option>
622   -<option value="900160">BERBERIAN Tiphaine</option>
623   -<option value="900245">BERLAND Caroline</option>
624   -<option value="900237">BERMUDEZ DIAZ Kenneth</option>
625   -<option value="900106">BERNARD Julien</option>
626   -<option value="900154">BERTROU CANTOU Arielle</option>
627   -<option value="900009">BEUGNET Pierre</option>
628   -<option value="900002">BEURET Maxime</option>
629   -<option value="900084">BLAIN Doriann</option>
630   -<option value="900216">BOCHARD Nicolas</option>
631   -<option value="900250">BOINEAU Aurore</option>
632   -<option value="900197">BOOMI Shadi</option>
633   -<option value="900133">BOTT Nicolas</option>
634   -<option value="900170">BOUHARRAS Zineb</option>
635   -<option value="900142">BOUILLOT Anaelle</option>
636   -<option value="900256">BOUKROUCHE Ryan</option>
637   -<option value="900253">BOURDELLE DE MICAS Jules</option>
638   -<option value="900088">BOURI Maleck</option>
639   -<option value="900132">BOUSSETTA Ines</option>
640   -<option value="900196">BRADJA Vincent</option>
641   -<option value="900099">BRISY Justine</option>
642   -<option value="900069">BROSSARD Mathilde</option>
643   -<option value="900007">CANTIN Thomas</option>
644   -<option value="900032">CARACALLA Hugo</option>
645   -<option value="900204">CARTON Cecile</option>
646   -<option value="900079">CHAIN Henri</option>
647   -<option value="900264">CHAMBOULEYRON Vincent</option>
648   -<option value="900111">CHAMOIS Guillaume</option>
649   -<option value="900137">CHERIF Hiba</option>
650   -<option value="900217">CHEVALIER Hadrien</option>
651   -<option value="900081">CHRISTINY Ivan</option>
652   -<option value="900226">CIOCCHETTA Michele</option>
653   -<option value="900131">CONCATO Virginie</option>
654   -<option value="900155">CORSERI Jean-louis</option>
655   -<option value="900019">COSTE Blanche</option>
656   -<option value="900208">COUDE DU FORESTO Laurianne</option>
657   -<option value="900061">COURTE Cyrille</option>
658   -<option value="900074">CULPIN Pierre-marie</option>
659   -<option value="900091">DA MOTA Rafael</option>
660   -<option value="900255">DABIDIN Keshika</option>
661   -<option value="900241">DALIPI Rea</option>
662   -<option value="900259">DAOUGLIN Quentin</option>
663   -<option value="900257">DAVOULT Jeanne</option>
664   -<option value="900039">DAVY Giulio</option>
665   -<option value="900223">DE WULF Emily</option>
666   -<option value="900118">DEBRAS Florian</option>
667   -<option value="900014">DELORME Jacques-robert</option>
668   -<option value="900094">DEMOISY Camille</option>
669   -<option value="900108">DENIS Gaspard</option>
670   -<option value="900156">DESJONQUERES Lucile</option>
671   -<option value="900012">DESMAREST Alexandre</option>
672   -<option value="900174">DIJOUX Camille</option>
673   -<option value="900173">DJADAOJEE Lionel</option>
674   -<option value="900015">DOMERGUE Julie</option>
675   -<option value="900070">DOUSSOT Aristide</option>
676   -<option value="900220">DREAU Guillaume</option>
677   -<option value="900247">DUBART Maxime</option>
678   -<option value="900141">DUFOING Cyril</option>
679   -<option value="900076">DUPARD Mathilde</option>
680   -<option value="900065">DURAND Sรฉbastien</option>
681   -<option value="900200">DURAND Eloise</option>
682   -<option value="900157">DURIEUX Remi</option>
683   -<option value="900227">DUVAUCHELLE Gregoire</option>
684   -<option value="900123">EL ADDAD El mahdi</option>
685   -<option value="900078">EL MORSY Mona</option>
686   -<option value="900262">ELLIEN Ianis</option>
687   -<option value="900121">ENNEYAH MARIE Alexandra</option>
688   -<option value="900148">EVEN Ariane</option>
689   -<option value="900044">FAURE Rafael</option>
690   -<option value="900054">FELLER Clรฉment</option>
691   -<option value="900246">FENELON Thomas</option>
692   -<option value="900110">FIORANI Mathilde</option>
693   -<option value="900151">FLECHT Tobias</option>
694   -<option value="900265">FLORIAN Philipot</option>
695   -<option value="900068">FLOURION Aurรฉlie</option>
696   -<option value="900229">FOUQUART Jimmy</option>
697   -<option value="900201">FUMACHI MONTES Ricardo</option>
698   -<option value="900180">GALLIANO Cyril</option>
699   -<option value="900046">GARGOURI Yosra</option>
700   -<option value="900175">GASCARD Thomas</option>
701   -<option value="900003">GATEAU Heidi</option>
702   -<option value="900023">GAUCHET Lucien</option>
703   -<option value="900172">GAUTHIER Gaetan</option>
704   -<option value="900098">GEHAN Charlotte</option>
705   -<option value="900100">GIRAUDEAU BARTHET Hugo</option>
706   -<option value="900249">GKOTSINAS Anastasios</option>
707   -<option value="900036">GOFAS SALAS Elena</option>
708   -<option value="900214">GONY Theo</option>
709   -<option value="900055">GOSSET Laura</option>
710   -<option value="900049">GOURGUES Ronan</option>
711   -<option value="900096">GRAVET Romaric</option>
712   -<option value="900228">GRENOT Mathurin</option>
713   -<option value="900083">GRIJALVA Denis</option>
714   -<option value="900058">GRITON Lรฉa</option>
715   -<option value="900056">GROSSET Lucas</option>
716   -<option value="900005">GROULD Marion</option>
717   -<option value="900242">GUIBERT Margaux</option>
718   -<option value="900135">GUY Yasmine</option>
719   -<option value="900150">HASPOT Victor</option>
720   -<option value="900234">HELLY D'ANGELIN Valentine</option>
721   -<option value="900097">HERBERT Benoit</option>
722   -<option value="900051">HERSERANT Willis</option>
723   -<option value="900188">HOANG Hong van</option>
724   -<option value="900161">HONG Min ji</option>
725   -<option value="900199">HOUDAYER Pierre</option>
726   -<option value="900057">HUX Jรฉrome</option>
727   -<option value="900152">IFREK Mourad</option>
728   -<option value="900115">IMACHE Meriem</option>
729   -<option value="900021">IMAD Nicolas</option>
730   -<option value="900153">JACOBS Thomas</option>
731   -<option value="900134">JAMALI Sara</option>
732   -<option value="900202">JARNOUX Theo</option>
733   -<option value="900101">JASINGHEGE DON Prasanna deshapriya</option>
734   -<option value="900252">JAZIRI Adam</option>
735   -<option value="900090">KAABAR Myriam</option>
736   -<option value="900107">KALEWICZ Thomas</option>
737   -<option value="900042">KEO Tony</option>
738   -<option value="900219">KERROUMY Samy</option>
739   -<option value="900192">KHAN Saniya</option>
740   -<option value="900254">KONIG Pierre-cecil</option>
741   -<option value="900087">KOUOTO Laetitia</option>
742   -<option value="900092">KOUTBI Sarah</option>
743   -<option value="900024">LAAMARTI Ouais</option>
744   -<option value="900144">LACOUR Baptiste</option>
745   -<option value="900263">LAFARGUE Fabien</option>
746   -<option value="900193">LAMPSON Paul alexandre</option>
747   -<option value="900163">LATTES Hugo</option>
748   -<option value="900035">LAVENANT Thibaut</option>
749   -<option value="900218">LAZRAC Gabriel</option>
750   -<option value="900185">LE GAL Maรซlle</option>
751   -<option value="900006">LE ROY Corentin</option>
752   -<option value="900267">LEBEC Laetitia</option>
753   -<option value="900033">LEBOULLEUX Lucie</option>
754   -<option value="900222">LECAROUX Charles</option>
755   -<option value="900239">LEMAITRE Elisabeth</option>
756   -<option value="900269">LEMONNIER Audric</option>
757   -<option value="900013">LIGIER Nicolas</option>
758   -<option value="900186">LINAN Luis</option>
759   -<option value="900017">LION Sonny</option>
760   -<option value="900052">LORENTZ Julien</option>
761   -<option value="900001">LOUIS Corentin</option>
762   -<option value="900210">MADELEINE Tristan</option>
763   -<option value="900120">MAGNAC Thomas</option>
764   -<option value="900145">MALTESE Blandine</option>
765   -<option value="900195">MANCHON Louis</option>
766   -<option value="900022">MANร‡OIS Vincent</option>
767   -<option value="900191">MARRET Alexis</option>
768   -<option value="900211">MARTINET Sร‰bastien</option>
769   -<option value="900232">MARTINEZ Nicolas</option>
770   -<option value="900266">MARTINVALET Stecy</option>
771   -<option value="900105">MATHE Christophe</option>
772   -<option value="900260">MATHONNET Olivia</option>
773   -<option value="900231">MENARD Tancrede</option>
774   -<option value="900138">MESBAHI Lyes</option>
775   -<option value="900139">MESLEM Jeremie</option>
776   -<option value="900177">MEZIANI Sofiane</option>
777   -<option value="900261">MEZIANI Sofian</option>
778   -<option value="900251">MEZIN Marine</option>
779   -<option value="900143">MICHEL Alexandre</option>
780   -<option value="900183">MOLNAR Kelly</option>
781   -<option value="900093">MONTAGUD Victor</option>
782   -<option value="900182">MUGUET Pauline</option>
783   -<option value="900122">MUNTEANU Laura</option>
784   -<option value="900004">MUSSET Sophie</option>
785   -<option value="900060">NAVRER-AGASSON Anyssa</option>
786   -<option value="900187">NGUYEN Tung lam</option>
787   -<option value="900248">NGUYEN Bich ngoc</option>
788   -<option value="900213">NICOLAZO Thomas</option>
789   -<option value="900053">ONGALA EDOUMOU Samuel</option>
790   -<option value="900189">OREL Inna</option>
791   -<option value="900075">ORKISZ Jan</option>
792   -<option value="900020">ORZEKOWSKA Julie</option>
793   -<option value="900136">OUAFO SIGHA Lucas</option>
794   -<option value="900198">PALLU Melody</option>
795   -<option value="900103">PANEBIANCO Vincenzo</option>
796   -<option value="900047">PAROT Nicolas</option>
797   -<option value="900037">PASCAL Louis</option>
798   -<option value="900109">PASQUIER Pierre</option>
799   -<option value="900008">PERENNES Cรฉdric</option>
800   -<option value="900010">PERROT Clรฉment</option>
801   -<option value="900146">PILLON Marie</option>
802   -<option value="900221">PILLOT Quentin</option>
803   -<option value="900050">PINร‡ON Charly</option>
804   -<option value="900045">PLOZNER Clรฉlia</option>
805   -<option value="900258">PRAET Alice</option>
806   -<option value="900104">PRUVOT Anais</option>
807   -<option value="900029">QUINSAC Gary</option>
808   -<option value="900073">RAJU Julia</option>
809   -<option value="900011">RAKOTO Virgile</option>
810   -<option value="900063">RAKOTONIRAINY Matthieu</option>
811   -<option value="900236">RAVEAU Thomas</option>
812   -<option value="900194">RAYANE Sanaa</option>
813   -<option value="900171">RAYMOND Christopher</option>
814   -<option value="900166">REGNAULT Florian</option>
815   -<option value="900071">RICHARD Nessim</option>
816   -<option value="900235">RICHARD Pauline</option>
817   -<option value="900038">RIGAUD Ines</option>
818   -<option value="900165">ROBIN Arthur</option>
819   -<option value="900130">ROUSSEAU Steven</option>
820   -<option value="900028">ROUSSET Mary</option>
821   -<option value="900240">SAID Zakaria</option>
822   -<option value="900040">SARRAZIN Geoffroy</option>
823   -<option value="900077">SAVREUX Nicolas</option>
824   -<option value="900159">SCHMITT Arthur</option>
825   -<option value="900025">SENECAL Luc</option>
826   -<option value="900168">SENTUC Julien</option>
827   -<option value="900230">SERRANO Fannie</option>
828   -<option value="900190">SHAW Vasundhara</option>
829   -<option value="900027">SLEIMI Oussema</option>
830   -<option value="900207">SOLTANINEJAD Fahimeh</option>
831   -<option value="900059">SOULAIN Anthony</option>
832   -<option value="900062">SPERONE LONGIN Damien</option>
833   -<option value="900102">STEPANOVA Olga</option>
834   -<option value="900067">TAIBI Wail</option>
835   -<option value="900205">TANG Guobao</option>
836   -<option value="900072">TARDITS Orane</option>
837   -<option value="900116">TARRICQ Yoann</option>
838   -<option value="900215">THEBAULT Guillaume</option>
839   -<option value="900043">THIJS Simone</option>
840   -<option value="900203">TORDEUX David</option>
841   -<option value="900117">TRIVELLATO Thomas</option>
842   -<option value="900184">TROUILLARD Thomas</option>
843   -<option value="900212">VACHER William</option>
844   -<option value="900162">VACHEY Mathieu</option>
845   -<option value="900244">VILLARET Kevin</option>
846   -<option value="900224">VOYEUX Alfred</option>
847   -<option value="900169">WARCHOL Patryk</option>
848   -<option value="900238">WELLENREITER Vanda</option>
849   -<option value="900085">WURMSER Basile</option>
850   -<option value="900140">YIN Zi</option>
851   -<option value="900030">ZABUKOVEC Antonin</option>
852   -<option value="900048">ZAFONTE Jordan</option>
853   - </optgroup>
854   - </select>
855   - </div>
856   - </div>
857   -
858   - <div class="form-group">
859   - <label class="col-sm-2 control-label" for="affectation_projet_id">Nom du projet *</label>
860   - <div class="col-sm-10">
861   - <select class="form-control" name="affectation[projet_id]" id="affectation_projet_id"><option value="">Choisir un projet</option>
862   -<option value="20">4Q++</option>
863   -<option value="69">Activitรฉ service C2L</option>
864   -<option value="82">Activitรฉ service COMCL</option>
865   -<option value="68">Activitรฉ service CPR</option>
866   -<option value="74">Activitรฉ service CQL</option>
867   -<option value="70">Activitรฉ service DIR</option>
868   -<option value="75">Activitรฉ service EXT</option>
869   -<option value="76">Activitรฉ service GEFL</option>
870   -<option value="77">Activitรฉ service GIGL</option>
871   -<option value="73">Activitรฉ service MESPAL</option>
872   -<option value="88">Activitรฉ service OPALE</option>
873   -<option value="78">Activitรฉ service SIEL</option>
874   -<option value="79">Activitรฉ service SIGAL</option>
875   -<option value="72">Activitรฉ service SII</option>
876   -<option value="80">Activitรฉ service SIS</option>
877   -<option value="71">Activitรฉ service SPL</option>
878   -<option value="81">Activitรฉ service SSL</option>
879   -<option value="105">cahier des charges DT insu</option>
880   -<option value="104">CASSIS</option>
881   -<option value="87">CIRCUS</option>
882   -<option value="6">CNES / COROT</option>
883   -<option value="43">CNES / NOIRE</option>
884   -<option value="89">CNES / R&amp;T MARBLL</option>
885   -<option value="83">CNES / R&amp;T radio - PERLS</option>
886   -<option value="35">CNES / R&amp;T radio - STAR</option>
887   -<option value="3">CNES / R&amp;T UVMAG</option>
888   -<option value="63">CNES / SPIM (BESTIAL)</option>
889   -<option value="2">CNES / UVMAG Phase 0</option>
890   -<option value="64">CNRS-GEMaC / uPPI</option>
891   -<option value="60">CTS (SIMENOM-C2ERES)</option>
892   -<option value="49">DGA / METEOSPACE</option>
893   -<option value="33">ESA-CNES / ALFVEN M5</option>
894   -<option value="4">ESA-CNES / Arago M5</option>
895   -<option value="7">ESA-CNES / ARIEL M4</option>
896   -<option value="41">ESA-CNES / Bepi-Colombo / SORBET</option>
897   -<option value="28">ESA-CNES / Bepi-Colombo / VIHI</option>
898   -<option value="37">ESA-CNES / CASSINI KRONOS</option>
899   -<option value="36">ESA-CNES / CLUSTER STAFF</option>
900   -<option value="22">ESA-CNES / ENVISION-VEM M5</option>
901   -<option value="27">ESA-CNES / JUICE MAJIS</option>
902   -<option value="42">ESA-CNES / JUICE RPWI</option>
903   -<option value="1">ESA-CNES / PLATO</option>
904   -<option value="24">ESA-CNES / ROSETTA MIRO</option>
905   -<option value="25">ESA-CNES / ROSETTA VIRTIS</option>
906   -<option value="46">ESA-CNES / SOLO STIX</option>
907   -<option value="29">ESA-CNES / SOLO/RPW</option>
908   -<option value="30">ESA-CNES / SOLO/RPW OPERATION CENTER (ROC)</option>
909   -<option value="31">ESA-CNES / THOR (M4)</option>
910   -<option value="26">ESA-CNES / VEX VIRTIS</option>
911   -<option value="10">ESO-ELT / CANARY</option>
912   -<option value="8">ESO-ELT / MICADO</option>
913   -<option value="11">ESO-ELT / MOSAIC</option>
914   -<option value="9">ESO-ELT / PYRCADO</option>
915   -<option value="12">ESO-VLT / GRAVITY</option>
916   -<option value="18">FIRST</option>
917   -<option value="13">GREENFLASH</option>
918   -<option value="54">HELIO</option>
919   -<option value="23">MIOSOTYS</option>
920   -<option value="48">MOTOCAB</option>
921   -<option value="61">Mutualisation MESPAL</option>
922   -<option value="17">NASA-CNES / JWST MIRI</option>
923   -<option value="40">NASA-CNES / PARKER SOLARPROBE FIELDS</option>
924   -<option value="39">NASA-CNES / STEREO WAVES</option>
925   -<option value="21">NASA-CNES / SUPERCAM</option>
926   -<option value="38">NASA-CNES / WIND WAVES</option>
927   -<option value="86">NASA-LUVOIR</option>
928   -<option value="85">NASA-VICI</option>
929   -<option value="102">NASA/OST-HERO</option>
930   -<option value="44">Observations radio-solaires (RH &amp; Orfรฉes)</option>
931   -<option value="53">ORME</option>
932   -<option value="58">Outils GED</option>
933   -<option value="56">OV hรฉliosphรจrique</option>
934   -<option value="57">OV planรฉto</option>
935   -<option value="19">PICSAT</option>
936   -<option value="59">POP</option>
937   -<option value="98">Potentiel en AIT/V</option>
938   -<option value="97">Potentiel en AIT/V manager et salles propres/protection planรฉtaire</option>
939   -<option value="99">Potentiel en AIT/V, mรฉcanique et optique</option>
940   -<option value="93">Potentiel en CdP et informatique scientifique</option>
941   -<option value="96">Potentiel en CdP, systรจme et รฉlectronique</option>
942   -<option value="91">Potentiel en CdP, systรจme et optique</option>
943   -<option value="94">Potentiel en conception รฉlectronique</option>
944   -<option value="101">Potentiel en conception mรฉcanique</option>
945   -<option value="100">Potentiel en conception mรฉcanique et analyses structurelles/thermiques</option>
946   -<option value="90">Potentiel en fabrication mรฉcanique</option>
947   -<option value="95">Potentiel en rรฉalisation รฉlectronique</option>
948   -<option value="92">Potentiel en systรจme et optique</option>
949   -<option value="55">S4I SOFT</option>
950   -<option value="84">SCAN4M</option>
951   -<option value="15">SCC@Palomar</option>
952   -<option value="50">SCD</option>
953   -<option value="45">SECCHIRH</option>
954   -<option value="52">Service BASS2000</option>
955   -<option value="51">Service CERCLE</option>
956   -<option value="67">Service OV</option>
957   -<option value="5">SPACE-INN</option>
958   -<option value="47">SPECTROCAM</option>
959   -<option value="106">SpherePlus</option>
960   -<option value="103">Test IAS</option>
961   -<option value="14">THD</option>
962   -<option value="16">ล’IL</option></select>
963   - </div>
964   - </div>
965   -
966   - <div class="form-group">
967   - <label class="col-sm-2 control-label" for="affectation_service_id">Nom du service *</label>
968   - <div class="col-sm-10">
969   - <select class="form-control" name="affectation[service_id]" id="affectation_service_id"><option value="">Choisir un service</option>
970   -<option value="8">CPR - Cellule Prรฉvention des Risques</option>
971   -<option value="7">CQL - Cellule Qualitรฉ du LESIA</option>
972   -<option value="15">COMCL - Commissions conseil de laboratoire</option>
973   -<option value="13">DIR - Direction</option>
974   -<option value="14">EXT - Extรฉrieur LESIA</option>
975   -<option value="1">GEFL - Groupe d'Etude et de Fabrications du LESIA</option>
976   -<option value="11">GIGL - Groupe Informatique Gรฉnรฉrale du LESIA</option>
977   -<option value="17">ITA - ITA Proejts</option>
978   -<option value="2">MESPAL - Moyens d'Essais Salles Propres AIT/AIV du LESIA</option>
979   -<option value="16">OPALE - OPticiens Au LEsia</option>
980   -<option value="18">PII - Plateformes et intรฉgrations instrumentales</option>
981   -<option value="12">SIEL - Service d'Ingรฉnierie Electronique du LESIA</option>
982   -<option value="6">SII - Service Informatique Instrumentale</option>
983   -<option value="5">SIS - Service Informatique Scientifique</option>
984   -<option value="9">SIGAL - Service Internet, Graphisme et Animations du LESIA</option>
985   -<option value="4">SSL - Service Solaire du LESIA</option>
986   -<option value="3">SPL - Soutien Projets du LESIA</option></select>
987   - </div>
988   - </div>
989   -
990   - <div class="form-group">
991   - <label class="col-sm-2 control-label" for="affectation_semestre_id">Choisir un semestre *</label>
992   - <div class="col-sm-10">
993   - <select class="form-control" name="affectation[semestre_id]" id="affectation_semestre_id"><option value="">Choisir un semestre</option>
994   -<option value="2022_S2">2022_S2</option>
995   -<option value="2022_S1">2022_S1</option>
996   -<option value="2021_S2">2021_S2</option>
997   -<option value="2021_S1">2021_S1</option>
998   -<option value="2020_S2">2020_S2</option>
999   -<option value="2020_S1">2020_S1</option>
1000   -<option value="2019_S2">2019_S2</option>
1001   -<option value="2019_S1">2019_S1</option>
1002   -<option value="2018_S2">2018_S2</option>
1003   -<option value="2018_S1">2018_S1</option>
1004   -<option value="2017_S2">2017_S2</option>
1005   -<option value="2017_S1">2017_S1</option>
1006   -<option value="2016_S2">2016_S2</option>
1007   -<option value="2016_S1">2016_S1</option>
1008   -<option value="2015_S2">2015_S2</option>
1009   -<option value="2015_S1">2015_S1</option>
1010   -<option value="2014_S2">2014_S2</option></select>
1011   - </div>
1012   - </div>
1013   -
1014   - <div class="form-group">
1015   - <label class="col-sm-2 control-label" for="affectation_fonction_id">Choisir une fonction</label>
1016   - <div class="col-sm-10">
1017   - <select class="form-control" name="affectation[fonction_id]" id="affectation_fonction_id"><option value="">Choisir une fonction</option>
1018   -<option value="42">Adjoint au chef de service</option>
1019   -<option value="14">Administration systรจme et rรฉseaux</option>
1020   -<option value="27">AIT/AIV</option>
1021   -<option value="24">Archi. Thermique</option>
1022   -<option value="34">ASR</option>
1023   -<option value="40">Assistant de prรฉvention</option>
1024   -<option value="41">Assistant de prรฉvention laser</option>
1025   -<option value="57">Assurance produit</option>
1026   -<option value="36">cรขblages - travaux</option>
1027   -<option value="3">Chef de projet</option>
1028   -<option value="33">Chef de service</option>
1029   -<option value="22">Deputy Project Manager</option>
1030   -<option value="30">Detector design</option>
1031   -<option value="28">Detector expertise &amp; tests</option>
1032   -<option value="31">Detector tests</option>
1033   -<option value="1">Direction</option>
1034   -<option value="16">Electronique AIT/AIV</option>
1035   -<option value="15">Electronique Dรฉveloppement</option>
1036   -<option value="59">Electronique numรฉrique</option>
1037   -<option value="17">Electronique Rรฉalisation</option>
1038   -<option value="44">Encadrement stagiaires 3รจme</option>
1039   -<option value="39">Enseignement</option>
1040   -<option value="49">Formation permanente</option>
1041   -<option value="21">Gestion de documentation</option>
1042   -<option value="47">Gestion des archives</option>
1043   -<option value="48">Gestion des composants spatiaux</option>
1044   -<option value="20">Graphisme</option>
1045   -<option value="37">Imprimantes</option>
1046   -<option value="10">Informatique Chef de Projet / Architecte</option>
1047   -<option value="11">Informatique Dรฉveloppement</option>
1048   -<option value="13">Informatique Ingรฉnieur Qualitรฉ</option>
1049   -<option value="38">Informatique Production de donnรฉes</option>
1050   -<option value="12">Informatique Spรฉcificateur / Validateur</option>
1051   -<option value="18">Instrumentation</option>
1052   -<option value="45">Inventaire matรฉriel</option>
1053   -<option value="19">Logistique</option>
1054   -<option value="54">Maintenance matรฉriel</option>
1055   -<option value="7">Mรฉcanique AIT/AIV</option>
1056   -<option value="6">Mรฉcanique BE</option>
1057   -<option value="32">Mechanical manufacturing</option>
1058   -<option value="29">Mechanics and thermal design</option>
1059   -<option value="50">Membre des instances de l'Observatoire</option>
1060   -<option value="51">Membre des instances du CNRS</option>
1061   -<option value="46">Observateur solaire</option>
1062   -<option value="8">Optique</option>
1063   -<option value="9">Optique AIT/AIV</option>
1064   -<option value="35">Parc micro - achats</option>
1065   -<option value="52">Personnel</option>
1066   -<option value="58">Potentiel</option>
1067   -<option value="26">Qualif. Meca/therm</option>
1068   -<option value="5">Qualitรฉ</option>
1069   -<option value="56">Qualitรฉ composants รฉlectroniques</option>
1070   -<option value="53">Rรฉponse aux appels d'offre</option>
1071   -<option value="2">Scientifique</option>
1072   -<option value="23">Syst. Eng. Optics</option>
1073   -<option value="25">System Structural analysis</option>
1074   -<option value="4">Systรจme</option>
1075   -<option value="55">Thermique AIT/AIV</option>
1076   -<option value="43">Webmestre</option></select>
1077   - </div>
1078   - </div>
1079   -
1080   - <div class="form-group">
1081   - <label class="col-sm-2 control-label" for="affectation_charge">Charge *</label>
1082   - <div class="col-sm-2">
1083   - <div class="input-group">
1084   - <input class="form-control" placeholder="Ex: 55" type="text" name="affectation[charge]" id="affectation_charge">
1085   - <div class="input-group-addon">%</div>
1086   - </div>
1087   - </div>
1088   - </div>
1089   -
1090   - <div class="form-group">
1091   - <div class="col-sm-offset-2 col-sm-10">
1092   - <input type="submit" name="commit" value="Envoyer" class="btn btn-primary">
1093   - </div>
1094   - </div>
1095   -
1096   - <p>* Ce champ est obligatoire.</p>
1097   -
1098   -
1099   -{% endblock %}
app/main/templates/charge_form.html 0 โ†’ 100644
... ... @@ -0,0 +1,60 @@
  1 +{% extends "base_page.html" %}
  2 +{% block content %}
  3 +
  4 + <!-- Invisible span to definte wich ul and a in the navbar are actived -->
  5 + <span id="nav_actived" style="display: none">cds,charge/add</span>
  6 +
  7 + <form id="charge_form" class="pdc-form" action="{{ url_for('main.charge_add') }}" method="post">
  8 + <div class="form-group">
  9 + <label for="agent_id">Nom de l'agent</label>
  10 + <select id="agent_id" name="agent_id" class="form-control">
  11 + <option selected>---</option>
  12 + {% for a in agents %}
  13 + <option value="{{ a.id }}" {{ "selected" if a.id == agent.id }}>{{ a.namefull }}</option>
  14 + {% endfor %}
  15 + </select>
  16 + </div>
  17 + <div class="form-group">
  18 + <label for="project_id">Choisir projet</label>
  19 + <select id="project_id" name="project_id" class="form-control">
  20 + <option selected>---</option>
  21 + {% for p in projects %}
  22 + <option value="{{ p.id }}" {{ "selected" if p.id == project.id }}>{{ p.name }}</option>
  23 + {% endfor %}
  24 + </select>
  25 + </div>
  26 + <div class="form-group">
  27 + <label for="service_id">Choisir le service</label>
  28 + <select id="service_id" name="service_id" class="form-control">
  29 + <option selected>---</option>
  30 + {% for s in services %}
  31 + <option value="{{ s.id }}">{{ s.name }}</option>
  32 + {% endfor %}
  33 + </select>
  34 + </div>
  35 + <div class="form-group">
  36 + <label for="period_id">Choisir la pรฉriode</label>
  37 + <select id="period_id" name="period_id" class="form-control">
  38 + <option selected>---</option>
  39 + {% for p in periods %}
  40 + <option value="{{ p.id }}">{{ p.name }}</option>
  41 + {% endfor %}
  42 + </select>
  43 + </div>
  44 + <div class="form-group">
  45 + <label for="capacity_id">Choisir la fonction</label>
  46 + <select id="capacity_id" name="capacity_id" class="form-control">
  47 + <option selected>---</option>
  48 + {% for c in capacities %}
  49 + <option value="{{ c.id }}">{{ c.name }}</option>
  50 + {% endfor %}
  51 + </select>
  52 + </div>
  53 + <div class="form-group">
  54 + <label for="charge_rate">Charge (%)</label>
  55 + <input class="form-control col-2" id="charge_rate" name="charge_rate" type="text" placeholder="Ex: 55">
  56 + </div>
  57 + <input class="pdc-form-submit" type="submit" value="Valider">
  58 + </form>
  59 +
  60 +{% endblock %}
... ...
app/main/templates/project.html
... ... @@ -8,6 +8,34 @@
8 8 <!-- Invisible span to definte wich ul and a in the navbar are actived -->
9 9 <span id="nav_actived" style="display: none">projet,projects</span>
10 10  
  11 +<div class="card">
  12 + <div class="card-header">
  13 + Fiche Projet
  14 + </div>
  15 + <div class="card-body">
  16 + <dl class="row">
  17 + <dt class="col-sm-2 text-right">ID :</dt>
  18 + <dd class="col-sm-10 text-left">{{project['id']}}</dd>
  19 + {% for category, labels in project['category_labels'].items() %}
  20 + <dt class="col-sm-2 text-right">{{category}} :</dt>
  21 + <dd class="col-sm-10 text-left">{{labels|join(",")}}</dd>
  22 + {% endfor %}
  23 + <dt class="col-sm-2 text-right">Etat :</dt>
  24 + <dd class="col-sm-10 text-left"></dd>
  25 + <dt class="col-sm-2 text-right">Date RSP :</dt>
  26 + <dd class="col-sm-10 text-left"></dd>
  27 + <dt class="col-sm-2 text-right">Fiche projet :</dt>
  28 + <dd class="col-sm-10 text-left"></dd>
  29 + <dt class="col-sm-2 text-right">Date MAJ de la fiche :</dt>
  30 + <dd class="col-sm-10 text-left"></dd>
  31 + <dt class="col-sm-2 text-right">Commentaire :</dt>
  32 + <dd class="col-sm-10 text-left"></dd>
  33 + </dl>
  34 + <a class="card-link" href="{{url_for('main.project_edit', project_id=project.id)}}">Modifier</a>
  35 + <a class="card-link" href="{{url_for('main.charge_add', project_id=project.id)}}">Charger</a>
  36 + </div>
  37 +</div>
  38 +
11 39 <div class="charge_chart" id="project_services_chart"></div>
12 40 <hr/>
13 41 <div class="charge_chart" id="project_capacities_chart"></div>
... ...
app/main/templates/projects.html
... ... @@ -7,17 +7,22 @@
7 7 <table class="table table-hover">
8 8 <thead>
9 9 <tr>
10   - <th scope="col">Projet</th>
11   - <th scope="col">Charge</th>
  10 + {% for c_title in projects[0][1:] %}
  11 + <th scope="col">{{c_title}}</th>
  12 + {% endfor %}
12 13 </tr>
13 14 </thead>
14 15 <tbody>
15   - {% for project in projects %}
  16 + {% for project in projects[1:] %}
16 17 <tr>
17   - <td><a href="{{url_for('main.project', project_id=project.id)}}">
18   - {{ project.name }}</a>
  18 + <td><a href="{{url_for('main.project', project_id=project[0])}}">
  19 + {{ project[1] }}</a>
19 20 </td>
20   - <td>{{ project.total_charge }} % </td>
  21 + {#the category cells#}
  22 + {% for c in project[2:-1] %}
  23 + <td>{{c|join(',')}}</td>
  24 + {% endfor %}
  25 + <td>{{ project[-1] /100}}</td>
21 26 </tr>
22 27 {% endfor %}
23 28 </tbody>
... ...
app/models.py
... ... @@ -5,38 +5,138 @@ db = SQLAlchemy()
5 5  
6 6  
7 7 #
  8 +#
  9 +#
  10 +
  11 +class Formable:
  12 + """
  13 + Parent class allowing some html form facilities
  14 +
  15 + """
  16 + export_keys = []
  17 +
  18 + def from_request(self, form_request):
  19 + """
  20 + Get a form request structure and fill in our fields
  21 +
  22 + :param form_request:
  23 + :return:
  24 + """
  25 + for key in self.export_keys:
  26 + setattr(self, key, form_request.form.get(key))
  27 +
  28 + def to_struct(self):
  29 + """
  30 + Export the orm object to a structure easily used in jinja
  31 +
  32 + :return: nothing
  33 + """
  34 + _struct = {'id': self.id}
  35 + for key in self.export_keys:
  36 + _value = getattr(self, key)
  37 + _struct[key] = '' if _value is None else _value
  38 + return _struct
  39 +
  40 +
  41 +#
8 42 # Categorized projects
9 43 #
  44 +# There is one label list,
  45 +# each label belongs to one or more categories.
  46 +#
  47 +# The projects are labelled by one or more label.
  48 +#
  49 +# Thus this is modeled with classes
  50 +# Project, Label and Category
  51 +# And many_to_many association are done through
  52 +# ProjectLabel and CategoryLabel
  53 +#
10 54  
11   -class Project(db.Model):
  55 +class Project(db.Model, Formable):
12 56 id = db.Column(db.Integer, primary_key=True)
13 57 name = db.Column(db.String)
14 58 labels = relationship("ProjectLabel", back_populates="project")
15 59  
  60 + # add keys to import/export
  61 + export_keys = ['name']
  62 +
  63 + def from_request(self, form_request):
  64 + """
  65 + overide parent method to deal with category labels
  66 +
  67 + :param form_request:
  68 + :return:
  69 + """
  70 + self.labels.clear()
  71 + form_labels = []
  72 + for _c in Category.query.all():
  73 + form_labels = form_labels + form_request.form.getlist(_c.name)
  74 + for label_id in form_labels:
  75 + n_l = Label.query.get(int(label_id))
  76 + n_pl = ProjectLabel(project=self, label=n_l)
  77 + self.labels.append(n_pl)
  78 +
  79 +
  80 + def to_struct(self):
  81 + """
  82 + overide parent method to include one key: agent.fullname
  83 +
  84 + Mainly we add the 'labels' element containing list of label names
  85 + and we add the 'category_labels' element containing a dict
  86 + where category key contains labels for that project.
  87 + :return:
  88 + """
  89 + _struct = super(Project, self).to_struct()
  90 + _struct['labels'] = [_l.label.name for _l in self.labels]
  91 + _struct['category_labels'] = {}
  92 + for _c in Category.query.all():
  93 + category_labels = []
  94 + for _l in self.labels:
  95 + if _l.label in [_cl.label for _cl in _c.labels]:
  96 + category_labels.append(_l.label.name)
  97 + _struct['category_labels'][_c.name] = category_labels
  98 + return _struct
16 99  
17   -class Category(db.Model):
  100 +
  101 +class ProjectLabel(db.Model):
  102 + """
  103 + Labelling projects.
  104 + On project can have many labels.
  105 + And one label will be set to many projects
  106 + """
18 107 id = db.Column(db.Integer, primary_key=True)
19   - name = db.Column(db.String)
20   - labels = relationship("Label", back_populates="category")
21   - projects = relationship("ProjectLabel", back_populates="category")
  108 + project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
  109 + label_id = db.Column(db.Integer, db.ForeignKey('label.id'))
  110 + project = relationship("Project", back_populates="labels")
  111 + label = relationship("Label", back_populates="projects")
22 112  
23 113  
24 114 class Label(db.Model):
25 115 id = db.Column(db.Integer, primary_key=True)
26 116 name = db.Column(db.String, unique=True)
27 117 category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
28   - category = relationship("Category", back_populates="labels")
29 118 projects = relationship("ProjectLabel", back_populates="label")
  119 + categories = relationship("CategoryLabel", back_populates="label")
30 120  
31 121  
32   -class ProjectLabel(db.Model):
33   - project_id = db.Column(db.Integer, db.ForeignKey('project.id'), primary_key=True)
34   - category_id = db.Column(db.Integer, db.ForeignKey('category.id'), primary_key=True)
  122 +class CategoryLabel(db.Model):
  123 + """
  124 + Categorizing labels:
  125 + one label can be added to many categories
  126 + one category hosts many labels
  127 + """
  128 + id = db.Column(db.Integer, primary_key=True)
  129 + category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
35 130 label_id = db.Column(db.Integer, db.ForeignKey('label.id'))
  131 + category = relationship("Category", back_populates="labels")
  132 + label = relationship("Label", back_populates="categories")
36 133  
37   - project = relationship("Project", back_populates="labels")
38   - category = relationship("Category", back_populates="projects")
39   - label = relationship("Label", back_populates="projects")
  134 +
  135 +class Category(db.Model):
  136 + id = db.Column(db.Integer, primary_key=True)
  137 + name = db.Column(db.String)
  138 + labels = relationship("Label", back_populates="category")
  139 + labels = relationship("CategoryLabel", back_populates="category")
40 140  
41 141  
42 142 #
... ... @@ -46,33 +146,62 @@ class ProjectLabel(db.Model):
46 146 class AgentBap(db.Model):
47 147 id = db.Column(db.Integer, primary_key=True)
48 148 name = db.Column(db.String(16))
  149 + agents = relationship("Agent", back_populates="bap")
49 150  
50 151  
51 152 class AgentGrade(db.Model):
52 153 id = db.Column(db.Integer, primary_key=True)
53 154 name = db.Column(db.String(16))
  155 + agents = relationship("Agent", back_populates="grade")
54 156  
55 157  
56 158 class AgentStatus(db.Model):
57 159 id = db.Column(db.Integer, primary_key=True)
58 160 name = db.Column(db.String(16))
  161 + agents = relationship("Agent", back_populates="status")
59 162  
60 163  
61 164 class Company(db.Model):
62 165 id = db.Column(db.Integer, primary_key=True)
63 166 name = db.Column(db.String(16))
  167 + agents = relationship("Agent", back_populates="company")
64 168  
65 169  
66   -class Agent(db.Model):
  170 +class Agent(db.Model, Formable):
67 171 id = db.Column(db.Integer, primary_key=True)
68 172 firstname = db.Column(db.String(100))
69 173 secondname = db.Column(db.String(100))
  174 + virtual = db.Column(db.Integer) # integer boolean
  175 + permanent = db.Column(db.Integer) # integer boolean
70 176 company_id = db.Column(db.Integer, db.ForeignKey('company.id'))
71 177 grade_id = db.Column(db.Integer, db.ForeignKey('agent_grade.id'))
72 178 status_id = db.Column(db.Integer, db.ForeignKey('agent_status.id'))
73 179 bap_id = db.Column(db.Integer, db.ForeignKey('agent_bap.id'))
74   - virtual = db.Column(db.Integer) # integer boolean
75   - permanent = db.Column(db.Integer) # integer boolean
  180 + grade = relationship("AgentGrade", back_populates="agents")
  181 + bap = relationship("AgentBap", back_populates="agents")
  182 + status = relationship("AgentStatus", back_populates="agents")
  183 + company = relationship("Company", back_populates="agents")
  184 +
  185 + @property
  186 + def fullname(self):
  187 + return f"{self.secondname} {self.firstname}"
  188 +
  189 + @property
  190 + def namefull(self):
  191 + return f"{self.firstname} {self.secondname}"
  192 +
  193 + # has to be set as we inherit Formable
  194 + #
  195 + export_keys = ['firstname', 'secondname', 'virtual', 'permanent', 'company_id', 'status_id', 'grade_id', 'bap_id']
  196 +
  197 + def to_struct(self):
  198 + """
  199 + overide parent method to include one key: agent.fullname
  200 + :return:
  201 + """
  202 + _struct = super(Agent, self).to_struct()
  203 + _struct['fullname'] = self.fullname
  204 + return _struct
76 205  
77 206  
78 207 class Service(db.Model):
... ... @@ -92,7 +221,7 @@ class Period(db.Model):
92 221 num_months = db.Column(db.Integer)
93 222  
94 223  
95   -class Charge(db.Model):
  224 +class Charge(db.Model, Formable):
96 225 id = db.Column(db.Integer, primary_key=True)
97 226 agent_id = db.Column(db.Integer, db.ForeignKey('agent.id'))
98 227 project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
... ... @@ -100,3 +229,7 @@ class Charge(db.Model):
100 229 capacity_id = db.Column(db.Integer, db.ForeignKey('capacity.id'))
101 230 period_id = db.Column(db.Integer, db.ForeignKey('period.id'))
102 231 charge_rate = db.Column(db.Integer)
  232 +
  233 + # Overwrite Formable default to fit our own members
  234 + #
  235 + export_keys = ['agent_id', 'project_id', 'service_id', 'capacity_id', 'period_id', 'charge_rate']
... ...
app/static/css/style.css
  1 +/* Update the navbar for small screens. Replace bootstrap css for small screens only */
  2 +@media (max-width: 768px) {
  3 + .sidebar {
  4 + position: static !important;
  5 + margin-top: 0px !important;
  6 + }
  7 +}
  8 +
  9 +@media (min-width: 1670px) {
  10 + .sidebar {
  11 + max-width: 12.499999995%;
  12 + }
  13 +}
  14 +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  15 +
1 16 html {
2 17 position: relative;
3 18 min-height: 100%;
... ... @@ -66,6 +81,7 @@ nav.sidebar a.disabled {
66 81 border-left: 5px solid #00c5ff;;
67 82 background-color: #e3e3e3;
68 83 }
  84 +
69 85 /* -- - - - - - - - - - per site icon settings - - - - - - - - - -- */
70 86  
71 87 .admin-icon {
... ... @@ -85,16 +101,14 @@ nav.sidebar a.disabled {
85 101 filter: hue-rotate(100deg) saturate(1000%);
86 102 }
87 103  
88   -/* Update the navbar for small screens. Replace bootstrap css for small screens only */
89   -@media (max-width: 768px) {
90   - .sidebar {
91   - position: static !important;
92   - margin-top: 0px !important;
93   - }
  104 +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  105 +
  106 +.card {
  107 + border: solid black 1pt;
  108 + margin-bottom: 3em;
  109 + border-radius: 0;
94 110 }
95 111  
96   -@media (min-width: 1670px) {
97   - .sidebar {
98   - max-width:12.499999995%;
99   - }
  112 +dl.agent {
  113 + line-height: 1.2;
100 114 }
101 115 \ No newline at end of file
... ...
app/templates/base_page.html
... ... @@ -2,28 +2,30 @@
2 2 <html lang="fr">
3 3 <head>
4 4 {% include 'heads.html' %}
5   - <link href="{{ url_for('static', filename='css/style.css', version=config.VERSION) }}" rel="stylesheet" type="text/css"/>
  5 + <link href="{{ url_for('static', filename='css/style.css', version=config.VERSION) }}" rel="stylesheet"
  6 + type="text/css"/>
6 7 {% block more_heads %}
7 8 {% endblock %}
8 9 </head>
9 10 <body>
10 11 <nav class="navbar navbar-dark sticky-top bg-dark navbar-expand-lg p-1">
11   - <a id="accueil" class="navbar-brand col-sm-3 col-md-2 " href="{{url_for('main.index')}}">
12   - <img class="m-0 mr-1 {{config.PDC_SITE_CLASS}}" src="{{ url_for('static', filename='img/pdc-ico.png', version=config.VERSION) }}" height="30" width="30"/>
  12 + <a id="accueil" class="navbar-brand col-sm-3 col-md-2 " href="{{ url_for('main.index') }}">
  13 + <img class="m-0 mr-1 {{ config.PDC_SITE_CLASS }}"
  14 + src="{{ url_for('static', filename='img/pdc-ico.png', version=config.VERSION) }}" height="30" width="30"/>
13 15  
14   - {{config.PDC_APP_NAME}} - {{config.PDC_SITE_NAME}}</a>
  16 + {{ config.PDC_APP_NAME }} - {{ config.PDC_SITE_NAME }}</a>
15 17 <ul class="navbar-nav flex-row ml-auto">
16 18 {% if not current_user.is_anonymous %}
17   - <li class="nav-item">
18   - <span class="navbar-brand">{{current_user.name}}</span>
19   - </li>
20   - <li class="nav-item text-nowrap">
21   - <a class="nav-link link_connect" href="{{ url_for('auth.logout') }}">Dรฉconnexion</a>
22   - </li>
  19 + <li class="nav-item">
  20 + <span class="navbar-brand">{{ current_user.name }}</span>
  21 + </li>
  22 + <li class="nav-item text-nowrap">
  23 + <a class="nav-link link_connect" href="{{ url_for('auth.logout') }}">Dรฉconnexion</a>
  24 + </li>
23 25 {% else %}
24   - <li class="nav-item text-nowrap">
25   - <a class="nav-link link_connect" href="{{ url_for('auth.login') }}">Connexion</a>
26   - </li>
  26 + <li class="nav-item text-nowrap">
  27 + <a class="nav-link link_connect" href="{{ url_for('auth.login') }}">Connexion</a>
  28 + </li>
27 29 {% endif %}
28 30 </ul>
29 31 </nav>
... ... @@ -31,22 +33,27 @@
31 33 <div class="container-fluid">
32 34 <div class="row">
33 35 <nav class="col-md-half col-lg-2 col-md-2 navbar-light bg-light navbar-expand-md sidebar mt-5">
34   - <button id="test" class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
35   - <span class="navbar-toggler-icon"></span>
36   - </button>
37   - <div class="sidebar-sticky collapse navbar-collapse" id="navbarSupportedContent">
  36 + <button id="test" class="navbar-toggler" type="button" data-toggle="collapse"
  37 + data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
  38 + aria-label="Toggle navigation">
  39 + <span class="navbar-toggler-icon"></span>
  40 + </button>
  41 + <div class="sidebar-sticky collapse navbar-collapse" id="navbarSupportedContent">
38 42 <ul class="nav flex-column" id="v_menu">
39 43 <li class="nav-item">
40   - <a aria-expanded="false" id="agents" class="main-nav nav-link" data-target="#agent" data-toggle="collapse" href="#">
  44 + <a aria-expanded="false" id="agents" class="main-nav nav-link" data-target="#agent" data-toggle="collapse"
  45 + href="#">
41 46 <span data-feather="user"></span>
42 47 Agent
43 48 </a>
44 49 <ul aria-expanded="false" class="collapse" class="nav" data-parent="#v_menu" id="agent">
45   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.agents')}}">Liste des agents</a>
  50 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.agents') }}">Liste des
  51 + agents</a>
46 52 </li>
47 53 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Statistiques</a></li>
48 54 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Liste des responsabilitรฉs</a></li>
49   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.capacities')}}">Liste des fonctions</a>
  55 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.capacities') }}">Liste des
  56 + fonctions</a>
50 57 </li>
51 58 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Liste des compรฉtences</a></li>
52 59 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Liste des pรดles</a></li>
... ... @@ -59,10 +66,12 @@
59 66 Projet
60 67 </a>
61 68 <ul aria-expanded="false" class="collapse" data-parent="#v_menu" id="projet">
62   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.projects')}}">Liste des projets</a></li>
  69 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.projects') }}">Liste des
  70 + projets</a></li>
63 71 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Statistiques</a></li>
64 72 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Listes des domaines</a></li>
65   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Listes des statuts de projets</a></li>
  73 + <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Listes des statuts de projets</a>
  74 + </li>
66 75 </ul>
67 76 </li>
68 77 <li class="nav-item">
... ... @@ -71,7 +80,8 @@
71 80 Service
72 81 </a>
73 82 <ul aria-expanded="false" class="collapse" data-parent="#v_menu" id="service">
74   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.services')}}">Liste des services</a>
  83 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.services') }}">Liste des
  84 + services</a>
75 85 </li>
76 86 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Statistiques</a></li>
77 87 </ul>
... ... @@ -82,9 +92,13 @@
82 92 Chef de service
83 93 </a>
84 94 <ul aria-expanded="false" class="collapse" data-parent="#v_menu" id="cds">
85   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Renseigner le statut d'un agent pour un
  95 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.agent_edit') }}">Ajouter un
  96 + Agent</a></li>
  97 + <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Renseigner le statut d'un agent pour
  98 + un
86 99 semestre</a></li>
87   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.charge_add')}}">Affecter un agent ร  un
  100 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.charge_add') }}">Affecter un
  101 + agent ร  un
88 102 projet/service</a></li>
89 103 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Crรฉation agent virtuel</a></li>
90 104 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Charge totale des agents</a></li>
... ... @@ -99,15 +113,18 @@
99 113 </a>
100 114 <ul aria-expanded="false" class="collapse" data-parent="#v_menu" id="admin">
101 115  
102   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouveau projet</a></li>
  116 + <li class="nav-item"><a class="sub_link nav-link" href="{{ url_for('main.project_edit') }}">Nouveau projet</a></li>
103 117 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouveau service</a></li>
104 118 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouvelle compรฉtence</a></li>
105 119 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouvelle fonction</a></li>
106 120 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouveau domaine</a></li>
107 121 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouvelle responsabilitรฉ</a></li>
108   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Affecter un responsable de projet</a></li>
109   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Affecter un responsable de service</a></li>
110   - <li class="nav-item"><a class="sub_link nav-link " href="{{url_for('main.periods')}}">Liste des pรฉriodes</a></li>
  122 + <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Affecter un responsable de
  123 + projet</a></li>
  124 + <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Affecter un responsable de
  125 + service</a></li>
  126 + <li class="nav-item"><a class="sub_link nav-link " href="{{ url_for('main.periods') }}">Liste des
  127 + pรฉriodes</a></li>
111 128 <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Nouveau semestre</a></li>
112 129  
113 130 </ul>
... ... @@ -120,7 +137,7 @@
120 137 </nav>
121 138  
122 139 <main id="main" class="ml-md-auto ml-sm-5 ml-xs-8 col-xs-4 col-sm-11 col-md-9 col-lg-10 pt-3 px-4" role="main">
123   - <h2 class="sub-header">{{subtitle}}</h2>
  140 + <h2 class="sub-header">{{ subtitle }}</h2>
124 141 {% include 'flash-messages.html' %}
125 142 {% block content %}
126 143 {% endblock %}
... ... @@ -134,10 +151,11 @@
134 151 <!-- Icons -->
135 152 <script src="https://unpkg.com/feather-icons/dist/feather.min.js"></script>
136 153 <script>
137   - feather.replace()
  154 + feather.replace()
138 155 </script>
139 156  
140   - <script type="text/javascript" src="{{ url_for('static', filename='js/navbar.js', version=config.VERSION) }}"></script>
  157 + <script type="text/javascript"
  158 + src="{{ url_for('static', filename='js/navbar.js', version=config.VERSION) }}"></script>
141 159  
142 160 {% block more_scripts %}
143 161 {% endblock %}
... ...
app/templates/bs4-scripts.html
... ... @@ -4,4 +4,8 @@
4 4 $(function () {
5 5 $('[data-toggle="tooltip"]').tooltip()
6 6 })
  7 +
  8 + // Set the bootstrap layout for forms
  9 + $(".pdc-form").addClass("col-12 col-md-10 col-lg-8 col-xl-6")
  10 + $(".pdc-form-submit").addClass("btn btn-dark col-12")
7 11 </script>
... ...
app/templates/project_form.html 0 โ†’ 100644
... ... @@ -0,0 +1,35 @@
  1 +{% extends "base_page.html" %}
  2 +
  3 +{# Set the title that will be used in base_page #}
  4 +{% if project['id'] and project['id'] != '' %}
  5 + {% set subtitle = "Modifier le projet "+ project['name'] %}
  6 +{% else %}
  7 + {% set subtitle = "Ajouter un nouveau projet" %}
  8 +{% endif %}
  9 +
  10 +{% block content %}
  11 +
  12 + <!-- Invisible span to define wich ul and a in the navbar are actived -->
  13 + <span id="nav_actived" style="display: none">admin,project/create</span>
  14 +
  15 + <form id="project_form" class="pdc-form" action="{{ url_for('main.project_edit') }}" method="post">
  16 + {% if project['id'] and project['id'] != '' %}
  17 + <input class="form-control" id="project_id" name="project_id" type="hidden" value="{{ project['id'] }}">
  18 + {% endif %}
  19 + <div class="form-group">
  20 + <label for="name">Nom</label>
  21 + <input class="form-control" id="name" name="name" type="text" value="{{ project['name'] }}">
  22 + </div>
  23 + {% for c in categories %}
  24 + <div class="form-group">
  25 + <label for="{{ c.name }}">{{ c.name }}</label>
  26 + <select id="{{ c.name }}" name="{{ c.name }}" class="form-control" multiple>
  27 + {% for l in c.labels %}
  28 + <option value="{{ l.id }}" {{ "selected" if l.name in project['labels'] }}>{{ l.name }}</option>
  29 + {% endfor %}
  30 + </select>
  31 + </div>
  32 + {% endfor %}
  33 + <input class="pdc-form-submit" type="submit" value="Valider">
  34 + </form>
  35 +{% endblock %}
... ...
resources/lesia-btp.sqlite
No preview for this file type
tests/backend_tests.py
1 1 import os
2   -import sys
3 2 import unittest
4   -from shutil import copyfile
  3 +from pprint import pprint
5 4  
6 5 from pdc_config import TestConfig
7   -from app import create_app, db_mgr, db
8   -from app.auth.models import User
  6 +from app import create_app, db_mgr
9 7 from tests.common_db_feed import resources_to_instancedb
10 8  
11 9  
... ... @@ -35,9 +33,32 @@ class BaseTestCase(unittest.TestCase):
35 33  
36 34 class DbMgrTestCase(BaseTestCase):
37 35  
  36 + def test_projects(self):
  37 + all_projects = db_mgr.projects()
  38 + self.assertEqual(102, len(all_projects))
  39 +
  40 + def test_projects_columns(self):
  41 + all_projects = db_mgr.projects()
  42 + self.assertEqual(5, len(all_projects[0]))
  43 +
  44 + def test_projects_all_columns(self):
  45 + all_projects = db_mgr.projects()
  46 + for p in all_projects:
  47 + with self.subTest(p):
  48 + self.assertEqual(5, len(p), f"Failed on {p[1]}")
  49 +
  50 + def test_projects_labels(self):
  51 + self.assertEqual(0, 0)
  52 + all_projects = db_mgr.projects()
  53 + # test all projects but skip headers first row
  54 + for p in all_projects[1:]:
  55 + with self.subTest(p):
  56 + # the number of labels for each category and any project should be less than 2
  57 + self.assertTrue(len(p[2]) <= 2, f"Failed on {p[1]}: " + ", ".join(p[2]))
  58 + self.assertTrue(len(p[3]) <= 2, f"Failed on {p[1]}: " + ", ".join(p[3]))
  59 +
38 60 def test_agents(self):
39 61 all_agents = db_mgr.agents()
40   - print(len(all_agents))
41 62 self.assertEqual(548, len(all_agents))
42 63  
43 64 def test_charges_by_agent(self):
... ... @@ -53,3 +74,9 @@ class DbMgrTestCase(BaseTestCase):
53 74 stacked_charges = db_mgr.charges_by_project_stacked(60)
54 75 # Waiting for 17 periods + headers line
55 76 self.assertEqual(18, len(stacked_charges))
  77 +
  78 + def test_category_labels(self):
  79 + category_labels = db_mgr.category_labels()
  80 + categories = [_cl['name'] for _cl in category_labels]
  81 + self.assertEqual(['Domaine', 'Pรดle'], categories)
  82 +
... ...
tests/common_db_feed.py
... ... @@ -14,41 +14,42 @@ def resources_to_instancedb(app):
14 14 copyfile(sqlite_file_name, db_path)
15 15 return db_path
16 16  
17   -
18   -categorized_labels = {'pole': ['Spatial', 'Sol'], 'domaine': ['soleil-terre', 'atmosphere', 'r&t', 'gรฉologie']}
19   -projects_categories = {'ChemCam': {'pole': 'Spatial', 'domaine': 'gรฉologie'},
20   - 'Pilot': {'pole': 'Spatial', 'domaine': 'atmosphere'},
21   - 'Ambre': {'pole': 'Spatial', 'domaine': 'soleil-terre'}}
22   -
23   -
24   -def feed_projects():
25   - for c_name, labels in categorized_labels.items():
26   - n_c = Category(name=c_name)
27   - db.session.add(n_c)
28   - for l_name in labels:
29   - n_l = Label(name=l_name, category=n_c)
30   - db.session.add(n_l)
31   -
32   - db.session.commit()
33   -
34   - project_names = projects_categories.keys()
35   -
36   - for p_name in set(project_names):
37   - n_p = Project(name=p_name)
38   - db.session.add(n_p)
39   - db.session.commit()
40   -
41   - for p, categories in projects_categories.items():
42   - n_p = db.session.query(Project).filter(Project.name == p).one()
43   - for c_name, l_name in categories.items():
44   - n_c = db.session.query(Category).filter(Category.name == c_name).one()
45   - n_l = db.session.query(Label).filter(Label.name == l_name).one()
46   - n_pc = ProjectLabel(project=n_p, category=n_c, label=n_l)
47   - db.session.add(n_pc)
48   -
49   - db.session.commit()
50   -
51   -
52   -def show_categories():
53   - for c in db.session.query(Category).all():
54   - print(c.name + ": ", ",".join([l.name for l in c.categorized_labels]))
  17 +# TODO: rewrite for new category/label model
  18 +#
  19 +# categorized_labels = {'pole': ['Spatial', 'Sol'], 'domaine': ['soleil-terre', 'atmosphere', 'r&t', 'gรฉologie']}
  20 +# projects_categories = {'ChemCam': {'pole': 'Spatial', 'domaine': 'gรฉologie'},
  21 +# 'Pilot': {'pole': 'Spatial', 'domaine': 'atmosphere'},
  22 +# 'Ambre': {'pole': 'Spatial', 'domaine': 'soleil-terre'}}
  23 +#
  24 +#
  25 +# def feed_projects():
  26 +# for c_name, labels in categorized_labels.items():
  27 +# n_c = Category(name=c_name)
  28 +# db.session.add(n_c)
  29 +# for l_name in labels:
  30 +# n_l = Label(name=l_name, category=n_c)
  31 +# db.session.add(n_l)
  32 +#
  33 +# db.session.commit()
  34 +#
  35 +# project_names = projects_categories.keys()
  36 +#
  37 +# for p_name in set(project_names):
  38 +# n_p = Project(name=p_name)
  39 +# db.session.add(n_p)
  40 +# db.session.commit()
  41 +#
  42 +# for p, categories in projects_categories.items():
  43 +# n_p = db.session.query(Project).filter(Project.name == p).one()
  44 +# for c_name, l_name in categories.items():
  45 +# n_c = db.session.query(Category).filter(Category.name == c_name).one()
  46 +# n_l = db.session.query(Label).filter(Label.name == l_name).one()
  47 +# n_pc = ProjectLabel(project=n_p, category=n_c, label=n_l)
  48 +# db.session.add(n_pc)
  49 +#
  50 +# db.session.commit()
  51 +#
  52 +#
  53 +# def show_categories():
  54 +# for c in db.session.query(Category).all():
  55 +# print(c.name + ": ", ",".join([l.name for l in c.categorized_labels]))
... ...
tests/db_tests.py
  1 +import os
1 2 import unittest
  3 +from pprint import pprint
2 4  
3 5 from app import create_app, db, User
4 6 from app.models import Project
5 7 from pdc_config import TestConfig
6   -from tests.common_db_feed import feed_projects
  8 +from tests.common_db_feed import resources_to_instancedb
7 9  
8 10  
9 11 class DbBaseTestCase(unittest.TestCase):
10   - def setUp(self):
  12 + def setUp(self, mode='memory'):
  13 + """
  14 +
  15 + :param mode: memory or btp
  16 + :return:
  17 + """
11 18 self.app = create_app(TestConfig)
12   - # force db uri to sqlite memory
  19 + self.mode = mode
  20 + # Set uri based on called mode
  21 + #
  22 + if self.mode == 'memory':
  23 + # force db uri to sqlite memory
  24 + uri = 'sqlite:///:memory:'
  25 + elif self.mode == 'btp':
  26 + self.db_path = resources_to_instancedb(self.app)
  27 + # force db path to newly create file
  28 + uri = 'sqlite:///' + self.db_path
13 29 self.app.config.update(
14   - SQLALCHEMY_DATABASE_URI='sqlite:///:memory:'
  30 + SQLALCHEMY_DATABASE_URI=uri
15 31 )
  32 + # Push context
  33 + #
16 34 self.app_context = self.app.app_context()
17 35 self.app_context.push()
18   - db.create_all()
19   - feed_projects()
  36 + # Create and feed db if empty
  37 + #
  38 + if self.mode == 'memory':
  39 + db.create_all()
  40 + # TODO: to be rewriten for new category/label model
  41 + # feed_projects()
20 42  
21 43 def tearDown(self):
22   - db.session.remove()
23   - db.drop_all()
  44 + if self.mode == 'memory':
  45 + db.session.remove()
  46 + db.drop_all()
  47 + elif self.mode == 'btp':
  48 + if os.path.isfile(self.db_path):
  49 + os.remove(self.db_path)
24 50 self.app_context.pop()
25 51  
26   - def test_first(self):
  52 +
  53 +class AnyModelTestCase(DbBaseTestCase):
  54 + def setUp(self):
  55 + DbBaseTestCase.setUp(self, 'btp')
  56 +
  57 + def tearDown(self):
  58 + DbBaseTestCase.tearDown(self)
  59 +
  60 + def test_projects_struct(self):
  61 + for project in Project.query.all():
  62 + with self.subTest(project):
  63 + categories = list(project.to_struct()['category_labels'].keys())
  64 + self.assertEqual(['Domaine', 'Pรดle'], categories,
  65 + f"Failed on {project.name} categories: {categories}")
  66 +
  67 +
  68 +class ChargeModelTestCase(DbBaseTestCase):
  69 + def setUp(self):
  70 + DbBaseTestCase.setUp(self, 'btp')
  71 +
  72 + def tearDown(self):
  73 + DbBaseTestCase.tearDown(self)
  74 +
  75 + def test_btp(self):
27 76 projects = Project.query.all()
28   - self.assertEqual(3, len(projects))
  77 + self.assertEqual(101, len(projects))
29 78  
30 79  
31 80 class AuthModelTestCase(DbBaseTestCase):
... ...
tests/frontend_tests.py
1 1 import os
  2 +import unittest
2 3 import urllib.request
3 4  
4 5 from flask import url_for
5 6 from flask_testing import LiveServerTestCase
6 7 from selenium import webdriver
  8 +from selenium.webdriver.common.keys import Keys
  9 +from selenium.webdriver.support.select import Select
7 10  
8 11 from app import create_app
  12 +from app.models import Agent, Charge
9 13 from pdc_config import TestConfig
10 14 from tests.common_db_feed import resources_to_instancedb
11 15  
... ... @@ -108,6 +112,20 @@ class AccessTestCase(BaseFrontTestCase):
108 112 self.driver.get(target_url)
109 113 self.assertEqual(self.driver.current_url, 'http://localhost:8943/projects')
110 114  
  115 + def test_projects_trs(self):
  116 + target_url = self.get_server_url() + url_for('main.projects')
  117 + self.driver.get(target_url)
  118 + td_elmts = self.driver.find_elements_by_xpath("//table/tbody/tr")
  119 + self.assertEqual(101, len(td_elmts))
  120 +
  121 + def test_projects_tds(self):
  122 + target_url = self.get_server_url() + url_for('main.projects')
  123 + self.driver.get(target_url)
  124 + for tr in self.driver.find_elements_by_xpath("//table/tbody/tr"):
  125 + with self.subTest(tr):
  126 + tds = tr.find_elements_by_tag_name('td')
  127 + self.assertEqual(4, len(tds), tds[0].text + " has wrong number of columns")
  128 +
111 129 def test_project_page(self):
112 130 project_id = 8
113 131 target_url = self.get_server_url() + url_for('main.project', project_id=project_id)
... ... @@ -115,3 +133,88 @@ class AccessTestCase(BaseFrontTestCase):
115 133 self.assertEqual(self.driver.current_url, f'http://localhost:8943/project/{project_id}')
116 134 td_elmts = self.driver.find_elements_by_xpath("//table[@id='charge_table']/tbody/tr/td")
117 135 self.assertEqual(1085, len(td_elmts))
  136 +
  137 + def test_project_page_categories(self):
  138 + project_id = 84
  139 + target_url = self.get_server_url() + url_for('main.project', project_id=project_id)
  140 + self.driver.get(target_url)
  141 + self.assertEqual(self.driver.current_url, f'http://localhost:8943/project/{project_id}')
  142 + # pole_dt_db = self.driver.find_element_by_xpath("//dt[text()='Pรดle']/following-sibling::dd")
  143 + pole_dt_db = self.driver.find_element_by_xpath("//dt[contains(text(),'Pรดle')]/following-sibling::dd")
  144 + self.assertEqual('Solaire', pole_dt_db.text)
  145 + domaine_dt_db = self.driver.find_element_by_xpath("//dt[contains(text(),'Domaine')]/following-sibling::dd")
  146 + self.assertTrue('LESIA' in domaine_dt_db.text)
  147 +
  148 +
  149 +class FormsTestCase(BaseFrontTestCase):
  150 +
  151 + def test_agent_add(self):
  152 + # load the form
  153 + target_url = self.get_server_url() + url_for('main.agent_edit')
  154 + self.driver.get(target_url)
  155 + # fill it in
  156 + firstname_input = self.driver.find_elements_by_xpath("//input[@id='firstname']")[0]
  157 + firstname_input.send_keys("Hitier")
  158 + secondname_input = self.driver.find_elements_by_xpath("//input[@id='secondname']")[0]
  159 + secondname_input.send_keys("Richard")
  160 + # submit
  161 + submit_button = self.driver.find_elements_by_xpath("//input[@type='submit']")[0]
  162 + submit_button.send_keys(Keys.ENTER)
  163 + # check on database
  164 + latest_agent = Agent.query.order_by(Agent.id.desc()).all()[0]
  165 + self.assertEqual('Hitier', latest_agent.firstname)
  166 +
  167 + # Test agent form
  168 + def test_agent_edit(self):
  169 + # load the form
  170 + target_url = self.get_server_url() + url_for('main.agent_edit', agent_id=1)
  171 + self.driver.get(target_url)
  172 + # fill it in
  173 + firstname_input = self.driver.find_elements_by_xpath("//input[@id='firstname']")[0]
  174 + self.assertEqual("Dubois", firstname_input.get_attribute('value'))
  175 + firstname_input.clear()
  176 + firstname_input.send_keys("Hitier")
  177 + # # submit
  178 + submit_button = self.driver.find_elements_by_xpath("//input[@type='submit']")[0]
  179 + submit_button.send_keys(Keys.ENTER)
  180 + # # check on database
  181 + latest_agent = Agent.query.filter(Agent.id == 1).one()
  182 + self.assertEqual('Hitier', latest_agent.firstname)
  183 +
  184 + # Test charge form add new
  185 + def test_charge_add(self):
  186 + # load the form
  187 + target_url = self.get_server_url() + url_for('main.charge_add')
  188 + self.driver.get(target_url)
  189 + # fill it in
  190 + agent_select = Select(self.driver.find_elements_by_xpath("//select[@id='agent_id']")[0])
  191 + agent_select.select_by_index(1)
  192 + project_select = Select(self.driver.find_elements_by_xpath("//select[@id='project_id']")[0])
  193 + project_select.select_by_index(1)
  194 + service_select = Select(self.driver.find_elements_by_xpath("//select[@id='service_id']")[0])
  195 + service_select.select_by_index(1)
  196 + period_select = Select(self.driver.find_elements_by_xpath("//select[@id='period_id']")[0])
  197 + period_select.select_by_index(1)
  198 + capacity_select = Select(self.driver.find_elements_by_xpath("//select[@id='capacity_id']")[0])
  199 + capacity_select.select_by_index(1)
  200 + charge_input = self.driver.find_elements_by_xpath("//input[@id='charge_rate']")[0]
  201 + charge_input.send_keys("99")
  202 +
  203 + # submit
  204 + submit_button = self.driver.find_elements_by_xpath("//input[@type='submit']")[0]
  205 + submit_button.send_keys(Keys.ENTER)
  206 + # check on database
  207 + latest_charge = Charge.query.order_by(Charge.id.desc()).all()[0]
  208 + self.assertEqual([514, 41, 17, 1, 1, 99],
  209 + [latest_charge.agent_id,
  210 + latest_charge.project_id,
  211 + latest_charge.service_id,
  212 + latest_charge.capacity_id,
  213 + latest_charge.period_id,
  214 + latest_charge.charge_rate]
  215 + )
  216 +
  217 + # Test charge form edit existing
  218 + @unittest.skip("charge edit to be implemented")
  219 + def test_charge_edit(self):
  220 + pass
... ...