Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openslides_backend/action/actions/meeting/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
raise ActionException(
"Cannot create a non-template meeting without administrators"
)
self.transform_json_fields(instance)
return instance

def _create_or_get_meeting_user(self, meeting_id: int, user_id: int) -> int:
Expand Down
40 changes: 39 additions & 1 deletion openslides_backend/action/actions/meeting/import_.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from datetime import datetime
from typing import Any

from psycopg.types.json import Jsonb

from openslides_backend.action.actions.meeting.mixins import MeetingPermissionMixin
from openslides_backend.migrations.migration_helper import MigrationHelper
from openslides_backend.models.base import model_registry
Expand All @@ -13,9 +15,11 @@
BaseRelationField,
GenericRelationField,
GenericRelationListField,
JSONField,
OnDelete,
RelationField,
RelationListField,
TimestampField,
)
from openslides_backend.models.models import Meeting
from openslides_backend.permissions.management_levels import OrganizationManagementLevel
Expand Down Expand Up @@ -161,9 +165,42 @@ def preprocess_data(self, instance: dict[str, Any]) -> dict[str, Any]:
self.remove_not_allowed_fields(instance)
self.set_committee_and_orga_relation(instance)
self.check_data_migration_index(instance)
self.transform_timestamps(instance)
self.unset_committee_and_orga_relation(instance)
return instance

def transform_timestamps(self, instance: dict[str, Any]) -> dict[str, Any]:
for collection, collection_data in instance["meeting"].items():
if model := model_registry.get(collection):
fields = list(model().get_fields())
timestamp_field_names = [
field.own_field_name
for field in fields
if isinstance(field, TimestampField)
]
if timestamp_field_names:
for mod in collection_data.values():
for field in timestamp_field_names:
if (iso := mod.get(field)) and isinstance(iso, str):
mod[field] = datetime.fromisoformat(iso)
return instance

def transform_json_fields(self, instance: dict[str, Any]) -> dict[str, Any]:
for collection, collection_data in instance["meeting"].items():
if model := model_registry.get(collection):
fields = list(model().get_fields())
json_field_names = [
field.own_field_name
for field in fields
if isinstance(field, JSONField)
]
if json_field_names:
for mod in collection_data.values():
for field in json_field_names:
if field in mod:
mod[field] = Jsonb(mod[field])
return instance

def check_one_meeting(self, instance: dict[str, Any]) -> None:
if len(instance["meeting"]["meeting"]) != 1:
raise ActionException("Need exactly one meeting in meeting collection.")
Expand Down Expand Up @@ -276,6 +313,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
raise ActionException(str(ce))
self.allowed_collections = checker.allowed_collections

self.transform_json_fields(instance)
self.check_limit_of_meetings()
self.update_meeting_and_users(instance)

Expand Down Expand Up @@ -752,7 +790,7 @@ def check_data_migration_index(self, instance: dict[str, Any]) -> None:
"""
Check for valid migration index.
"""
start_migration_index = instance["meeting"].pop("_migration_index")
start_migration_index = instance["meeting"].get("_migration_index")
backend_migration_index = MigrationHelper.get_backend_migration_index()
if backend_migration_index < start_migration_index:
raise ActionException(
Expand Down
9 changes: 7 additions & 2 deletions openslides_backend/models/fields.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from decimal import Decimal
from decimal import Decimal, InvalidOperation
from enum import StrEnum
from typing import Any, cast

Expand Down Expand Up @@ -253,7 +253,12 @@ def validate(self, value: Any, payload: dict[str, Any] = {}) -> Any:
if value is not None or self.required:
if (min_ := self.constraints.get("minimum")) is not None:
if isinstance(value, str):
value = Decimal(value)
try:
value = Decimal(value)
except InvalidOperation:
raise ActionException(
f"{self.own_field_name}: value '{value}' couldn't be converted to decimal."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that error message for this error became more descriptive.

elif not isinstance(value, Decimal | None):
raise NotImplementedError(
f"Unexpected type: {type(value)} (value: {value}) for field {self.get_own_field_name()}"
Expand Down
4 changes: 3 additions & 1 deletion openslides_backend/presenter/export_meeting.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def get_result(self) -> Any:
msg = "You are not allowed to perform presenter export_meeting."
msg += f" Missing permission: {OrganizationManagementLevel.SUPERADMIN}"
raise PermissionDenied(msg)
export_data = export_meeting(self.datastore, self.data["meeting_id"])
export_data = export_meeting(
self.datastore, self.data["meeting_id"], datetime_decimal_to_string=True
)
if id_ := next(
(
id_
Expand Down
11 changes: 11 additions & 0 deletions openslides_backend/shared/export_helper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import datetime
from collections.abc import Iterable
from decimal import Decimal
from typing import Any

from openslides_backend.migrations.migration_helper import MigrationHelper
Expand Down Expand Up @@ -34,6 +36,7 @@ def export_meeting(
meeting_id: int,
internal_target: bool = False,
update_mediafiles: bool = False,
datetime_decimal_to_string: bool = False,
) -> dict[str, Any]:
export: dict[str, Any] = {}

Expand Down Expand Up @@ -212,6 +215,14 @@ def export_meeting(
export[collection] = dict(
sorted(instances.items(), key=lambda item: int(item[0]))
)
if datetime_decimal_to_string and isinstance(instances, dict):
for data in instances.values():
for field, value in data.items():
if isinstance(value, datetime.datetime):
data[field] = value.isoformat()
if isinstance(value, Decimal):
data[field] = str(value)

return export


Expand Down
50 changes: 50 additions & 0 deletions tests/system/action/meeting/test_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,37 @@ def test_clone_amendment_paragraphs(self) -> None:
response.json["message"],
)

def test_clone_amendment_paragraphs_regular(self) -> None:
self.set_test_data()
self.set_user_groups(1, [1])
self.set_models(
{
"motion/1": {
"list_of_speakers_id": 1,
"meeting_id": 1,
"state_id": 1,
"title": "dummy",
"amendment_paragraphs": Jsonb(
{
"1": "<p>test</p>",
}
),
},
"list_of_speakers/1": {
"content_object_id": "motion/1",
"meeting_id": 1,
},
}
)
Comment thread
vkrasnovyd marked this conversation as resolved.
Outdated
response = self.request(
"meeting.clone",
{
"meeting_id": 1,
"admin_ids": [1],
},
)
self.assert_status_code(response, 200)

def test_permissions_oml_locked_meeting(self) -> None:
self.create_meeting(
meeting_data={"locked_from_inside": True, "template_for_organization_id": 1}
Expand Down Expand Up @@ -2134,3 +2165,22 @@ def test_clone_with_structured_published_orga_files(self) -> None:
for fqid, model in models.items():
self.assert_model_exists(fqid, model)
self.media.duplicate_mediafile.assert_not_called()

def test_clone_require_duplicate_from_allowed(self) -> None:
self.set_test_data_with_admin()
self.set_models(
{
"meeting/1": {"template_for_organization_id": 1, "name": "m1"},
"organization/1": {
"template_meeting_ids": [1],
},
Comment thread
vkrasnovyd marked this conversation as resolved.
Outdated
"user/1": {
"organization_management_level": None,
"committee_ids": [1],
"committee_management_ids": [1],
},
"committee/60": {"user_ids": [1], "manager_ids": [1]},
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case committee_ids and committee_management_ids should be 60. And it's not even needed to set these fields manually because they are a back relation and a calculated field.

I'd suggest using self.set_committee_management_level([60]) instead of manually setting these 4 fields.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, also used set_organization_management_level

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case you can also get completely rid of set_models by updating self.meeting_data before calling self.set_test_data_with_admin()

)
response = self.request("meeting.clone", {"meeting_id": 1})
self.assert_status_code(response, 200)
Loading
Loading