add m2m operations to as_operations implementation
This commit is contained in:
parent
2749091168
commit
fb36071bd1
2 changed files with 66 additions and 24 deletions
|
@ -4,7 +4,7 @@ import operator
|
||||||
import random
|
import random
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Type, Any, Union, Self, TypeVar, Generic
|
from typing import Type, Any, Union, Self, TypeVar, Generic, NamedTuple
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
|
@ -17,7 +17,7 @@ from pydantic.config import ConfigDict
|
||||||
from c3nav.api.schema import BaseSchema
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.editor.operations import DatabaseOperationCollection, CreateObjectOperation, UpdateObjectOperation, \
|
from c3nav.editor.operations import DatabaseOperationCollection, CreateObjectOperation, UpdateObjectOperation, \
|
||||||
DeleteObjectOperation, ClearManyToManyOperation, FieldValuesDict, ObjectReference, PreviousObjectCollection, \
|
DeleteObjectOperation, ClearManyToManyOperation, FieldValuesDict, ObjectReference, PreviousObjectCollection, \
|
||||||
DatabaseOperation, ObjectID, FieldName, ModelName, CreateMultipleObjectsOperation
|
DatabaseOperation, ObjectID, FieldName, ModelName, CreateMultipleObjectsOperation, UpdateManyToManyOperation
|
||||||
from c3nav.mapdata.fields import I18nField
|
from c3nav.mapdata.fields import I18nField
|
||||||
|
|
||||||
|
|
||||||
|
@ -223,9 +223,13 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
ids.setdefault(related_model._meta.model_name, set()).update(field_changes.removed)
|
ids.setdefault(related_model._meta.model_name, set()).update(field_changes.removed)
|
||||||
# todo: move this to some kind of "usage explanation" function, implement rest of this
|
# todo: move this to some kind of "usage explanation" function, implement rest of this
|
||||||
|
|
||||||
|
class OperationsWithDependencies(NamedTuple):
|
||||||
|
obj_operations: list[OperationWithDependencies]
|
||||||
|
m2m_operations: list[SingleOperationWithDependencies]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def as_operations_with_dependencies(self) -> list[OperationWithDependencies]:
|
def as_operations_with_dependencies(self) -> OperationsWithDependencies:
|
||||||
operations_with_dependencies: list[OperationWithDependencies] = []
|
operations_with_dependencies = self.OperationsWithDependencies(obj_operations=[], m2m_operations=[])
|
||||||
for model_name, changed_objects in self.objects.items():
|
for model_name, changed_objects in self.objects.items():
|
||||||
model = apps.get_model("mapdata", model_name)
|
model = apps.get_model("mapdata", model_name)
|
||||||
|
|
||||||
|
@ -237,7 +241,7 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
if changed_obj.deleted:
|
if changed_obj.deleted:
|
||||||
if changed_obj.created:
|
if changed_obj.created:
|
||||||
continue
|
continue
|
||||||
operations_with_dependencies.append(
|
operations_with_dependencies.obj_operations.append(
|
||||||
SingleOperationWithDependencies(
|
SingleOperationWithDependencies(
|
||||||
uid=(changed_obj.obj, "delete"),
|
uid=(changed_obj.obj, "delete"),
|
||||||
operation=DeleteObjectOperation(obj=changed_obj.obj),
|
operation=DeleteObjectOperation(obj=changed_obj.obj),
|
||||||
|
@ -295,21 +299,51 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not obj_sub_operations:
|
if not obj_sub_operations:
|
||||||
operations_with_dependencies.append(obj_main_operation)
|
operations_with_dependencies.obj_operations.append(obj_main_operation)
|
||||||
else:
|
else:
|
||||||
operations_with_dependencies.append(MergableOperationsWithDependencies(
|
operations_with_dependencies.obj_operations.append(MergableOperationsWithDependencies(
|
||||||
main_op=obj_main_operation,
|
main_op=obj_main_operation,
|
||||||
sub_ops=obj_sub_operations,
|
sub_ops=obj_sub_operations,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
for field_name, m2m_changes in changed_obj.m2m_changes.items():
|
||||||
|
if m2m_changes.cleared:
|
||||||
|
operations_with_dependencies.m2m_operations.append(SingleOperationWithDependencies(
|
||||||
|
uid=(changed_obj.obj, f"m2mclear_{field_name}"),
|
||||||
|
operation=ClearManyToManyOperation(
|
||||||
|
obj=changed_obj.obj,
|
||||||
|
field=field_name,
|
||||||
|
),
|
||||||
|
dependencies={OperationDependencyObjectExists(obj=changed_obj.obj)},
|
||||||
|
))
|
||||||
|
if m2m_changes.added or m2m_changes.removed:
|
||||||
|
operations_with_dependencies.m2m_operations.append(SingleOperationWithDependencies(
|
||||||
|
uid=(changed_obj.obj, f"m2mupdate_{field_name}"),
|
||||||
|
operation=UpdateManyToManyOperation(
|
||||||
|
obj=changed_obj.obj,
|
||||||
|
field=field_name,
|
||||||
|
add_values=m2m_changes.added,
|
||||||
|
remove_values=m2m_changes.removed,
|
||||||
|
),
|
||||||
|
dependencies={OperationDependencyObjectExists(obj=changed_obj.obj)},
|
||||||
|
))
|
||||||
|
|
||||||
return operations_with_dependencies
|
return operations_with_dependencies
|
||||||
|
|
||||||
def create_start_operation_situation(self) -> tuple[OperationSituation, dict[ModelName, dict[FieldName: set]]]:
|
class CreateStartOperationResult(NamedTuple):
|
||||||
|
situation: OperationSituation
|
||||||
|
unique_values_needed: dict[ModelName, dict[FieldName: set]]
|
||||||
|
m2m_operations: list[SingleOperationWithDependencies]
|
||||||
|
|
||||||
|
def create_start_operation_situation(self) -> CreateStartOperationResult:
|
||||||
operations_with_dependencies = self.as_operations_with_dependencies
|
operations_with_dependencies = self.as_operations_with_dependencies
|
||||||
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
pprint(operations_with_dependencies)
|
pprint(operations_with_dependencies)
|
||||||
|
|
||||||
start_situation = OperationSituation(remaining_operations_with_dependencies=operations_with_dependencies)
|
start_situation = OperationSituation(
|
||||||
|
remaining_operations_with_dependencies=operations_with_dependencies.obj_operations
|
||||||
|
)
|
||||||
|
|
||||||
referenced_objects: dict[ModelName, set[ObjectID]] = {} # objects that need to exist before
|
referenced_objects: dict[ModelName, set[ObjectID]] = {} # objects that need to exist before
|
||||||
deleted_existing_objects: dict[ModelName, set[ObjectID]] = {} # objects that need to exist before
|
deleted_existing_objects: dict[ModelName, set[ObjectID]] = {} # objects that need to exist before
|
||||||
|
@ -388,7 +422,11 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
on_delete=model_cls._meta.get_field(field).on_delete.__name__)
|
on_delete=model_cls._meta.get_field(field).on_delete.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
return start_situation, unique_values_needed
|
return self.CreateStartOperationResult(
|
||||||
|
situation=start_situation,
|
||||||
|
unique_values_needed=unique_values_needed,
|
||||||
|
m2m_operations=operations_with_dependencies.m2m_operations
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def as_operations(self) -> DatabaseOperationCollection:
|
def as_operations(self) -> DatabaseOperationCollection:
|
||||||
|
@ -402,7 +440,7 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
start_situation, unique_values_needed = self.create_start_operation_situation()
|
start_situation, unique_values_needed, m2m_operations = self.create_start_operation_situation()
|
||||||
|
|
||||||
# situations still to deal with, sorted by number of operations
|
# situations still to deal with, sorted by number of operations
|
||||||
open_situations: list[OperationSituation] = [start_situation]
|
open_situations: list[OperationSituation] = [start_situation]
|
||||||
|
@ -623,16 +661,20 @@ class ChangedObjectCollection(BaseSchema):
|
||||||
if not continued:
|
if not continued:
|
||||||
ended_situations.append(situation)
|
ended_situations.append(situation)
|
||||||
|
|
||||||
if done_situation:
|
if not done_situation:
|
||||||
operations = done_situation.operations
|
# todo: choose best option
|
||||||
else:
|
|
||||||
# todo: what to do if we can't fully solve it?
|
|
||||||
raise NotImplementedError('couldnt fully solve as_operations')
|
raise NotImplementedError('couldnt fully solve as_operations')
|
||||||
|
|
||||||
# todo: m2m
|
# add m2m
|
||||||
|
for m2m_operation_with_dependencies in m2m_operations:
|
||||||
|
if not done_situation.fulfils_dependencies(m2m_operation_with_dependencies.dependencies):
|
||||||
|
done_situation.remaining_operations_with_dependencies.append(m2m_operation_with_dependencies)
|
||||||
|
continue
|
||||||
|
done_situation.operations.append(m2m_operation_with_dependencies.operation)
|
||||||
|
|
||||||
result = DatabaseOperationCollection(
|
# todo: describe what couldn't be done
|
||||||
|
|
||||||
|
return DatabaseOperationCollection(
|
||||||
prev=self.prev,
|
prev=self.prev,
|
||||||
|
operations=done_situation.operations,
|
||||||
)
|
)
|
||||||
result.extend(operations)
|
|
||||||
return result
|
|
||||||
|
|
|
@ -216,19 +216,19 @@ class DatabaseOperationCollection(BaseSchema):
|
||||||
Iterable as a list of DatabaseOperation instances.
|
Iterable as a list of DatabaseOperation instances.
|
||||||
"""
|
"""
|
||||||
prev: PreviousObjectCollection = PreviousObjectCollection()
|
prev: PreviousObjectCollection = PreviousObjectCollection()
|
||||||
_operations: list[DatabaseOperation] = []
|
operations: list[DatabaseOperation] = []
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[DatabaseOperation]:
|
def __iter__(self) -> Iterator[DatabaseOperation]:
|
||||||
yield from self._operations
|
yield from self.operations
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._operations)
|
return len(self.operations)
|
||||||
|
|
||||||
def extend(self, items: list[DatabaseOperation]):
|
def extend(self, items: list[DatabaseOperation]):
|
||||||
self._operations.extend(items)
|
self.operations.extend(items)
|
||||||
|
|
||||||
def append(self, item: DatabaseOperation):
|
def append(self, item: DatabaseOperation):
|
||||||
self._operations.append(item)
|
self.operations.append(item)
|
||||||
|
|
||||||
def prefetch(self) -> "PrefetchedDatabaseOperationCollection":
|
def prefetch(self) -> "PrefetchedDatabaseOperationCollection":
|
||||||
return PrefetchedDatabaseOperationCollection(operations=self, instances=self.prev.get_instances())
|
return PrefetchedDatabaseOperationCollection(operations=self, instances=self.prev.get_instances())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue