Remove references to node from projects when the node is deleted.
Removes node references in project fields header_node, nodes_blog, nodes_featured, nodes_latest.
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
|
import collections
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from flask import current_app
|
|
||||||
from werkzeug import exceptions as wz_exceptions
|
from werkzeug import exceptions as wz_exceptions
|
||||||
|
|
||||||
|
from pillar import current_app
|
||||||
import pillar.markdown
|
import pillar.markdown
|
||||||
from pillar.api.activities import activity_subscribe, activity_object_add
|
from pillar.api.activities import activity_subscribe, activity_object_add
|
||||||
from pillar.api.file_storage_backends.gcs import update_file_name
|
from pillar.api.file_storage_backends.gcs import update_file_name
|
||||||
from pillar.api.node_types import PILLAR_NAMED_NODE_TYPES
|
from pillar.api.node_types import PILLAR_NAMED_NODE_TYPES
|
||||||
|
from pillar.api.utils import random_etag
|
||||||
from pillar.api.utils.authorization import check_permissions
|
from pillar.api.utils.authorization import check_permissions
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -243,6 +246,44 @@ def nodes_set_default_picture(nodes):
|
|||||||
|
|
||||||
def before_deleting_node(node: dict):
|
def before_deleting_node(node: dict):
|
||||||
check_permissions('nodes', node, 'DELETE')
|
check_permissions('nodes', node, 'DELETE')
|
||||||
|
remove_project_references(node)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_project_references(node):
|
||||||
|
project_id = node.get('project')
|
||||||
|
if not project_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
node_id = node['_id']
|
||||||
|
log.info('Removing references to node %s from project %s', node_id, project_id)
|
||||||
|
|
||||||
|
projects_col = current_app.db('projects')
|
||||||
|
project = projects_col.find_one({'_id': project_id})
|
||||||
|
updates = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
if project.get('header_node') == node_id:
|
||||||
|
updates['$unset']['header_node'] = node_id
|
||||||
|
|
||||||
|
project_reference_lists = ('nodes_blog', 'nodes_featured', 'nodes_latest')
|
||||||
|
for list_name in project_reference_lists:
|
||||||
|
references = project.get(list_name)
|
||||||
|
if not references:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
references.remove(node_id)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
updates['$set'][list_name] = references
|
||||||
|
|
||||||
|
if not updates:
|
||||||
|
return
|
||||||
|
|
||||||
|
updates['$set']['_etag'] = random_etag()
|
||||||
|
result = projects_col.update_one({'_id': project_id}, updates)
|
||||||
|
if result.modified_count != 1:
|
||||||
|
log.warning('Removing references to node %s from project %s resulted in %d modified documents (expected 1)',
|
||||||
|
node_id, project_id, result.modified_count)
|
||||||
|
|
||||||
|
|
||||||
def after_deleting_node(item):
|
def after_deleting_node(item):
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import typing
|
import typing
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
@@ -633,3 +634,56 @@ class TaggedNodesTest(AbstractPillarTest):
|
|||||||
resp = do_query()
|
resp = do_query()
|
||||||
for node in resp:
|
for node in resp:
|
||||||
self.assertNotIn('view_progress', node)
|
self.assertNotIn('view_progress', node)
|
||||||
|
|
||||||
|
|
||||||
|
class NodesReferencedByProjectTest(AbstractPillarTest):
|
||||||
|
def setUp(self, **kwargs):
|
||||||
|
super().setUp(**kwargs)
|
||||||
|
node = copy.deepcopy(ctd.EXAMPLE_NODE)
|
||||||
|
self.pid, self.project = self.ensure_project_exists(
|
||||||
|
project_overrides={'picture_header':None,
|
||||||
|
'picture_square': None}
|
||||||
|
)
|
||||||
|
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
|
||||||
|
|
||||||
|
node['project'] = self.pid
|
||||||
|
self.node_id = self.create_node(node)
|
||||||
|
self.node_etag = node['_etag']
|
||||||
|
|
||||||
|
with self.app.app_context():
|
||||||
|
self.app.db('projects').update(
|
||||||
|
{'_id': self.pid},
|
||||||
|
{'$set': {
|
||||||
|
'header_node': self.node_id,
|
||||||
|
'nodes_blog': [self.node_id],
|
||||||
|
'nodes_featured': [self.node_id],
|
||||||
|
'nodes_latest': [self.node_id],
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_node(self):
|
||||||
|
with self.app.app_context():
|
||||||
|
self.delete(f'/api/nodes/{self.node_id}',
|
||||||
|
auth_token='token',
|
||||||
|
headers={'If-Match': self.node_etag},
|
||||||
|
expected_status=204)
|
||||||
|
|
||||||
|
node_after = self.app.db('nodes').find_one(self.node_id)
|
||||||
|
self.assertTrue(node_after.get('_deleted'))
|
||||||
|
|
||||||
|
project_after = self.app.db('projects').find_one(self.pid)
|
||||||
|
self.assertIsNone(project_after.get('header_node'))
|
||||||
|
self.assertNotEqual(self.project['_etag'], project_after['_etag'])
|
||||||
|
self.assertNotIn(self.node_id, project_after['nodes_blog'])
|
||||||
|
self.assertNotIn(self.node_id, project_after['nodes_featured'])
|
||||||
|
self.assertNotIn(self.node_id, project_after['nodes_latest'])
|
||||||
|
|
||||||
|
# Verifying that the project is still valid
|
||||||
|
from pillar.api.utils import remove_private_keys
|
||||||
|
self.put(f'/api/projects/{self.pid}', json=remove_private_keys(project_after),
|
||||||
|
etag=project_after['_etag'],
|
||||||
|
auth_token='token')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user