handle prune and delete for lists and backrefs

This commit is contained in:
Alexander Graf
2021-02-15 22:57:37 +01:00
parent 8929912dea
commit 70a1c79f81
2 changed files with 50 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ from marshmallow import pre_load, post_load, post_dump, fields, Schema
from marshmallow.utils import ensure_text_type
from marshmallow.exceptions import ValidationError
from marshmallow_sqlalchemy import SQLAlchemyAutoSchemaOpts
from marshmallow_sqlalchemy.fields import RelatedList
from flask_marshmallow import Marshmallow
@@ -39,8 +40,6 @@ ma = Marshmallow()
# - when modifying, nothing is required (only the primary key, but this key is in the uri)
# - the primary key from post data must not differ from the key in the uri
# - when creating all fields without default or auto-increment are required
# TODO: what about deleting list items and prung lists?
# - domain.alternatives, user.forward_destination, user.manager_of, aliases.destination
# TODO: validate everything!
@@ -652,7 +651,7 @@ class BaseSchema(ma.SQLAlchemyAutoSchema):
if '__delete__' in data:
# deletion of non-existent item requested
raise ValidationError(
f'item not found: {data[self._primary]!r}',
f'item to delete not found: {data[self._primary]!r}',
field_name=f'?.{self._primary}',
)
@@ -665,6 +664,44 @@ class BaseSchema(ma.SQLAlchemyAutoSchema):
# delete instance when marked
if '__delete__' in data:
self.opts.sqla_session.delete(instance)
# delete item from lists or prune lists
# currently: domain.alternatives, user.forward_destination,
# user.manager_of, aliases.destination
for key, value in data.items():
if isinstance(value, list):
new_value = set(value)
# handle list pruning
if '-prune-' in value:
value.remove('-prune-')
new_value.remove('-prune-')
else:
for old in getattr(instance, key):
# using str() is okay for now (see above)
new_value.add(str(old))
# handle item deletion
for item in value:
if item.startswith('-'):
new_value.remove(item)
try:
new_value.remove(item[1:])
except KeyError as exc:
raise ValidationError(
f'item to delete not found: {item[1:]!r}',
field_name=f'?.{key}',
) from exc
# deduplicate and sort list
data[key] = sorted(new_value)
# log backref modification not catched by hook
if isinstance(self.fields[key], RelatedList):
if callback := self.context.get('callback'):
callback(self, instance, {
'key': key,
'target': str(instance),
'before': [str(v) for v in getattr(instance, key)],
'after': data[key],
})
# add attributes required for validation from db
# TODO: this will cause validation errors if value from database does not validate