c2cgeoportal-admin 2.6.0__py3-none-any.whl → 2.9rc44__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. c2cgeoportal_admin/__init__.py +42 -12
  2. c2cgeoportal_admin/lib/lingva_extractor.py +77 -0
  3. c2cgeoportal_admin/lib/ogcserver_synchronizer.py +170 -57
  4. c2cgeoportal_admin/py.typed +0 -0
  5. c2cgeoportal_admin/routes.py +18 -6
  6. c2cgeoportal_admin/schemas/dimensions.py +16 -10
  7. c2cgeoportal_admin/schemas/functionalities.py +59 -21
  8. c2cgeoportal_admin/schemas/interfaces.py +26 -18
  9. c2cgeoportal_admin/schemas/metadata.py +101 -48
  10. c2cgeoportal_admin/schemas/restriction_areas.py +25 -19
  11. c2cgeoportal_admin/schemas/roles.py +12 -6
  12. c2cgeoportal_admin/schemas/treegroup.py +46 -21
  13. c2cgeoportal_admin/schemas/treeitem.py +3 -4
  14. c2cgeoportal_admin/static/layertree.css +3 -4
  15. c2cgeoportal_admin/static/navbar.css +36 -35
  16. c2cgeoportal_admin/static/theme.css +19 -9
  17. c2cgeoportal_admin/subscribers.py +3 -3
  18. c2cgeoportal_admin/templates/404.jinja2 +18 -2
  19. c2cgeoportal_admin/templates/layertree.jinja2 +31 -9
  20. c2cgeoportal_admin/templates/navigation_navbar.jinja2 +33 -0
  21. c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +12 -0
  22. c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
  23. c2cgeoportal_admin/templates/widgets/metadata.pt +7 -1
  24. c2cgeoportal_admin/views/__init__.py +29 -0
  25. c2cgeoportal_admin/views/dimension_layers.py +14 -9
  26. c2cgeoportal_admin/views/functionalities.py +52 -18
  27. c2cgeoportal_admin/views/home.py +5 -5
  28. c2cgeoportal_admin/views/interfaces.py +26 -20
  29. c2cgeoportal_admin/views/layer_groups.py +36 -25
  30. c2cgeoportal_admin/views/layers.py +17 -13
  31. c2cgeoportal_admin/views/layers_cog.py +135 -0
  32. c2cgeoportal_admin/views/layers_vectortiles.py +62 -27
  33. c2cgeoportal_admin/views/layers_wms.py +55 -34
  34. c2cgeoportal_admin/views/layers_wmts.py +54 -34
  35. c2cgeoportal_admin/views/layertree.py +38 -29
  36. c2cgeoportal_admin/views/logged_views.py +83 -0
  37. c2cgeoportal_admin/views/logs.py +91 -0
  38. c2cgeoportal_admin/views/oauth2_clients.py +30 -18
  39. c2cgeoportal_admin/views/ogc_servers.py +132 -36
  40. c2cgeoportal_admin/views/restriction_areas.py +39 -27
  41. c2cgeoportal_admin/views/roles.py +42 -28
  42. c2cgeoportal_admin/views/themes.py +47 -35
  43. c2cgeoportal_admin/views/themes_ordering.py +19 -14
  44. c2cgeoportal_admin/views/treeitems.py +21 -17
  45. c2cgeoportal_admin/views/users.py +46 -26
  46. c2cgeoportal_admin/widgets.py +17 -14
  47. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/METADATA +12 -12
  48. c2cgeoportal_admin-2.9rc44.dist-info/RECORD +97 -0
  49. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/WHEEL +1 -1
  50. c2cgeoportal_admin-2.9rc44.dist-info/entry_points.txt +5 -0
  51. tests/__init__.py +24 -20
  52. tests/conftest.py +22 -11
  53. tests/test_edit_url.py +11 -14
  54. tests/test_functionalities.py +52 -14
  55. tests/test_home.py +0 -1
  56. tests/test_interface.py +34 -11
  57. tests/test_layer_groups.py +57 -27
  58. tests/test_layers_cog.py +243 -0
  59. tests/test_layers_vectortiles.py +43 -25
  60. tests/test_layers_wms.py +67 -45
  61. tests/test_layers_wmts.py +47 -26
  62. tests/test_layertree.py +99 -16
  63. tests/test_left_menu.py +0 -1
  64. tests/test_lingva_extractor_config.py +64 -0
  65. tests/test_logs.py +102 -0
  66. tests/test_main.py +3 -1
  67. tests/test_metadatas.py +34 -21
  68. tests/test_oauth2_clients.py +40 -11
  69. tests/test_ogc_servers.py +84 -35
  70. tests/test_restriction_areas.py +38 -15
  71. tests/test_role.py +71 -43
  72. tests/test_themes.py +71 -37
  73. tests/test_themes_ordering.py +1 -2
  74. tests/test_treegroup.py +2 -2
  75. tests/test_user.py +56 -19
  76. tests/themes_ordering.py +1 -2
  77. c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -33
  78. c2cgeoportal_admin-2.6.0.dist-info/RECORD +0 -89
  79. c2cgeoportal_admin-2.6.0.dist-info/entry_points.txt +0 -3
  80. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/top_level.txt +0 -0
tests/test_themes.py CHANGED
@@ -25,21 +25,26 @@ def theme_test_data(dbsession, transact):
25
25
  )
26
26
 
27
27
  interfaces = [Interface(name) for name in ["desktop", "mobile", "edit", "routing"]]
28
+ dbsession.add_all(interfaces)
28
29
 
29
- groups = [LayerGroup(name="layer_group_{}".format(i)) for i in range(0, 5)]
30
+ groups = [LayerGroup(name=f"layer_group_{i}") for i in range(0, 5)]
31
+ dbsession.add_all(groups)
30
32
 
31
33
  layer = LayerWMS(name="layer_wms")
32
34
  layer.ogc_server = OGCServer(name="server")
33
- dbsession.add(layer)
34
35
  layers = [layer]
36
+ dbsession.add_all(layers)
35
37
 
38
+ # Note that "default_theme" is not relevant for themes
36
39
  functionalities = [
37
- Functionality(name=name, value="value_{}".format(v))
38
- for name in ("default_basemap", "location")
40
+ Functionality(name=name, value=f"value_{v}")
41
+ for name in ("default_basemap", "default_theme")
39
42
  for v in range(0, 4)
40
43
  ]
44
+ dbsession.add_all(functionalities)
41
45
 
42
46
  roles = [Role("secretary_" + str(i)) for i in range(0, 4)]
47
+ dbsession.add_all(roles)
43
48
 
44
49
  metadatas_protos = [
45
50
  ("copyable", "true"),
@@ -48,7 +53,7 @@ def theme_test_data(dbsession, transact):
48
53
  ]
49
54
  themes = []
50
55
  for i in range(0, 25):
51
- theme = Theme(name="theme_{}".format(i), ordering=1, icon="icon_{}".format(i))
56
+ theme = Theme(name=f"theme_{i}", ordering=1, icon=f"icon_{i}")
52
57
  theme.public = 1 == i % 2
53
58
  theme.interfaces = [interfaces[i % 4], interfaces[(i + 2) % 4]]
54
59
  theme.metadatas = [
@@ -57,7 +62,7 @@ def theme_test_data(dbsession, transact):
57
62
  ]
58
63
  for metadata in theme.metadatas:
59
64
  metadata.item = theme
60
- theme.functionalities = [functionalities[i % 8], functionalities[(i + 3) % 8]]
65
+ theme.functionalities = [functionalities[i % 4]]
61
66
  theme.restricted_roles = [roles[i % 4], roles[(i + 2) % 4]]
62
67
 
63
68
  dbsession.add(
@@ -86,7 +91,6 @@ def theme_test_data(dbsession, transact):
86
91
 
87
92
  @pytest.mark.usefixtures("theme_test_data", "test_app")
88
93
  class TestTheme(TestTreeGroup):
89
-
90
94
  _prefix = "/admin/themes"
91
95
 
92
96
  def test_index_rendering(self, test_app):
@@ -117,7 +121,7 @@ class TestTheme(TestTreeGroup):
117
121
 
118
122
  assert first_theme.id == int(first_row["_id_"])
119
123
  assert first_theme.name == first_row["name"]
120
- assert "default_basemap=value_0, default_basemap=value_3" == first_row["functionalities"]
124
+ assert "default_basemap=value_0" == first_row["functionalities"]
121
125
  assert "secretary_0, secretary_2" == first_row["restricted_roles"]
122
126
  assert "desktop, edit" == first_row["interfaces"]
123
127
  assert 'copyable: true, snappingConfig: {"tolerance": 50}' == first_row["metadatas"]
@@ -146,16 +150,18 @@ class TestTheme(TestTreeGroup):
146
150
 
147
151
  def test_public_checkbox_edit(self, test_app, theme_test_data):
148
152
  theme = theme_test_data["themes"][10]
149
- form10 = test_app.get("/admin/themes/{}".format(theme.id), status=200).form
153
+ form10 = test_app.get(f"/admin/themes/{theme.id}", status=200).form
150
154
  assert not form10["public"].checked
151
155
  theme = theme_test_data["themes"][11]
152
- form11 = test_app.get("/admin/themes/{}".format(theme.id), status=200).form
156
+ form11 = test_app.get(f"/admin/themes/{theme.id}", status=200).form
153
157
  assert form11["public"].checked
154
158
 
155
159
  def test_edit(self, test_app, theme_test_data, dbsession):
160
+ from c2cgeoportal_commons.models.main import Log, LogAction
161
+
156
162
  theme = theme_test_data["themes"][0]
157
163
 
158
- resp = test_app.get("/admin/themes/{}".format(theme.id), status=200)
164
+ resp = test_app.get(f"/admin/themes/{theme.id}", status=200)
159
165
  form = resp.form
160
166
 
161
167
  assert str(theme.id) == self.get_first_field_named(form, "id").value
@@ -166,21 +172,24 @@ class TestTheme(TestTreeGroup):
166
172
  assert theme.public == form["public"].checked
167
173
 
168
174
  interfaces = theme_test_data["interfaces"]
169
- assert set((interfaces[0].id, interfaces[2].id)) == set(i.id for i in theme.interfaces)
175
+ assert {interfaces[0].id, interfaces[2].id} == {i.id for i in theme.interfaces}
170
176
  self._check_interfaces(form, interfaces, theme)
171
177
 
172
178
  functionalities = theme_test_data["functionalities"]
173
- assert set((functionalities[0].id, functionalities[3].id)) == set(f.id for f in theme.functionalities)
179
+ assert {functionalities[0].id} == {f.id for f in theme.functionalities}
174
180
  self.check_checkboxes(
175
181
  form,
176
182
  "functionalities",
177
183
  [
178
184
  {
179
- "label": "{}={}".format(f.name, f.value),
185
+ "label": f"{f.name}={f.value}",
180
186
  "value": str(f.id),
181
187
  "checked": f in theme.functionalities,
182
188
  }
183
- for f in sorted(functionalities, key=lambda f: (f.name, f.value))
189
+ for f in sorted(
190
+ [f for f in functionalities if f.name in ("default_basemap")],
191
+ key=lambda f: (f.name, f.value),
192
+ )
184
193
  ],
185
194
  )
186
195
 
@@ -220,21 +229,25 @@ class TestTheme(TestTreeGroup):
220
229
  assert value == getattr(theme, key)
221
230
  else:
222
231
  assert str(value or "") == str(getattr(theme, key) or "")
223
- assert set([interfaces[1].id, interfaces[3].id]) == set(
224
- [interface.id for interface in theme.interfaces]
225
- )
226
- assert set([functionalities[2].id]) == set(
227
- [functionality.id for functionality in theme.functionalities]
228
- )
232
+ assert {interfaces[1].id, interfaces[3].id} == {interface.id for interface in theme.interfaces}
233
+ assert {functionalities[2].id} == {functionality.id for functionality in theme.functionalities}
229
234
  assert 0 == len(theme.restricted_roles)
230
235
 
236
+ log = dbsession.query(Log).one()
237
+ assert log.date != None
238
+ assert log.action == LogAction.UPDATE
239
+ assert log.element_type == "theme"
240
+ assert log.element_id == theme.id
241
+ assert log.element_name == theme.name
242
+ assert log.username == "test_user"
243
+
231
244
  def test_post_new_with_children_invalid(self, test_app, theme_test_data):
232
245
  """
233
246
  Check there is no rendering error when validation fails.
234
247
  """
235
248
  groups = theme_test_data["groups"]
236
249
  resp = test_app.post(
237
- "{}/new".format(self._prefix),
250
+ f"{self._prefix}/new",
238
251
  (
239
252
  ("_charset_", "UTF-8"),
240
253
  ("__formid__", "deform"),
@@ -254,9 +267,11 @@ class TestTheme(TestTreeGroup):
254
267
  self._check_submission_problem(resp, "Required")
255
268
 
256
269
  def test_post_new_with_children_success(self, test_app, dbsession, theme_test_data):
270
+ from c2cgeoportal_commons.models.main import Log, LogAction
271
+
257
272
  groups = theme_test_data["groups"]
258
273
  resp = test_app.post(
259
- "{}/new".format(self._prefix),
274
+ f"{self._prefix}/new",
260
275
  (
261
276
  ("_charset_", "UTF-8"),
262
277
  ("__formid__", "deform"),
@@ -298,13 +313,21 @@ class TestTheme(TestTreeGroup):
298
313
  rel.treeitem_id for rel in theme.children_relation
299
314
  ]
300
315
 
316
+ log = dbsession.query(Log).one()
317
+ assert log.date != None
318
+ assert log.action == LogAction.INSERT
319
+ assert log.element_type == "theme"
320
+ assert log.element_id == theme.id
321
+ assert log.element_name == theme.name
322
+ assert log.username == "test_user"
323
+
301
324
  def test_post_new_with_child_layer(self, theme_test_data, test_app):
302
325
  """
303
326
  Check layers are rejected by the validator (also means that they are not proposed to the user).
304
327
  """
305
328
  layers = theme_test_data["layers"]
306
329
  resp = test_app.post(
307
- "{}/new".format(self._prefix),
330
+ f"{self._prefix}/new",
308
331
  (
309
332
  ("_charset_", "UTF-8"),
310
333
  ("__formid__", "deform"),
@@ -324,7 +347,7 @@ class TestTheme(TestTreeGroup):
324
347
  status=200,
325
348
  )
326
349
  assert (
327
- "Value {} does not exist in table treeitem or is not allowed to avoid cycles".format(layers[0].id)
350
+ f"Value {layers[0].id} does not exist in table treeitem or is not allowed to avoid cycles"
328
351
  == resp.html.select_one(".item-children_relation + .help-block").getText().strip()
329
352
  )
330
353
 
@@ -333,7 +356,7 @@ class TestTheme(TestTreeGroup):
333
356
 
334
357
  theme = theme_test_data["themes"][1]
335
358
 
336
- resp = test_app.get("{}/{}/duplicate".format(self._prefix, theme.id), status=200)
359
+ resp = test_app.get(f"{self._prefix}/{theme.id}/duplicate", status=200)
337
360
  form = resp.form
338
361
 
339
362
  assert "" == self.get_first_field_named(form, "id").value
@@ -345,22 +368,25 @@ class TestTheme(TestTreeGroup):
345
368
  assert theme.public == form["public"].checked
346
369
 
347
370
  interfaces = theme_test_data["interfaces"]
348
- assert set((interfaces[1].id, interfaces[3].id)) == set(i.id for i in theme.interfaces)
371
+ assert {interfaces[1].id, interfaces[3].id} == {i.id for i in theme.interfaces}
349
372
 
350
373
  self._check_interfaces(form, interfaces, theme)
351
374
 
352
375
  functionalities = theme_test_data["functionalities"]
353
- assert set((functionalities[1].id, functionalities[4].id)) == set(f.id for f in theme.functionalities)
376
+ assert {functionalities[1].id} == {f.id for f in theme.functionalities}
354
377
  self.check_checkboxes(
355
378
  form,
356
379
  "functionalities",
357
380
  [
358
381
  {
359
- "label": "{}={}".format(f.name, f.value),
382
+ "label": f"{f.name}={f.value}",
360
383
  "value": str(f.id),
361
384
  "checked": f in theme.functionalities,
362
385
  }
363
- for f in sorted(functionalities, key=lambda f: (f.name, f.value))
386
+ for f in sorted(
387
+ [f for f in functionalities if f.name in ("default_basemap")],
388
+ key=lambda f: (f.name, f.value),
389
+ )
364
390
  ],
365
391
  )
366
392
 
@@ -381,23 +407,31 @@ class TestTheme(TestTreeGroup):
381
407
  duplicated = dbsession.query(Theme).filter(Theme.name == "duplicated").one()
382
408
 
383
409
  assert str(duplicated.id) == re.match(
384
- r"http://localhost{}/(.*)\?msg_col=submit_ok".format(self._prefix), resp.location
410
+ rf"http://localhost{self._prefix}/(.*)\?msg_col=submit_ok", resp.location
385
411
  ).group(1)
386
412
  assert duplicated.id != theme.id
387
413
  assert duplicated.children_relation[0].id != theme.children_relation[0].id
388
414
  assert duplicated.children_relation[0].treeitem.id == theme.children_relation[0].treeitem.id
389
415
 
390
416
  def test_delete(self, test_app, dbsession):
391
- from c2cgeoportal_commons.models.main import Theme
417
+ from c2cgeoportal_commons.models.main import Log, LogAction, Theme
418
+
419
+ theme = dbsession.query(Theme).first()
420
+ test_app.delete(f"/admin/themes/{theme.id}", status=200)
421
+ assert dbsession.query(Theme).get(theme.id) is None
392
422
 
393
- theme_id = dbsession.query(Theme.id).first().id
394
- test_app.delete("/admin/themes/{}".format(theme_id), status=200)
395
- assert dbsession.query(Theme).get(theme_id) is None
423
+ log = dbsession.query(Log).one()
424
+ assert log.date != None
425
+ assert log.action == LogAction.DELETE
426
+ assert log.element_type == "theme"
427
+ assert log.element_id == theme.id
428
+ assert log.element_name == theme.name
429
+ assert log.username == "test_user"
396
430
 
397
431
  def test_unicity_validator(self, theme_test_data, test_app):
398
432
  theme = theme_test_data["themes"][1]
399
- resp = test_app.get("{}/{}/duplicate".format(self._prefix, theme.id), status=200)
433
+ resp = test_app.get(f"{self._prefix}/{theme.id}/duplicate", status=200)
400
434
 
401
435
  resp = resp.form.submit("submit")
402
436
 
403
- self._check_submission_problem(resp, "{} is already used.".format(theme.name))
437
+ self._check_submission_problem(resp, f"{theme.name} is already used.")
@@ -14,7 +14,7 @@ def theme_test_data(dbsession, transact):
14
14
 
15
15
  themes = []
16
16
  for i in range(0, 3):
17
- theme = Theme(name="theme_{}".format(i), ordering=i, icon="icon_{}".format(i))
17
+ theme = Theme(name=f"theme_{i}", ordering=i, icon=f"icon_{i}")
18
18
 
19
19
  dbsession.add(theme)
20
20
  themes.append(theme)
@@ -28,7 +28,6 @@ def theme_test_data(dbsession, transact):
28
28
 
29
29
  @pytest.mark.usefixtures("theme_test_data", "test_app")
30
30
  class TestThemeOrdering(TestTreeGroup):
31
-
32
31
  _prefix = "/admin/layertree/ordering"
33
32
 
34
33
  def test_edit(self, test_app, theme_test_data, dbsession):
tests/test_treegroup.py CHANGED
@@ -5,10 +5,10 @@ from . import AbstractViewsTests
5
5
 
6
6
  class TestTreeGroup(AbstractViewsTests):
7
7
  def check_children(self, form, group, expected):
8
- form_group = form.html.select_one(".item-{}".format(group))
8
+ form_group = form.html.select_one(f".item-{group}")
9
9
  items = form_group.select(".deform-seq-item")
10
10
  assert len(expected) == len(items)
11
11
  for item, exp in zip(items, expected):
12
12
  assert exp["label"] == item.select_one(".well").getText().strip()
13
13
  for key, value in exp["values"].items():
14
- assert value == item.select_one('input[name="{}"]'.format(key))["value"]
14
+ assert value == item.select_one(f'input[name="{key}"]')["value"]
tests/test_user.py CHANGED
@@ -4,6 +4,7 @@ import email
4
4
  import re
5
5
  from unittest.mock import MagicMock, patch
6
6
 
7
+ import pyramid.httpexceptions
7
8
  import pytest
8
9
  from pyramid.testing import DummyRequest
9
10
 
@@ -20,13 +21,13 @@ def users_test_data(dbsession, transact):
20
21
 
21
22
  roles = []
22
23
  for i in range(0, 4):
23
- roles.append(Role("secretary_{}".format(i)))
24
+ roles.append(Role(f"secretary_{i}"))
24
25
  dbsession.add(roles[i])
25
26
  users = []
26
27
  for i in range(0, 23):
27
28
  user = User(
28
- "babar_{}".format(i),
29
- email="mail{}@valid.net".format(i),
29
+ f"babar_{i}",
30
+ email=f"mail{i}@valid.net",
30
31
  settings_role=roles[i % 4],
31
32
  roles=[roles[i % 4]],
32
33
  )
@@ -48,7 +49,6 @@ EXPECTED_WELCOME_MAIL = (
48
49
 
49
50
  @pytest.mark.usefixtures("users_test_data", "test_app")
50
51
  class TestUser(AbstractViewsTests):
51
-
52
52
  _prefix = "/admin/users"
53
53
 
54
54
  def test_index_rendering(self, test_app):
@@ -60,6 +60,7 @@ class TestUser(AbstractViewsTests):
60
60
  ("actions", "", "false"),
61
61
  ("id", "id", "true"),
62
62
  ("username", "Username"),
63
+ ("display_name", "Display name", "true"),
63
64
  ("email", "Email"),
64
65
  ("last_login", "Last login"),
65
66
  ("expire_on", "Expiration date"),
@@ -89,11 +90,14 @@ class TestUser(AbstractViewsTests):
89
90
  ]
90
91
  self.check_grid_headers(resp, expected, new="Nouveau")
91
92
 
92
- def test_view_edit(self, test_app, users_test_data):
93
+ def test_view_edit(self, test_app, users_test_data, dbsession):
94
+ from c2cgeoportal_commons.models.main import LogAction
95
+ from c2cgeoportal_commons.models.static import Log
96
+
93
97
  user = users_test_data["users"][9]
94
98
  roles = users_test_data["roles"]
95
99
 
96
- resp = test_app.get("/admin/users/{}".format(user.id), status=200)
100
+ resp = test_app.get(f"/admin/users/{user.id}", status=200)
97
101
 
98
102
  assert resp.form["username"].value == user.username
99
103
  assert resp.form["email"].value == user.email
@@ -120,17 +124,34 @@ class TestUser(AbstractViewsTests):
120
124
  assert value == getattr(user, key)
121
125
  else:
122
126
  assert str(value or "") == str(getattr(user, key) or "")
123
- assert set([roles[2].id, roles[3].id]) == set([role.id for role in user.roles])
127
+ assert {roles[2].id, roles[3].id} == {role.id for role in user.roles}
128
+
129
+ log = dbsession.query(Log).one()
130
+ assert log.date != None
131
+ assert log.action == LogAction.UPDATE
132
+ assert log.element_type == "user"
133
+ assert log.element_id == user.id
134
+ assert log.element_name == user.username
135
+ assert log.username == "test_user"
124
136
 
125
137
  def test_delete(self, test_app, users_test_data, dbsession):
126
- from c2cgeoportal_commons.models.static import User, user_role
138
+ from c2cgeoportal_commons.models.main import LogAction
139
+ from c2cgeoportal_commons.models.static import Log, User, user_role
127
140
 
128
141
  user = users_test_data["users"][9]
129
142
  deleted_id = user.id
130
- test_app.delete("/admin/users/{}".format(deleted_id), status=200)
143
+ test_app.delete(f"/admin/users/{deleted_id}", status=200)
131
144
  assert dbsession.query(User).get(deleted_id) is None
132
145
  assert dbsession.query(user_role).filter(user_role.c.user_id == user.id).count() == 0
133
146
 
147
+ log = dbsession.query(Log).one()
148
+ assert log.date != None
149
+ assert log.action == LogAction.DELETE
150
+ assert log.element_type == "user"
151
+ assert log.element_id == user.id
152
+ assert log.element_name == user.username
153
+ assert log.username == "test_user"
154
+
134
155
  @patch("c2cgeoportal_commons.lib.email_.smtplib.SMTP")
135
156
  @patch("c2cgeoportal_admin.views.users.pwgenerator.generate")
136
157
  def test_submit_update(self, pw_gen_mock, smtp_mock, dbsession, test_app, users_test_data):
@@ -138,7 +159,7 @@ class TestUser(AbstractViewsTests):
138
159
  roles = users_test_data["roles"]
139
160
 
140
161
  resp = test_app.post(
141
- "/admin/users/{}".format(user.id),
162
+ f"/admin/users/{user.id}",
142
163
  (
143
164
  ("__formid__", "deform"),
144
165
  ("_charset_", "UTF-8"),
@@ -146,6 +167,7 @@ class TestUser(AbstractViewsTests):
146
167
  ("item_type", "user"),
147
168
  ("id", user.id),
148
169
  ("username", "new_name_withéàô"),
170
+ ("display_name", "New name withéàô"),
149
171
  ("email", "new_mail@valid.net"),
150
172
  ("settings_role_id", roles[2].id),
151
173
  ("__start__", "roles:sequence"),
@@ -155,13 +177,13 @@ class TestUser(AbstractViewsTests):
155
177
  ),
156
178
  status=302,
157
179
  )
158
- assert resp.location == "http://localhost/admin/users/{}?msg_col=submit_ok".format(user.id)
180
+ assert resp.location == f"http://localhost/admin/users/{user.id}?msg_col=submit_ok"
159
181
 
160
182
  dbsession.expire(user)
161
183
  assert user.username == "new_name_withéàô"
162
184
  assert user.email == "new_mail@valid.net"
163
185
  assert user.settings_role.name == "secretary_2"
164
- assert set(r.id for r in user.roles) == set(roles[i].id for i in [0, 3])
186
+ assert {r.id for r in user.roles} == {roles[i].id for i in [0, 3]}
165
187
  assert user.validate_password("pré$ident")
166
188
 
167
189
  assert not pw_gen_mock.called, "method should not have been called"
@@ -172,7 +194,7 @@ class TestUser(AbstractViewsTests):
172
194
  roles = users_test_data["roles"]
173
195
 
174
196
  resp = test_app.post(
175
- "/admin/users/{}".format(user.id),
197
+ f"/admin/users/{user.id}",
176
198
  {
177
199
  "__formid__": "deform",
178
200
  "_charset_": "UTF-8",
@@ -199,7 +221,7 @@ class TestUser(AbstractViewsTests):
199
221
  user = users_test_data["users"][7]
200
222
  roles = users_test_data["roles"]
201
223
 
202
- resp = test_app.get("/admin/users/{}/duplicate".format(user.id), status=200)
224
+ resp = test_app.get(f"/admin/users/{user.id}/duplicate", status=200)
203
225
  form = resp.form
204
226
 
205
227
  assert "" == form["id"].value
@@ -219,7 +241,7 @@ class TestUser(AbstractViewsTests):
219
241
  ).group(1)
220
242
  assert user.id != new_user.id
221
243
  assert user.settings_role_id == new_user.settings_role_id
222
- assert set([role.id for role in user.roles]) == set([role.id for role in new_user.roles])
244
+ assert {role.id for role in user.roles} == {role.id for role in new_user.roles}
223
245
  assert not new_user.is_password_changed
224
246
  assert not new_user.validate_password("pré$ident")
225
247
 
@@ -233,10 +255,12 @@ class TestUser(AbstractViewsTests):
233
255
  @patch("c2cgeoportal_admin.views.users.pwgenerator.generate")
234
256
  @pytest.mark.usefixtures("test_app")
235
257
  def test_submit_new(self, pw_gen_mock, smtp_mock, dbsession, test_app, users_test_data):
258
+ from c2cgeoportal_commons.models.main import LogAction
259
+ from c2cgeoportal_commons.models.static import Log, User
260
+
236
261
  sender_mock = MagicMock()
237
262
  smtp_mock.return_value = sender_mock
238
263
  pw_gen_mock.return_value = "basile"
239
- from c2cgeoportal_commons.models.static import User
240
264
 
241
265
  roles = users_test_data["roles"]
242
266
 
@@ -249,6 +273,7 @@ class TestUser(AbstractViewsTests):
249
273
  ("item_type", "user"),
250
274
  ("id", ""),
251
275
  ("username", "new_user"),
276
+ ("display_name", "New user"),
252
277
  ("email", "valid@email.net"),
253
278
  ("settings_role_id", roles[2].id),
254
279
  ),
@@ -262,6 +287,7 @@ class TestUser(AbstractViewsTests):
262
287
  ).group(1)
263
288
 
264
289
  assert user.username == "new_user"
290
+ assert user.display_name == "New user"
265
291
  assert user.email == "valid@email.net"
266
292
  assert user.settings_role_id == roles[2].id
267
293
  assert user.password is not None and len(user.password)
@@ -275,6 +301,14 @@ class TestUser(AbstractViewsTests):
275
301
  ).decode("utf8")
276
302
  assert "valid@email.net" == parts[0].items()[3][1]
277
303
 
304
+ log = dbsession.query(Log).one()
305
+ assert log.date != None
306
+ assert log.action == LogAction.INSERT
307
+ assert log.element_type == "user"
308
+ assert log.element_id == user.id
309
+ assert log.element_name == user.username
310
+ assert log.username == "test_user"
311
+
278
312
  def test_invalid_email(self, test_app):
279
313
  resp = test_app.post(
280
314
  "/admin/users/new",
@@ -285,6 +319,7 @@ class TestUser(AbstractViewsTests):
285
319
  "item_type": "user",
286
320
  "id": "",
287
321
  "username": "invalid_email",
322
+ "display_name": "Invalid email",
288
323
  "email": "new_mail",
289
324
  "role_name": "secretary_2",
290
325
  "is_password_changed": "false",
@@ -300,11 +335,13 @@ class TestUser(AbstractViewsTests):
300
335
  from c2cgeoportal_admin.views.users import UserViews
301
336
 
302
337
  request = DummyRequest(dbsession=dbsession, params={"offset": 0, "limit": 10})
303
- info = UserViews(request).grid()
304
- assert info.status_int == 500, "Expected 500 status when db error"
338
+ with pytest.raises(pyramid.httpexceptions.HTTPInternalServerError):
339
+ UserViews(request).grid()
305
340
 
306
341
  def test_grid_settings_role_none(self, dbsession, test_app):
307
- """Grid view must work even if a user's settings_role is None"""
342
+ """
343
+ Grid view must work even if a user's settings_role is None.
344
+ """
308
345
  from c2cgeoportal_commons.models.static import User
309
346
 
310
347
  dbsession.add(User("test", email="test@valid.net"))
tests/themes_ordering.py CHANGED
@@ -14,7 +14,7 @@ def themes_ordering_test_data(dbsession, transact):
14
14
 
15
15
  themes = []
16
16
  for i in range(0, 25):
17
- theme = Theme(name="theme_{}".format(i), ordering=100)
17
+ theme = Theme(name=f"theme_{i}", ordering=100)
18
18
  dbsession.add(theme)
19
19
  themes.append(theme)
20
20
 
@@ -25,7 +25,6 @@ def themes_ordering_test_data(dbsession, transact):
25
25
 
26
26
  @pytest.mark.usefixtures("themes_ordering_test_data", "test_app")
27
27
  class TestThemesOrdering(TestTreeGroup):
28
-
29
28
  _prefix = "/admin/layertree/ordering"
30
29
 
31
30
  def test_edit(self, test_app, themes_ordering_test_data):
@@ -1,33 +0,0 @@
1
- {#
2
- # The MIT License (MIT)
3
- #
4
- # Copyright (c) Camptocamp SA
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy of
7
- # this software and associated documentation files (the "Software"), to deal in
8
- # the Software without restriction, including without limitation the rights to
9
- # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
- # the Software, and to permit persons to whom the Software is furnished to do so,
11
- # subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in all
14
- # copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
- # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
- # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
- # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #}
23
-
24
- <ul id="main-menu" class="nav nav-pills nav-stacked">
25
- <li role="presentation" class="{{'active' if request.matched_route.name == 'layertree' else ''}}">
26
- <a href="{{request.route_url('layertree')}}">{{_("Layer tree")}}</a>
27
- </li>
28
- {% for table in tables %}
29
- <li role="presentation" class="{{'active' if request.matchdict and request.matchdict.get('table') == table['key'] else ''}}">
30
- <a href="{{request.route_url('c2cgeoform_index', table=table['key'])}}">{{table['plural']}}</a>
31
- </li>
32
- {% endfor %}
33
- </ul>