c2cgeoportal-admin 2.6.0__py3-none-any.whl → 2.9rc45__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.
- c2cgeoportal_admin/__init__.py +42 -12
- c2cgeoportal_admin/lib/lingva_extractor.py +77 -0
- c2cgeoportal_admin/lib/ogcserver_synchronizer.py +170 -57
- c2cgeoportal_admin/py.typed +0 -0
- c2cgeoportal_admin/routes.py +18 -6
- c2cgeoportal_admin/schemas/dimensions.py +16 -10
- c2cgeoportal_admin/schemas/functionalities.py +59 -21
- c2cgeoportal_admin/schemas/interfaces.py +26 -18
- c2cgeoportal_admin/schemas/metadata.py +101 -48
- c2cgeoportal_admin/schemas/restriction_areas.py +25 -19
- c2cgeoportal_admin/schemas/roles.py +12 -6
- c2cgeoportal_admin/schemas/treegroup.py +46 -21
- c2cgeoportal_admin/schemas/treeitem.py +3 -4
- c2cgeoportal_admin/static/layertree.css +3 -4
- c2cgeoportal_admin/static/navbar.css +36 -35
- c2cgeoportal_admin/static/theme.css +19 -9
- c2cgeoportal_admin/subscribers.py +3 -3
- c2cgeoportal_admin/templates/404.jinja2 +18 -2
- c2cgeoportal_admin/templates/layertree.jinja2 +31 -9
- c2cgeoportal_admin/templates/navigation_navbar.jinja2 +33 -0
- c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +12 -0
- c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
- c2cgeoportal_admin/templates/widgets/metadata.pt +7 -1
- c2cgeoportal_admin/views/__init__.py +29 -0
- c2cgeoportal_admin/views/dimension_layers.py +14 -9
- c2cgeoportal_admin/views/functionalities.py +52 -18
- c2cgeoportal_admin/views/home.py +5 -5
- c2cgeoportal_admin/views/interfaces.py +26 -20
- c2cgeoportal_admin/views/layer_groups.py +36 -25
- c2cgeoportal_admin/views/layers.py +17 -13
- c2cgeoportal_admin/views/layers_cog.py +135 -0
- c2cgeoportal_admin/views/layers_vectortiles.py +62 -27
- c2cgeoportal_admin/views/layers_wms.py +55 -34
- c2cgeoportal_admin/views/layers_wmts.py +54 -34
- c2cgeoportal_admin/views/layertree.py +38 -29
- c2cgeoportal_admin/views/logged_views.py +83 -0
- c2cgeoportal_admin/views/logs.py +91 -0
- c2cgeoportal_admin/views/oauth2_clients.py +30 -18
- c2cgeoportal_admin/views/ogc_servers.py +132 -36
- c2cgeoportal_admin/views/restriction_areas.py +39 -27
- c2cgeoportal_admin/views/roles.py +42 -28
- c2cgeoportal_admin/views/themes.py +47 -35
- c2cgeoportal_admin/views/themes_ordering.py +19 -14
- c2cgeoportal_admin/views/treeitems.py +21 -17
- c2cgeoportal_admin/views/users.py +46 -26
- c2cgeoportal_admin/widgets.py +17 -14
- {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc45.dist-info}/METADATA +12 -12
- c2cgeoportal_admin-2.9rc45.dist-info/RECORD +97 -0
- {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc45.dist-info}/WHEEL +1 -1
- c2cgeoportal_admin-2.9rc45.dist-info/entry_points.txt +5 -0
- tests/__init__.py +24 -20
- tests/conftest.py +22 -11
- tests/test_edit_url.py +11 -14
- tests/test_functionalities.py +52 -14
- tests/test_home.py +0 -1
- tests/test_interface.py +34 -11
- tests/test_layer_groups.py +57 -27
- tests/test_layers_cog.py +243 -0
- tests/test_layers_vectortiles.py +43 -25
- tests/test_layers_wms.py +67 -45
- tests/test_layers_wmts.py +47 -26
- tests/test_layertree.py +99 -16
- tests/test_left_menu.py +0 -1
- tests/test_lingva_extractor_config.py +64 -0
- tests/test_logs.py +102 -0
- tests/test_main.py +3 -1
- tests/test_metadatas.py +34 -21
- tests/test_oauth2_clients.py +40 -11
- tests/test_ogc_servers.py +84 -35
- tests/test_restriction_areas.py +38 -15
- tests/test_role.py +71 -43
- tests/test_themes.py +71 -37
- tests/test_themes_ordering.py +1 -2
- tests/test_treegroup.py +2 -2
- tests/test_user.py +56 -19
- tests/themes_ordering.py +1 -2
- c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -33
- c2cgeoportal_admin-2.6.0.dist-info/RECORD +0 -89
- c2cgeoportal_admin-2.6.0.dist-info/entry_points.txt +0 -3
- {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.9rc45.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_{}"
|
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_{}"
|
38
|
-
for name in ("default_basemap", "
|
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_{}"
|
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 %
|
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
|
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/{
|
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/{
|
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/{
|
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
|
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
|
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": "{}={
|
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(
|
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
|
224
|
-
|
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"
|
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"
|
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"
|
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"
|
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"
|
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
|
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
|
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": "{}={
|
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(
|
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
|
-
|
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
|
-
|
394
|
-
|
395
|
-
assert
|
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"
|
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."
|
437
|
+
self._check_submission_problem(resp, f"{theme.name} is already used.")
|
tests/test_themes_ordering.py
CHANGED
@@ -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_{}"
|
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-{}"
|
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="{}"]'
|
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_{}"
|
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_{}"
|
29
|
-
email="mail{}@valid.net"
|
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/{
|
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
|
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.
|
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/{}"
|
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/{
|
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"
|
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
|
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/{
|
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"
|
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
|
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
|
-
|
304
|
-
|
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
|
-
"""
|
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_{}"
|
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>
|