Commit ecd54252f437cc431dd46a3b4cb41d59e1d854d8
Exists in
master
and in
4 other branches
Add edit forms
with data models update
Showing
23 changed files
with
1076 additions
and
1287 deletions
Show diff stats
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
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 %} | ... | ... |
... | ... | @@ -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
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&T MARBLL</option> | |
885 | -<option value="83">CNES / R&T radio - PERLS</option> | |
886 | -<option value="35">CNES / R&T radio - STAR</option> | |
887 | -<option value="3">CNES / R&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 & 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 & 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 %} |
... | ... | @@ -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
... | ... | @@ -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 | ... | ... |