c2cgeoportal-commons 2.8.1.146__py3-none-any.whl → 2.9.0.350__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_commons/__init__.py +1 -1
- c2cgeoportal_commons/alembic/env.py +14 -10
- c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +2 -2
- c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +3 -1
- c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +12 -12
- c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +2 -2
- c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +3 -3
- c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +2 -2
- c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +2 -2
- c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +2 -2
- c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +2 -2
- c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +2 -2
- c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +2 -2
- c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +2 -2
- c2cgeoportal_commons/alembic/main/2e57710fecfe_update_the_ogc_server_for_ogc_api.py +77 -0
- c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +2 -2
- c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +3 -1
- c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +2 -2
- c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +2 -2
- c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +8 -6
- c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +4 -3
- c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +5 -3
- c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +2 -2
- c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +2 -2
- c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +2 -2
- c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +2 -2
- c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +2 -2
- c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +2 -2
- c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +2 -2
- c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +2 -2
- c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +15 -10
- c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +2 -2
- c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +2 -2
- c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +2 -2
- c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +2 -2
- c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +2 -2
- c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +2 -2
- c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +4 -4
- c2cgeoportal_commons/alembic/main/a4558f032d7d_add_support_of_cog_layers.py +74 -0
- c2cgeoportal_commons/alembic/main/a4f1aac9bda_merge_1_6_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +2 -2
- c2cgeoportal_commons/alembic/main/b6b09f414fe8_sync_model_database.py +174 -0
- c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +2 -2
- c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +2 -2
- c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +2 -2
- c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +3 -1
- c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +2 -2
- c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +2 -2
- c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +2 -2
- c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +2 -2
- c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +2 -2
- c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +3 -1
- c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +2 -2
- c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +2 -2
- c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +2 -2
- c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +3 -3
- c2cgeoportal_commons/alembic/static/267b4c1bde2e_add_display_name_in_the_user_for_better_.py +62 -0
- c2cgeoportal_commons/alembic/static/3f89a7d71a5e_alter_column_url_to_remove_limitation.py +2 -2
- c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +6 -4
- c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +2 -2
- c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +2 -2
- c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +3 -1
- c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +3 -1
- c2cgeoportal_commons/alembic/static/910b4ca53b68_sync_model_database.py +186 -0
- c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py +64 -0
- c2cgeoportal_commons/alembic/static/ae5e88f35669_add_table_user_role.py +2 -2
- c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +2 -2
- c2cgeoportal_commons/lib/email_.py +15 -19
- c2cgeoportal_commons/lib/literal.py +3 -3
- c2cgeoportal_commons/lib/url.py +15 -16
- c2cgeoportal_commons/lib/validators.py +1 -1
- c2cgeoportal_commons/models/__init__.py +15 -8
- c2cgeoportal_commons/models/main.py +323 -228
- c2cgeoportal_commons/models/sqlalchemy.py +11 -10
- c2cgeoportal_commons/models/static.py +125 -76
- c2cgeoportal_commons/testing/__init__.py +10 -6
- c2cgeoportal_commons/testing/initializedb.py +7 -6
- {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/METADATA +4 -9
- c2cgeoportal_commons-2.9.0.350.dist-info/RECORD +89 -0
- {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/WHEEL +1 -1
- tests/conftest.py +1 -1
- c2cgeoportal_commons-2.8.1.146.dist-info/RECORD +0 -83
- {c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2012-
|
|
1
|
+
# Copyright (c) 2012-2025, Camptocamp SA
|
|
2
2
|
# All rights reserved.
|
|
3
3
|
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
# of the authors and should not be interpreted as representing official policies,
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
|
27
27
|
import json
|
|
28
|
-
from typing import Any
|
|
28
|
+
from typing import Any
|
|
29
29
|
|
|
30
30
|
from sqlalchemy.engine import Dialect
|
|
31
31
|
from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
|
|
@@ -33,34 +33,35 @@ from sqlalchemy.types import VARCHAR, TypeDecorator, UserDefinedType
|
|
|
33
33
|
|
|
34
34
|
# get from https://docs.sqlalchemy.org/en/latest/orm/extensions/
|
|
35
35
|
# mutable.html#establishing-mutability-on-scalar-column-values
|
|
36
|
-
class JSONEncodedDict(TypeDecorator):
|
|
36
|
+
class JSONEncodedDict(TypeDecorator[Any]):
|
|
37
37
|
"""Represent an immutable structure as a json-encoded string."""
|
|
38
38
|
|
|
39
39
|
impl = VARCHAR
|
|
40
40
|
|
|
41
|
-
def process_bind_param(self, value:
|
|
41
|
+
def process_bind_param(self, value: dict[str, Any] | None, _: Dialect) -> str | None:
|
|
42
42
|
return json.dumps(value) if value is not None else None
|
|
43
43
|
|
|
44
|
-
def process_result_value(self, value:
|
|
44
|
+
def process_result_value(self, value: str | None, _: Dialect) -> dict[str, Any] | None:
|
|
45
45
|
return json.loads(value) if value is not None else None
|
|
46
46
|
|
|
47
47
|
@property
|
|
48
|
-
def python_type(self) ->
|
|
48
|
+
def python_type(self) -> type[Any]:
|
|
49
49
|
return dict
|
|
50
50
|
|
|
51
|
-
def process_literal_param(self, value:
|
|
51
|
+
def process_literal_param(self, value: Any | None, dialect: Any) -> str:
|
|
52
|
+
assert isinstance(value, str)
|
|
52
53
|
del dialect
|
|
53
54
|
return json.dumps(value)
|
|
54
55
|
|
|
55
56
|
|
|
56
|
-
class TsVector(UserDefinedType): #
|
|
57
|
+
class TsVector(UserDefinedType[dict[str, str]]): # pylint: disable=abstract-method
|
|
57
58
|
"""A custom type for PostgreSQL's tsvector type."""
|
|
58
59
|
|
|
59
60
|
cache_ok = True
|
|
60
61
|
|
|
61
|
-
def get_col_spec(self) -> str:
|
|
62
|
+
def get_col_spec(self) -> str: # type: ignore[override] # pylint: disable=arguments-differ
|
|
62
63
|
return "TSVECTOR"
|
|
63
64
|
|
|
64
65
|
@property
|
|
65
|
-
def python_type(self) ->
|
|
66
|
+
def python_type(self) -> type[Any]:
|
|
66
67
|
return dict
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2011-
|
|
1
|
+
# Copyright (c) 2011-2025, Camptocamp SA
|
|
2
2
|
# All rights reserved.
|
|
3
3
|
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
|
|
29
29
|
import crypt
|
|
30
30
|
import logging
|
|
31
|
-
|
|
31
|
+
import os
|
|
32
|
+
from datetime import datetime, timezone
|
|
32
33
|
from hashlib import sha1
|
|
33
34
|
from hmac import compare_digest as compare_hash
|
|
34
|
-
from typing import Any
|
|
35
|
+
from typing import Any
|
|
35
36
|
|
|
36
|
-
import pytz
|
|
37
37
|
import sqlalchemy.schema
|
|
38
38
|
from c2c.template.config import config
|
|
39
39
|
from sqlalchemy import Column, ForeignKey, Table
|
|
40
40
|
from sqlalchemy.dialects.postgresql import HSTORE
|
|
41
41
|
from sqlalchemy.ext.mutable import MutableDict
|
|
42
|
-
from sqlalchemy.orm import backref, relationship
|
|
42
|
+
from sqlalchemy.orm import Mapped, backref, mapped_column, relationship
|
|
43
43
|
from sqlalchemy.types import Boolean, DateTime, Integer, String, Unicode
|
|
44
44
|
|
|
45
45
|
from c2cgeoportal_commons.lib.literal import Literal
|
|
@@ -51,7 +51,7 @@ try:
|
|
|
51
51
|
from colander import Email, drop
|
|
52
52
|
from deform.widget import DateTimeInputWidget, HiddenWidget
|
|
53
53
|
except ModuleNotFoundError:
|
|
54
|
-
drop = None
|
|
54
|
+
drop = None # pylint: disable=invalid-name
|
|
55
55
|
|
|
56
56
|
class GenericClass:
|
|
57
57
|
"""Generic class."""
|
|
@@ -63,10 +63,11 @@ except ModuleNotFoundError:
|
|
|
63
63
|
HiddenWidget = GenericClass
|
|
64
64
|
DateTimeInputWidget = GenericClass
|
|
65
65
|
CollenderGeometry = GenericClass
|
|
66
|
-
RelationSelect2Widget = GenericClass
|
|
66
|
+
RelationSelect2Widget = GenericClass # type: ignore[misc,assignment]
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
_LOG = logging.getLogger(__name__)
|
|
70
|
+
_OPENID_CONNECT_ENABLED = os.environ.get("OPENID_CONNECT_ENABLED", "false").lower() in ("true", "yes", "1")
|
|
70
71
|
|
|
71
72
|
_schema: str = config["schema_static"] or "static"
|
|
72
73
|
|
|
@@ -123,31 +124,49 @@ class User(Base): # type: ignore
|
|
|
123
124
|
),
|
|
124
125
|
}
|
|
125
126
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
126
|
-
item_type =
|
|
127
|
+
item_type: Mapped[str] = mapped_column(
|
|
127
128
|
"type", String(10), nullable=False, info={"colanderalchemy": {"widget": HiddenWidget()}}
|
|
128
129
|
)
|
|
129
130
|
__mapper_args__ = {"polymorphic_on": item_type, "polymorphic_identity": "user"}
|
|
130
131
|
|
|
131
|
-
id
|
|
132
|
-
|
|
132
|
+
id: Mapped[int] = mapped_column(
|
|
133
|
+
Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}}
|
|
134
|
+
)
|
|
135
|
+
username: Mapped[str] = mapped_column(
|
|
133
136
|
Unicode,
|
|
134
137
|
unique=True,
|
|
135
138
|
nullable=False,
|
|
139
|
+
info={
|
|
140
|
+
"colanderalchemy": (
|
|
141
|
+
{
|
|
142
|
+
"title": _("Username"),
|
|
143
|
+
"description": _("Name used for authentication (must be unique)."),
|
|
144
|
+
}
|
|
145
|
+
if not _OPENID_CONNECT_ENABLED
|
|
146
|
+
else {"widget": HiddenWidget()}
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
display_name: Mapped[str] = mapped_column(
|
|
151
|
+
Unicode,
|
|
136
152
|
info={
|
|
137
153
|
"colanderalchemy": {
|
|
138
|
-
"title": _("
|
|
139
|
-
"description": _("Name
|
|
154
|
+
"title": _("Display name"),
|
|
155
|
+
"description": _("Name displayed in the application."),
|
|
140
156
|
}
|
|
141
157
|
},
|
|
142
158
|
)
|
|
143
|
-
_password =
|
|
144
|
-
|
|
159
|
+
_password: Mapped[str] = mapped_column(
|
|
160
|
+
"password", Unicode, nullable=False, info={"colanderalchemy": {"exclude": True}}
|
|
161
|
+
)
|
|
162
|
+
temp_password: Mapped[str | None] = mapped_column(
|
|
145
163
|
"temp_password", Unicode, nullable=True, info={"colanderalchemy": {"exclude": True}}
|
|
146
164
|
)
|
|
147
|
-
tech_data =
|
|
148
|
-
email =
|
|
165
|
+
tech_data = mapped_column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": {"exclude": True}})
|
|
166
|
+
email: Mapped[str] = mapped_column(
|
|
149
167
|
Unicode,
|
|
150
168
|
nullable=False,
|
|
169
|
+
index=True,
|
|
151
170
|
info={
|
|
152
171
|
"colanderalchemy": {
|
|
153
172
|
"title": _("Email"),
|
|
@@ -158,19 +177,24 @@ class User(Base): # type: ignore
|
|
|
158
177
|
}
|
|
159
178
|
},
|
|
160
179
|
)
|
|
161
|
-
is_password_changed =
|
|
180
|
+
is_password_changed: Mapped[bool] = mapped_column(
|
|
162
181
|
Boolean,
|
|
163
182
|
default=False,
|
|
164
183
|
info={
|
|
165
|
-
"colanderalchemy":
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
"colanderalchemy": (
|
|
185
|
+
{
|
|
186
|
+
"title": _("The user changed his password"),
|
|
187
|
+
"description": _("Indicates if user has changed his password."),
|
|
188
|
+
}
|
|
189
|
+
if not _OPENID_CONNECT_ENABLED
|
|
190
|
+
else {"exclude": True}
|
|
191
|
+
)
|
|
169
192
|
},
|
|
170
193
|
)
|
|
171
194
|
|
|
172
|
-
settings_role_id =
|
|
173
|
-
Integer,
|
|
195
|
+
settings_role_id: Mapped[int] = mapped_column(
|
|
196
|
+
Integer(),
|
|
197
|
+
nullable=True,
|
|
174
198
|
info={
|
|
175
199
|
"colanderalchemy": {
|
|
176
200
|
"title": _("Settings from role"),
|
|
@@ -213,36 +237,49 @@ class User(Base): # type: ignore
|
|
|
213
237
|
},
|
|
214
238
|
)
|
|
215
239
|
|
|
216
|
-
last_login =
|
|
240
|
+
last_login: Mapped[datetime] = mapped_column(
|
|
217
241
|
DateTime(timezone=True),
|
|
242
|
+
nullable=True,
|
|
218
243
|
info={
|
|
219
|
-
"colanderalchemy":
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
244
|
+
"colanderalchemy": (
|
|
245
|
+
{
|
|
246
|
+
"title": _("Last login"),
|
|
247
|
+
"description": _("Date of the user's last login."),
|
|
248
|
+
"missing": drop,
|
|
249
|
+
"widget": DateTimeInputWidget(readonly=True),
|
|
250
|
+
}
|
|
251
|
+
if not _OPENID_CONNECT_ENABLED
|
|
252
|
+
else {"exclude": True}
|
|
253
|
+
)
|
|
225
254
|
},
|
|
226
255
|
)
|
|
227
256
|
|
|
228
|
-
expire_on =
|
|
257
|
+
expire_on: Mapped[datetime | None] = mapped_column(
|
|
229
258
|
DateTime(timezone=True),
|
|
230
259
|
info={
|
|
231
|
-
"colanderalchemy":
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
"colanderalchemy": (
|
|
261
|
+
{
|
|
262
|
+
"title": _("Expiration date"),
|
|
263
|
+
"description": _("After this date the user will not be able to login anymore."),
|
|
264
|
+
}
|
|
265
|
+
if not _OPENID_CONNECT_ENABLED
|
|
266
|
+
else {"exclude": True}
|
|
267
|
+
)
|
|
235
268
|
},
|
|
236
269
|
)
|
|
237
270
|
|
|
238
|
-
deactivated =
|
|
271
|
+
deactivated: Mapped[bool] = mapped_column(
|
|
239
272
|
Boolean,
|
|
240
273
|
default=False,
|
|
241
274
|
info={
|
|
242
|
-
"colanderalchemy":
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
275
|
+
"colanderalchemy": (
|
|
276
|
+
{
|
|
277
|
+
"title": _("Deactivated"),
|
|
278
|
+
"description": _("Deactivate a user without removing it completely."),
|
|
279
|
+
}
|
|
280
|
+
if not _OPENID_CONNECT_ENABLED
|
|
281
|
+
else {"exclude": True}
|
|
282
|
+
)
|
|
246
283
|
},
|
|
247
284
|
)
|
|
248
285
|
|
|
@@ -252,12 +289,14 @@ class User(Base): # type: ignore
|
|
|
252
289
|
password: str = "",
|
|
253
290
|
email: str = "",
|
|
254
291
|
is_password_changed: bool = False,
|
|
255
|
-
settings_role:
|
|
256
|
-
roles:
|
|
257
|
-
expire_on:
|
|
292
|
+
settings_role: Role | None = None,
|
|
293
|
+
roles: list[Role] | None = None,
|
|
294
|
+
expire_on: datetime | None = None,
|
|
258
295
|
deactivated: bool = False,
|
|
296
|
+
display_name: str | None = None,
|
|
259
297
|
) -> None:
|
|
260
298
|
self.username = username
|
|
299
|
+
self.display_name = display_name or username
|
|
261
300
|
self.password = password
|
|
262
301
|
self.tech_data = {}
|
|
263
302
|
self.email = email
|
|
@@ -271,7 +310,7 @@ class User(Base): # type: ignore
|
|
|
271
310
|
@property
|
|
272
311
|
def password(self) -> str:
|
|
273
312
|
"""Get the password."""
|
|
274
|
-
return self._password
|
|
313
|
+
return self._password
|
|
275
314
|
|
|
276
315
|
@password.setter
|
|
277
316
|
def password(self, password: str) -> None:
|
|
@@ -322,10 +361,10 @@ class User(Base): # type: ignore
|
|
|
322
361
|
return False
|
|
323
362
|
|
|
324
363
|
def expired(self) -> bool:
|
|
325
|
-
return self.expire_on is not None and self.expire_on < datetime.now(
|
|
364
|
+
return self.expire_on is not None and self.expire_on < datetime.now(timezone.utc)
|
|
326
365
|
|
|
327
366
|
def update_last_login(self) -> None:
|
|
328
|
-
self.last_login = datetime.now(
|
|
367
|
+
self.last_login = datetime.now(timezone.utc)
|
|
329
368
|
|
|
330
369
|
def __str__(self) -> str:
|
|
331
370
|
return self.username or ""
|
|
@@ -336,13 +375,13 @@ class Shorturl(Base): # type: ignore
|
|
|
336
375
|
|
|
337
376
|
__tablename__ = "shorturl"
|
|
338
377
|
__table_args__ = {"schema": _schema}
|
|
339
|
-
id =
|
|
340
|
-
url =
|
|
341
|
-
ref =
|
|
342
|
-
creator_email =
|
|
343
|
-
creation =
|
|
344
|
-
last_hit =
|
|
345
|
-
nb_hits =
|
|
378
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
379
|
+
url: Mapped[str] = mapped_column(Unicode)
|
|
380
|
+
ref: Mapped[str] = mapped_column(String(20), index=True, unique=True, nullable=False)
|
|
381
|
+
creator_email: Mapped[str | None] = mapped_column(Unicode(200), nullable=True)
|
|
382
|
+
creation: Mapped[datetime] = mapped_column(DateTime)
|
|
383
|
+
last_hit: Mapped[datetime] = mapped_column(DateTime, nullable=True)
|
|
384
|
+
nb_hits: Mapped[int] = mapped_column(Integer)
|
|
346
385
|
|
|
347
386
|
|
|
348
387
|
class OAuth2Client(Base): # type: ignore
|
|
@@ -352,8 +391,10 @@ class OAuth2Client(Base): # type: ignore
|
|
|
352
391
|
__table_args__ = {"schema": _schema}
|
|
353
392
|
__colanderalchemy_config__ = {"title": _("OAuth2 Client"), "plural": _("OAuth2 Clients")}
|
|
354
393
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
355
|
-
id
|
|
356
|
-
|
|
394
|
+
id: Mapped[int] = mapped_column(
|
|
395
|
+
Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}}
|
|
396
|
+
)
|
|
397
|
+
client_id: Mapped[str] = mapped_column(
|
|
357
398
|
Unicode,
|
|
358
399
|
unique=True,
|
|
359
400
|
info={
|
|
@@ -363,7 +404,7 @@ class OAuth2Client(Base): # type: ignore
|
|
|
363
404
|
}
|
|
364
405
|
},
|
|
365
406
|
)
|
|
366
|
-
secret =
|
|
407
|
+
secret: Mapped[str] = mapped_column(
|
|
367
408
|
Unicode,
|
|
368
409
|
info={
|
|
369
410
|
"colanderalchemy": {
|
|
@@ -372,7 +413,7 @@ class OAuth2Client(Base): # type: ignore
|
|
|
372
413
|
}
|
|
373
414
|
},
|
|
374
415
|
)
|
|
375
|
-
redirect_uri =
|
|
416
|
+
redirect_uri: Mapped[str] = mapped_column(
|
|
376
417
|
Unicode,
|
|
377
418
|
info={
|
|
378
419
|
"colanderalchemy": {
|
|
@@ -386,7 +427,7 @@ class OAuth2Client(Base): # type: ignore
|
|
|
386
427
|
}
|
|
387
428
|
},
|
|
388
429
|
)
|
|
389
|
-
state_required =
|
|
430
|
+
state_required: Mapped[bool] = mapped_column(
|
|
390
431
|
Boolean,
|
|
391
432
|
default=False,
|
|
392
433
|
info={
|
|
@@ -399,7 +440,7 @@ class OAuth2Client(Base): # type: ignore
|
|
|
399
440
|
}
|
|
400
441
|
},
|
|
401
442
|
)
|
|
402
|
-
pkce_required =
|
|
443
|
+
pkce_required: Mapped[bool] = mapped_column(
|
|
403
444
|
Boolean,
|
|
404
445
|
default=False,
|
|
405
446
|
info={
|
|
@@ -425,15 +466,19 @@ class OAuth2BearerToken(Base): # type: ignore
|
|
|
425
466
|
"schema": _schema,
|
|
426
467
|
},
|
|
427
468
|
)
|
|
428
|
-
id =
|
|
429
|
-
client_id =
|
|
469
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
470
|
+
client_id: Mapped[int] = mapped_column(
|
|
471
|
+
Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False
|
|
472
|
+
)
|
|
430
473
|
client = relationship(OAuth2Client)
|
|
431
|
-
user_id =
|
|
474
|
+
user_id: Mapped[int] = mapped_column(
|
|
475
|
+
Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False
|
|
476
|
+
)
|
|
432
477
|
user = relationship(User)
|
|
433
|
-
access_token =
|
|
434
|
-
refresh_token =
|
|
435
|
-
expire_at =
|
|
436
|
-
state =
|
|
478
|
+
access_token: Mapped[str] = mapped_column(Unicode(100), unique=True)
|
|
479
|
+
refresh_token: Mapped[str] = mapped_column(Unicode(100), unique=True)
|
|
480
|
+
expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) # in one hour
|
|
481
|
+
state = mapped_column(String, nullable=True)
|
|
437
482
|
|
|
438
483
|
|
|
439
484
|
class OAuth2AuthorizationCode(Base): # type: ignore
|
|
@@ -446,17 +491,21 @@ class OAuth2AuthorizationCode(Base): # type: ignore
|
|
|
446
491
|
"schema": _schema,
|
|
447
492
|
},
|
|
448
493
|
)
|
|
449
|
-
id =
|
|
450
|
-
client_id =
|
|
494
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
495
|
+
client_id: Mapped[int] = mapped_column(
|
|
496
|
+
Integer, ForeignKey(_schema + ".oauth2_client.id", ondelete="CASCADE"), nullable=False
|
|
497
|
+
)
|
|
451
498
|
client = relationship(OAuth2Client)
|
|
452
|
-
user_id =
|
|
499
|
+
user_id: Mapped[int] = mapped_column(
|
|
500
|
+
Integer, ForeignKey(_schema + ".user.id", ondelete="CASCADE"), nullable=False
|
|
501
|
+
)
|
|
453
502
|
user = relationship(User)
|
|
454
|
-
redirect_uri =
|
|
455
|
-
code =
|
|
456
|
-
state =
|
|
457
|
-
challenge =
|
|
458
|
-
challenge_method =
|
|
459
|
-
expire_at =
|
|
503
|
+
redirect_uri: Mapped[str] = mapped_column(Unicode)
|
|
504
|
+
code: Mapped[str] = mapped_column(Unicode(100), unique=True, nullable=True)
|
|
505
|
+
state: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
506
|
+
challenge: Mapped[str] = mapped_column(String(128), nullable=True)
|
|
507
|
+
challenge_method: Mapped[str] = mapped_column(String(6), nullable=True)
|
|
508
|
+
expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) # in 10 minutes
|
|
460
509
|
|
|
461
510
|
|
|
462
511
|
class Log(AbstractLog):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2018-
|
|
1
|
+
# Copyright (c) 2018-2025, Camptocamp SA
|
|
2
2
|
# All rights reserved.
|
|
3
3
|
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
from typing import Any
|
|
29
|
+
from typing import Any
|
|
30
30
|
|
|
31
31
|
import zope.sqlalchemy
|
|
32
32
|
from sqlalchemy import engine_from_config
|
|
@@ -35,19 +35,22 @@ from sqlalchemy.orm import Session, configure_mappers, sessionmaker
|
|
|
35
35
|
from transaction import TransactionManager
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def get_engine(settings:
|
|
38
|
+
def get_engine(settings: dict[str, Any], prefix: str = "sqlalchemy.") -> Engine:
|
|
39
39
|
"""Get the engine."""
|
|
40
40
|
return engine_from_config(settings, prefix)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def get_session_factory(engine: Engine) -> sessionmaker:
|
|
43
|
+
def get_session_factory(engine: Engine) -> sessionmaker[Session]: # pylint: disable=unsubscriptable-object
|
|
44
44
|
"""Get the session factory."""
|
|
45
45
|
factory = sessionmaker()
|
|
46
46
|
factory.configure(bind=engine)
|
|
47
47
|
return factory
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def get_tm_session(
|
|
50
|
+
def get_tm_session(
|
|
51
|
+
session_factory: sessionmaker[Session], # pylint: disable=unsubscriptable-object
|
|
52
|
+
transaction_manager: TransactionManager,
|
|
53
|
+
) -> Session:
|
|
51
54
|
"""
|
|
52
55
|
Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
|
|
53
56
|
|
|
@@ -68,6 +71,7 @@ def get_tm_session(session_factory: sessionmaker, transaction_manager: Transacti
|
|
|
68
71
|
dbsession = get_tm_session(session_factory, transaction.manager)
|
|
69
72
|
"""
|
|
70
73
|
dbsession = session_factory()
|
|
74
|
+
assert isinstance(dbsession, Session)
|
|
71
75
|
zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
|
|
72
76
|
return dbsession
|
|
73
77
|
|
|
@@ -86,7 +90,7 @@ def generate_mappers() -> None:
|
|
|
86
90
|
|
|
87
91
|
|
|
88
92
|
def get_session(
|
|
89
|
-
settings:
|
|
93
|
+
settings: dict[str, Any], transaction_manager: TransactionManager, prefix: str = "sqlalchemy."
|
|
90
94
|
) -> Session:
|
|
91
95
|
"""Get the session."""
|
|
92
96
|
configure_mappers()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2017-
|
|
1
|
+
# Copyright (c) 2017-2025, Camptocamp SA
|
|
2
2
|
# All rights reserved.
|
|
3
3
|
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
|
|
29
29
|
import os
|
|
30
30
|
import sys
|
|
31
|
-
from typing import
|
|
31
|
+
from typing import cast
|
|
32
32
|
|
|
33
|
+
import sqlalchemy
|
|
33
34
|
from sqlalchemy.engine import Connection
|
|
34
35
|
from sqlalchemy.orm import Session
|
|
35
36
|
|
|
36
37
|
from c2cgeoportal_commons.models import Base
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def usage(argv:
|
|
40
|
+
def usage(argv: list[str]) -> None:
|
|
40
41
|
"""Get the usage."""
|
|
41
42
|
cmd = os.path.basename(argv[0])
|
|
42
43
|
print(f"usage: {cmd} <config_uri> [var=value]\n" '(example: "{cmd} development.ini")')
|
|
@@ -50,15 +51,15 @@ SELECT count(*) AS count
|
|
|
50
51
|
FROM information_schema.schemata
|
|
51
52
|
WHERE schema_name = '{schema_name}';
|
|
52
53
|
"""
|
|
53
|
-
result = connection.execute(sql)
|
|
54
|
+
result = connection.execute(sqlalchemy.text(sql))
|
|
54
55
|
row = result.first()
|
|
55
|
-
return cast(bool, row[0] == 1)
|
|
56
|
+
return cast(bool, row[0] == 1) # type: ignore[index]
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def truncate_tables(connection: Connection) -> None:
|
|
59
60
|
"""Truncate all the tables defined in the model."""
|
|
60
61
|
for t in Base.metadata.sorted_tables:
|
|
61
|
-
connection.execute(f"TRUNCATE TABLE {t.schema}.{t.name} CASCADE;")
|
|
62
|
+
connection.execute(sqlalchemy.text(f"TRUNCATE TABLE {t.schema}.{t.name} CASCADE;"))
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
def setup_test_data(dbsession: Session) -> None:
|
{c2cgeoportal_commons-2.8.1.146.dist-info → c2cgeoportal_commons-2.9.0.350.dist-info}/METADATA
RENAMED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: c2cgeoportal-commons
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0.350
|
|
4
4
|
Summary: c2cgeoportal commons
|
|
5
5
|
Home-page: https://github.com/camptocamp/c2cgeoportal/
|
|
6
6
|
Author: Camptocamp
|
|
7
7
|
Author-email: info@camptocamp.com
|
|
8
|
-
License: UNKNOWN
|
|
9
8
|
Keywords: web gis geoportail c2cgeoportal geocommune pyramid
|
|
10
|
-
Platform: UNKNOWN
|
|
11
9
|
Classifier: Development Status :: 6 - Mature
|
|
12
10
|
Classifier: Environment :: Web Environment
|
|
13
11
|
Classifier: Framework :: Pyramid
|
|
@@ -16,16 +14,15 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
16
14
|
Classifier: Operating System :: OS Independent
|
|
17
15
|
Classifier: Programming Language :: Python
|
|
18
16
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
18
|
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
21
19
|
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
Requires-Dist: GeoAlchemy2
|
|
24
|
-
Requires-Dist: c2c.template
|
|
25
|
-
Requires-Dist: jinja2 (>=3.1.3)
|
|
26
23
|
Requires-Dist: papyrus
|
|
27
24
|
Requires-Dist: pyramid
|
|
28
|
-
Requires-Dist: setuptools
|
|
25
|
+
Requires-Dist: setuptools >=78.1.1
|
|
29
26
|
Requires-Dist: sqlalchemy
|
|
30
27
|
Requires-Dist: transaction
|
|
31
28
|
Requires-Dist: zope.event
|
|
@@ -67,5 +64,3 @@ use `common/testing/initialized.py` to create the database (`development.ini` at
|
|
|
67
64
|
```
|
|
68
65
|
make test
|
|
69
66
|
```
|
|
70
|
-
|
|
71
|
-
|