fill more gaps in the as_operations implementation

This commit is contained in:
Laura Klünder 2024-11-21 18:42:43 +01:00
parent 7a22718e46
commit b5472c307b
2 changed files with 49 additions and 5 deletions

View file

@ -1,11 +1,13 @@
import bisect import bisect
import operator import operator
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
from django.apps import apps from django.apps import apps
from django.db.models import Model, Q from django.db.models import Model, Q, CharField, SlugField, DecimalField
from django.db.models.fields import IntegerField, SmallIntegerField, PositiveIntegerField, PositiveSmallIntegerField
from django.db.models.fields.reverse_related import ManyToOneRel, OneToOneRel from django.db.models.fields.reverse_related import ManyToOneRel, OneToOneRel
from pydantic.config import ConfigDict from pydantic.config import ConfigDict
@ -60,7 +62,11 @@ OperationDependency = Union[
] ]
class SingleOperationWithDependencies[OperationT: Type[DatabaseOperation]](BaseSchema): # todo: switch to new syntax once pydantic supports it
OperationT = TypeVar('OperationT', bound=DatabaseOperation)
class SingleOperationWithDependencies(BaseSchema, Generic[OperationT]):
uid: tuple uid: tuple
operation: OperationT operation: OperationT
dependencies: set[OperationDependency] = set() dependencies: set[OperationDependency] = set()
@ -114,7 +120,7 @@ class OperationSituation(BaseSchema):
missing_objects: dict[ModelName, set[ObjectID]] = {} missing_objects: dict[ModelName, set[ObjectID]] = {}
# unique values relevant for these operations that are currently not free # unique values relevant for these operations that are currently not free
occupied_unique_values: dict[ModelName, dict[FieldName: set]] = {} occupied_unique_values: dict[ModelName, dict[FieldName, set]] = {}
# references to objects that need to be removed for in this run # references to objects that need to be removed for in this run
obj_references: dict[ModelName, dict[ObjectID, set[FoundObjectReference]]] = {} obj_references: dict[ModelName, dict[ObjectID, set[FoundObjectReference]]] = {}
@ -407,7 +413,45 @@ class ChangedObjectCollection(BaseSchema):
else: else:
new_remaining_operations.append(sub_op) new_remaining_operations.append(sub_op)
# todo: placeholder for references or unique values if isinstance(new_operation, (CreateObjectOperation, UpdateObjectOperation)):
model_cls = apps.get_model('mapdata', new_operation.obj.model)
for field_name, value in tuple(new_operation.fields.items()):
if value is DummyValue:
field = model_cls._meta.get_field(field_name)
if field.null:
new_operation.fields[field_name] = None
continue
# todo: tell user about DummyValue result somehow
if field.is_relation:
qs = field.related_model.objects.only('pk')
if field.unique:
qs = qs.exclude(
**{f'{field_name}__in': model_cls.objects.values_list(field_name, flat=True)}
)
first = qs.first()
if first is None:
raise NotImplementedError # todo: inform user about impossibility
new_operation.fields[field_name] = first.pk
continue
if field.is_relation:
occupied = (
frozenset(model_cls.objects.values_list(field_name, flat=True))
if field.unique else frozenset()
)
if isinstance(field, (SlugField, CharField)):
new_val = "dummyvalue"
while new_val in occupied:
new_val = "dummyvalue"+str(random.randrange(1, 10000000))
elif isinstance(field, (DecimalField, IntegerField, SmallIntegerField,
PositiveIntegerField, PositiveSmallIntegerField)):
new_val = 0
while new_val in occupied:
new_val += 1
else:
raise NotImplementedError
new_operation.fields[field_name] = new_val
# construct new situation # construct new situation
new_situation = situation.model_copy(deep=True) new_situation = situation.model_copy(deep=True)

View file

@ -177,7 +177,7 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
class SpecificLocation(Location, models.Model): class SpecificLocation(Location, models.Model):
groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name__=_('Location Groups'), blank=True) groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name=_('Location Groups'), blank=True)
label_settings = models.ForeignKey('mapdata.LabelSettings', null=True, blank=True, on_delete=models.PROTECT, label_settings = models.ForeignKey('mapdata.LabelSettings', null=True, blank=True, on_delete=models.PROTECT,
verbose_name=_('label settings')) verbose_name=_('label settings'))
label_override = I18nField(_('Label override'), plural_name='label_overrides', blank=True, fallback_any=True) label_override = I18nField(_('Label override'), plural_name='label_overrides', blank=True, fallback_any=True)