diff --git a/src/c3nav/editor/changes.py b/src/c3nav/editor/changes.py index afed874e..c3b003fe 100644 --- a/src/c3nav/editor/changes.py +++ b/src/c3nav/editor/changes.py @@ -17,7 +17,7 @@ from pydantic.config import ConfigDict from c3nav.api.schema import BaseSchema from c3nav.editor.operations import DatabaseOperationCollection, CreateObjectOperation, UpdateObjectOperation, \ DeleteObjectOperation, ClearManyToManyOperation, FieldValuesDict, ObjectReference, PreviousObjectCollection, \ - DatabaseOperation, ObjectID, FieldName, ModelName + DatabaseOperation, ObjectID, FieldName, ModelName, CreateMultipleObjectsOperation from c3nav.mapdata.fields import I18nField @@ -514,10 +514,25 @@ class ChangedObjectCollection(BaseSchema): raise NotImplementedError new_operation.fields[field_name] = new_val - # construct new situation # todo: merge create operations one day + # construct new situation new_situation = situation.model_copy(deep=True) + + if isinstance(new_operation, CreateObjectOperation) and new_situation.operations: + last_operation = new_situation.operations[-1] + if (isinstance(last_operation, CreateObjectOperation) and + last_operation.obj.model == new_operation.obj.model): + new_situation.operations[-1] = CreateMultipleObjectsOperation( + objects=[last_operation, new_operation], + ) + elif (isinstance(last_operation, CreateMultipleObjectsOperation) and + last_operation.objects[-1].obj.model == new_operation.obj.model): + last_operation.objects.append(new_operation) + else: + new_situation.operations.append(new_operation) + else: + new_situation.operations.append(new_operation) + new_situation.remaining_operations_with_dependencies.pop(i) - new_situation.operations.append(new_operation) new_situation.remaining_operations_with_dependencies.extend(new_remaining_operations) new_situation.operation_uids = new_situation.operation_uids | uids_to_add diff --git a/src/c3nav/editor/operations.py b/src/c3nav/editor/operations.py index f97b2cd7..77a640bf 100644 --- a/src/c3nav/editor/operations.py +++ b/src/c3nav/editor/operations.py @@ -85,7 +85,7 @@ class CreateObjectOperation(BaseOperation): type: Literal["create"] = "create" fields: FieldValuesDict - def apply_create(self) -> Model: + def get_data(self): model = apps.get_model('mapdata', self.obj.model) data = [] if issubclass(model, LocationSlug): @@ -104,10 +104,30 @@ class CreateObjectOperation(BaseOperation): "pk": self.obj.id, "fields": values, }) + return data + + def apply_create(self) -> dict[ObjectReference, Model]: + data = self.get_data() instances = list(serializers.deserialize("json", json.dumps(data))) for instance in instances: instance.save(save_m2m=False) - return instances[-1].object + return {self.obj: instances[-1].object} + + +class CreateMultipleObjectsOperation(BaseSchema): + type: Literal["create"] = "create_multiple" + objects: list[CreateObjectOperation] = [] + + def apply_create(self) -> dict[ObjectReference, Model]: + indexes = {} + data = [] + for obj in self.objects: + data.extend(obj.get_data()) + indexes[obj.obj] = len(data)-1 + instances = list(serializers.deserialize("json", json.dumps(data))) + for instance in instances: + instance.save(save_m2m=False) + return {ref: instances[i] for ref, i in indexes.items()} class UpdateObjectOperation(BaseOperation): @@ -179,6 +199,7 @@ class ClearManyToManyOperation(BaseOperation): DatabaseOperation = Annotated[ Union[ CreateObjectOperation, + CreateMultipleObjectsOperation, UpdateObjectOperation, DeleteObjectOperation, UpdateManyToManyOperation, @@ -221,8 +242,8 @@ class PrefetchedDatabaseOperationCollection: def apply(self): # todo: what if unique constraint error occurs? for operation in self.operations: - if isinstance(operation, CreateObjectOperation): - self.instances[operation.obj] = operation.apply_create() + if isinstance(operation, (CreateObjectOperation, CreateMultipleObjectsOperation)): + self.instances.update(operation.apply_create()) else: prev_obj = self.operations.prev.get(operation.obj) if prev_obj is None: