c2cgeoportal-commons 2.6.0__py3-none-any.whl → 2.8.1.180__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.

Potentially problematic release.


This version of c2cgeoportal-commons might be problematic. Click here for more details.

Files changed (82) hide show
  1. c2cgeoportal_commons/__init__.py +2 -5
  2. c2cgeoportal_commons/alembic/env.py +40 -28
  3. c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +10 -7
  4. c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +60 -0
  5. c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +42 -43
  6. c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +7 -8
  7. c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +9 -6
  8. c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +21 -24
  9. c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +9 -6
  10. c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +14 -8
  11. c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +9 -6
  12. c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +9 -6
  13. c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +21 -20
  14. c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +9 -6
  15. c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +7 -8
  16. c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +9 -6
  17. c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +15 -12
  18. c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +7 -8
  19. c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +25 -24
  20. c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +45 -43
  21. c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +70 -0
  22. c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +10 -9
  23. c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +58 -0
  24. c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +9 -6
  25. c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +17 -14
  26. c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +10 -8
  27. c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +13 -14
  28. c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +9 -6
  29. c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +9 -6
  30. c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +13 -20
  31. c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +21 -20
  32. c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +27 -48
  33. c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +9 -6
  34. c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +9 -6
  35. c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +9 -6
  36. c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +9 -6
  37. c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +11 -8
  38. c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +7 -8
  39. c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +9 -6
  40. c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +9 -6
  41. c2cgeoportal_commons/alembic/main/a4f1aac9bda_merge_1_6_and_master_branches.py +7 -8
  42. c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +9 -6
  43. c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +9 -6
  44. c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +13 -14
  45. c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +15 -12
  46. c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +7 -8
  47. c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +7 -8
  48. c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +15 -32
  49. c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +13 -14
  50. c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +9 -6
  51. c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +13 -10
  52. c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +11 -14
  53. c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +7 -8
  54. c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +9 -6
  55. c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +9 -6
  56. c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +7 -6
  57. c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +18 -19
  58. c2cgeoportal_commons/alembic/static/3f89a7d71a5e_alter_column_url_to_remove_limitation.py +8 -7
  59. c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +70 -0
  60. c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +9 -6
  61. c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +9 -6
  62. c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +68 -0
  63. c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +7 -6
  64. c2cgeoportal_commons/alembic/static/ae5e88f35669_add_table_user_role.py +15 -18
  65. c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +10 -8
  66. c2cgeoportal_commons/lib/email_.py +11 -13
  67. c2cgeoportal_commons/lib/literal.py +44 -0
  68. c2cgeoportal_commons/lib/url.py +164 -57
  69. c2cgeoportal_commons/lib/validators.py +2 -4
  70. c2cgeoportal_commons/models/__init__.py +7 -10
  71. c2cgeoportal_commons/models/main.py +968 -127
  72. c2cgeoportal_commons/models/sqlalchemy.py +13 -18
  73. c2cgeoportal_commons/models/static.py +212 -34
  74. c2cgeoportal_commons/py.typed +0 -0
  75. c2cgeoportal_commons/testing/__init__.py +11 -10
  76. c2cgeoportal_commons/testing/initializedb.py +12 -12
  77. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/METADATA +16 -11
  78. c2cgeoportal_commons-2.8.1.180.dist-info/RECORD +83 -0
  79. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/WHEEL +1 -1
  80. tests/conftest.py +0 -2
  81. c2cgeoportal_commons-2.6.0.dist-info/RECORD +0 -76
  82. {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2013-2021, Camptocamp SA
1
+ # Copyright (c) 2013-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -27,89 +25,198 @@
27
25
  # of the authors and should not be interpreted as representing official policies,
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
-
28
+ import logging
29
+ import re
31
30
  import urllib.parse
32
31
  from typing import Dict, Optional, Set
33
32
 
34
33
  from pyramid.request import Request
35
34
 
35
+ LOG = logging.getLogger(__name__)
36
+
37
+
38
+ class Url:
39
+ """Object representation of an URI."""
40
+
41
+ scheme = ""
42
+ _netloc = ""
43
+ _hostname: Optional[str] = None
44
+ _port: Optional[int] = None
45
+ path = ""
46
+ query: Dict[str, str] = {}
47
+ fragment = ""
48
+
49
+ def __init__(self, url: Optional[str] = None):
50
+ if url:
51
+ url_split = urllib.parse.urlsplit(url)
52
+ self.scheme = url_split.scheme
53
+ self._netloc = url_split.netloc
54
+ self._hostname = url_split.hostname
55
+ try:
56
+ self._port = url_split.port
57
+ except ValueError as error:
58
+ LOG.debug(error)
59
+ self.path = url_split.path
60
+ self.query = dict(urllib.parse.parse_qsl(url_split.query))
61
+ self.fragment = url_split.fragment
62
+
63
+ def clone(self) -> "Url":
64
+ result = Url()
65
+ result.scheme = self.scheme
66
+ result._netloc = self._netloc # pylint: disable=protected-access
67
+ result._hostname = self._hostname # pylint: disable=protected-access
68
+ result._port = self._port # pylint: disable=protected-access
69
+ result.path = self.path
70
+ result.query = dict(self.query)
71
+ result.fragment = self.fragment
72
+ return result
73
+
74
+ @staticmethod
75
+ def _is_valid_hostname(hostname: str) -> bool:
76
+ if len(hostname) > 255:
77
+ return False
78
+ if hostname[-1] == ".":
79
+ hostname = hostname[:-1] # strip exactly one dot from the right, if present
80
+ allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE)
81
+ return all(allowed.match(x) for x in hostname.split("."))
82
+
83
+ @property
84
+ def netloc(self) -> str:
85
+ return self._netloc
86
+
87
+ @netloc.setter
88
+ def netloc(self, netloc: str) -> None:
89
+ netloc_split = netloc.split(":")
90
+ if len(netloc_split) > 2:
91
+ raise RuntimeError(f"The netloc '{netloc}' in invalid")
92
+ if not self._is_valid_hostname(netloc_split[0]):
93
+ raise RuntimeError(f"The netloc '{netloc}' in invalid")
94
+ if len(netloc_split) == 2:
95
+ allowed = re.compile(r"^[0-9]+$")
96
+ if not allowed.match(netloc_split[1]):
97
+ LOG.debug("The netloc '%s' contains invalid port", netloc)
98
+ self._port = None
99
+ else:
100
+ self._port = int(netloc_split[1])
101
+ else:
102
+ self._port = None
103
+ self._netloc = netloc
104
+ self._hostname = netloc_split[0]
105
+
106
+ @property
107
+ def hostname(self) -> Optional[str]:
108
+ return self._hostname
109
+
110
+ @hostname.setter
111
+ def hostname(self, hostname: str) -> None:
112
+ if not self._is_valid_hostname(hostname):
113
+ raise RuntimeError(f"The hostname '{hostname}' in invalid")
114
+ self._hostname = hostname
115
+ self.netloc = hostname if self._port is None else f"{hostname}:{self._port}"
116
+
117
+ @property
118
+ def port(self) -> Optional[int]:
119
+ return self._port
120
+
121
+ @port.setter
122
+ def port(self, port: Optional[int]) -> None:
123
+ self._port = port
124
+ self.netloc = (self._hostname or "") if port is None else f"{self._hostname}:{port}"
125
+
126
+ def add_query(self, query: Dict[str, str], force: bool = False) -> "Url":
127
+ if query:
128
+ for key, value in query.items():
129
+ if force or key not in self.query:
130
+ self.query[key] = value
131
+ return self
132
+
133
+ @property
134
+ def query_lower(self) -> Dict[str, str]:
135
+ return {k.lower(): v for k, v in self.query.items()}
136
+
137
+ def url(self) -> str:
138
+ return urllib.parse.urlunsplit(
139
+ (
140
+ self.scheme,
141
+ self._netloc,
142
+ self.path,
143
+ urllib.parse.urlencode(self.query),
144
+ self.fragment,
145
+ )
146
+ )
36
147
 
37
- def add_url_params(url: str, params: Optional[Dict[str, str]]) -> str:
38
- if not params:
39
- return url
40
- return add_spliturl_params(urllib.parse.urlsplit(url), params)
148
+ def __str__(self) -> str:
149
+ return self.url()
41
150
 
151
+ def __repr__(self) -> str:
152
+ return self.url()
42
153
 
43
- def add_spliturl_params(spliturl: urllib.parse.SplitResult, params: Dict[str, str]) -> str:
44
- query = {k: v[-1] for k, v in list(urllib.parse.parse_qs(spliturl.query).items())}
45
- for key, value in list(params.items()):
46
- if key not in query:
47
- query[key] = value
48
154
 
49
- return urllib.parse.urlunsplit(
50
- (spliturl.scheme, spliturl.netloc, spliturl.path, urllib.parse.urlencode(query), spliturl.fragment)
51
- )
155
+ def get_url2(
156
+ name: str, url: str, request: Request, errors: Set[str], servers: Optional[Dict[str, str]] = None
157
+ ) -> Optional[Url]:
158
+ """
159
+ Get the real URL from the URI of the administration interface.
52
160
 
161
+ Manage the schema: static and config.
162
+ """
163
+ if servers is None:
164
+ servers = request.registry.settings.get("servers", {})
53
165
 
54
- def get_url2(name: str, url: str, request: Request, errors: Set[str]) -> Optional[str]:
166
+ url_obj = Url(url)
55
167
  url_split = urllib.parse.urlsplit(url)
56
- if url_split.scheme == "":
57
- if url_split.netloc == "" and url_split.path not in ("", "/"):
168
+ if url_obj.scheme == "":
169
+ if url_obj.netloc == "" and url_obj.path not in ("", "/"):
58
170
  # Relative URL like: /dummy/static/url or dummy/static/url
59
- return urllib.parse.urlunsplit(url_split)
60
- errors.add("{}='{}' is not an URL.".format(name, url))
171
+ return url_obj
172
+ errors.add(f"{name}='{url}' is not an URL.")
61
173
  return None
62
- if url_split.scheme in ("http", "https"):
63
- if url_split.netloc == "":
64
- errors.add("{}='{}' is not a valid URL.".format(name, url))
174
+ if url_obj.scheme in ("http", "https"):
175
+ if url_obj.netloc == "":
176
+ errors.add(f"{name}='{url}' is not a valid URL.")
65
177
  return None
66
- return urllib.parse.urlunsplit(url_split)
67
- if url_split.scheme == "static":
68
- if url_split.path in ("", "/"):
69
- errors.add("{}='{}' cannot have an empty path.".format(name, url))
178
+ return url_obj
179
+ if url_obj.scheme == "static":
180
+ if request is None:
181
+ errors.add(f"{name}='{url}' The request is required for static URL.")
182
+ if url_obj.path in ("", "/"):
183
+ errors.add(f"{name}='{url}' cannot have an empty path.")
70
184
  return None
71
- proj = url_split.netloc
185
+ proj = url_obj.netloc
72
186
  package = request.registry.settings["package"]
73
187
  if proj in ("", "static"):
74
188
  proj = "/etc/geomapfish/static"
75
189
  elif ":" not in proj:
76
190
  if proj == "static-ngeo":
77
191
  errors.add(
78
- "{}='{}' static-ngeo shouldn't be used out of webpack because it don't has "
79
- "cache bustering.".format(name, url)
192
+ f"{name}='{url}' static-ngeo shouldn't be used out of webpack because it don't has "
193
+ "cache bustering."
80
194
  )
81
- proj = "{}_geoportal:{}".format(package, proj)
82
- return request.static_url("{}{}".format(proj, url_split.path))
83
- if url_split.scheme == "config":
84
- if url_split.netloc == "":
85
- errors.add("{}='{}' cannot have an empty netloc.".format(name, url))
195
+ proj = f"{package}_geoportal:{proj}"
196
+ return Url(request.static_url(f"{proj}{url_split.path}"))
197
+ if url_obj.scheme == "config":
198
+ if url_obj.netloc == "":
199
+ errors.add(f"{name}='{url}' cannot have an empty netloc.")
86
200
  return None
87
- server = request.registry.settings.get("servers", {}).get(url_split.netloc)
201
+ server = servers.get(url_obj.netloc)
88
202
  if server is None:
89
203
  errors.add(
90
- "{}: The server '{}' ({}) is not found in the config: [{}]".format(
91
- name,
92
- url_split.netloc,
93
- url,
94
- ", ".join(request.registry.settings.get("servers", {}).keys()),
95
- )
204
+ f"{name}: The server '{url_obj.netloc}' ({url}) is not found in the config: "
205
+ f"[{', '.join(servers.keys())}]"
96
206
  )
97
207
  return None
98
208
 
99
209
  if isinstance(server, dict):
100
- url = server["url"]
101
- params: Dict[str, str] = server.get("params", {})
210
+ url_obj_server = Url(server["url"])
211
+ url_obj_server.add_query(server.get("params", {}))
102
212
  else:
103
- url_split_server = urllib.parse.urlsplit(server)
104
- params = dict(urllib.parse.parse_qsl(url_split_server.query))
105
- url = urllib.parse.urlunsplit(url_split_server._replace(query=""))
106
- params.update(dict(urllib.parse.parse_qsl(url_split.query)))
107
-
108
- if url_split.path != "":
109
- if url[-1] != "/":
110
- url += "/"
111
- url = urllib.parse.urljoin(url, url_split.path[1:])
112
-
113
- return url if not params else "{}?{}".format(url, urllib.parse.urlencode(params))
114
-
213
+ url_obj_server = Url(server)
214
+
215
+ if url_obj.path != "":
216
+ if url_obj_server.path == "" or url_obj_server.path[-1] != "/":
217
+ url_obj_server.path += "/"
218
+ url_obj_server.path = urllib.parse.urljoin(url_obj_server.path, url_obj.path[1:])
219
+ url_obj_server.add_query(url_obj.query, force=True)
220
+ url_obj_server.fragment = url_obj.fragment
221
+ return url_obj_server
115
222
  return None
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2019-2020, Camptocamp SA
1
+ # Copyright (c) 2019-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -33,5 +31,5 @@ import re
33
31
  import colander
34
32
 
35
33
  # Custom url validator that allow c2cgeoportal static urls.
36
- URL_REGEX = r"(?:{}|^(:?static|config)://\S+$)".format(colander.URL_REGEX)
34
+ URL_REGEX = rf"(?:{colander.URL_REGEX}|^(:?static|config)://\S+$)"
37
35
  url = colander.Regex(URL_REGEX, msg=colander._("Must be a URL"), flags=re.IGNORECASE)
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2011-2020, Camptocamp SA
1
+ # Copyright (c) 2011-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -29,11 +27,10 @@
29
27
 
30
28
 
31
29
  import logging
32
- from typing import Any, Dict, List, Optional, Union # noqa
30
+ from typing import Any, Dict, List
33
31
 
34
32
  import sqlalchemy.ext.declarative
35
- import sqlalchemy.ext.declarative.api
36
- import sqlalchemy.orm # noqa
33
+ import sqlalchemy.orm
37
34
  import zope.event
38
35
 
39
36
  try:
@@ -44,9 +41,9 @@ except ModuleNotFoundError:
44
41
  pass
45
42
 
46
43
 
47
- # Should be filed on application initialisation
44
+ # Should be filed on application initialization
48
45
  DBSession: sqlalchemy.orm.Session = None
49
- Base: sqlalchemy.ext.declarative.api.ConcreteBase = sqlalchemy.ext.declarative.declarative_base()
46
+ Base: sqlalchemy.ext.declarative.ConcreteBase = sqlalchemy.ext.declarative.declarative_base()
50
47
  DBSessions: Dict[str, sqlalchemy.orm.Session] = {}
51
48
 
52
49
 
@@ -54,10 +51,11 @@ LOG = logging.getLogger(__name__)
54
51
 
55
52
 
56
53
  class InvalidateCacheEvent:
57
- pass
54
+ """Event to be broadcast."""
58
55
 
59
56
 
60
57
  def cache_invalidate_cb(*args: List[Any]) -> None:
58
+ """Invalidate the cache on a broadcast event."""
61
59
  _cache_invalidate_cb()
62
60
 
63
61
 
@@ -68,6 +66,5 @@ try:
68
66
  def _cache_invalidate_cb() -> None:
69
67
  zope.event.notify(InvalidateCacheEvent())
70
68
 
71
-
72
69
  except ModuleNotFoundError:
73
70
  LOG.error("c2cwsgiutils broadcast not found")