Skip to content

Commit

Permalink
resources: more work on tree operations
Browse files Browse the repository at this point in the history
  • Loading branch information
ergo committed Oct 31, 2016
1 parent 7839266 commit 380d1d8
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 125 deletions.
48 changes: 4 additions & 44 deletions ziggurat_foundations/models/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ def users(self):
__table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}

def __repr__(self):
return '<Resource: %s, %s, id: %s>' % (self.resource_type,
self.resource_name,
self.resource_id,)
return '<Resource: %s, %s, id: %s position: %s>' % (
self.resource_type, self.resource_name, self.resource_id,
self.ordering)

@property
def __acl__(self):
Expand Down Expand Up @@ -228,44 +228,4 @@ def groups_for_perm(self, perm_name, group_ids=None,
return ResourceService.groups_for_perm(
self, perm_name=perm_name, group_ids=group_ids,
limit_group_permissions=limit_group_permissions,
db_session=db_session)

@classmethod
@deprecation.deprecate("Resource.subtree_deeper "
"will be removed in 0.8, use service instead")
def subtree_deeper(cls, object_id, limit_depth=1000000, flat=True,
db_session=None):
"""
.. deprecated:: 0.8
:param object_id:
:param limit_depth:
:param flat:
:param db_session:
:return:
"""
db_session = get_db_session(db_session)
return ResourceService.subtree_deeper(
object_id=object_id, limit_depth=limit_depth, flat=flat,
db_session=db_session)

@classmethod
@deprecation.deprecate("Resource.path_upper "
"will be removed in 0.8, use service instead")
def path_upper(cls, object_id, limit_depth=1000000, flat=True,
db_session=None):
"""
.. deprecated:: 0.8
:param object_id:
:param limit_depth:
:param flat:
:param db_session:
:return:
"""
db_session = get_db_session(db_session)
return ResourceService.path_upper(
object_id=object_id, limit_depth=limit_depth, flat=flat,
db_session=db_session)
db_session=db_session)
135 changes: 109 additions & 26 deletions ziggurat_foundations/models/services/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class ZigguratResourceTreePathException(ZigguratException):
pass


class ZigguratResourceOutOfBoundaryException(ZigguratException):
pass


class ZigguratResourceWrongPositionException(ZigguratException):
pass


class ResourceService(BaseService):
@classmethod
def get(cls, resource_id, db_session=None):
Expand Down Expand Up @@ -213,12 +221,12 @@ def groups_for_perm(cls, instance, perm_name, group_ids=None,
return group_perms

@classmethod
def subtree_deeper(cls, object_id, limit_depth=1000000, db_session=None):
def from_resource_deeper(cls, resource_id=None, limit_depth=1000000, db_session=None):
"""
This returns you subree of ordered objects relative
to the start object id currently only postgresql
This returns you subtree of ordered objects relative
to the start resource_id (currently only implemented in postgresql)
:param object_id:
:param resource_id:
:param limit_depth:
:param db_session:
:return:
Expand All @@ -239,11 +247,48 @@ def subtree_deeper(cls, object_id, limit_depth=1000000, db_session=None):
"""
db_session = get_db_session(db_session)
text_obj = sa.text(raw_q)
q = db_session.query(cls.model, 'depth', 'sorting',
'path').from_statement(
text_obj).params(
resource_id=object_id, depth=limit_depth)
return q
query = db_session.query(cls.model, 'depth', 'sorting', 'path')
query = query.from_statement(text_obj)
query = query.params(resource_id=resource_id, depth=limit_depth)
return query

@classmethod
def from_parent_deeper(cls, parent_id=None, limit_depth=1000000, db_session=None):
"""
This returns you subtree of ordered objects relative
to the start resource_id (currently only implemented in postgresql)
:param resource_id:
:param limit_depth:
:param db_session:
:return:
"""

if parent_id:
limiting_clause = 'res.parent_id = :parent_id'
else:
limiting_clause = 'res.parent_id is null'

raw_q = """
WITH RECURSIVE subtree AS (
SELECT res.*, 1 AS depth, res.ordering::CHARACTER VARYING AS sorting,
res.resource_id::CHARACTER VARYING AS path
FROM resources AS res WHERE {}
UNION ALL
SELECT res_u.*, depth+1 AS depth,
(st.sorting::CHARACTER VARYING || '/' || res_u.ordering::CHARACTER VARYING ) AS sorting,
(st.path::CHARACTER VARYING || '/' || res_u.resource_id::CHARACTER VARYING ) AS path
FROM resources res_u, subtree st
WHERE res_u.parent_id = st.resource_id
)
SELECT * FROM subtree WHERE depth<=:depth ORDER BY sorting;
""".format(limiting_clause)
db_session = get_db_session(db_session)
text_obj = sa.text(raw_q)
query = db_session.query(cls.model, 'depth', 'sorting', 'path')
query = query.from_statement(text_obj)
query = query.params(parent_id=parent_id, depth=limit_depth)
return query

@classmethod
def build_subtree_strut(self, result):
Expand All @@ -255,15 +300,14 @@ def build_subtree_strut(self, result):
:return:
"""
items = list(result)
struct_dict = OrderedDict()
root_elem = {'node': None, 'children': OrderedDict()}
if len(items) == 0:
return struct_dict[0]
root_elem = {'node': items[0].Resource, 'children': OrderedDict()}
for i, node in enumerate(items[1:]):
return root_elem
for i, node in enumerate(items):
new_elem = {'node': node.Resource, 'children': OrderedDict()}
path = list(map(int, node.path.split('/')))
parent_node = root_elem
normalized_path = path[1:-1]
normalized_path = path[:-1]
if normalized_path:
for path_part in normalized_path:
parent_node = parent_node['children'][path_part]
Expand Down Expand Up @@ -313,40 +357,79 @@ def lock_resource_for_update(cls, resource_id, db_session):

@classmethod
def move_to_position(cls, resource_id, to_position,
parent_id=None,
new_parent_id=None,
db_session=None):
"""
Moves node to new location in the tree
:param resource_id: resource to move
:param to_position: new position
:param parent_id: current parent id
:param new_parent_id: new parent id
:param db_session:
:return:
"""
db_session = get_db_session(db_session)
parent = None
new_parent = None

if parent_id:
parent = cls.lock_resource_for_update(
resource_id=parent_id,
db_session=db_session)
if not parent:
raise ZigguratResourceTreeMissingException('Parent node not found')
item_count = 0
resource = cls.lock_resource_for_update(
resource_id=resource_id,
db_session=db_session)
parent = cls.lock_resource_for_update(
resource_id=resource.parent_id,
db_session=db_session)

if new_parent_id == resource.parent_id and new_parent_id is not None:
raise ZigguratResourceTreePathException(
'New parent is the same as old parent')
if new_parent_id:
new_parent = cls.lock_resource_for_update(
resource_id=new_parent_id,
db_session=db_session)
if not new_parent:
raise ZigguratResourceTreeMissingException('New parent node not found')
raise ZigguratResourceTreeMissingException(
'New parent node not found')

result = ResourceService.path_upper(new_parent_id,
db_session=db_session)
path_ids = [r.resource_id for r in result]
if resource_id in path_ids:
raise ZigguratResourceTreePathException(
'Trying to insert node into itself')

if not new_parent_id and to_position == resource.ordering:
raise ZigguratResourceWrongPositionException(
'Position is the same as old one')
if to_position < 1:
raise ZigguratResourceOutOfBoundaryException(
'Position is lower than 1')

query = db_session.query(cls.model.resource_id)
parent_id = parent.resource_id if parent else new_parent_id
query = query.filter(cls.model.parent_id == parent_id)
item_count = query.count()

if (to_position > item_count and new_parent_id is None) or \
(to_position > item_count + 1 and new_parent_id is not None):
raise ZigguratResourceOutOfBoundaryException(
'Position is higher than item count')

# move on same branch
if new_parent_id is None:
order_range = list(sorted((resource.ordering, to_position)))
move_down = resource.ordering > to_position

query = db_session.query(cls.model)
query = query.filter(cls.model.parent_id == parent.resource_id)
query = query.filter(cls.model.ordering.between(*order_range))
if move_down:
query.update({cls.model.ordering: cls.model.ordering + 1},
synchronize_session=False)
else:
query.update({cls.model.ordering: cls.model.ordering - 1},
synchronize_session=False)
db_session.flush()
resource.ordering = to_position
db_session.flush()
# move between branches
elif new_parent_id is not None or resource.parent_id is not None:
pass
return True

0 comments on commit 380d1d8

Please sign in to comment.