diff --git a/pyproject.toml b/pyproject.toml index 1770dab6f..0dc53083a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "gunicorn~=21.0", "jinja2~=3.0", "jsonschema~=4.0", + "logics-py~=0.0.11", "pillow~=10.0", "pyotp~=2.0", "pytz~=2023.0", diff --git a/src/viur/core/skeleton.py b/src/viur/core/skeleton.py index a39492ed6..2afcbefa9 100644 --- a/src/viur/core/skeleton.py +++ b/src/viur/core/skeleton.py @@ -10,6 +10,7 @@ import time import typing as t import warnings +import logics from deprecated.sphinx import deprecated from functools import partial from itertools import chain @@ -18,7 +19,9 @@ BaseBone, DateBone, KeyBone, + RawBone, ReadFromClientException, + RecordBone, RelationalBone, RelationalConsistency, RelationalUpdateLevel, @@ -33,7 +36,13 @@ ReadFromClientErrorSeverity, getSystemInitialized, ) -from viur.core.tasks import CallDeferred, CallableTask, CallableTaskBase, QueryIter +from viur.core.tasks import ( + CallableTask, + CallableTaskBase, + CallDeferred, + DeleteEntitiesIter, + QueryIter, +) _UNDEFINED = object() ABSTRACT_SKEL_CLS_SUFFIX = "AbstractSkel" @@ -631,6 +640,8 @@ def fromClient( complete = True skel.errors = [] + print(data) + for key, bone in skel.items(): if (ignore is None and bone.readOnly) or key in (ignore or ()): continue @@ -2093,6 +2104,96 @@ def processVacuumRelationsChunk( logging.exception(f"Failed to notify {notify}") +### TaskClearKind + +@CallableTask +class TaskClearKind(CallableTaskBase): + key = "TaskClearKind" + name = "Clear all entities of a kind" + descr = "This task can be called to clean your database from a specific kind." + + def canCall(self): + user = current.user.get() + return user and "root" in user["access"] + + class dataSkel(RelSkel): + kinds = SelectBone( + descr="Kind", + values=listKnownSkeletons, + required=True, + multiple=True + ) + + # class FilterRow(RelSkel): + # name = StringBone( + # required=True, + # ) + # + # op = SelectBone( + # required=True, + # values={ + # " ": "=", + # "$lt": "<", + # "$gt": ">", + # "$lk": "like", + # }, + # defaultValue=" ", + # ) + # + # value = StringBone( + # required=True, + # ) + # + # filters = RecordBone( + # descr="Filter", + # using=FilterRow, + # multiple=True, + # format="$(name)$(op)=$(value)" + # ) + + condition = RawBone( + descr="Condition", + required=True, + defaultValue="False # Fused, doesn't delete anything!\n", + params={ + "tooltip": "Enter a Logics expression here to filter entries by specific skeleton values." + }, + ) + + def execute(self, **kwargs): + user = current.user.get() + if not (user and "root" in user["access"]): + raise errors.Unauhtorized() + + skel = self.dataSkel() + if not skel.fromClient(kwargs): + return self.render.action("add", skel) + + try: + condition = logics.Logics(skel["condition"]) + except logics.ParseException as e: + raise errors.BadRequest(f"Error parsing condition {e}") + + class TaskClearKindIter(DeleteEntitiesIter): + @classmethod + def handleEntry(cls, skel, data): + if condition.run(skel): + logging.warning(f"{skel['key']!r} is a candidate for being deleted") + # super().handleEntry(skel, data) + else: + logging.info(f"{skel['key']!r} is ingored") + + for kind in skel["kinds"]: + q = skeletonByKind(kind)().all() + + # print(skel["filters"]) + # for flt in skel["filters"]: + # print(flt) + # q.mergeExternalFilter({flt["name"] + flt["op"]: flt["value"]}) + + TaskClearKindIter.startIterOnQuery(q) + + # Forward our references to SkelInstance to the database (needed for queries) db.config["SkeletonInstanceRef"] = SkeletonInstance diff --git a/src/viur/core/tasks.py b/src/viur/core/tasks.py index 217e3625b..025471f7a 100644 --- a/src/viur/core/tasks.py +++ b/src/viur/core/tasks.py @@ -360,7 +360,6 @@ def execute(self, taskID, *args, **kwargs): return self.render.addSuccess(skel) -TaskHandler.admin = True TaskHandler.vi = True TaskHandler.html = True