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
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -31,15 +29,23 @@
|
|
31
29
|
from functools import partial
|
32
30
|
|
33
31
|
import colander
|
32
|
+
import sqlalchemy.orm.query
|
34
33
|
from c2cgeoform.schema import GeoFormManyToManySchemaNode, GeoFormSchemaNode
|
35
|
-
from c2cgeoform.views.abstract_views import
|
34
|
+
from c2cgeoform.views.abstract_views import (
|
35
|
+
DeleteResponse,
|
36
|
+
GridResponse,
|
37
|
+
IndexResponse,
|
38
|
+
ListField,
|
39
|
+
ObjectResponse,
|
40
|
+
SaveResponse,
|
41
|
+
)
|
36
42
|
from deform.widget import FormWidget
|
37
43
|
from pyramid.view import view_config, view_defaults
|
38
44
|
from sqlalchemy.orm import subqueryload
|
39
45
|
|
40
|
-
from c2cgeoportal_admin import _
|
41
46
|
from c2cgeoportal_admin.schemas.functionalities import functionalities_schema_node
|
42
47
|
from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node
|
48
|
+
from c2cgeoportal_admin.views.logged_views import LoggedViews
|
43
49
|
from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
|
44
50
|
from c2cgeoportal_commons.models.main import Role
|
45
51
|
from c2cgeoportal_commons.models.static import User
|
@@ -47,12 +53,13 @@ from c2cgeoportal_commons.models.static import User
|
|
47
53
|
_list_field = partial(ListField, Role)
|
48
54
|
|
49
55
|
base_schema = GeoFormSchemaNode(Role, widget=FormWidget(fields_template="role_fields"))
|
50
|
-
base_schema.add_before("extent", functionalities_schema_node.
|
51
|
-
base_schema.add_before("extent", restrictionareas_schema_node.
|
56
|
+
base_schema.add_before("extent", functionalities_schema_node(Role.functionalities, Role))
|
57
|
+
base_schema.add_before("extent", restrictionareas_schema_node(Role.restrictionareas))
|
52
58
|
base_schema.add_unique_validator(Role.name, Role.id)
|
53
59
|
|
54
60
|
|
55
61
|
def users(node, kw): # pylint: disable=unused-argument
|
62
|
+
"""Get the user serializable metadata."""
|
56
63
|
dbsession = kw["request"].dbsession
|
57
64
|
query = dbsession.query(User).order_by(User.username)
|
58
65
|
return [
|
@@ -75,7 +82,7 @@ base_schema.add(
|
|
75
82
|
colander.SequenceSchema(
|
76
83
|
GeoFormManyToManySchemaNode(
|
77
84
|
User,
|
78
|
-
name="
|
85
|
+
name="user",
|
79
86
|
includes=["id"],
|
80
87
|
widget=ChildWidget(
|
81
88
|
input_name="id",
|
@@ -89,23 +96,28 @@ base_schema.add(
|
|
89
96
|
),
|
90
97
|
),
|
91
98
|
),
|
92
|
-
name=
|
93
|
-
title=
|
99
|
+
name=Role.users.key,
|
100
|
+
title=Role.users.info["colanderalchemy"]["title"],
|
101
|
+
description=Role.users.info["colanderalchemy"]["description"],
|
94
102
|
candidates=colander.deferred(users),
|
95
103
|
widget=ChildrenWidget(child_input_name="id", orderable=False, category="structural"),
|
96
104
|
)
|
97
105
|
)
|
106
|
+
# Not possible to overwrite this in constructor.
|
107
|
+
base_schema["users"].children[0].description = ""
|
98
108
|
|
99
109
|
|
100
110
|
@view_defaults(match_param="table=roles")
|
101
|
-
class RoleViews(
|
111
|
+
class RoleViews(LoggedViews[Role]):
|
112
|
+
"""The roles administration view."""
|
113
|
+
|
102
114
|
_list_fields = [
|
103
115
|
_list_field("id"),
|
104
116
|
_list_field("name"),
|
105
117
|
_list_field("description"),
|
106
118
|
_list_field(
|
107
119
|
"functionalities",
|
108
|
-
renderer=lambda role: ", ".join(["{}={
|
120
|
+
renderer=lambda role: ", ".join([f"{f.name}={f.value}" for f in role.functionalities]),
|
109
121
|
),
|
110
122
|
_list_field(
|
111
123
|
"restrictionareas", renderer=lambda role: ", ".join([r.name or "" for r in role.restrictionareas])
|
@@ -115,35 +127,37 @@ class RoleViews(AbstractViews):
|
|
115
127
|
_model = Role
|
116
128
|
_base_schema = base_schema
|
117
129
|
|
118
|
-
def _base_query(self):
|
130
|
+
def _base_query(self) -> sqlalchemy.orm.query.Query[Role]:
|
131
|
+
session = self._request.dbsession
|
132
|
+
assert isinstance(session, sqlalchemy.orm.Session)
|
119
133
|
return (
|
120
|
-
|
121
|
-
.options(subqueryload(
|
122
|
-
.options(subqueryload(
|
134
|
+
session.query(Role)
|
135
|
+
.options(subqueryload(Role.functionalities))
|
136
|
+
.options(subqueryload(Role.restrictionareas))
|
123
137
|
)
|
124
138
|
|
125
|
-
@view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
|
126
|
-
def index(self):
|
139
|
+
@view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
|
140
|
+
def index(self) -> IndexResponse:
|
127
141
|
return super().index()
|
128
142
|
|
129
|
-
@view_config(route_name="c2cgeoform_grid", renderer="fast_json")
|
130
|
-
def grid(self):
|
143
|
+
@view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
|
144
|
+
def grid(self) -> GridResponse:
|
131
145
|
return super().grid()
|
132
146
|
|
133
|
-
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
|
134
|
-
def view(self):
|
147
|
+
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
148
|
+
def view(self) -> ObjectResponse:
|
135
149
|
return super().edit()
|
136
150
|
|
137
|
-
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
|
138
|
-
def save(self):
|
151
|
+
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
152
|
+
def save(self) -> SaveResponse:
|
139
153
|
return super().save()
|
140
154
|
|
141
|
-
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
|
142
|
-
def delete(self):
|
155
|
+
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
|
156
|
+
def delete(self) -> DeleteResponse:
|
143
157
|
return super().delete()
|
144
158
|
|
145
|
-
@view_config(
|
159
|
+
@view_config( # type: ignore[misc]
|
146
160
|
route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
|
147
161
|
)
|
148
|
-
def duplicate(self):
|
162
|
+
def duplicate(self) -> ObjectResponse:
|
149
163
|
return super().duplicate()
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -29,9 +27,18 @@
|
|
29
27
|
|
30
28
|
|
31
29
|
from functools import partial
|
30
|
+
from typing import cast
|
32
31
|
|
32
|
+
import sqlalchemy
|
33
33
|
from c2cgeoform.schema import GeoFormSchemaNode
|
34
|
-
from c2cgeoform.views.abstract_views import
|
34
|
+
from c2cgeoform.views.abstract_views import (
|
35
|
+
DeleteResponse,
|
36
|
+
GridResponse,
|
37
|
+
IndexResponse,
|
38
|
+
ListField,
|
39
|
+
ObjectResponse,
|
40
|
+
SaveResponse,
|
41
|
+
)
|
35
42
|
from deform.widget import FormWidget
|
36
43
|
from pyramid.view import view_config, view_defaults
|
37
44
|
from sqlalchemy.orm import subqueryload
|
@@ -39,7 +46,7 @@ from sqlalchemy.sql.functions import concat
|
|
39
46
|
|
40
47
|
from c2cgeoportal_admin.schemas.functionalities import functionalities_schema_node
|
41
48
|
from c2cgeoportal_admin.schemas.interfaces import interfaces_schema_node
|
42
|
-
from c2cgeoportal_admin.schemas.metadata import
|
49
|
+
from c2cgeoportal_admin.schemas.metadata import metadata_schema_node
|
43
50
|
from c2cgeoportal_admin.schemas.roles import roles_schema_node
|
44
51
|
from c2cgeoportal_admin.schemas.treegroup import children_schema_node
|
45
52
|
from c2cgeoportal_admin.views.treeitems import TreeItemViews
|
@@ -49,18 +56,19 @@ _list_field = partial(ListField, Theme)
|
|
49
56
|
|
50
57
|
base_schema = GeoFormSchemaNode(Theme, widget=FormWidget(fields_template="theme_fields"))
|
51
58
|
base_schema.add(children_schema_node(only_groups=True))
|
52
|
-
base_schema.add(functionalities_schema_node.
|
53
|
-
base_schema.add(roles_schema_node(
|
54
|
-
base_schema.add(interfaces_schema_node.
|
55
|
-
base_schema.add(
|
59
|
+
base_schema.add(functionalities_schema_node(Theme.functionalities, Theme))
|
60
|
+
base_schema.add(roles_schema_node(Theme.restricted_roles))
|
61
|
+
base_schema.add(interfaces_schema_node(Theme.interfaces))
|
62
|
+
base_schema.add(metadata_schema_node(Theme.metadatas, Theme))
|
56
63
|
base_schema.add_unique_validator(Theme.name, Theme.id)
|
57
64
|
|
58
65
|
|
59
66
|
@view_defaults(match_param="table=themes")
|
60
|
-
class ThemeViews(TreeItemViews):
|
67
|
+
class ThemeViews(TreeItemViews[Theme]):
|
68
|
+
"""The theme administration view."""
|
61
69
|
|
62
70
|
_list_fields = (
|
63
|
-
TreeItemViews._list_fields
|
71
|
+
TreeItemViews._list_fields # type: ignore[misc] # pylint: disable=protected-access
|
64
72
|
+ [
|
65
73
|
_list_field("ordering"),
|
66
74
|
_list_field("public"),
|
@@ -69,8 +77,8 @@ class ThemeViews(TreeItemViews):
|
|
69
77
|
"functionalities",
|
70
78
|
renderer=lambda themes: ", ".join(
|
71
79
|
[
|
72
|
-
"{}={
|
73
|
-
for f in sorted(themes.functionalities, key=lambda f: f.name)
|
80
|
+
f"{f.name}={f.value}"
|
81
|
+
for f in sorted(themes.functionalities, key=lambda f: cast(str, f.name))
|
74
82
|
]
|
75
83
|
),
|
76
84
|
filter_column=concat(Functionality.name, "=", Functionality.value),
|
@@ -83,52 +91,56 @@ class ThemeViews(TreeItemViews):
|
|
83
91
|
_list_field(
|
84
92
|
"interfaces",
|
85
93
|
renderer=lambda themes: ", ".join(
|
86
|
-
[i.name or "" for i in sorted(themes.interfaces, key=lambda i: i.name)]
|
94
|
+
[i.name or "" for i in sorted(themes.interfaces, key=lambda i: cast(str, i.name))]
|
87
95
|
),
|
88
96
|
filter_column=Interface.name,
|
89
97
|
),
|
90
98
|
]
|
91
|
-
+ TreeItemViews._extra_list_fields_no_parents
|
99
|
+
+ TreeItemViews._extra_list_fields_no_parents # pylint: disable=protected-access
|
92
100
|
)
|
93
101
|
|
94
102
|
_id_field = "id"
|
95
103
|
_model = Theme
|
96
104
|
_base_schema = base_schema
|
97
105
|
|
98
|
-
def _base_query(self
|
99
|
-
return super().
|
106
|
+
def _base_query(self) -> sqlalchemy.orm.query.Query[Theme]:
|
107
|
+
return super()._sub_query(
|
100
108
|
self._request.dbsession.query(Theme)
|
101
109
|
.distinct()
|
102
|
-
.outerjoin(
|
103
|
-
.outerjoin(
|
104
|
-
.outerjoin(
|
105
|
-
.options(subqueryload(
|
106
|
-
.options(subqueryload(
|
107
|
-
.options(subqueryload(
|
110
|
+
.outerjoin(Theme.interfaces)
|
111
|
+
.outerjoin(Theme.restricted_roles)
|
112
|
+
.outerjoin(Theme.functionalities)
|
113
|
+
.options(subqueryload(Theme.functionalities))
|
114
|
+
.options(subqueryload(Theme.restricted_roles))
|
115
|
+
.options(subqueryload(Theme.interfaces))
|
108
116
|
)
|
109
117
|
|
110
|
-
|
111
|
-
|
118
|
+
def _sub_query(self, query: sqlalchemy.orm.query.Query[Theme]) -> sqlalchemy.orm.query.Query[Theme]:
|
119
|
+
del query
|
120
|
+
return self._base_query()
|
121
|
+
|
122
|
+
@view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
|
123
|
+
def index(self) -> IndexResponse:
|
112
124
|
return super().index()
|
113
125
|
|
114
|
-
@view_config(route_name="c2cgeoform_grid", renderer="fast_json")
|
115
|
-
def grid(self):
|
126
|
+
@view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
|
127
|
+
def grid(self) -> GridResponse:
|
116
128
|
return super().grid()
|
117
129
|
|
118
|
-
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
|
119
|
-
def view(self):
|
130
|
+
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
131
|
+
def view(self) -> ObjectResponse:
|
120
132
|
return super().edit()
|
121
133
|
|
122
|
-
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
|
123
|
-
def save(self):
|
134
|
+
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
135
|
+
def save(self) -> SaveResponse:
|
124
136
|
return super().save()
|
125
137
|
|
126
|
-
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
|
127
|
-
def delete(self):
|
138
|
+
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
|
139
|
+
def delete(self) -> DeleteResponse:
|
128
140
|
return super().delete()
|
129
141
|
|
130
|
-
@view_config(
|
142
|
+
@view_config( # type: ignore[misc]
|
131
143
|
route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
|
132
144
|
)
|
133
|
-
def duplicate(self):
|
145
|
+
def duplicate(self) -> ObjectResponse:
|
134
146
|
return super().duplicate()
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -30,7 +28,7 @@
|
|
30
28
|
|
31
29
|
import colander
|
32
30
|
from c2cgeoform.schema import GeoFormSchemaNode
|
33
|
-
from c2cgeoform.views.abstract_views import AbstractViews
|
31
|
+
from c2cgeoform.views.abstract_views import AbstractViews, ObjectResponse, SaveResponse
|
34
32
|
from deform import ValidationFailure
|
35
33
|
from pyramid.httpexceptions import HTTPFound
|
36
34
|
from pyramid.view import view_config
|
@@ -42,6 +40,8 @@ from c2cgeoportal_commons.models.main import Theme, TreeItem
|
|
42
40
|
|
43
41
|
|
44
42
|
class ThemeOrderSchema(GeoFormSchemaNode): # pylint: disable=abstract-method
|
43
|
+
"""The theme order schema."""
|
44
|
+
|
45
45
|
def objectify(self, dict_, context=None):
|
46
46
|
context = self.dbsession.query(Theme).get(dict_["id"])
|
47
47
|
context = super().objectify(dict_, context)
|
@@ -50,14 +50,16 @@ class ThemeOrderSchema(GeoFormSchemaNode): # pylint: disable=abstract-method
|
|
50
50
|
|
51
51
|
@colander.deferred
|
52
52
|
def themes(node, kw): # pylint: disable=unused-argument
|
53
|
+
"""Get some theme metadata."""
|
53
54
|
query = kw["dbsession"].query(Theme).order_by(Theme.ordering, Theme.name)
|
54
55
|
return [
|
55
|
-
{"id": item.id, "label": item.name, "icon_class": "icon-{
|
56
|
+
{"id": item.id, "label": item.name, "icon_class": f"icon-{item.item_type}", "group": "All"}
|
56
57
|
for item in query
|
57
58
|
]
|
58
59
|
|
59
60
|
|
60
61
|
def themes_validator(node, cstruct):
|
62
|
+
"""Validate the theme."""
|
61
63
|
for dict_ in cstruct:
|
62
64
|
if not dict_["id"] in [item["id"] for item in node.candidates]:
|
63
65
|
raise colander.Invalid(
|
@@ -66,7 +68,9 @@ def themes_validator(node, cstruct):
|
|
66
68
|
)
|
67
69
|
|
68
70
|
|
69
|
-
class ThemesOrderingSchema(colander.MappingSchema):
|
71
|
+
class ThemesOrderingSchema(colander.MappingSchema): # type: ignore[misc]
|
72
|
+
"""The theme ordering schema."""
|
73
|
+
|
70
74
|
themes = colander.SequenceSchema(
|
71
75
|
ThemeOrderSchema(
|
72
76
|
Theme,
|
@@ -76,7 +80,7 @@ class ThemesOrderingSchema(colander.MappingSchema):
|
|
76
80
|
input_name="id",
|
77
81
|
model=TreeItem,
|
78
82
|
label_field="name",
|
79
|
-
icon_class=lambda item: "icon-{
|
83
|
+
icon_class=lambda item: f"icon-{item.item_type}",
|
80
84
|
edit_url=treeitem_edit_url,
|
81
85
|
),
|
82
86
|
),
|
@@ -87,12 +91,13 @@ class ThemesOrderingSchema(colander.MappingSchema):
|
|
87
91
|
)
|
88
92
|
|
89
93
|
|
90
|
-
class ThemesOrdering(AbstractViews):
|
94
|
+
class ThemesOrdering(AbstractViews[ThemesOrderingSchema]):
|
95
|
+
"""The theme ordering admin view."""
|
91
96
|
|
92
97
|
_base_schema = ThemesOrderingSchema()
|
93
98
|
|
94
|
-
@view_config(route_name="layertree_ordering", request_method="GET", renderer="../templates/edit.jinja2")
|
95
|
-
def view(self):
|
99
|
+
@view_config(route_name="layertree_ordering", request_method="GET", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
100
|
+
def view(self) -> ObjectResponse:
|
96
101
|
form = self._form()
|
97
102
|
dict_ = {
|
98
103
|
"themes": [
|
@@ -103,13 +108,13 @@ class ThemesOrdering(AbstractViews):
|
|
103
108
|
return {
|
104
109
|
"title": form.title,
|
105
110
|
"form": form,
|
106
|
-
"form_render_args":
|
111
|
+
"form_render_args": [dict_],
|
107
112
|
"form_render_kwargs": {"request": self._request, "actions": []},
|
108
113
|
"deform_dependencies": form.get_widget_resources(),
|
109
114
|
}
|
110
115
|
|
111
|
-
@view_config(route_name="layertree_ordering", request_method="POST", renderer="../templates/edit.jinja2")
|
112
|
-
def save(self):
|
116
|
+
@view_config(route_name="layertree_ordering", request_method="POST", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
117
|
+
def save(self) -> SaveResponse:
|
113
118
|
try:
|
114
119
|
form = self._form()
|
115
120
|
form_data = self._request.POST.items()
|
@@ -126,7 +131,7 @@ class ThemesOrdering(AbstractViews):
|
|
126
131
|
return {
|
127
132
|
"title": form.title,
|
128
133
|
"form": e,
|
129
|
-
"form_render_args":
|
134
|
+
"form_render_args": [],
|
130
135
|
"form_render_kwargs": {"request": self._request, "actions": []},
|
131
136
|
"deform_dependencies": form.get_widget_resources(),
|
132
137
|
}
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -29,18 +27,26 @@
|
|
29
27
|
|
30
28
|
|
31
29
|
from functools import partial
|
30
|
+
from typing import Generic, TypeVar
|
32
31
|
|
33
|
-
|
32
|
+
import sqlalchemy
|
33
|
+
from c2cgeoform.views.abstract_views import ListField, SaveResponse
|
34
34
|
from pyramid.view import view_config
|
35
35
|
from sqlalchemy.orm import subqueryload
|
36
36
|
from sqlalchemy.sql.functions import concat
|
37
37
|
|
38
|
+
from c2cgeoportal_admin.views.logged_views import LoggedViews
|
38
39
|
from c2cgeoportal_commons.models.main import LayergroupTreeitem, Metadata, TreeGroup, TreeItem
|
39
40
|
|
40
41
|
_list_field = partial(ListField, TreeItem)
|
41
42
|
|
42
43
|
|
43
|
-
|
44
|
+
_T = TypeVar("_T", bound=TreeItem)
|
45
|
+
|
46
|
+
|
47
|
+
class TreeItemViews(LoggedViews[_T], Generic[_T]):
|
48
|
+
"""The admin tree item view."""
|
49
|
+
|
44
50
|
_list_fields = [
|
45
51
|
_list_field("id"),
|
46
52
|
_list_field("name"),
|
@@ -50,9 +56,7 @@ class TreeItemViews(AbstractViews):
|
|
50
56
|
_extra_list_fields_no_parents = [
|
51
57
|
_list_field(
|
52
58
|
"metadatas",
|
53
|
-
renderer=lambda
|
54
|
-
["{}: {}".format(m.name, m.value) or "" for m in layers_group.metadatas]
|
55
|
-
),
|
59
|
+
renderer=lambda treeitem: ", ".join([f"{m.name}: {m.value}" or "" for m in treeitem.metadatas]),
|
56
60
|
filter_column=concat(Metadata.name, ": ", Metadata.value).label("metadata"),
|
57
61
|
)
|
58
62
|
]
|
@@ -68,22 +72,22 @@ class TreeItemViews(AbstractViews):
|
|
68
72
|
)
|
69
73
|
] + _extra_list_fields_no_parents
|
70
74
|
|
71
|
-
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
|
72
|
-
def save(self):
|
75
|
+
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
76
|
+
def save(self) -> SaveResponse:
|
73
77
|
response = super().save()
|
74
78
|
# correctly handles the validation error as if there is a validation error, cstruct is empty
|
75
|
-
|
79
|
+
has_to_be_registered_in_parent = (
|
76
80
|
hasattr(self, "_appstruct") and self._appstruct is not None and self._appstruct.get("parent_id")
|
77
81
|
)
|
78
|
-
if
|
79
|
-
parent = self._request.dbsession.query(TreeGroup).get(
|
82
|
+
if has_to_be_registered_in_parent:
|
83
|
+
parent = self._request.dbsession.query(TreeGroup).get(has_to_be_registered_in_parent)
|
80
84
|
rel = LayergroupTreeitem(parent, self._obj, 100)
|
81
85
|
self._request.dbsession.add(rel)
|
82
86
|
return response
|
83
87
|
|
84
|
-
def
|
88
|
+
def _sub_query(self, query: sqlalchemy.orm.query.Query[TreeItem]) -> sqlalchemy.orm.query.Query[TreeItem]:
|
85
89
|
return (
|
86
|
-
query.outerjoin(
|
87
|
-
.options(subqueryload(
|
88
|
-
.options(subqueryload(
|
90
|
+
query.outerjoin(TreeItem.metadatas)
|
91
|
+
.options(subqueryload(TreeItem.parents_relation).joinedload(LayergroupTreeitem.treegroup))
|
92
|
+
.options(subqueryload(TreeItem.metadatas))
|
89
93
|
)
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2017-2020, Camptocamp SA
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -28,10 +26,18 @@
|
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
29
27
|
|
30
28
|
|
29
|
+
import os
|
31
30
|
from functools import partial
|
32
31
|
|
33
32
|
from c2cgeoform.schema import GeoFormSchemaNode
|
34
|
-
from c2cgeoform.views.abstract_views import
|
33
|
+
from c2cgeoform.views.abstract_views import (
|
34
|
+
DeleteResponse,
|
35
|
+
GridResponse,
|
36
|
+
IndexResponse,
|
37
|
+
ListField,
|
38
|
+
ObjectResponse,
|
39
|
+
SaveResponse,
|
40
|
+
)
|
35
41
|
from deform.widget import FormWidget
|
36
42
|
from passwordgenerator import pwgenerator
|
37
43
|
from pyramid.httpexceptions import HTTPFound
|
@@ -39,28 +45,36 @@ from pyramid.view import view_config, view_defaults
|
|
39
45
|
from sqlalchemy.orm import aliased, subqueryload
|
40
46
|
|
41
47
|
from c2cgeoportal_admin.schemas.roles import roles_schema_node
|
48
|
+
from c2cgeoportal_admin.views.logged_views import LoggedViews
|
42
49
|
from c2cgeoportal_commons.lib.email_ import send_email_config
|
43
50
|
from c2cgeoportal_commons.models.main import Role
|
44
|
-
from c2cgeoportal_commons.models.static import User
|
51
|
+
from c2cgeoportal_commons.models.static import Log, User
|
45
52
|
|
46
53
|
_list_field = partial(ListField, User)
|
47
54
|
|
48
55
|
base_schema = GeoFormSchemaNode(User, widget=FormWidget(fields_template="user_fields"))
|
49
|
-
base_schema.add(roles_schema_node(
|
56
|
+
base_schema.add(roles_schema_node(User.roles))
|
50
57
|
base_schema.add_unique_validator(User.username, User.id)
|
51
58
|
|
52
59
|
settings_role = aliased(Role)
|
53
60
|
|
61
|
+
_OPENID_CONNECT_ENABLED = os.environ.get("OPENID_CONNECT_ENABLED", "false").lower() in ("true", "yes", "1")
|
62
|
+
|
54
63
|
|
55
64
|
@view_defaults(match_param="table=users")
|
56
|
-
class UserViews(
|
65
|
+
class UserViews(LoggedViews[User]):
|
66
|
+
"""The admin user view."""
|
67
|
+
|
57
68
|
_list_fields = [
|
58
69
|
_list_field("id"),
|
59
|
-
_list_field("username"),
|
70
|
+
*([_list_field("username")] if not _OPENID_CONNECT_ENABLED else []),
|
71
|
+
_list_field("display_name"),
|
60
72
|
_list_field("email"),
|
61
|
-
|
62
|
-
|
63
|
-
|
73
|
+
*(
|
74
|
+
[_list_field("last_login"), _list_field("expire_on"), _list_field("deactivated")]
|
75
|
+
if not _OPENID_CONNECT_ENABLED
|
76
|
+
else []
|
77
|
+
),
|
64
78
|
_list_field(
|
65
79
|
"settings_role",
|
66
80
|
renderer=lambda user: user.settings_role.name if user.settings_role else "",
|
@@ -76,31 +90,33 @@ class UserViews(AbstractViews):
|
|
76
90
|
_id_field = "id"
|
77
91
|
_model = User
|
78
92
|
_base_schema = base_schema
|
93
|
+
_log_model = Log
|
94
|
+
_name_field = "username"
|
79
95
|
|
80
96
|
def _base_query(self):
|
81
97
|
return (
|
82
98
|
self._request.dbsession.query(User)
|
83
99
|
.distinct()
|
84
100
|
.outerjoin(settings_role, settings_role.id == User.settings_role_id)
|
85
|
-
.outerjoin(
|
86
|
-
.options(subqueryload(
|
87
|
-
.options(subqueryload(
|
101
|
+
.outerjoin(User.roles)
|
102
|
+
.options(subqueryload(User.settings_role))
|
103
|
+
.options(subqueryload(User.roles))
|
88
104
|
)
|
89
105
|
|
90
|
-
@view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
|
91
|
-
def index(self):
|
106
|
+
@view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
|
107
|
+
def index(self) -> IndexResponse:
|
92
108
|
return super().index()
|
93
109
|
|
94
|
-
@view_config(route_name="c2cgeoform_grid", renderer="fast_json")
|
95
|
-
def grid(self):
|
110
|
+
@view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
|
111
|
+
def grid(self) -> GridResponse:
|
96
112
|
return super().grid()
|
97
113
|
|
98
|
-
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
|
99
|
-
def view(self):
|
114
|
+
@view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
115
|
+
def view(self) -> ObjectResponse:
|
100
116
|
return super().edit()
|
101
117
|
|
102
|
-
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
|
103
|
-
def save(self):
|
118
|
+
@view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2") # type: ignore[misc]
|
119
|
+
def save(self) -> SaveResponse:
|
104
120
|
if self._is_new():
|
105
121
|
response = super().save()
|
106
122
|
|
@@ -108,9 +124,11 @@ class UserViews(AbstractViews):
|
|
108
124
|
password = pwgenerator.generate()
|
109
125
|
|
110
126
|
user = self._obj
|
127
|
+
assert user is not None
|
111
128
|
user.password = password
|
112
129
|
user.is_password_changed = False
|
113
130
|
user = self._request.dbsession.merge(user)
|
131
|
+
assert user is not None
|
114
132
|
self._request.dbsession.flush()
|
115
133
|
|
116
134
|
send_email_config(
|
@@ -119,18 +137,20 @@ class UserViews(AbstractViews):
|
|
119
137
|
email=user.email,
|
120
138
|
user=user.username,
|
121
139
|
password=password,
|
140
|
+
application_url=self._request.route_url("base"),
|
141
|
+
current_url=self._request.current_route_url(),
|
122
142
|
)
|
123
143
|
|
124
144
|
return response
|
125
145
|
|
126
146
|
return super().save()
|
127
147
|
|
128
|
-
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
|
129
|
-
def delete(self):
|
148
|
+
@view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
|
149
|
+
def delete(self) -> DeleteResponse:
|
130
150
|
return super().delete()
|
131
151
|
|
132
|
-
@view_config(
|
152
|
+
@view_config( # type: ignore[misc]
|
133
153
|
route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
|
134
154
|
)
|
135
|
-
def duplicate(self):
|
155
|
+
def duplicate(self) -> ObjectResponse:
|
136
156
|
return super().duplicate()
|