Skip to content
This repository has been archived by the owner on Feb 21, 2019. It is now read-only.

Feature/806 add label filter and sort content by label in contents endpoint #45

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 32 additions & 4 deletions backend/tracim_backend/lib/core/content.py
Expand Up @@ -18,6 +18,7 @@
from sqlalchemy.orm import aliased
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.attributes import get_history
from sqlalchemy.orm.attributes import QueryableAttribute
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.elements import and_
Expand Down Expand Up @@ -1002,14 +1003,19 @@ def _get_all_query(
self,
parent_id: int = None,
content_type_slug: str = CONTENT_TYPES.Any_SLUG,
workspace: Workspace = None
workspace: Workspace = None,
label:str = None,
order_by_properties: typing.List[typing.Union[str, QueryableAttribute]] = (),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some problems here:

  • typing say List, default value is a tuple (it should be empty list)
  • we can't give an list instance here, example:
>>> def foo(a=[]):
...   a.append(1)
...   print(a)
... 
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]
>>> foo()
[1, 1, 1, 1]

Prefer:

def foo(a=None):
  a = a or []  # FDV

FDV = Forced Default Value

) -> Query:
"""
Extended filter for better "get all data" query
:param parent_id: filter by parent_id
:param content_type_slug: filter by content_type slug
:param workspace: filter by workspace
:return:
:param order_by_properties: filter by properties can be both string of
attribute or attribute of Model object from sqlalchemy(preferred way,
QueryableAttribute object)
:return: Query object
"""
assert parent_id is None or isinstance(parent_id, int)
assert content_type_slug is not None
Expand All @@ -1028,11 +1034,33 @@ def _get_all_query(
resultset = resultset.filter(Content.parent_id==parent_id)
if parent_id == 0 or parent_id is False:
resultset = resultset.filter(Content.parent_id == None)
if label:
resultset = resultset.filter(Content.label.ilike('%{}%'.format(label))) # nopep8

for _property in order_by_properties:
resultset = resultset.order_by(_property)

return resultset

def get_all(self, parent_id: int=None, content_type: str=CONTENT_TYPES.Any_SLUG, workspace: Workspace=None) -> typing.List[Content]:
return self._get_all_query(parent_id, content_type, workspace).all()
def get_all(
self,
parent_id: int=None,
content_type: str=CONTENT_TYPES.Any_SLUG,
workspace: Workspace=None,
label: str=None,
order_by_properties: typing.List[typing.Union[str, QueryableAttribute]] = () # nopep8
) -> typing.List[Content]:
"""
Return all content using some filters
:param parent_id: filter by parent_id
:param content_type: filter by content_type slug
:param workspace: filter by workspace
:param order_by_properties: filter by properties can be both string of
attribute or attribute of Model object from sqlalchemy(preferred way,
QueryableAttribute object)
:return: List of contents
"""
return self._get_all_query(parent_id, content_type, workspace, label, order_by_properties).all()

# TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
# def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> typing.List[Content]:
Expand Down
2 changes: 2 additions & 0 deletions backend/tracim_backend/models/context_models.py
Expand Up @@ -280,6 +280,7 @@ def __init__(
show_deleted: int = 0,
show_active: int = 1,
content_type: str = None,
label: str = None,
offset: int = None,
limit: int = None,
) -> None:
Expand All @@ -290,6 +291,7 @@ def __init__(
self.show_active = bool(show_active)
self.limit = limit
self.offset = offset
self.label = label
self.content_type = content_type


Expand Down
132 changes: 85 additions & 47 deletions backend/tracim_backend/tests/functional/test_workspaces.py
Expand Up @@ -2120,18 +2120,16 @@ def test_api__get_workspace_content__ok_200__get_default(self):
# TODO - G.M - 30-05-2018 - Check this test
assert len(res) == 3
content = res[0]
assert content['content_id'] == 1
assert content['content_type'] == 'folder'
assert content['content_id'] == 11
assert content['content_type'] == 'html-document'
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'Tools'
assert content['parent_id'] is None
assert content['label'] == 'Current Menu'
assert content['parent_id'] == 2
assert content['show_in_ui'] is True
assert content['slug'] == 'tools'
assert content['slug'] == 'current-menu'
assert content['status'] == 'open'
assert len(content['sub_content_types']) > 1
assert 'comment' in content['sub_content_types']
assert 'folder' in content['sub_content_types']
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 1
content = res[1]
assert content['content_id'] == 2
Expand All @@ -2148,16 +2146,18 @@ def test_api__get_workspace_content__ok_200__get_default(self):
assert 'folder' in content['sub_content_types']
assert content['workspace_id'] == 1
content = res[2]
assert content['content_id'] == 11
assert content['content_type'] == 'html-document'
assert content['content_id'] == 1
assert content['content_type'] == 'folder'
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'Current Menu'
assert content['parent_id'] == 2
assert content['label'] == 'Tools'
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'] == 'current-menu'
assert content['slug'] == 'tools'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert len(content['sub_content_types']) > 1
assert 'comment' in content['sub_content_types']
assert 'folder' in content['sub_content_types']
assert content['workspace_id'] == 1

def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
Expand Down Expand Up @@ -2216,20 +2216,20 @@ def test_api__get_workspace_content__ok_200__get_all_root_content__legacy_html_s
).json_body # nopep8
# TODO - G.M - 30-05-2018 - Check this test
assert len(res) == 4
content = res[1]
content = res[0]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 15
assert content['content_id'] == 17
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'] == 'new-fruit-salad'
assert content['slug'].startswith('bad-fruit-salad')
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 3

content = res[2]
content = res[1]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 16
assert content['is_archived'] is True
Expand All @@ -2244,13 +2244,13 @@ def test_api__get_workspace_content__ok_200__get_all_root_content__legacy_html_s

content = res[3]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 17
assert content['content_id'] == 15
assert content['is_archived'] is False
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'].startswith('bad-fruit-salad')
assert content['slug'] == 'new-fruit-salad'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 3
Expand Down Expand Up @@ -2279,20 +2279,20 @@ def test_api__get_workspace_content__ok_200__get_all_root_content(self):
).json_body # nopep8
# TODO - G.M - 30-05-2018 - Check this test
assert len(res) == 4
content = res[1]
content = res[0]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 15
assert content['content_id'] == 17
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'] == 'new-fruit-salad'
assert content['slug'].startswith('bad-fruit-salad')
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 3

content = res[2]
content = res[1]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 16
assert content['is_archived'] is True
Expand All @@ -2307,13 +2307,51 @@ def test_api__get_workspace_content__ok_200__get_all_root_content(self):

content = res[3]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 17
assert content['content_id'] == 15
assert content['is_archived'] is False
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'].startswith('bad-fruit-salad')
assert content['slug'] == 'new-fruit-salad'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 3

def test_api__get_workspace_content__ok_200__get_all_root_content_filter_by_label(self): # nopep8
"""
Check obtain workspace all root contents
"""
params = {
'parent_id': 0,
'show_archived': 1,
'show_deleted': 1,
'show_active': 1,
'label': 'ew'
}
self.testapp.authorization = (
'Basic',
(
'bob@fsf.local',
'foobarbaz'
)
)
res = self.testapp.get(
'/api/v2/workspaces/3/contents',
status=200,
params=params,
).json_body # nopep8
# TODO - G.M - 30-05-2018 - Check this test
assert len(res) == 1
content = res[0]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 15
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['parent_id'] is None
assert content['show_in_ui'] is True
assert content['slug'] == 'new-fruit-salad'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'}
assert content['workspace_id'] == 3
Expand Down Expand Up @@ -2660,26 +2698,26 @@ def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_leg
assert content['content_id']
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'test_page'
assert content['label'] == 'test_html_page'
assert content['parent_id'] == 1
assert content['show_in_ui'] is True
assert content['slug'] == 'test-page'
assert content['slug'] == 'test-html-page'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'} # nopep8
assert content['workspace_id'] == 1
assert res[0]['content_id'] != res[1]['content_id']
content = res[1]
assert content['content_type'] == 'html-document'
assert content['content_id']
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'test_html_page'
assert content['label'] == 'test_page'
assert content['parent_id'] == 1
assert content['show_in_ui'] is True
assert content['slug'] == 'test-html-page'
assert content['slug'] == 'test-page'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'} # nopep8
assert content['workspace_id'] == 1
assert res[0]['content_id'] != res[1]['content_id']

def test_api__get_workspace_content__ok_200__get_all_folder_content(self):
"""
Expand Down Expand Up @@ -2707,13 +2745,13 @@ def test_api__get_workspace_content__ok_200__get_all_folder_content(self):
assert len(res) == 3
content = res[0]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 12
assert content['content_id'] == 14
assert content['is_archived'] is False
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['parent_id'] == 10
assert content['show_in_ui'] is True
assert content['slug'] == 'new-fruit-salad'
assert content['slug'].startswith('bad-fruit-salad')
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'} # nopep8
assert content['workspace_id'] == 2
Expand All @@ -2733,13 +2771,13 @@ def test_api__get_workspace_content__ok_200__get_all_folder_content(self):

content = res[2]
assert content['content_type'] == 'html-document'
assert content['content_id'] == 14
assert content['content_id'] == 12
assert content['is_archived'] is False
assert content['is_deleted'] is True
assert content['label'].startswith('Bad Fruit Salad')
assert content['is_deleted'] is False
assert content['label'] == 'New Fruit Salad'
assert content['parent_id'] == 10
assert content['show_in_ui'] is True
assert content['slug'].startswith('bad-fruit-salad')
assert content['slug'] == 'new-fruit-salad'
assert content['status'] == 'open'
assert set(content['sub_content_types']) == {'comment'} # nopep8
assert content['workspace_id'] == 2
Expand Down
17 changes: 14 additions & 3 deletions backend/tracim_backend/views/core_api/schemas.py
Expand Up @@ -440,6 +440,12 @@ class FilterContentQuerySchema(marshmallow.Schema):
default=CONTENT_TYPES.Any_SLUG,
validate=all_content_types_validator
)
label = marshmallow.fields.String(
example='myfilename',
default=None,
allow_none=True,
description='Filter by content label'
)

@post_load
def make_content_filter(self, data):
Expand Down Expand Up @@ -796,11 +802,14 @@ def make_move_params(self, data):
class ContentCreationSchema(marshmallow.Schema):
label = marshmallow.fields.String(
example='contract for client XXX',
description='Title of the content to create'
description='Title of the content to create',
validate=Length(min=1),
required=True
)
content_type = marshmallow.fields.String(
example='html-document',
validate=all_content_types_validator,
required=True,
)
parent_id = marshmallow.fields.Integer(
example=35,
Expand Down Expand Up @@ -982,7 +991,8 @@ class CommentSchema(marshmallow.Schema):

class SetCommentSchema(marshmallow.Schema):
raw_content = marshmallow.fields.String(
example='<p>This is just an html comment !</p>'
example='<p>This is just an html comment !</p>',
validate=Length(min=1)
)

@post_load()
Expand All @@ -994,7 +1004,8 @@ class ContentModifyAbstractSchema(marshmallow.Schema):
label = marshmallow.fields.String(
required=True,
example='contract for client XXX',
description='New title of the content'
description='New title of the content',
validate=Length(min=1)
)


Expand Down
3 changes: 3 additions & 0 deletions backend/tracim_backend/views/core_api/workspace_controller.py
Expand Up @@ -32,6 +32,7 @@
from tracim_backend.models.context_models import ContentInContext
from tracim_backend.models.context_models import UserRoleWorkspaceInContext
from tracim_backend.models.data import ActionDescription
from tracim_backend.models.data import Content
from tracim_backend.models.data import UserRoleInWorkspace
from tracim_backend.models.revision_protection import new_revision
from tracim_backend.models.roles import WorkspaceRoles
Expand Down Expand Up @@ -414,6 +415,8 @@ def workspace_content(
parent_id=content_filter.parent_id,
workspace=request.current_workspace,
content_type=content_filter.content_type or CONTENT_TYPES.Any_SLUG,
label=content_filter.label,
order_by_properties=[Content.label]
)
contents = [
api.get_content_in_context(content) for content in contents
Expand Down