-
Notifications
You must be signed in to change notification settings - Fork 31
[rel-db] Fix base tests and deletion #3119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/relational-db
Are you sure you want to change the base?
Changes from 3 commits
f17aacc
c1ebfc9
bffbb40
cd5f505
793d51b
21ac611
def9f58
ef7976c
d16cdf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
from collections.abc import Iterable | ||
from typing import Any | ||
from typing import Any, cast | ||
|
||
from ...models.fields import OnDelete | ||
from ...models.fields import BaseRelationField, OnDelete | ||
from ...shared.exceptions import ActionException, ProtectedModelsException | ||
from ...shared.interfaces.event import Event, EventType | ||
from ...shared.patterns import ( | ||
|
@@ -26,13 +26,19 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: | |
""" | ||
Takes care of on_delete handling. | ||
""" | ||
# Fetch db instance with all relevant fields | ||
# Executed before update_instance so that actions can manually set a | ||
# DeletedModel or other changed_models without changing the result of this. | ||
this_fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) | ||
|
||
# Have been here. Seen it all. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
if self.datastore.is_to_be_deleted(this_fqid): | ||
return instance | ||
self.datastore.apply_to_be_deleted(this_fqid) | ||
|
||
relevant_fields = [ | ||
field.get_own_field_name() for field in self.model.get_relation_fields() | ||
] | ||
# Fetch db instance with all relevant fields | ||
# Executed before update_instance so that actions can manually set a | ||
# DeletedModel or other changed_models without changing the result of this. | ||
db_instance = self.datastore.get( | ||
fqid=this_fqid, | ||
mapped_fields=relevant_fields, | ||
|
@@ -43,28 +49,31 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: | |
|
||
# Update instance and set relation fields to None. | ||
# Gather all delete actions with action data and also all models to be deleted | ||
delete_actions: list[tuple[FullQualifiedId, type[Action], ActionData]] = [] | ||
self.datastore.apply_changed_model(this_fqid, DeletedModel()) | ||
for field in self.model.get_relation_fields(): | ||
delete_actions: list[tuple[type[Action], ActionData]] = [] | ||
for field_name in sorted(db_instance): | ||
vkrasnovyd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if field_name == "id": | ||
continue | ||
field = cast(BaseRelationField, self.model.get_field(field_name)) | ||
# Check on_delete. | ||
if field.on_delete != OnDelete.SET_NULL: | ||
# Extract all foreign keys as fqids from the model | ||
foreign_fqids: list[FullQualifiedId] = [] | ||
value = db_instance.get(field.get_own_field_name(), []) | ||
value = db_instance.get(field_name, []) | ||
foreign_fqids = transform_to_fqids(value, field.get_target_collection()) | ||
|
||
if field.on_delete == OnDelete.PROTECT: | ||
protected_fqids = [ | ||
fqid for fqid in foreign_fqids if not self.is_deleted(fqid) | ||
fqid | ||
for fqid in foreign_fqids | ||
if not self.datastore.is_to_be_deleted_for_protected(fqid) | ||
] | ||
if protected_fqids: | ||
raise ProtectedModelsException(this_fqid, protected_fqids) | ||
else: | ||
# field.on_delete == OnDelete.CASCADE | ||
# Execute the delete action for all fqids | ||
for fqid in foreign_fqids: | ||
if self.is_deleted(fqid): | ||
# skip models that are already deleted | ||
if self.datastore.is_to_be_deleted(fqid): | ||
# skip models that are already tracked for deletion | ||
continue | ||
delete_action_class = actions_map.get( | ||
f"{collection_from_fqid(fqid)}.delete" | ||
|
@@ -76,37 +85,38 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: | |
) | ||
# Assume that the delete action uses the standard action data | ||
action_data = [{"id": id_from_fqid(fqid)}] | ||
delete_actions.append((fqid, delete_action_class, action_data)) | ||
delete_actions.append((delete_action_class, action_data)) | ||
self.datastore.apply_to_be_deleted_for_protected(fqid) | ||
else: | ||
# field.on_delete == OnDelete.SET_NULL | ||
instance[field.get_own_field_name()] = None | ||
instance[field_name] = None | ||
|
||
# Add additional relation models and execute all previously gathered delete actions | ||
# catch all protected models exception to gather all protected fqids | ||
all_protected_fqids: list[FullQualifiedId] = [] | ||
for fqid, delete_action_class, delete_action_data in delete_actions: | ||
for delete_action_class, delete_action_data in delete_actions: | ||
try: | ||
self.execute_other_action(delete_action_class, delete_action_data) | ||
self.datastore.apply_changed_model(fqid, DeletedModel()) | ||
except ProtectedModelsException as e: | ||
all_protected_fqids.extend(e.fqids) | ||
|
||
if all_protected_fqids: | ||
raise ProtectedModelsException(this_fqid, all_protected_fqids) | ||
|
||
self.datastore.apply_changed_model(this_fqid, DeletedModel()) | ||
return instance | ||
|
||
def create_events(self, instance: dict[str, Any]) -> Iterable[Event]: | ||
fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) | ||
yield self.build_event(EventType.Delete, fqid) | ||
|
||
def is_meeting_deleted(self, meeting_id: int) -> bool: | ||
def is_meeting_to_be_deleted(self, meeting_id: int) -> bool: | ||
""" | ||
Returns whether the given meeting was deleted during this request or not. | ||
Returns whether the given meeting was/will be deleted during this request or not. | ||
""" | ||
return self.datastore.is_deleted( | ||
return self.datastore.is_to_be_deleted( | ||
fqid_from_collection_and_id("meeting", meeting_id) | ||
) | ||
|
||
def is_deleted(self, fqid: FullQualifiedId) -> bool: | ||
return self.datastore.is_deleted(fqid) | ||
def is_to_be_deleted(self, fqid: FullQualifiedId) -> bool: | ||
return self.datastore.is_to_be_deleted(fqid) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,6 +99,8 @@ def __init__( | |
self.env = env | ||
self.logger = logging.getLogger(__name__) | ||
self._changed_models = defaultdict(lambda: defaultdict(dict)) | ||
self._to_be_deleted: set[FullQualifiedId] = set() | ||
self._to_be_deleted_for_protected: set[FullQualifiedId] = set() | ||
self.connection = connection | ||
self.database_reader = DatabaseReader(self.connection, logging, env) | ||
self.database_writer = DatabaseWriter(self.connection, logging, env) | ||
|
@@ -118,6 +120,14 @@ def apply_changed_model( | |
if "id" not in self._changed_models[collection][id_]: | ||
self._changed_models[collection][id_]["id"] = id_ | ||
|
||
def apply_to_be_deleted(self, fqid: FullQualifiedId) -> None: | ||
"""Meaning both: to be deleted in the future and the past.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this comment mean? (The same question also concerns the other three below it that have a similar format) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The model will be marked as to be deleted. This will not be undone when it is marked as deleted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then write that. |
||
self._to_be_deleted.add(fqid) | ||
|
||
def apply_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> None: | ||
"""Meaning both: to be deleted in the future and the past. Only used for protected models deletion.""" | ||
self._to_be_deleted_for_protected.add(fqid) | ||
|
||
def get_changed_model( | ||
self, collection_or_fqid: str, id_: int | None = None | ||
) -> PartialModel: | ||
|
@@ -128,6 +138,14 @@ def get_changed_model( | |
def get_changed_models(self, collection: str) -> dict[Id, PartialModel]: | ||
return self._changed_models.get(collection, dict()) | ||
|
||
def is_to_be_deleted(self, fqid: FullQualifiedId) -> bool: | ||
"""Meaning both: to be deleted in the future and the past.""" | ||
return fqid in self._to_be_deleted | ||
|
||
def is_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> bool: | ||
"""Meaning both: to be deleted in the future and the past. Only used for protected models deletion.""" | ||
return fqid in self._to_be_deleted_for_protected or fqid in self._to_be_deleted | ||
|
||
def get( | ||
self, | ||
fqid: FullQualifiedId, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary? Shouldn't topics be cascade deleted?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are cascade deleted but the problem lies in the fact that the
list_of_speakers
content_object_id
will be set toNone
if not deleted as one of the first models. That leads later to thetopics
list_of_speakers_id
to also be set toNone
by the relation handling, which is not legal as it is a required field. By first deleting the topic this is no problem any longer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this is supposed to refer to. Please rephrase it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rephrased it. I'm currently thinking if this would better be solved in the los delete action. But there also seems to be a problem with the other referenced collections like motion_block.