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
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
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 AbstractViews, ListField
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.clone())
51
- base_schema.add_before("extent", restrictionareas_schema_node.clone())
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="layer",
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="users",
93
- title=_("Users"),
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(AbstractViews):
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(["{}={}".format(f.name, f.value) for f in role.functionalities]),
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
- self._request.dbsession.query(Role)
121
- .options(subqueryload("functionalities"))
122
- .options(subqueryload("restrictionareas"))
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
- # -*- coding: utf-8 -*-
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 ListField
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 metadatas_schema_node
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.clone())
53
- base_schema.add(roles_schema_node("restricted_roles"))
54
- base_schema.add(interfaces_schema_node.clone())
55
- base_schema.add(metadatas_schema_node.clone())
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
- "{}={}".format(f.name, f.value)
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, query=None):
99
- return super()._base_query(
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("interfaces")
103
- .outerjoin("restricted_roles")
104
- .outerjoin("functionalities")
105
- .options(subqueryload("functionalities"))
106
- .options(subqueryload("restricted_roles"))
107
- .options(subqueryload("interfaces"))
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
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
111
- def index(self):
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
- # -*- coding: utf-8 -*-
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-{}".format(item.item_type), "group": "All"}
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-{}".format(item.item_type),
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": (dict_,),
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": tuple(),
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
- # -*- coding: utf-8 -*-
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
- from c2cgeoform.views.abstract_views import AbstractViews, ListField
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
- class TreeItemViews(AbstractViews):
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 layers_group: ", ".join(
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
- has_to_be_registred_in_parent = (
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 has_to_be_registred_in_parent:
79
- parent = self._request.dbsession.query(TreeGroup).get(has_to_be_registred_in_parent)
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 _base_query(self, query): # pylint: disable=arguments-differ
88
+ def _sub_query(self, query: sqlalchemy.orm.query.Query[TreeItem]) -> sqlalchemy.orm.query.Query[TreeItem]:
85
89
  return (
86
- query.outerjoin("metadatas")
87
- .options(subqueryload("parents_relation").joinedload("treegroup"))
88
- .options(subqueryload("metadatas"))
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
- # -*- coding: utf-8 -*-
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 AbstractViews, ListField
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("roles"))
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(AbstractViews):
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
- _list_field("last_login"),
62
- _list_field("expire_on"),
63
- _list_field("deactivated"),
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("roles")
86
- .options(subqueryload("settings_role"))
87
- .options(subqueryload("roles"))
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()