Prechádzať zdrojové kódy

Bury and resurrect inbound and descendants.

Stefano Cossu 6 rokov pred
rodič
commit
1f4f357a24

+ 4 - 20
lakesuperior/api/resource.py

@@ -292,7 +292,7 @@ def create_version(uid, ver_uid):
 
 
 @transaction(True)
-def delete(uid, soft=True):
+def delete(uid, soft=True, inbound=True):
     """
     Delete a resource.
 
@@ -306,27 +306,11 @@ def delete(uid, soft=True):
     inbound = True if refint else inbound
     repr_opts = {'incl_inbound' : True} if refint else {}
 
-    children = env.app_globals.rdfly.get_descendants(uid)
-
+    rsrc = LdpFactory.from_stored(uid, repr_opts)
     if soft:
-        rsrc = LdpFactory.from_stored(uid, repr_opts)
-        ret = rsrc.bury(inbound)
-
-        for child_uri in children:
-            try:
-                child_rsrc = LdpFactory.from_stored(
-                    env.app_globals.rdfly.uri_to_uid(child_uri),
-                    repr_opts={'incl_children' : False})
-            except (TombstoneError, ResourceNotExistsError):
-                continue
-            child_rsrc.bury(inbound, tstone_pointer=rsrc.uri)
+        return rsrc.bury(inbound)
     else:
-        ret = env.app_globals.rdfly.forget_rsrc(uid, inbound)
-        for child_uri in children:
-            child_uid = env.app_globals.rdfly.uri_to_uid(child_uri)
-            ret = env.app_globals.rdfly.forget_rsrc(child_uid, inbound)
-
-    return ret
+        return rsrc.forget(inbound)
 
 
 @transaction(True)

+ 31 - 6
lakesuperior/model/ldpr.py

@@ -416,7 +416,11 @@ class Ldpr(metaclass=ABCMeta):
         """
         Delete a single resource and create a tombstone.
 
-        :param boolean inbound: Whether to delete the inbound relationships.
+        :param bool inbound: Whether inbound relationships are
+            removed. If ``False``, resources will keep referring
+            to the deleted resource; their link will point to a tombstone
+            (which will raise a ``TombstoneError`` in the Python API or a
+            ``410 Gone`` in the LDP API).
         :param rdflib.URIRef tstone_pointer: If set to a URI, this creates a
             pointer to the tombstone of the resource that used to contain the
             deleted resource. Otherwise the deleted resource becomes a
@@ -437,17 +441,28 @@ class Ldpr(metaclass=ABCMeta):
                 (self.uri, nsc['fcsystem'].buried, thread_env.timestamp_term),
             }
 
-        ib_rsrc_uris = self.imr.subjects(None, self.uri)
-        self.modify(RES_DELETED, remove_trp, add_trp)
-
+        # Bury descendants.
+        from lakesuperior.model.ldp_factory import LdpFactory
+        for desc_uri in rdfly.get_descendants(self.uid):
+            try:
+                desc_rsrc = LdpFactory.from_stored(
+                    env.app_globals.rdfly.uri_to_uid(desc_uri),
+                    repr_opts={'incl_children' : False})
+            except (TombstoneError, ResourceNotExistsError):
+                continue
+            desc_rsrc.bury(inbound, tstone_pointer=self.uri)
+
+        # Cut inbound relationships
         if inbound:
-            for ib_rsrc_uri in ib_rsrc_uris:
+            for ib_rsrc_uri in self.imr.subjects(None, self.uri):
                 remove_trp = {(ib_rsrc_uri, None, self.uri)}
                 ib_rsrc = Ldpr(ib_rsrc_uri)
                 # To preserve inbound links in history, create a snapshot
                 ib_rsrc.create_version()
                 ib_rsrc.modify(RES_UPDATED, remove_trp)
 
+        self.modify(RES_DELETED, remove_trp, add_trp)
+
         return RES_DELETED
 
 
@@ -458,9 +473,12 @@ class Ldpr(metaclass=ABCMeta):
         logger.info('Purging resource {}'.format(self.uid))
         refint = rdfly.config['referential_integrity']
         inbound = True if refint else inbound
+
+        for desc_uri in rdfly.get_descendants(self.uid):
+            rdfly.forget_rsrc(rdfly.uri_to_uuid(desc_uri), inbound)
+
         rdfly.forget_rsrc(self.uid, inbound)
 
-        # @TODO This could be a different event type.
         return RES_DELETED
 
 
@@ -479,6 +497,13 @@ class Ldpr(metaclass=ABCMeta):
 
         self.modify(RES_CREATED, remove_trp, add_trp)
 
+        # Resurrect descendants.
+        from lakesuperior.model.ldp_factory import LdpFactory
+        descendants = env.app_globals.rdfly.get_descendants(self.uid)
+        for desc_uri in descendants:
+            LdpFactory.from_stored(
+                    rdfly.uri_to_uid(desc_uri), strict=False).resurrect()
+
         return self.uri
 
 

+ 15 - 0
lakesuperior/store/ldp_rs/rsrc_centric_layout.py

@@ -418,6 +418,21 @@ class RsrcCentricLayout:
             else ds.graph(ctx_uri)[subj_uri : nsc['ldp'].contains : ])
 
 
+    def get_last_version_uid(self, uid):
+        """
+        Get the UID of the last version of a resource.
+
+        This can be used for tombstones too.
+        """
+        ver_info = self.get_version_info(uid)
+        last_version_uri = sorted(
+            [trp for trp in ver_info if trp[1] == nsc['fcrepo'].created],
+            key=lambda trp:trp[2]
+        )[-1][0]
+
+        return str(last_version_uri).split(VERS_CONT_LABEL + '/')[-1]
+
+
     def patch_rsrc(self, uid, qry):
         """
         Patch a resource with SPARQL-Update statements.

+ 50 - 2
tests/api/test_resource_api.py

@@ -364,7 +364,7 @@ class TestResourceCRUD:
 
     def test_soft_delete(self):
         """
-        Soft-delete a resource.
+        Soft-delete (bury) a resource.
         """
         uid = '/test_soft_delete01'
         rsrc_api.create_or_replace(uid)
@@ -375,7 +375,7 @@ class TestResourceCRUD:
 
     def test_resurrect(self):
         """
-        Resurrect a soft-deleted resource.
+        Restore (resurrect) a soft-deleted resource.
         """
         uid = '/test_soft_delete02'
         rsrc_api.create_or_replace(uid)
@@ -386,6 +386,54 @@ class TestResourceCRUD:
         assert nsc['ldp'].Resource in rsrc.ldp_types
 
 
+    def test_hard_delete(self):
+        """
+        Hard-delete (forget) a resource.
+        """
+        uid = '/test_hard_delete01'
+        rsrc_api.create_or_replace(uid)
+        rsrc_api.delete(uid, False)
+        with pytest.raises(ResourceNotExistsError):
+            rsrc_api.get(uid)
+        with pytest.raises(ResourceNotExistsError):
+            rsrc_api.resurrect(uid)
+
+
+    def test_delete_children(self):
+        """
+        Soft-delete a resource with children.
+        """
+        uid = '/test_soft_delete_children01'
+        rsrc_api.create_or_replace(uid)
+        for i in range(3):
+            rsrc_api.create_or_replace('{}/child{}'.format(uid, i))
+        rsrc_api.delete(uid)
+        with pytest.raises(TombstoneError):
+            rsrc_api.get(uid)
+        for i in range(3):
+            with pytest.raises(TombstoneError):
+                rsrc_api.get('{}/child{}'.format(uid, i))
+            # Cannot resurrect children of a tombstone.
+            with pytest.raises(TombstoneError):
+                rsrc_api.resurrect('{}/child{}'.format(uid, i))
+
+
+    def test_resurrect_children(self):
+        """
+        Resurrect a resource with its children.
+
+        This uses fixtures from the previous test.
+        """
+        uid = '/test_soft_delete_children01'
+        rsrc_api.resurrect(uid)
+        parent_rsrc = rsrc_api.get(uid)
+        assert nsc['ldp'].Resource in parent_rsrc.ldp_types
+        for i in range(3):
+            child_rsrc = rsrc_api.get('{}/child{}'.format(uid, i))
+            assert nsc['ldp'].Resource in child_rsrc.ldp_types
+
+
+
 @pytest.mark.usefixtures('db')
 class TestResourceVersioning:
     '''