Commit 0011bed0cf4dd584f00e88ce1a9cdbfb178d8c87
Exists in
master
and in
4 other branches
Update category/label data model
Showing
11 changed files
with
270 additions
and
112 deletions
Show diff stats
app/commands/commands.py
@@ -10,7 +10,7 @@ from sqlalchemy.exc import IntegrityError | @@ -10,7 +10,7 @@ from sqlalchemy.exc import IntegrityError | ||
10 | from sqlalchemy.sql import func | 10 | from sqlalchemy.sql import func |
11 | 11 | ||
12 | from app.models import db, Agent, Service, Project, Capacity, Period, Charge, AgentStatus, Company, AgentBap, \ | 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 | # TODO: rename to methods and add get_roles() | 14 | # TODO: rename to methods and add get_roles() |
15 | from app.auth.models import User, _nameToRole, _roleToName | 15 | from app.auth.models import User, _nameToRole, _roleToName |
16 | 16 | ||
@@ -79,15 +79,22 @@ def feed_from_irap(csv_file_name): | @@ -79,15 +79,22 @@ def feed_from_irap(csv_file_name): | ||
79 | grades = [] | 79 | grades = [] |
80 | companies = [] | 80 | companies = [] |
81 | statuses = [] | 81 | statuses = [] |
82 | + labels = [] | ||
82 | 83 | ||
83 | - # build a category dict of lists | 84 | + # Build a category dict of lists |
84 | # key being the category name, | 85 | # key being the category name, |
85 | # list being filled with corresponding labels | 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 | for r in rows: | 98 | for r in rows: |
92 | services.append(r[service_key]) | 99 | services.append(r[service_key]) |
93 | baps.append(r[bap_key]) | 100 | baps.append(r[bap_key]) |
@@ -95,16 +102,20 @@ def feed_from_irap(csv_file_name): | @@ -95,16 +102,20 @@ def feed_from_irap(csv_file_name): | ||
95 | companies.append(r[company_key]) | 102 | companies.append(r[company_key]) |
96 | statuses.append(r[status_key]) | 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 | for k in categorie_keys: | 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 | agents.append({ | 119 | agents.append({ |
109 | 'firstname': r[firstname_key], | 120 | 'firstname': r[firstname_key], |
110 | 'secondname': r[secondname_key], | 121 | 'secondname': r[secondname_key], |
@@ -124,25 +135,25 @@ def feed_from_irap(csv_file_name): | @@ -124,25 +135,25 @@ def feed_from_irap(csv_file_name): | ||
124 | # | 135 | # |
125 | # 1- first remove empty string with filter() | 136 | # 1- first remove empty string with filter() |
126 | # 2- then keep only uniq item with set() | 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 | services = sorted(set(filter(None, services))) | 140 | services = sorted(set(filter(None, services))) |
130 | baps = sorted(set(filter(None, baps))) | 141 | baps = sorted(set(filter(None, baps))) |
131 | grades = sorted(set(filter(None, grades))) | 142 | grades = sorted(set(filter(None, grades))) |
132 | companies = sorted(set(filter(None, companies))) | 143 | companies = sorted(set(filter(None, companies))) |
133 | statuses = sorted(set(filter(None, statuses))) | 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 | # At least, as agents is a list of dicts, sorting is a bit tricky | 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,6 +164,12 @@ def feed_from_irap(csv_file_name): | ||
153 | agents = list({(a['firstname'], a['secondname']): a for a in agents}.values()) | 164 | agents = list({(a['firstname'], a['secondname']): a for a in agents}.values()) |
154 | agents = sorted(agents, key=lambda a: (a['firstname'], a['secondname'])) | 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 | # Feed baps from column | 173 | # Feed baps from column |
157 | # | 174 | # |
158 | for b in baps: | 175 | for b in baps: |
@@ -188,6 +205,20 @@ def feed_from_irap(csv_file_name): | @@ -188,6 +205,20 @@ def feed_from_irap(csv_file_name): | ||
188 | db.session.add(n_p) | 205 | db.session.add(n_p) |
189 | db.session.commit() | 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 | # Feed services from column | 222 | # Feed services from column |
192 | # | 223 | # |
193 | for s in services: | 224 | for s in services: |
@@ -212,25 +243,25 @@ def feed_from_irap(csv_file_name): | @@ -212,25 +243,25 @@ def feed_from_irap(csv_file_name): | ||
212 | 243 | ||
213 | # Feed categories and labels | 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.capitalize()) | ||
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 | db.session.commit() | 255 | db.session.commit() |
225 | 256 | ||
226 | # Feed project's labels | 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 | db.session.add(n_pl) | 265 | db.session.add(n_pl) |
235 | db.session.commit() | 266 | db.session.commit() |
236 | 267 | ||
@@ -313,8 +344,9 @@ def feed_from_lesia(): | @@ -313,8 +344,9 @@ def feed_from_lesia(): | ||
313 | domain_category = Category(name="Domaine") | 344 | domain_category = Category(name="Domaine") |
314 | domains = lesia_session.query(lesia_domains) | 345 | domains = lesia_session.query(lesia_domains) |
315 | for d in domains: | 346 | for d in domains: |
316 | - n_l = Label(name=d.nom, category=domain_category) | ||
317 | - db.session.add(n_l) | 347 | + n_l = Label(name=d.nom) |
348 | + n_cl = CategoryLabel(category=domain_category, label=n_l) | ||
349 | + db.session.add(n_cl) | ||
318 | db.session.commit() | 350 | db.session.commit() |
319 | 351 | ||
320 | # Feed all lesia 'pôle' names and link to new category "Pôle" | 352 | # Feed all lesia 'pôle' names and link to new category "Pôle" |
@@ -322,8 +354,9 @@ def feed_from_lesia(): | @@ -322,8 +354,9 @@ def feed_from_lesia(): | ||
322 | pole_category = Category(name="Pôle") | 354 | pole_category = Category(name="Pôle") |
323 | poles = lesia_session.query(lesia_poles) | 355 | poles = lesia_session.query(lesia_poles) |
324 | for p in poles: | 356 | for p in poles: |
325 | - n_l = Label(name=p.nom, category=pole_category) | ||
326 | - db.session.add(n_l) | 357 | + n_l = Label(name=p.nom) |
358 | + n_cl = CategoryLabel(category=pole_category, label=n_l) | ||
359 | + db.session.add(n_cl) | ||
327 | db.session.commit() | 360 | db.session.commit() |
328 | 361 | ||
329 | # Feed lesia project with proper "pôle" | 362 | # Feed lesia project with proper "pôle" |
@@ -338,7 +371,7 @@ def feed_from_lesia(): | @@ -338,7 +371,7 @@ def feed_from_lesia(): | ||
338 | pole_name = lesia_session.query(lesia_poles).filter(lesia_poles.id == p.pole_id).one().nom | 371 | pole_name = lesia_session.query(lesia_poles).filter(lesia_poles.id == p.pole_id).one().nom |
339 | # search corresponding Label and store in ProjectLabel table | 372 | # search corresponding Label and store in ProjectLabel table |
340 | n_l = Label.query.filter(Label.name == pole_name).one() | 373 | n_l = Label.query.filter(Label.name == pole_name).one() |
341 | - n_pl = ProjectLabel(project=n_p, category=n_l.category, label=n_l) | 374 | + n_pl = ProjectLabel(project=n_p, label=n_l) |
342 | db.session.add(n_pl) | 375 | db.session.add(n_pl) |
343 | db.session.commit() | 376 | db.session.commit() |
344 | 377 | ||
@@ -350,7 +383,7 @@ def feed_from_lesia(): | @@ -350,7 +383,7 @@ def feed_from_lesia(): | ||
350 | domain_name = lesia_session.query(lesia_domains).filter(lesia_domains.id == dp.domaine_id).one().nom | 383 | domain_name = lesia_session.query(lesia_domains).filter(lesia_domains.id == dp.domaine_id).one().nom |
351 | n_p = Project.query.filter(Project.name == project_name).one() | 384 | n_p = Project.query.filter(Project.name == project_name).one() |
352 | n_l = Label.query.filter(Label.name == domain_name).one() | 385 | n_l = Label.query.filter(Label.name == domain_name).one() |
353 | - n_pl = ProjectLabel(project=n_p, category=n_l.category, label=n_l) | 386 | + n_pl = ProjectLabel(project=n_p, label=n_l) |
354 | db.session.add(n_pl) | 387 | db.session.add(n_pl) |
355 | # Some projects have 2 domain labels in lesia db | 388 | # Some projects have 2 domain labels in lesia db |
356 | # That is not allowed any more in the new model. | 389 | # That is not allowed any more in the new model. |
app/db_mgr.py
@@ -10,7 +10,13 @@ charge_unit = 100 | @@ -10,7 +10,13 @@ charge_unit = 100 | ||
10 | 10 | ||
11 | def projects(): | 11 | def projects(): |
12 | """ | 12 | """ |
13 | - Build the list of all agents, with their charges for the current period | 13 | + Build the list of all projects, with their charges for the current period |
14 | + | ||
15 | + Returns a table with headers in the first line: | ||
16 | + Id, Project name, Category_1, ... , Category_n, Total_Charge_for_the_period | ||
17 | + | ||
18 | + The category cells embed the list of the labels of the project for that category. | ||
19 | + | ||
14 | :return: | 20 | :return: |
15 | """ | 21 | """ |
16 | current_period_id = get_current_period() | 22 | current_period_id = get_current_period() |
@@ -26,21 +32,33 @@ def projects(): | @@ -26,21 +32,33 @@ def projects(): | ||
26 | projects_charges = db.session.execute(sql_txt).fetchall() | 32 | projects_charges = db.session.execute(sql_txt).fetchall() |
27 | # First row is table titles | 33 | # First row is table titles |
28 | all_projects = [["Id", "Projet"]] | 34 | all_projects = [["Id", "Projet"]] |
29 | - # Add all categories as last table titles | ||
30 | - for c in Category.query.all(): | 35 | + # Add all categories as next table titles |
36 | + # headers will then look like [["Id", "Projet", "Domaine", "Pôle"]] | ||
37 | + categories = Category.query.all() | ||
38 | + for c in categories: | ||
31 | all_projects[0].append(c.name) | 39 | all_projects[0].append(c.name) |
40 | + # then add charge title | ||
41 | + # headers become [["Id", "Projet", "Domaine", "Pôle", "Charge"]] | ||
32 | all_projects[0].append("Charge") | 42 | all_projects[0].append("Charge") |
33 | # Build the table row by row | 43 | # Build the table row by row |
34 | - for pc in projects_charges: | ||
35 | - p_id = pc[0] | ||
36 | - p = Project.query.get(int(p_id)) | ||
37 | - p_labels = p.labels | ||
38 | - # adding 3 first columns: id, name and total charge | ||
39 | - project_row = [p.id, p.name] | ||
40 | - # then labels, one for each category | ||
41 | - for pl in p_labels: | ||
42 | - project_row.append(pl.label.name) | ||
43 | - project_row.append(pc[1]) | 44 | + for _pc in projects_charges: |
45 | + p_id = _pc[0] | ||
46 | + project = Project.query.get(int(p_id)) | ||
47 | + # adding 2 first columns: id, name | ||
48 | + project_row = [project.id, project.name] | ||
49 | + # then labels, many for each category | ||
50 | + for category in categories: | ||
51 | + # we build the labels.name list of the intersection of current category's labels with project's labels | ||
52 | + # Comprehensive shortcut is: | ||
53 | + # labels = [_cl.label.name for _cl in category.labels if _cl.label in [_pl.label for _pl in project.labels]] | ||
54 | + # | ||
55 | + category_labels = [_cl.label for _cl in category.labels] | ||
56 | + project_labels = [_pl.label for _pl in project.labels] | ||
57 | + intersection_labels = list(set.intersection(set(project_labels), set(category_labels))) | ||
58 | + labels = [label.name for label in intersection_labels] | ||
59 | + project_row.append(labels) | ||
60 | + # then add total charge | ||
61 | + project_row.append(_pc[1]) | ||
44 | all_projects.append(project_row) | 62 | all_projects.append(project_row) |
45 | return all_projects | 63 | return all_projects |
46 | 64 |
app/main/routes.py
@@ -91,7 +91,7 @@ def project(project_id): | @@ -91,7 +91,7 @@ def project(project_id): | ||
91 | this_project = Project.query.get(int(project_id)) | 91 | this_project = Project.query.get(int(project_id)) |
92 | charges_table = db_mgr.charges_by_project(project_id) | 92 | charges_table = db_mgr.charges_by_project(project_id) |
93 | return render_template('project.html', | 93 | return render_template('project.html', |
94 | - project=this_project, | 94 | + project=this_project.to_struct(), |
95 | charges=charges_table, | 95 | charges=charges_table, |
96 | subtitle="{}".format(this_project.name)) | 96 | subtitle="{}".format(this_project.name)) |
97 | 97 |
app/main/templates/project.html
@@ -15,10 +15,10 @@ | @@ -15,10 +15,10 @@ | ||
15 | <div class="card-body"> | 15 | <div class="card-body"> |
16 | <dl class="row"> | 16 | <dl class="row"> |
17 | <dt class="col-sm-2 text-right">ID :</dt> | 17 | <dt class="col-sm-2 text-right">ID :</dt> |
18 | - <dd class="col-sm-10 text-left">{{project.id}}</dd> | ||
19 | - {% for pl in project.labels %} | ||
20 | - <dt class="col-sm-2 text-right">{{pl.label.category.name}} :</dt> | ||
21 | - <dd class="col-sm-10 text-left">{{pl.label.name}}</dd> | 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 %} | 22 | {% endfor %} |
23 | <dt class="col-sm-2 text-right">Etat :</dt> | 23 | <dt class="col-sm-2 text-right">Etat :</dt> |
24 | <dd class="col-sm-10 text-left"></dd> | 24 | <dd class="col-sm-10 text-left"></dd> |
app/main/templates/projects.html
@@ -18,8 +18,9 @@ | @@ -18,8 +18,9 @@ | ||
18 | <td><a href="{{url_for('main.project', project_id=project[0])}}"> | 18 | <td><a href="{{url_for('main.project', project_id=project[0])}}"> |
19 | {{ project[1] }}</a> | 19 | {{ project[1] }}</a> |
20 | </td> | 20 | </td> |
21 | + {#the category cells#} | ||
21 | {% for c in project[2:-1] %} | 22 | {% for c in project[2:-1] %} |
22 | - <td>{{c}}</td> | 23 | + <td>{{c|join(',')}}</td> |
23 | {% endfor %} | 24 | {% endfor %} |
24 | <td>{{ project[-1] /100}}</td> | 25 | <td>{{ project[-1] /100}}</td> |
25 | </tr> | 26 | </tr> |
app/models.py
@@ -41,36 +41,82 @@ class Formable: | @@ -41,36 +41,82 @@ class Formable: | ||
41 | # | 41 | # |
42 | # Categorized projects | 42 | # Categorized projects |
43 | # | 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 | +# | ||
44 | 54 | ||
45 | -class Project(db.Model): | 55 | +class Project(db.Model, Formable): |
46 | id = db.Column(db.Integer, primary_key=True) | 56 | id = db.Column(db.Integer, primary_key=True) |
47 | name = db.Column(db.String) | 57 | name = db.Column(db.String) |
48 | labels = relationship("ProjectLabel", back_populates="project") | 58 | labels = relationship("ProjectLabel", back_populates="project") |
49 | 59 | ||
60 | + export_keys = ['name'] | ||
50 | 61 | ||
51 | -class Category(db.Model): | 62 | + def to_struct(self): |
63 | + """ | ||
64 | + overide parent method to include one key: agent.fullname | ||
65 | + | ||
66 | + Mainly we add the 'labels' element containing a dict | ||
67 | + where category key contains labels for that project. | ||
68 | + :return: | ||
69 | + """ | ||
70 | + _struct = super(Project, self).to_struct() | ||
71 | + _struct['category_labels'] = {} | ||
72 | + for _c in Category.query.all(): | ||
73 | + category_labels = [] | ||
74 | + for _l in self.labels: | ||
75 | + if _l.label in [_cl.label for _cl in _c.labels]: | ||
76 | + category_labels.append(_l.label.name) | ||
77 | + _struct['category_labels'][_c.name] = category_labels | ||
78 | + return _struct | ||
79 | + | ||
80 | + | ||
81 | +class ProjectLabel(db.Model): | ||
82 | + """ | ||
83 | + Labelling projects. | ||
84 | + On project can have many labels. | ||
85 | + And one label will be set to many projects | ||
86 | + """ | ||
52 | id = db.Column(db.Integer, primary_key=True) | 87 | id = db.Column(db.Integer, primary_key=True) |
53 | - name = db.Column(db.String) | ||
54 | - labels = relationship("Label", back_populates="category") | ||
55 | - projects = relationship("ProjectLabel", back_populates="category") | 88 | + project_id = db.Column(db.Integer, db.ForeignKey('project.id')) |
89 | + label_id = db.Column(db.Integer, db.ForeignKey('label.id')) | ||
90 | + project = relationship("Project", back_populates="labels") | ||
91 | + label = relationship("Label", back_populates="projects") | ||
56 | 92 | ||
57 | 93 | ||
58 | class Label(db.Model): | 94 | class Label(db.Model): |
59 | id = db.Column(db.Integer, primary_key=True) | 95 | id = db.Column(db.Integer, primary_key=True) |
60 | name = db.Column(db.String, unique=True) | 96 | name = db.Column(db.String, unique=True) |
61 | category_id = db.Column(db.Integer, db.ForeignKey('category.id')) | 97 | category_id = db.Column(db.Integer, db.ForeignKey('category.id')) |
62 | - category = relationship("Category", back_populates="labels") | ||
63 | projects = relationship("ProjectLabel", back_populates="label") | 98 | projects = relationship("ProjectLabel", back_populates="label") |
99 | + categories = relationship("CategoryLabel", back_populates="label") | ||
64 | 100 | ||
65 | 101 | ||
66 | -class ProjectLabel(db.Model): | ||
67 | - project_id = db.Column(db.Integer, db.ForeignKey('project.id'), primary_key=True) | ||
68 | - category_id = db.Column(db.Integer, db.ForeignKey('category.id'), primary_key=True) | 102 | +class CategoryLabel(db.Model): |
103 | + """ | ||
104 | + Categorizing labels: | ||
105 | + one label can be added to many categories | ||
106 | + one category hosts many labels | ||
107 | + """ | ||
108 | + id = db.Column(db.Integer, primary_key=True) | ||
109 | + category_id = db.Column(db.Integer, db.ForeignKey('category.id')) | ||
69 | label_id = db.Column(db.Integer, db.ForeignKey('label.id')) | 110 | label_id = db.Column(db.Integer, db.ForeignKey('label.id')) |
111 | + category = relationship("Category", back_populates="labels") | ||
112 | + label = relationship("Label", back_populates="categories") | ||
70 | 113 | ||
71 | - project = relationship("Project", back_populates="labels") | ||
72 | - category = relationship("Category", back_populates="projects") | ||
73 | - label = relationship("Label", back_populates="projects") | 114 | + |
115 | +class Category(db.Model): | ||
116 | + id = db.Column(db.Integer, primary_key=True) | ||
117 | + name = db.Column(db.String) | ||
118 | + labels = relationship("Label", back_populates="category") | ||
119 | + labels = relationship("CategoryLabel", back_populates="category") | ||
74 | 120 | ||
75 | 121 | ||
76 | # | 122 | # |
resources/lesia-btp.sqlite
No preview for this file type
tests/backend_tests.py
1 | import os | 1 | import os |
2 | import unittest | 2 | import unittest |
3 | +from pprint import pprint | ||
3 | 4 | ||
4 | from pdc_config import TestConfig | 5 | from pdc_config import TestConfig |
5 | from app import create_app, db_mgr | 6 | from app import create_app, db_mgr |
@@ -40,6 +41,22 @@ class DbMgrTestCase(BaseTestCase): | @@ -40,6 +41,22 @@ class DbMgrTestCase(BaseTestCase): | ||
40 | all_projects = db_mgr.projects() | 41 | all_projects = db_mgr.projects() |
41 | self.assertEqual(5, len(all_projects[0])) | 42 | self.assertEqual(5, len(all_projects[0])) |
42 | 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 | + | ||
43 | def test_agents(self): | 60 | def test_agents(self): |
44 | all_agents = db_mgr.agents() | 61 | all_agents = db_mgr.agents() |
45 | self.assertEqual(548, len(all_agents)) | 62 | self.assertEqual(548, len(all_agents)) |
tests/common_db_feed.py
@@ -14,41 +14,42 @@ def resources_to_instancedb(app): | @@ -14,41 +14,42 @@ def resources_to_instancedb(app): | ||
14 | copyfile(sqlite_file_name, db_path) | 14 | copyfile(sqlite_file_name, db_path) |
15 | return db_path | 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 | import os |
2 | import unittest | 2 | import unittest |
3 | +from pprint import pprint | ||
3 | 4 | ||
4 | from app import create_app, db, User | 5 | from app import create_app, db, User |
5 | from app.models import Project | 6 | from app.models import Project |
6 | from pdc_config import TestConfig | 7 | from pdc_config import TestConfig |
7 | -from tests.common_db_feed import feed_projects, resources_to_instancedb | 8 | +from tests.common_db_feed import resources_to_instancedb |
8 | 9 | ||
9 | 10 | ||
10 | class DbBaseTestCase(unittest.TestCase): | 11 | class DbBaseTestCase(unittest.TestCase): |
@@ -36,7 +37,8 @@ class DbBaseTestCase(unittest.TestCase): | @@ -36,7 +37,8 @@ class DbBaseTestCase(unittest.TestCase): | ||
36 | # | 37 | # |
37 | if self.mode == 'memory': | 38 | if self.mode == 'memory': |
38 | db.create_all() | 39 | db.create_all() |
39 | - feed_projects() | 40 | + # TODO: to be rewriten for new category/label model |
41 | + # feed_projects() | ||
40 | 42 | ||
41 | def tearDown(self): | 43 | def tearDown(self): |
42 | if self.mode == 'memory': | 44 | if self.mode == 'memory': |
@@ -48,6 +50,21 @@ class DbBaseTestCase(unittest.TestCase): | @@ -48,6 +50,21 @@ class DbBaseTestCase(unittest.TestCase): | ||
48 | self.app_context.pop() | 50 | self.app_context.pop() |
49 | 51 | ||
50 | 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 | + | ||
51 | class ChargeModelTestCase(DbBaseTestCase): | 68 | class ChargeModelTestCase(DbBaseTestCase): |
52 | def setUp(self): | 69 | def setUp(self): |
53 | DbBaseTestCase.setUp(self, 'btp') | 70 | DbBaseTestCase.setUp(self, 'btp') |
tests/frontend_tests.py
@@ -112,6 +112,20 @@ class AccessTestCase(BaseFrontTestCase): | @@ -112,6 +112,20 @@ class AccessTestCase(BaseFrontTestCase): | ||
112 | self.driver.get(target_url) | 112 | self.driver.get(target_url) |
113 | self.assertEqual(self.driver.current_url, 'http://localhost:8943/projects') | 113 | self.assertEqual(self.driver.current_url, 'http://localhost:8943/projects') |
114 | 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 | + | ||
115 | def test_project_page(self): | 129 | def test_project_page(self): |
116 | project_id = 8 | 130 | project_id = 8 |
117 | target_url = self.get_server_url() + url_for('main.project', project_id=project_id) | 131 | target_url = self.get_server_url() + url_for('main.project', project_id=project_id) |
@@ -120,6 +134,17 @@ class AccessTestCase(BaseFrontTestCase): | @@ -120,6 +134,17 @@ class AccessTestCase(BaseFrontTestCase): | ||
120 | td_elmts = self.driver.find_elements_by_xpath("//table[@id='charge_table']/tbody/tr/td") | 134 | td_elmts = self.driver.find_elements_by_xpath("//table[@id='charge_table']/tbody/tr/td") |
121 | self.assertEqual(1085, len(td_elmts)) | 135 | self.assertEqual(1085, len(td_elmts)) |
122 | 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 | + | ||
123 | 148 | ||
124 | class FormsTestCase(BaseFrontTestCase): | 149 | class FormsTestCase(BaseFrontTestCase): |
125 | 150 |