tracim.lib.core.content   F
last analyzed

Complexity

Total Complexity 207

Size/Duplication

Total Lines 1250
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 746
dl 0
loc 1250
rs 0.6314
c 0
b 0
f 0
wmc 207

2 Functions

Rating   Name   Duplication   Size   Complexity  
B compare_content_for_sorting_by_type_and_name() 0 37 6
A compare_tree_items_for_sorting_by_type_and_name() 0 5 1

56 Methods

Rating   Name   Duplication   Size   Complexity  
A ContentApi.undelete() 0 4 1
B ContentApi.move() 0 15 6
B ContentApi.__revisions_real_base_query() 0 21 6
A ContentApi.content_under_deleted() 0 7 4
B ContentApi.create() 0 25 6
A ContentApi.set_allowed_content() 0 14 1
A ContentApi.generate_folder_label() 0 20 2
A ContentApi.get_revision_join() 0 12 1
D ContentApi.copy() 0 47 8
B ContentApi.get_one_by_label_and_parent() 0 40 3
A ContentApi.sort_tree_items() 0 14 2
A ContentApi.sort_content() 0 7 1
A ContentApi.get_base_query() 0 5 1
B ContentApi.get_all() 0 16 7
B ContentApi.find_one_by_unique_property() 0 27 1
C ContentApi.__real_base_query() 0 30 7
A ContentApi.mark_read__all() 0 10 2
A ContentApi.delete() 0 12 1
A ContentApi.move_recursively() 0 9 3
B ContentApi.get_folder_with_workspace_path_labels() 0 42 3
A ContentApi.update_file_data() 0 14 3
B ContentApi.get_all_with_filter() 0 17 5
A ContentApi.__init__() 0 22 2
A ContentApi.create_comment() 0 14 3
A ContentApi.mark_read__workspace() 0 11 2
A ContentApi.search() 0 16 4
A ContentApi.update_content() 0 10 4
A ContentApi.filter_query_for_content_label_as_path() 0 47 2
F ContentApi.get_child_folders() 0 50 12
D ContentApi.save() 0 37 8
A ContentApi.copy_children() 0 3 2
B ContentApi.mark_unread() 0 17 6
A ContentApi.get_one() 0 9 3
A ContentApi.get_all_types() 0 7 2
A ContentApi.get_canonical_query() 0 7 1
A ContentApi.get_all_without_exception() 0 9 3
A ContentApi.get_one_by_label_and_parent_labels() 0 52 3
F ContentApi.mark_read() 0 44 11
A ContentApi.get_children() 0 17 3
B ContentApi.show() 0 27 1
A ContentApi._revisions_base_query() 0 16 4
A ContentApi.set_status() 0 6 2
B ContentApi._hard_filtered_base_query() 0 34 4
F ContentApi.get_last_active() 0 34 10
A ContentApi.flush() 0 2 1
A ContentApi.archive() 0 12 1
C ContentApi.get_last_unread() 0 56 10
A ContentApi.exclude_unavailable() 0 12 4
A ContentApi.content_under_archived() 0 7 4
A ContentApi._base_query() 0 13 4
A ContentApi.get_one_from_revision() 0 19 2
A ContentApi.get_one_revision_filepath() 0 12 1
A ContentApi.get_one_revision() 0 11 2
A ContentApi.do_notify() 0 11 1
A ContentApi.unarchive() 0 4 1
A ContentApi.get_keywords() 0 13 3

How to fix   Complexity   

Complexity

Complex classes like tracim.lib.core.content often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
0 ignored issues
show
Coding Style introduced by
This module should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
coding-style introduced by
Too many lines in module (1250/1000)
Loading history...
2
from contextlib import contextmanager
3
4
import os
5
6
from operator import itemgetter
7
8
import transaction
9
from sqlalchemy import func
10
from sqlalchemy.orm import Query
11
12
__author__ = 'damien'
13
14
import datetime
0 ignored issues
show
introduced by
standard import "import datetime" should be placed before "import transaction"
Loading history...
15
import re
0 ignored issues
show
introduced by
standard import "import re" should be placed before "import transaction"
Loading history...
16
import typing
0 ignored issues
show
introduced by
standard import "import typing" should be placed before "import transaction"
Loading history...
17
18
from tracim.lib.utils.translation import fake_translator as _
19
20
from depot.manager import DepotManager
21
from depot.io.utils import FileIntent
22
23
import sqlalchemy
0 ignored issues
show
introduced by
Imports from package sqlalchemy are not grouped
Loading history...
24
from sqlalchemy.orm import aliased
25
from sqlalchemy.orm import joinedload
26
from sqlalchemy.orm.attributes import get_history
27
from sqlalchemy.orm.session import Session
28
from sqlalchemy import desc
29
from sqlalchemy import distinct
30
from sqlalchemy import or_
31
from sqlalchemy.sql.elements import and_
32
from tracim.lib.utils.utils import cmp_to_key
0 ignored issues
show
introduced by
Imports from package tracim are not grouped
Loading history...
33
from tracim.lib.core.notifications import NotifierFactory
34
from tracim.exceptions import SameValueError
35
from tracim.lib.utils.utils import current_date_for_filename
36
from tracim.models.revision_protection import new_revision
37
from tracim.models.auth import User
38
from tracim.models.data import ActionDescription
39
from tracim.models.data import ContentStatus
40
from tracim.models.data import ContentRevisionRO
41
from tracim.models.data import Content
42
from tracim.models.data import ContentType
43
from tracim.models.data import NodeTreeItem
44
from tracim.models.data import RevisionReadStatus
45
from tracim.models.data import UserRoleInWorkspace
46
from tracim.models.data import Workspace
47
48
49
def compare_content_for_sorting_by_type_and_name(
0 ignored issues
show
Coding Style Naming introduced by
The name compare_content_for_sorting_by_type_and_name does not conform to the function naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
50
        content1: Content,
51
        content2: Content
52
) -> int:
53
    """
54
    :param content1:
55
    :param content2:
56
    :return:    1 if content1 > content2
57
                -1 if content1 < content2
58
                0 if content1 = content2
59
    """
60
61
    if content1.type == content2.type:
62
        if content1.get_label().lower()>content2.get_label().lower():
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
63
            return 1
64
        elif content1.get_label().lower()<content2.get_label().lower():
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
65
            return -1
66
        return 0
67
    else:
68
        # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
69
        content_type_order = [
70
            ContentType.Folder,
71
            ContentType.Page,
72
            ContentType.Thread,
73
            ContentType.File,
74
        ]
75
76
        content_1_type_index = content_type_order.index(content1.type)
77
        content_2_type_index = content_type_order.index(content2.type)
78
        result = content_1_type_index - content_2_type_index
79
80
        if result < 0:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
81
            return -1
82
        elif result > 0:
83
            return 1
84
        else:
85
            return 0
86
87
88
def compare_tree_items_for_sorting_by_type_and_name(
0 ignored issues
show
Coding Style Naming introduced by
The name compare_tree_items_for_sorting_by_type_and_name does not conform to the function naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
89
        item1: NodeTreeItem,
90
        item2: NodeTreeItem
91
) -> int:
92
    return compare_content_for_sorting_by_type_and_name(item1.node, item2.node)
93
94
95
class ContentApi(object):
0 ignored issues
show
Coding Style introduced by
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
best-practice introduced by
Too many public methods (50/20)
Loading history...
best-practice introduced by
Too many instance attributes (10/7)
Loading history...
96
97
    SEARCH_SEPARATORS = ',| '
98
    SEARCH_DEFAULT_RESULT_NB = 50
99
100
    DISPLAYABLE_CONTENTS = (
101
        ContentType.Folder,
102
        ContentType.File,
103
        ContentType.Comment,
104
        ContentType.Thread,
105
        ContentType.Page,
106
    )
107
108
    def __init__(
0 ignored issues
show
best-practice introduced by
Too many arguments (10/5)
Loading history...
109
            self,
110
            session: Session,
111
            current_user: typing.Optional[User],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable typing does not seem to be defined.
Loading history...
112
            config,
113
            show_archived: bool = False,
114
            show_deleted: bool = False,
115
            show_temporary: bool = False,
116
            all_content_in_treeview: bool = True,
117
            force_show_all_types: bool = False,
118
            disable_user_workspaces_filter: bool = False,
119
    ) -> None:
120
        self._session = session
121
        self._user = current_user
122
        self._config = config
123
        self._user_id = current_user.user_id if current_user else None
124
        self._show_archived = show_archived
125
        self._show_deleted = show_deleted
126
        self._show_temporary = show_temporary
127
        self._show_all_type_of_contents_in_treeview = all_content_in_treeview
128
        self._force_show_all_types = force_show_all_types
129
        self._disable_user_workspaces_filter = disable_user_workspaces_filter
130
131
    @contextmanager
132
    def show(
133
            self,
134
            show_archived: bool=False,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
135
            show_deleted: bool=False,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
136
            show_temporary: bool=False,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
137
    ) -> typing.Generator['ContentApi', None, None]:
138
        """
139
        Use this method as context manager to update show_archived,
140
        show_deleted and show_temporary properties during context.
141
        :param show_archived: show archived contents
142
        :param show_deleted:  show deleted contents
143
        :param show_temporary:  show temporary contents
144
        """
145
        previous_show_archived = self._show_archived
146
        previous_show_deleted = self._show_deleted
147
        previous_show_temporary = self._show_temporary
148
149
        try:
150
            self._show_archived = show_archived
151
            self._show_deleted = show_deleted
152
            self._show_temporary = show_temporary
153
            yield self
154
        finally:
155
            self._show_archived = previous_show_archived
156
            self._show_deleted = previous_show_deleted
157
            self._show_temporary = previous_show_temporary
158
159
    def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
160
        """
161
        Return the Content/ContentRevision query join condition
162
        :return: Content/ContentRevision query join condition
163
        """
164
        return and_(Content.id == ContentRevisionRO.content_id,
165
                    ContentRevisionRO.revision_id == self._session.query(
166
                        ContentRevisionRO.revision_id)
167
                    .filter(ContentRevisionRO.content_id == Content.id)
168
                    .order_by(ContentRevisionRO.revision_id.desc())
169
                    .limit(1)
170
                    .correlate(Content))
171
172
    def get_canonical_query(self) -> Query:
173
        """
174
        Return the Content/ContentRevision base query who join these table on the last revision.
175
        :return: Content/ContentRevision Query
176
        """
177
        return self._session.query(Content)\
178
            .join(ContentRevisionRO, self.get_revision_join())
179
180
    @classmethod
181
    def sort_tree_items(
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
182
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
183
        content_list: typing.List[NodeTreeItem],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable typing does not seem to be defined.
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
184
    )-> typing.List[NodeTreeItem]:
185
        news = []
186
        for item in content_list:
187
            news.append(item)
188
189
        content_list.sort(key=cmp_to_key(
190
            compare_tree_items_for_sorting_by_type_and_name,
191
        ))
192
193
        return content_list
194
195
    @classmethod
196
    def sort_content(
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
197
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
198
        content_list: typing.List[Content],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable typing does not seem to be defined.
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
199
    ) -> typing.List[Content]:
200
        content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
201
        return content_list
202
203
    def __real_base_query(
204
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
205
        workspace: Workspace = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
206
    ) -> Query:
207
        result = self.get_canonical_query()
208
209
        # Exclude non displayable types
210
        if not self._force_show_all_types:
211
            result = result.filter(Content.type.in_(self.DISPLAYABLE_CONTENTS))
212
213
        if workspace:
214
            result = result.filter(Content.workspace_id == workspace.workspace_id)
215
216
        # Security layer: if user provided, filter
217
        # with user workspaces privileges
218
        if self._user and not self._disable_user_workspaces_filter:
219
            user = self._session.query(User).get(self._user_id)
220
            # Filter according to user workspaces
221
            workspace_ids = [r.workspace_id for r in user.roles \
222
                             if r.role>=UserRoleInWorkspace.READER]
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
223
            result = result.filter(or_(
224
                Content.workspace_id.in_(workspace_ids),
225
                # And allow access to non workspace document when he is owner
226
                and_(
227
                    Content.workspace_id == None,
0 ignored issues
show
introduced by
Comparison to None should be 'expr is None'
Loading history...
228
                    Content.owner_id == self._user_id,
229
                )
230
            ))
231
232
        return result
233
234
    def _base_query(self, workspace: Workspace=None) -> Query:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
235
        result = self.__real_base_query(workspace)
236
237
        if not self._show_deleted:
238
            result = result.filter(Content.is_deleted==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
239
240
        if not self._show_archived:
241
            result = result.filter(Content.is_archived==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
242
243
        if not self._show_temporary:
244
            result = result.filter(Content.is_temporary==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
245
246
        return result
247
248
    def __revisions_real_base_query(
249
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
250
        workspace: Workspace=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
251
    ) -> Query:
252
        result = self._session.query(ContentRevisionRO)
253
254
        # Exclude non displayable types
255
        if not self._force_show_all_types:
256
            result = result.filter(Content.type.in_(self.DISPLAYABLE_CONTENTS))
257
258
        if workspace:
259
            result = result.filter(ContentRevisionRO.workspace_id==workspace.workspace_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
260
261
        if self._user:
262
            user = self._session.query(User).get(self._user_id)
263
            # Filter according to user workspaces
264
            workspace_ids = [r.workspace_id for r in user.roles \
265
                             if r.role>=UserRoleInWorkspace.READER]
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
266
            result = result.filter(ContentRevisionRO.workspace_id.in_(workspace_ids))
267
268
        return result
269
270
    def _revisions_base_query(
271
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
272
        workspace: Workspace=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
273
    ) -> Query:
274
        result = self.__revisions_real_base_query(workspace)
275
276
        if not self._show_deleted:
277
            result = result.filter(ContentRevisionRO.is_deleted==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
278
279
        if not self._show_archived:
280
            result = result.filter(ContentRevisionRO.is_archived==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
281
282
        if not self._show_temporary:
283
            result = result.filter(Content.is_temporary==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
284
285
        return result
286
287
    def _hard_filtered_base_query(
288
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
289
        workspace: Workspace=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
290
    ) -> Query:
291
        """
292
        If set to True, then filterign on is_deleted and is_archived will also
293
        filter parent properties. This is required for search() function which
294
        also search in comments (for example) which may be 'not deleted' while
295
        the associated content is deleted
296
297
        :param hard_filtering:
298
        :return:
299
        """
300
        result = self.__real_base_query(workspace)
301
302
        if not self._show_deleted:
303
            parent = aliased(Content)
304
            result = result.join(parent, Content.parent).\
305
                filter(Content.is_deleted==False).\
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
306
                filter(parent.is_deleted==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
307
308
        if not self._show_archived:
309
            parent = aliased(Content)
310
            result = result.join(parent, Content.parent).\
311
                filter(Content.is_archived==False).\
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
312
                filter(parent.is_archived==False)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
313
314
        if not self._show_temporary:
315
            parent = aliased(Content)
316
            result = result.join(parent, Content.parent). \
317
                filter(Content.is_temporary == False). \
0 ignored issues
show
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
318
                filter(parent.is_temporary == False)
0 ignored issues
show
introduced by
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
319
320
        return result
321
322
    def get_base_query(
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
323
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
324
        workspace: Workspace,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
325
    ) -> Query:
326
        return self._base_query(workspace)
327
328
    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (199/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
best-practice introduced by
Too many arguments (6/5)
Loading history...
329
        """
330
        This method returns child items (folders or items) for left bar treeview.
331
332
        :param parent:
333
        :param workspace:
334
        :param filter_by_allowed_content_types:
335
        :param removed_item_ids:
336
        :param allowed_node_types: This parameter allow to hide folders for which the given type of content is not allowed.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (123/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
337
               For example, if you want to move a Page from a folder to another, you should show only folders that accept pages
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (127/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
338
        :return:
339
        """
340
        filter_by_allowed_content_types = filter_by_allowed_content_types or []  # FDV
341
        removed_item_ids = removed_item_ids or []  # FDV
342
343
        if not allowed_node_types:
344
            allowed_node_types = [ContentType.Folder]
345
        elif allowed_node_types==ContentType.Any:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
346
            allowed_node_types = ContentType.all()
347
348
        parent_id = parent.content_id if parent else None
349
        folders = self._base_query(workspace).\
350
            filter(Content.parent_id==parent_id).\
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
351
            filter(Content.type.in_(allowed_node_types)).\
352
            filter(Content.content_id.notin_(removed_item_ids)).\
353
            all()
354
355
        if not filter_by_allowed_content_types or \
356
                        len(filter_by_allowed_content_types)<=0:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
357
            # Standard case for the left treeview: we want to show all contents
358
            # in the left treeview... so we still filter because for example
359
            # comments must not appear in the treeview
360
            return [folder for folder in folders \
361
                    if folder.type in ContentType.allowed_types_for_folding()]
362
363
        # Now this is a case of Folders only (used for moving content)
364
        # When moving a content, you must get only folders that allow to be filled
365
        # with the type of content you want to move
366
        result = []
367
        for folder in folders:
368
            for allowed_content_type in filter_by_allowed_content_types:
369
370
                is_folder = folder.type == ContentType.Folder
371
                content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (106/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
Comparison to True should be just 'expr' or 'expr is True'
Loading history...
372
373
                if is_folder and content_type__allowed:
374
                    result.append(folder)
375
                    break
376
377
        return result
378
379
    def create(self, content_type: str, workspace: Workspace, parent: Content=None, label:str ='', do_save=False, is_temporary: bool=False) -> Content:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (151/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Exactly one space required after :
Loading history...
Coding Style introduced by
Exactly one space required after keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
best-practice introduced by
Too many arguments (7/5)
Loading history...
380
        assert content_type in ContentType.allowed_types()
381
382
        if content_type == ContentType.Folder and not label:
383
            label = self.generate_folder_label(workspace, parent)
384
385
        content = Content()
386
        content.owner = self._user
387
        content.parent = parent
388
        content.workspace = workspace
389
        content.type = content_type
390
        content.label = label
391
        content.is_temporary = is_temporary
392
        content.revision_type = ActionDescription.CREATION
393
394
        if content.type in (
395
                ContentType.Page,
396
                ContentType.Thread,
397
        ):
398
            content.file_extension = '.html'
399
400
        if do_save:
401
            self._session.add(content)
402
            self.save(content, ActionDescription.CREATION)
403
        return content
404
405
406
    def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (121/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Exactly one space required after :
Loading history...
Coding Style introduced by
Exactly one space required after keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
407
        assert parent  and parent.type!=ContentType.Folder
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
408
        item = Content()
409
        item.owner = self._user
410
        item.parent = parent
411
        item.workspace = workspace
412
        item.type = ContentType.Comment
413
        item.description = content
414
        item.label = ''
415
        item.revision_type = ActionDescription.COMMENT
416
417
        if do_save:
418
            self.save(item, ActionDescription.COMMENT)
419
        return item
420
421
422
    def get_one_from_revision(self, content_id: int, content_type: str, workspace: Workspace=None, revision_id=None) -> Content:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (128/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
423
        """
424
        This method is a hack to convert a node revision item into a node
425
        :param content_id:
426
        :param content_type:
427
        :param workspace:
428
        :param revision_id:
429
        :return:
430
        """
431
432
        content = self.get_one(content_id, content_type, workspace)
433
        revision = self._session.query(ContentRevisionRO).filter(ContentRevisionRO.revision_id==revision_id).one()
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (114/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around comparison
Loading history...
434
435
        if revision.content_id==content.content_id:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
436
            content.revision_to_serialize = revision.revision_id
437
        else:
438
            raise ValueError('Revision not found for given content')
439
440
        return content
441
442
    def get_one(self, content_id: int, content_type: str, workspace: Workspace=None) -> Content:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
443
444
        if not content_id:
445
            return None
446
447
        if content_type==ContentType.Any:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
448
            return self._base_query(workspace).filter(Content.content_id==content_id).one()
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
449
450
        return self._base_query(workspace).filter(Content.content_id==content_id).filter(Content.type==content_type).one()
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (122/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around comparison
Loading history...
451
452
    def get_one_revision(self, revision_id: int = None) -> ContentRevisionRO:
453
        """
454
        This method allow us to get directly any revision with its id
455
        :param revision_id: The content's revision's id that we want to return
456
        :return: An item Content linked with the correct revision
457
        """
458
        assert revision_id is not None# DYN_REMOVE
459
460
        revision = self._session.query(ContentRevisionRO).filter(ContentRevisionRO.revision_id == revision_id).one()
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (116/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
461
462
        return revision
463
464
    # INFO - A.P - 2017-07-03 - python file object getter
465
    # in case of we cook a version of preview manager that allows a pythonic
466
    # access to files
467
    # def get_one_revision_file(self, revision_id: int = None):
468
    #     """
469
    #     This function allows us to directly get a Python file object from its
470
    #     revision identifier.
471
    #     :param revision_id: The revision id of the file we want to return
472
    #     :return: The corresponding Python file object
473
    #     """
474
    #     revision = self.get_one_revision(revision_id)
475
    #     return DepotManager.get().get(revision.depot_file)
476
477
    def get_one_revision_filepath(self, revision_id: int = None) -> str:
478
        """
479
        This method allows us to directly get a file path from its revision
480
        identifier.
481
        :param revision_id: The revision id of the filepath we want to return
482
        :return: The corresponding filepath
483
        """
484
        revision = self.get_one_revision(revision_id)
485
        depot = DepotManager.get()
486
        depot_stored_file = depot.get(revision.depot_file)  # type: StoredFile
487
        depot_file_path = depot_stored_file._file_path  # type: str
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _file_path was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
488
        return depot_file_path
489
490
    def get_one_by_label_and_parent(
491
            self,
492
            content_label: str,
493
            content_parent: Content=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
494
    ) -> Content:
495
        """
496
        This method let us request the database to obtain a Content with its name and parent
497
        :param content_label: Either the content's label or the content's filename if the label is None
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
498
        :param content_parent: The parent's content
499
        :param workspace: The workspace's content
500
        :return The corresponding Content
501
        """
502
        workspace = content_parent.workspace if content_parent else None
503
        query = self._base_query(workspace)
504
        parent_id = content_parent.content_id if content_parent else None
505
        query = query.filter(Content.parent_id == parent_id)
506
507
        file_name, file_extension = os.path.splitext(content_label)
508
509
        return query.filter(
510
            or_(
511
                and_(
512
                    Content.type == ContentType.File,
513
                    Content.label == file_name,
514
                    Content.file_extension == file_extension,
515
                ),
516
                and_(
517
                    Content.type == ContentType.Thread,
518
                    Content.label == file_name,
519
                ),
520
                and_(
521
                    Content.type == ContentType.Page,
522
                    Content.label == file_name,
523
                ),
524
                and_(
525
                    Content.type == ContentType.Folder,
526
                    Content.label == content_label,
527
                ),
528
            )
529
        ).one()
530
531
    def get_one_by_label_and_parent_labels(
0 ignored issues
show
Coding Style Naming introduced by
The name get_one_by_label_and_parent_labels does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
532
            self,
533
            content_label: str,
534
            workspace: Workspace,
535
            content_parent_labels: [str]=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
536
    ):
537
        """
538
        Return content with it's label, workspace and parents labels (optional)
539
        :param content_label: label of content (label or file_name)
540
        :param workspace: workspace containing all of this
541
        :param content_parent_labels: Ordered list of labels representing path
542
            of folder (without workspace label).
543
        E.g.: ['foo', 'bar'] for complete path /Workspace1/foo/bar folder
544
        :return: Found Content
545
        """
546
        query = self._base_query(workspace)
547
        parent_folder = None
548
549
        # Grab content parent folder if parent path given
550
        if content_parent_labels:
551
            parent_folder = self.get_folder_with_workspace_path_labels(
552
                content_parent_labels,
553
                workspace,
554
            )
555
556
        # Build query for found content by label
557
        content_query = self.filter_query_for_content_label_as_path(
558
            query=query,
559
            content_label_as_file=content_label,
560
        )
561
562
        # Modify query to apply parent folder filter if any
563
        if parent_folder:
564
            content_query = content_query.filter(
565
                Content.parent_id == parent_folder.content_id,
566
            )
567
        else:
568
            content_query = content_query.filter(
569
                Content.parent_id == None,
0 ignored issues
show
introduced by
Comparison to None should be 'expr is None'
Loading history...
570
            )
571
572
        # Filter with workspace
573
        content_query = content_query.filter(
574
            Content.workspace_id == workspace.workspace_id,
575
        )
576
577
        # Return the content
578
        return content_query\
579
            .order_by(
580
                Content.revision_id.desc(),
581
            )\
582
            .one()
583
584
    def get_folder_with_workspace_path_labels(
0 ignored issues
show
Coding Style Naming introduced by
The name get_folder_with_workspace_path_labels does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
585
            self,
586
            path_labels: [str],
587
            workspace: Workspace,
588
    ) -> Content:
589
        """
590
        Return a Content folder for given relative path.
591
        TODO BS 20161124: Not safe if web interface allow folder duplicate names
592
        :param path_labels: List of labels representing path of folder
593
        (without workspace label).
594
        E.g.: ['foo', 'bar'] for complete path /Workspace1/foo/bar folder
595
        :param workspace: workspace of folders
596
        :return: Content folder
597
        """
598
        query = self._base_query(workspace)
599
        folder = None
600
601
        for label in path_labels:
602
            # Filter query on label
603
            folder_query = query \
604
                .filter(
605
                    Content.type == ContentType.Folder,
606
                    Content.label == label,
607
                    Content.workspace_id == workspace.workspace_id,
608
                )
609
610
            # Search into parent folder (if already deep)
611
            if folder:
612
                folder_query = folder_query\
613
                    .filter(
614
                        Content.parent_id == folder.content_id,
615
                    )
616
            else:
617
                folder_query = folder_query \
618
                    .filter(Content.parent_id == None)
0 ignored issues
show
introduced by
Comparison to None should be 'expr is None'
Loading history...
619
620
            # Get thirst corresponding folder
621
            folder = folder_query \
622
                .order_by(Content.revision_id.desc()) \
623
                .one()
624
625
        return folder
626
627
    def filter_query_for_content_label_as_path(
0 ignored issues
show
Coding Style Naming introduced by
The name filter_query_for_content_label_as_path does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
628
            self,
629
            query: Query,
630
            content_label_as_file: str,
631
            is_case_sensitive: bool = False,
632
    ) -> Query:
633
        """
634
        Apply normalised filters to found Content corresponding as given label.
635
        :param query: query to modify
636
        :param content_label_as_file: label in this
637
        FILE version, use Content.get_label_as_file().
638
        :param is_case_sensitive: Take care about case or not
639
        :return: modified query
640
        """
641
        file_name, file_extension = os.path.splitext(content_label_as_file)
642
643
        label_filter = Content.label == content_label_as_file
644
        file_name_filter = Content.label == file_name
645
        file_extension_filter = Content.file_extension == file_extension
646
647
        if not is_case_sensitive:
648
            label_filter = func.lower(Content.label) == \
649
                           func.lower(content_label_as_file)
650
            file_name_filter = func.lower(Content.label) == \
651
                               func.lower(file_name)
652
            file_extension_filter = func.lower(Content.file_extension) == \
653
                                    func.lower(file_extension)
654
655
        return query.filter(or_(
656
            and_(
657
                Content.type == ContentType.File,
658
                file_name_filter,
659
                file_extension_filter,
660
            ),
661
            and_(
662
                Content.type == ContentType.Thread,
663
                file_name_filter,
664
                file_extension_filter,
665
            ),
666
            and_(
667
                Content.type == ContentType.Page,
668
                file_name_filter,
669
                file_extension_filter,
670
            ),
671
            and_(
672
                Content.type == ContentType.Folder,
673
                label_filter,
674
            ),
675
        ))
676
677
    def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (129/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
678
        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
679
        assert content_type is not None# DYN_REMOVE
680
        assert isinstance(content_type, str) # DYN_REMOVE
681
682
        resultset = self._base_query(workspace)
683
684
        if content_type!=ContentType.Any:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
685
            resultset = resultset.filter(Content.type==content_type)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
686
687
        if parent_id:
688
            resultset = resultset.filter(Content.parent_id==parent_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
689
        if parent_id is False:
690
            resultset = resultset.filter(Content.parent_id == None)
0 ignored issues
show
introduced by
Comparison to None should be 'expr is None'
Loading history...
691
692
        return resultset.all()
693
694
    def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
695
        """
696
        Return parent_id childs of given content_types
697
        :param parent_id: parent id
698
        :param content_types: list of types
699
        :param workspace: workspace filter
700
        :return: list of content
701
        """
702
        resultset = self._base_query(workspace)
703
        resultset = resultset.filter(Content.type.in_(content_types))
704
705
        if parent_id:
706
            resultset = resultset.filter(Content.parent_id==parent_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
707
        if parent_id is False:
708
            resultset = resultset.filter(Content.parent_id == None)
0 ignored issues
show
introduced by
Comparison to None should be 'expr is None'
Loading history...
709
710
        return resultset.all()
711
712
    # TODO find an other name to filter on is_deleted / is_archived
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
713
    def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (141/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
714
        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
715
        assert content_type is not None# DYN_REMOVE
716
        assert isinstance(content_type, str) # DYN_REMOVE
717
718
        resultset = self._base_query(workspace)
719
720
        if content_type != ContentType.Any:
721
            resultset = resultset.filter(Content.type==content_type)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
722
723
        resultset = resultset.filter(Content.is_deleted == self._show_deleted)
724
        resultset = resultset.filter(Content.is_archived == self._show_archived)
725
        resultset = resultset.filter(Content.is_temporary == self._show_temporary)
726
727
        resultset = resultset.filter(Content.parent_id==parent_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
728
729
        return resultset.all()
730
731
    def get_all_without_exception(self, content_type: str, workspace: Workspace=None) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (110/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
732
        assert content_type is not None# DYN_REMOVE
733
734
        resultset = self._base_query(workspace)
735
736
        if content_type != ContentType.Any:
737
            resultset = resultset.filter(Content.type==content_type)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
738
739
        return resultset.all()
740
741
    def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (126/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
742
        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
743
        assert content_type is not None# DYN_REMOVE
744
        assert isinstance(content_type, str) # DYN_REMOVE
745
746
        resultset = self._base_query(workspace) \
747
            .filter(Content.workspace_id == Workspace.workspace_id) \
748
            .filter(Workspace.is_deleted.is_(False)) \
749
            .order_by(desc(Content.updated))
750
751
        if content_type!=ContentType.Any:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
752
            resultset = resultset.filter(Content.type==content_type)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
753
754
        if parent_id:
755
            resultset = resultset.filter(Content.parent_id==parent_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
756
757
        result = []
758
        for item in resultset:
759
            new_item = None
760
            if ContentType.Comment == item.type:
761
                new_item = item.parent
762
            else:
763
                new_item = item
764
765
            # INFO - D.A. - 2015-05-20
766
            # We do not want to show only one item if the last 10 items are
767
            # comments about one thread for example
768
            if new_item not in result:
769
                result.append(new_item)
770
771
            if len(result) >= limit:
772
                break
773
774
        return result
775
776
    def get_last_unread(self, parent_id: int, content_type: str,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
777
                        workspace: Workspace=None, limit=10) -> typing.List[Content]:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
778
        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
779
        assert content_type is not None# DYN_REMOVE
780
        assert isinstance(content_type, str) # DYN_REMOVE
781
782
        read_revision_ids = self._session.query(RevisionReadStatus.revision_id) \
783
            .filter(RevisionReadStatus.user_id==self._user_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
784
785
        not_read_revisions = self._revisions_base_query(workspace) \
786
            .filter(~ContentRevisionRO.revision_id.in_(read_revision_ids)) \
787
            .filter(ContentRevisionRO.workspace_id == Workspace.workspace_id) \
788
            .filter(Workspace.is_deleted.is_(False)) \
789
            .subquery()
790
791
        not_read_content_ids_query = self._session.query(
792
            distinct(not_read_revisions.c.content_id)
793
        )
794
        not_read_content_ids = list(map(
795
            itemgetter(0),
796
            not_read_content_ids_query,
797
        ))
798
799
        not_read_contents = self._base_query(workspace) \
800
            .filter(Content.content_id.in_(not_read_content_ids)) \
801
            .order_by(desc(Content.updated))
802
803
        if content_type != ContentType.Any:
804
            not_read_contents = not_read_contents.filter(
805
                Content.type==content_type)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
806
        else:
807
            not_read_contents = not_read_contents.filter(
808
                Content.type!=ContentType.Folder)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
809
810
        if parent_id:
811
            not_read_contents = not_read_contents.filter(
812
                Content.parent_id==parent_id)
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
813
814
        result = []
815
        for item in not_read_contents:
816
            new_item = None
817
            if ContentType.Comment == item.type:
818
                new_item = item.parent
819
            else:
820
                new_item = item
821
822
            # INFO - D.A. - 2015-05-20
823
            # We do not want to show only one item if the last 10 items are
824
            # comments about one thread for example
825
            if new_item not in result:
826
                result.append(new_item)
827
828
            if len(result) >= limit:
829
                break
830
831
        return result
832
833
    def set_allowed_content(self, folder: Content, allowed_content_dict:dict):
0 ignored issues
show
Coding Style introduced by
Exactly one space required after :
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
834
        """
835
        :param folder: the given folder instance
836
        :param allowed_content_dict: must be something like this:
837
            dict(
838
                folder = True
839
                thread = True,
840
                file = False,
841
                page = True
842
            )
843
        :return:
844
        """
845
        properties = dict(allowed_content = allowed_content_dict)
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
846
        folder.properties = properties
847
848
    def set_status(self, content: Content, new_status: str):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
849
        if new_status in ContentStatus.allowed_values():
850
            content.status = new_status
851
            content.revision_type = ActionDescription.STATUS_UPDATE
852
        else:
853
            raise ValueError('The given value {} is not allowed'.format(new_status))
854
855
    def move(self, item: Content,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
856
             new_parent: Content,
857
             must_stay_in_same_workspace:bool=True,
0 ignored issues
show
Coding Style introduced by
Exactly one space required after :
Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
858
             new_workspace:Workspace=None):
0 ignored issues
show
Coding Style introduced by
Exactly one space required after :
Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
859
        if must_stay_in_same_workspace:
860
            if new_parent and new_parent.workspace_id != item.workspace_id:
861
                raise ValueError('the item should stay in the same workspace')
862
863
        item.parent = new_parent
864
        if new_parent:
865
            item.workspace = new_parent.workspace
866
        elif new_workspace:
867
            item.workspace = new_workspace
868
869
        item.revision_type = ActionDescription.MOVE
870
871
    def copy(
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
872
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
873
        item: Content,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
874
        new_parent: Content=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
875
        new_label: str=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
876
        do_save: bool=True,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
877
        do_notify: bool=True,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
878
    ) -> Content:
879
        """
880
        Copy nearly all content, revision included. Children not included, see
881
        "copy_children" for this.
882
        :param item: Item to copy
883
        :param new_parent: new parent of the new copied item
884
        :param new_label: new label of the new copied item
885
        :param do_notify: notify copy or not
886
        :return: Newly copied item
887
        """
888
        if (not new_parent and not new_label) or (new_parent == item.parent and new_label == item.label):  # nopep8
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
889
            # TODO - G.M - 08-03-2018 - Use something else than value error
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
890
            raise ValueError("You can't copy file into itself")
891
        if new_parent:
892
            workspace = new_parent.workspace
893
            parent = new_parent
894
        else:
895
            workspace = item.workspace
896
            parent = item.parent
897
        label = new_label or item.label
898
899
        content = item.copy(parent)
900
        # INFO - GM - 15-03-2018 - add "copy" revision
901
        with new_revision(
902
            session=self._session,
903
            tm=transaction.manager,
904
            content=content,
905
            force_create_new_revision=True
906
        ) as rev:
907
            rev.parent = parent
908
            rev.workspace = workspace
909
            rev.label = label
910
            rev.revision_type = ActionDescription.COPY
911
            rev.properties['origin'] = {
912
                'content': item.id,
913
                'revision': item.last_revision.revision_id,
914
            }
915
        if do_save:
916
            self.save(content, ActionDescription.COPY, do_notify=do_notify)
917
        return content
918
919
    def copy_children(self, origin_content: Content, new_content: Content):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
920
        for child in origin_content.children:
921
            self.copy(child, new_content)
922
923
    def move_recursively(self, item: Content,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
924
                         new_parent: Content, new_workspace: Workspace):
925
        self.move(item, new_parent, False, new_workspace)
926
        self.save(item, do_notify=False)
927
928
        for child in item.children:
929
            with new_revision(child):
0 ignored issues
show
Bug introduced by
It seems like a value for argument tm is missing in the function call.
Loading history...
Bug introduced by
It seems like a value for argument content is missing in the function call.
Loading history...
930
                self.move_recursively(child, item, new_workspace)
931
        return
932
933
    def update_content(self, item: Content, new_label: str, new_content: str=None) -> Content:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
934
        if item.label==new_label and item.description==new_content:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
935
            # TODO - G.M - 20-03-2018 - Fix internatization for webdav access.
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
936
            # Internatization disabled in libcontent for now.
937
            raise SameValueError('The content did not changed')
938
        item.owner = self._user
939
        item.label = new_label
940
        item.description = new_content if new_content else item.description # TODO: convert urls into links
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
Coding Style introduced by
This line is too long as per the coding-style (107/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
941
        item.revision_type = ActionDescription.EDITION
942
        return item
943
944
    def update_file_data(self, item: Content, new_filename: str, new_mimetype: str, new_content: bytes) -> Content:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
945
        if new_mimetype == item.file_mimetype and \
946
                new_content == item.depot_file.file.read():
947
            raise SameValueError('The content did not changed')
948
        item.owner = self._user
949
        item.file_name = new_filename
950
        item.file_mimetype = new_mimetype
951
        item.depot_file = FileIntent(
952
            new_content,
953
            new_filename,
954
            new_mimetype,
955
        )
956
        item.revision_type = ActionDescription.REVISION
957
        return item
958
959
    def archive(self, content: Content):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
960
        content.owner = self._user
961
        content.is_archived = True
962
        # TODO - G.M - 12-03-2018 - Inspect possible label conflict problem
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
963
        # INFO - G.M - 12-03-2018 - Set label name to avoid trouble when
964
        # un-archiving file.
965
        content.label = '{label}-{action}-{date}'.format(
966
            label=content.label,
967
            action='archived',
968
            date=current_date_for_filename()
969
        )
970
        content.revision_type = ActionDescription.ARCHIVING
971
972
    def unarchive(self, content: Content):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
973
        content.owner = self._user
974
        content.is_archived = False
975
        content.revision_type = ActionDescription.UNARCHIVING
976
977
    def delete(self, content: Content):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
978
        content.owner = self._user
979
        content.is_deleted = True
980
        # TODO - G.M - 12-03-2018 - Inspect possible label conflict problem
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
981
        # INFO - G.M - 12-03-2018 - Set label name to avoid trouble when
982
        # un-deleting file.
983
        content.label = '{label}-{action}-{date}'.format(
984
            label=content.label,
985
            action='deleted',
986
            date=current_date_for_filename()
987
        )
988
        content.revision_type = ActionDescription.DELETION
989
990
    def undelete(self, content: Content):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
991
        content.owner = self._user
992
        content.is_deleted = False
993
        content.revision_type = ActionDescription.UNDELETION
994
995
    def mark_read__all(self,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
996
                       read_datetime: datetime=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
997
                       do_flush: bool=True,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
998
                       recursive: bool=True
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
999
                       ):
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 1 space).
Loading history...
1000
1001
        itemset = self.get_last_unread(None, ContentType.Any)
1002
1003
        for item in itemset:
1004
            self.mark_read(item, read_datetime, do_flush, recursive)
1005
1006
    def mark_read__workspace(self,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1007
                       workspace : Workspace,
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 6 spaces).
Loading history...
Coding Style introduced by
No space allowed before :
Loading history...
1008
                       read_datetime: datetime=None,
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 6 spaces).
Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1009
                       do_flush: bool=True,
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 6 spaces).
Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1010
                       recursive: bool=True
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 6 spaces).
Loading history...
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1011
                       ):
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 5 spaces).
Loading history...
1012
1013
        itemset = self.get_last_unread(None, ContentType.Any, workspace)
1014
1015
        for item in itemset:
1016
            self.mark_read(item, read_datetime, do_flush, recursive)
1017
1018
    def mark_read(self, content: Content,
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1019
                  read_datetime: datetime=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1020
                  do_flush: bool=True, recursive: bool=True) -> Content:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1021
1022
        assert self._user
1023
        assert content
1024
1025
        # The algorithm is:
1026
        # 1. define the read datetime
1027
        # 2. update all revisions related to current Content
1028
        # 3. do the same for all child revisions
1029
        #    (ie parent_id is content_id of current content)
1030
1031
        if not read_datetime:
1032
            read_datetime = datetime.datetime.now()
1033
1034
        viewed_revisions = self._session.query(ContentRevisionRO) \
1035
            .filter(ContentRevisionRO.content_id==content.content_id).all()
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
1036
1037
        for revision in viewed_revisions:
1038
            revision.read_by[self._user] = read_datetime
1039
1040
        if recursive:
1041
            # mark read :
1042
            # - all children
1043
            # - parent stuff (if you mark a comment as read,
1044
            #                 then you have seen the parent)
1045
            # - parent comments
1046
            for child in content.get_valid_children():
1047
                self.mark_read(child, read_datetime=read_datetime,
1048
                               do_flush=False)
1049
1050
            if ContentType.Comment == content.type:
1051
                self.mark_read(content.parent, read_datetime=read_datetime,
1052
                               do_flush=False, recursive=False)
1053
                for comment in content.parent.get_comments():
1054
                    if comment != content:
1055
                        self.mark_read(comment, read_datetime=read_datetime,
1056
                                       do_flush=False, recursive=False)
1057
1058
        if do_flush:
1059
            self.flush()
1060
1061
        return content
1062
1063
    def mark_unread(self, content: Content, do_flush=True) -> Content:
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1064
        assert self._user
1065
        assert content
1066
1067
        revisions = self._session.query(ContentRevisionRO) \
1068
            .filter(ContentRevisionRO.content_id==content.content_id).all()
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
1069
1070
        for revision in revisions:
1071
            del revision.read_by[self._user]
1072
1073
        for child in content.get_valid_children():
1074
            self.mark_unread(child, do_flush=False)
1075
1076
        if do_flush:
1077
            self.flush()
1078
1079
        return content
1080
1081
    def flush(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1082
        self._session.flush()
1083
1084
    def save(self, content: Content, action_description: str=None, do_flush=True, do_notify=True):
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1085
        """
1086
        Save an object, flush the session and set the revision_type property
1087
        :param content:
1088
        :param action_description:
1089
        :return:
1090
        """
1091
        assert action_description is None or action_description in ActionDescription.allowed_values()
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (101/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1092
1093
        if not action_description:
1094
            # See if the last action has been modified
1095
            if content.revision_type==None or len(get_history(content.revision, 'revision_type'))<=0:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (101/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required around comparison
Loading history...
introduced by
Comparison to None should be 'expr is None'
Loading history...
1096
                # The action has not been modified, so we set it to default edition
1097
                action_description = ActionDescription.EDITION
1098
1099
        if action_description:
1100
            content.revision_type = action_description
1101
1102
        if do_flush:
1103
            # INFO - 2015-09-03 - D.A.
1104
            # There are 2 flush because of the use
1105
            # of triggers for content creation
1106
            #
1107
            # (when creating a content, actually this is an insert of a new
1108
            # revision in content_revisions ; so the mark_read operation need
1109
            # to get full real data from database before to be prepared.
1110
1111
            self._session.add(content)
1112
            self._session.flush()
1113
1114
            # TODO - 2015-09-03 - D.A. - Do not use triggers
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
1115
            # We should create a new ContentRevisionRO object instead of Content
1116
            # This would help managing view/not viewed status
1117
            self.mark_read(content, do_flush=True)
1118
1119
        if do_notify:
1120
            self.do_notify(content)
1121
1122
    def do_notify(self, content: Content):
1123
        """
1124
        Allow to force notification for a given content. By default, it is
1125
        called during the .save() operation
1126
        :param content:
1127
        :return:
1128
        """
1129
        NotifierFactory.create(
1130
            self._config,
1131
            self._user
1132
        ).notify_content_update(content)
1133
1134
    def get_keywords(self, search_string, search_string_separators=None) -> [str]:
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
1135
        """
1136
        :param search_string: a list of coma-separated keywords
1137
        :return: a list of str (each keyword = 1 entry
1138
        """
1139
1140
        search_string_separators = search_string_separators or ContentApi.SEARCH_SEPARATORS
1141
1142
        keywords = []
1143
        if search_string:
1144
            keywords = [keyword.strip() for keyword in re.split(search_string_separators, search_string)]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1145
1146
        return keywords
1147
1148
    def search(self, keywords: [str]) -> Query:
1149
        """
1150
        :return: a sorted list of Content items
1151
        """
1152
1153
        if len(keywords)<=0:
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
Loading history...
1154
            return None
1155
1156
        filter_group_label = list(Content.label.ilike('%{}%'.format(keyword)) for keyword in keywords)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (102/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1157
        filter_group_desc = list(Content.description.ilike('%{}%'.format(keyword)) for keyword in keywords)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (107/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1158
        title_keyworded_items = self._hard_filtered_base_query().\
1159
            filter(or_(*(filter_group_label+filter_group_desc))).\
1160
            options(joinedload('children_revisions')).\
1161
            options(joinedload('parent'))
1162
1163
        return title_keyworded_items
1164
1165
    def get_all_types(self) -> typing.List[ContentType]:
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
1166
        labels = ContentType.all()
1167
        content_types = []
1168
        for label in labels:
1169
            content_types.append(ContentType(label))
1170
1171
        return ContentType.sorted(content_types)
1172
1173
    def exclude_unavailable(
1174
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1175
        contents: typing.List[Content],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable typing does not seem to be defined.
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1176
    ) -> typing.List[Content]:
1177
        """
1178
        Update and return list with content under archived/deleted removed.
1179
        :param contents: List of contents to parse
1180
        """
1181
        for content in contents[:]:
1182
            if self.content_under_deleted(content) or self.content_under_archived(content):
1183
                contents.remove(content)
1184
        return contents
1185
1186
    def content_under_deleted(self, content: Content) -> bool:
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1187
        if content.parent:
1188
            if content.parent.is_deleted:
1189
                return True
1190
            if content.parent.parent:
1191
                return self.content_under_deleted(content.parent)
1192
        return False
1193
1194
    def content_under_archived(self, content: Content) -> bool:
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
1195
        if content.parent:
1196
            if content.parent.is_archived:
1197
                return True
1198
            if content.parent.parent:
1199
                return self.content_under_archived(content.parent)
1200
        return False
1201
1202
    def find_one_by_unique_property(
1203
            self,
1204
            property_name: str,
1205
            property_value: str,
1206
            workspace: Workspace=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1207
    ) -> Content:
1208
        """
1209
        Return Content who contains given property.
1210
        Raise sqlalchemy.orm.exc.MultipleResultsFound if more than one Content
1211
        contains this property value.
1212
        :param property_name: Name of property
1213
        :param property_value: Value of property
1214
        :param workspace: Workspace who contains Content
1215
        :return: Found Content
1216
        """
1217
        # TODO - 20160602 - Bastien: Should be JSON type query
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
1218
        # see https://www.compose.io/articles/using-json-extensions-in-\
1219
        # postgresql-from-python-2/
1220
        query = self._base_query(workspace=workspace).filter(
1221
            Content._properties.like(
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _properties was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
1222
                '%"{property_name}": "{property_value}"%'.format(
1223
                    property_name=property_name,
1224
                    property_value=property_value,
1225
                )
1226
            )
1227
        )
1228
        return query.one()
1229
1230
    def generate_folder_label(
1231
            self,
1232
            workspace: Workspace,
1233
            parent: Content=None,
0 ignored issues
show
Coding Style introduced by
Exactly one space required around keyword argument assignment
Loading history...
1234
    ) -> str:
1235
        """
1236
        Generate a folder label
1237
        :param workspace: Future folder workspace
1238
        :param parent: Parent of foture folder (can be None)
1239
        :return: Generated folder name
1240
        """
1241
        query = self._base_query(workspace=workspace)\
1242
            .filter(Content.label.ilike('{0}%'.format(
1243
                _('New folder'),
1244
            )))
1245
        if parent:
1246
            query = query.filter(Content.parent == parent)
1247
1248
        return _('New folder {0}').format(
1249
            query.count() + 1,
1250
        )
1251