Passed
Pull Request — develop (#31)
by Bastien
01:43
created

UserCreation.__init__()   A

Complexity

Conditions 1

Size

Total Lines 19
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 19
rs 9.6
c 0
b 0
f 0
cc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
# coding=utf-8
2
import typing
3
from datetime import datetime
4
from enum import Enum
5
6
from slugify import slugify
7
from sqlalchemy.orm import Session
8
from tracim_backend.config import CFG
9
from tracim_backend.config import PreviewDim
10
from tracim_backend.extensions import app_list
11
from tracim_backend.lib.core.application import ApplicationApi
12
from tracim_backend.lib.utils.utils import get_root_frontend_url
13
from tracim_backend.lib.utils.utils import password_generator
14
from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
15
from tracim_backend.lib.utils.utils import WORKSPACE_FRONTEND_URL_SCHEMA
16
from tracim_backend.models import User
17
from tracim_backend.models.auth import Profile
18
from tracim_backend.models.auth import Group
19
from tracim_backend.models.data import Content
20
from tracim_backend.models.data import ContentRevisionRO
21
from tracim_backend.models.data import Workspace
22
from tracim_backend.models.data import UserRoleInWorkspace
23
from tracim_backend.models.roles import WorkspaceRoles
24
from tracim_backend.app_models.workspace_menu_entries import WorkspaceMenuEntry
25
from tracim_backend.app_models.contents import CONTENT_TYPES
26
27
28
class PreviewAllowedDim(object):
29
30
    def __init__(
31
            self,
32
            restricted:bool,
33
            dimensions: typing.List[PreviewDim]
34
    ) -> None:
35
        self.restricted = restricted
36
        self.dimensions = dimensions
37
38
39
class MoveParams(object):
40
    """
41
    Json body params for move action model
42
    """
43
    def __init__(self, new_parent_id: str, new_workspace_id: str = None) -> None:  # nopep8
44
        self.new_parent_id = new_parent_id
45
        self.new_workspace_id = new_workspace_id
46
47
48
class LoginCredentials(object):
49
    """
50
    Login credentials model for login model
51
    """
52
53
    def __init__(self, email: str, password: str) -> None:
54
        self.email = email
55
        self.password = password
56
57
58
class ResetPasswordRequest(object):
59
    """
60
    Reset password : request to reset password of user
61
    """
62
    def __init__(self, email: str) -> None:
63
        self.email = email
64
65
66
class ResetPasswordCheckToken(object):
67
    """
68
    Reset password : check reset password token
69
    """
70
    def __init__(
71
        self,
72
        reset_password_token: str,
73
        email: str,
74
    ) -> None:
75
        self.email = email
76
        self.reset_password_token = reset_password_token
77
78
79
class ResetPasswordModify(object):
80
    """
81
    Reset password : modification step
82
    """
83
    def __init__(
84
        self,
85
        reset_password_token: str,
86
        email: str,
87
        new_password: str,
88
        new_password2: str
89
    ) -> None:
90
        self.email = email
91
        self.reset_password_token = reset_password_token
92
        self.new_password = new_password
93
        self.new_password2 = new_password2
94
95
96
class SetEmail(object):
97
    """
98
    Just an email and password
99
    """
100
    def __init__(self, loggedin_user_password: str, email: str) -> None:
101
        self.loggedin_user_password = loggedin_user_password
102
        self.email = email
103
104
105
class SetPassword(object):
106
    """
107
    Just an password
108
    """
109
    def __init__(
110
        self,
111
        loggedin_user_password: str,
112
        new_password: str,
113
        new_password2: str
114
    ) -> None:
115
        self.loggedin_user_password = loggedin_user_password
116
        self.new_password = new_password
117
        self.new_password2 = new_password2
118
119
120
class UserInfos(object):
121
    """
122
    Just some user infos
123
    """
124
    def __init__(self, timezone: str, public_name: str, lang: str) -> None:
125
        self.timezone = timezone
126
        self.public_name = public_name
127
        self.lang = lang
128
129
130
class UserProfile(object):
131
    """
132
    Just some user infos
133
    """
134
    def __init__(self, profile: str) -> None:
135
        self.profile = profile
136
137
138
class UserCreation(object):
139
    """
140
    Just some user infos
141
    """
142
    def __init__(
143
            self,
144
            email: str,
145
            password: str = None,
146
            public_name: str = None,
147
            timezone: str = None,
148
            profile: str = None,
149
            lang: str = None,
150
            email_notification: bool = True,
151
    ) -> None:
152
        self.email = email
153
        # INFO - G.M - 2018-08-16 - cleartext password, default value
154
        # is auto-generated.
155
        self.password = password or password_generator()
156
        self.public_name = public_name or None
157
        self.timezone = timezone or ''
158
        self.lang = lang or None
159
        self.profile = profile or Group.TIM_USER_GROUPNAME
160
        self.email_notification = email_notification
161
162
163
class WorkspaceAndContentPath(object):
164
    """
165
    Paths params with workspace id and content_id model
166
    """
167
    def __init__(self, workspace_id: int, content_id: int) -> None:
168
        self.content_id = content_id
169
        self.workspace_id = workspace_id
170
171
172
class WorkspaceAndContentRevisionPath(object):
173
    """
174
    Paths params with workspace id and content_id model
175
    """
176
    def __init__(self, workspace_id: int, content_id: int, revision_id) -> None:
177
        self.content_id = content_id
178
        self.revision_id = revision_id
179
        self.workspace_id = workspace_id
180
181
182
class ContentPreviewSizedPath(object):
183
    """
184
    Paths params with workspace id and content_id, width, heigth
185
    """
186
    def __init__(self, workspace_id: int, content_id: int, width: int, height: int) -> None:  # nopep8
187
        self.content_id = content_id
188
        self.workspace_id = workspace_id
189
        self.width = width
190
        self.height = height
191
192
193
class RevisionPreviewSizedPath(object):
194
    """
195
    Paths params with workspace id and content_id, revision_id width, heigth
196
    """
197
    def __init__(self, workspace_id: int, content_id: int, revision_id: int, width: int, height: int) -> None:  # nopep8
198
        self.content_id = content_id
199
        self.revision_id = revision_id
200
        self.workspace_id = workspace_id
201
        self.width = width
202
        self.height = height
203
204
205
class WorkspaceAndUserPath(object):
206
    """
207
    Paths params with workspace id and user_id
208
    """
209
    def __init__(self, workspace_id: int, user_id: int):
210
        self.workspace_id = workspace_id
211
        self.user_id = user_id
212
213
214
class UserWorkspaceAndContentPath(object):
215
    """
216
    Paths params with user_id, workspace id and content_id model
217
    """
218
    def __init__(self, user_id: int, workspace_id: int, content_id: int) -> None:  # nopep8
219
        self.content_id = content_id
220
        self.workspace_id = workspace_id
221
        self.user_id = user_id
222
223
224
class CommentPath(object):
225
    """
226
    Paths params with workspace id and content_id and comment_id model
227
    """
228
    def __init__(
229
        self,
230
        workspace_id: int,
231
        content_id: int,
232
        comment_id: int
233
    ) -> None:
234
        self.content_id = content_id
235
        self.workspace_id = workspace_id
236
        self.comment_id = comment_id
237
238
239
class AutocompleteQuery(object):
240
    """
241
    Autocomplete query model
242
    """
243
    def __init__(self, acp: str):
244
        self.acp = acp
245
246
247
class FileQuery(object):
248
    """
249
    File query model
250
    """
251
    def __init__(
252
        self,
253
        force_download: int = 0,
254
    ):
255
        self.force_download = force_download
256
257
258
class PageQuery(object):
259
    """
260
    Page query model
261
    """
262
    def __init__(
263
            self,
264
            force_download: int = 0,
265
            page: int = 1
266
    ):
267
        self.force_download = force_download
268
        self.page = page
269
270
271
class ContentFilter(object):
272
    """
273
    Content filter model
274
    """
275
    def __init__(
276
            self,
277
            workspace_id: int = None,
278
            parent_id: int = None,
279
            show_archived: int = 0,
280
            show_deleted: int = 0,
281
            show_active: int = 1,
282
            content_type: str = None,
283
            offset: int = None,
284
            limit: int = None,
285
    ) -> None:
286
        self.parent_id = parent_id
287
        self.workspace_id = workspace_id
288
        self.show_archived = bool(show_archived)
289
        self.show_deleted = bool(show_deleted)
290
        self.show_active = bool(show_active)
291
        self.limit = limit
292
        self.offset = offset
293
        self.content_type = content_type
294
295
296
class ActiveContentFilter(object):
297
    def __init__(
298
            self,
299
            limit: int = None,
300
            before_content_id: datetime = None,
301
    ):
302
        self.limit = limit
303
        self.before_content_id = before_content_id
304
305
306
class ContentIdsQuery(object):
307
    def __init__(
308
            self,
309
            contents_ids: typing.List[int] = None,
310
    ):
311
        self.contents_ids = contents_ids
312
313
314
class RoleUpdate(object):
315
    """
316
    Update role
317
    """
318
    def __init__(
319
        self,
320
        role: str,
321
    ):
322
        self.role = role
323
324
325
class WorkspaceMemberInvitation(object):
326
    """
327
    Workspace Member Invitation
328
    """
329
    def __init__(
330
        self,
331
        user_id: int = None,
332
        user_email_or_public_name: str = None,
333
        role: str = None,
334
    ):
335
        self.role = role
336
        self.user_email_or_public_name = user_email_or_public_name
337
        self.user_id = user_id
338
339
340
class WorkspaceUpdate(object):
341
    """
342
    Update workspace
343
    """
344
    def __init__(
345
        self,
346
        label: str,
347
        description: str,
348
    ):
349
        self.label = label
350
        self.description = description
351
352
353
class ContentCreation(object):
354
    """
355
    Content creation model
356
    """
357
    def __init__(
358
        self,
359
        label: str,
360
        content_type: str,
361
        parent_id: typing.Optional[int] = None,
362
    ) -> None:
363
        self.label = label
364
        self.content_type = content_type
365
        self.parent_id = parent_id or None
366
367
368
class CommentCreation(object):
369
    """
370
    Comment creation model
371
    """
372
    def __init__(
373
        self,
374
        raw_content: str,
375
    ) -> None:
376
        self.raw_content = raw_content
377
378
379
class SetContentStatus(object):
380
    """
381
    Set content status
382
    """
383
    def __init__(
384
        self,
385
        status: str,
386
    ) -> None:
387
        self.status = status
388
389
390
class TextBasedContentUpdate(object):
391
    """
392
    TextBasedContent update model
393
    """
394
    def __init__(
395
        self,
396
        label: str,
397
        raw_content: str,
398
    ) -> None:
399
        self.label = label
400
        self.raw_content = raw_content
401
402
403
class FolderContentUpdate(object):
404
    """
405
    Folder Content update model
406
    """
407
    def __init__(
408
        self,
409
        label: str,
410
        raw_content: str,
411
        sub_content_types: typing.List[str],
412
    ) -> None:
413
        self.label = label
414
        self.raw_content = raw_content
415
        self.sub_content_types = sub_content_types
416
417
418
class TypeUser(Enum):
419
    """Params used to find user"""
420
    USER_ID = 'found_id'
421
    EMAIL = 'found_email'
422
    PUBLIC_NAME = 'found_public_name'
423
424
425
class UserInContext(object):
426
    """
427
    Interface to get User data and User data related to context.
428
    """
429
430
    def __init__(self, user: User, dbsession: Session, config: CFG):
431
        self.user = user
432
        self.dbsession = dbsession
433
        self.config = config
434
435
    # Default
436
437
    @property
438
    def email(self) -> str:
439
        return self.user.email
440
441
    @property
442
    def user_id(self) -> int:
443
        return self.user.user_id
444
445
    @property
446
    def public_name(self) -> str:
447
        return self.display_name
448
449
    @property
450
    def display_name(self) -> str:
451
        return self.user.display_name
452
453
    @property
454
    def created(self) -> datetime:
455
        return self.user.created
456
457
    @property
458
    def is_active(self) -> bool:
459
        return self.user.is_active
460
461
    @property
462
    def timezone(self) -> str:
463
        return self.user.timezone
464
465
    @property
466
    def lang(self) -> str:
467
        return self.user.lang
468
469
    @property
470
    def profile(self) -> Profile:
471
        return self.user.profile.name
472
473
    @property
474
    def is_deleted(self) -> bool:
475
        return self.user.is_deleted
476
477
    # Context related
478
479
    @property
480
    def calendar_url(self) -> typing.Optional[str]:
481
        # TODO - G-M - 20-04-2018 - [Calendar] Replace calendar code to get
482
        # url calendar url.
483
        #
484
        # from tracim.lib.calendar import CalendarManager
485
        # calendar_manager = CalendarManager(None)
486
        # return calendar_manager.get_workspace_calendar_url(self.workspace_id)
487
        return None
488
489
    @property
490
    def avatar_url(self) -> typing.Optional[str]:
491
        # TODO - G-M - 20-04-2018 - [Avatar] Add user avatar feature
492
        return None
493
494
495
class WorkspaceInContext(object):
496
    """
497
    Interface to get Workspace data and Workspace data related to context.
498
    """
499
500
    def __init__(self, workspace: Workspace, dbsession: Session, config: CFG):
501
        self.workspace = workspace
502
        self.dbsession = dbsession
503
        self.config = config
504
505
    @property
506
    def workspace_id(self) -> int:
507
        """
508
        numeric id of the workspace.
509
        """
510
        return self.workspace.workspace_id
511
512
    @property
513
    def id(self) -> int:
514
        """
515
        alias of workspace_id
516
        """
517
        return self.workspace_id
518
519
    @property
520
    def label(self) -> str:
521
        """
522
        get workspace label
523
        """
524
        return self.workspace.label
525
526
    @property
527
    def description(self) -> str:
528
        """
529
        get workspace description
530
        """
531
        return self.workspace.description
532
533
    @property
534
    def slug(self) -> str:
535
        """
536
        get workspace slug
537
        """
538
        return slugify(self.workspace.label)
539
540
    @property
541
    def is_deleted(self) -> bool:
542
        """
543
        Is the workspace deleted ?
544
        """
545
        return self.workspace.is_deleted
546
547
    @property
548
    def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
549
        """
550
        get sidebar entries, those depends on activated apps.
551
        """
552
        # TODO - G.M - 22-05-2018 - Rework on this in
553
        # order to not use hardcoded list
554
        # list should be able to change (depending on activated/disabled
555
        # apps)
556
        app_api = ApplicationApi(
557
            app_list
558
        )
559
        return app_api.get_default_workspace_menu_entry(self.workspace)
560
561
    @property
562
    def frontend_url(self):
563
        root_frontend_url = get_root_frontend_url(self.config)
564
        workspace_frontend_url = WORKSPACE_FRONTEND_URL_SCHEMA.format(
565
            workspace_id=self.workspace_id,
566
        )
567
        return root_frontend_url + workspace_frontend_url
568
569
570
class UserRoleWorkspaceInContext(object):
571
    """
572
    Interface to get UserRoleInWorkspace data and related content
573
574
    """
575
    def __init__(
576
            self,
577
            user_role: UserRoleInWorkspace,
578
            dbsession: Session,
579
            config: CFG,
580
            # Extended params
581
            newly_created: bool = None,
582
            email_sent: bool = None
583
    )-> None:
584
        self.user_role = user_role
585
        self.dbsession = dbsession
586
        self.config = config
587
        # Extended params
588
        self.newly_created = newly_created
589
        self.email_sent = email_sent
590
591
    @property
592
    def user_id(self) -> int:
593
        """
594
        User who has the role has this id
595
        :return: user id as integer
596
        """
597
        return self.user_role.user_id
598
599
    @property
600
    def workspace_id(self) -> int:
601
        """
602
        This role apply only on the workspace with this workspace_id
603
        :return: workspace id as integer
604
        """
605
        return self.user_role.workspace_id
606
607
    # TODO - G.M - 23-05-2018 - Check the API spec for this this !
608
609
    @property
610
    def role_id(self) -> int:
611
        """
612
        role as int id, each value refer to a different role.
613
        """
614
        return self.user_role.role
615
616
    @property
617
    def role(self) -> str:
618
        return self.role_slug
619
620
    @property
621
    def role_slug(self) -> str:
622
        """
623
        simple name of the role of the user.
624
        can be anything from UserRoleInWorkspace SLUG, like
625
        'not_applicable', 'reader',
626
        'contributor', 'content-manager', 'workspace-manager'
627
        :return: user workspace role as slug.
628
        """
629
        return WorkspaceRoles.get_role_from_level(self.user_role.role).slug
630
631
    @property
632
    def is_active(self) -> bool:
633
        return self.user.is_active
634
635
    @property
636
    def do_notify(self) -> bool:
637
        return self.user_role.do_notify
638
639
    @property
640
    def user(self) -> UserInContext:
641
        """
642
        User who has this role, with context data
643
        :return: UserInContext object
644
        """
645
        return UserInContext(
646
            self.user_role.user,
647
            self.dbsession,
648
            self.config
649
        )
650
651
    @property
652
    def workspace(self) -> WorkspaceInContext:
653
        """
654
        Workspace related to this role, with his context data
655
        :return: WorkspaceInContext object
656
        """
657
        return WorkspaceInContext(
658
            self.user_role.workspace,
659
            self.dbsession,
660
            self.config
661
        )
662
663
664
class ContentInContext(object):
665
    """
666
    Interface to get Content data and Content data related to context.
667
    """
668
669
    def __init__(self, content: Content, dbsession: Session, config: CFG, user: User=None):  # nopep8
670
        self.content = content
671
        self.dbsession = dbsession
672
        self.config = config
673
        self._user = user
674
675
    # Default
676
    @property
677
    def content_id(self) -> int:
678
        return self.content.content_id
679
680
    @property
681
    def parent_id(self) -> int:
682
        """
683
        Return parent_id of the content
684
        """
685
        return self.content.parent_id
686
687
    @property
688
    def workspace_id(self) -> int:
689
        return self.content.workspace_id
690
691
    @property
692
    def label(self) -> str:
693
        return self.content.label
694
695
    @property
696
    def content_type(self) -> str:
697
        content_type = CONTENT_TYPES.get_one_by_slug(self.content.type)
698
        return content_type.slug
699
700
    @property
701
    def sub_content_types(self) -> typing.List[str]:
702
        return [_type.slug for _type in self.content.get_allowed_content_types()]  # nopep8
703
704
    @property
705
    def status(self) -> str:
706
        return self.content.status
707
708
    @property
709
    def is_archived(self) -> bool:
710
        return self.content.is_archived
711
712
    @property
713
    def is_deleted(self) -> bool:
714
        return self.content.is_deleted
715
716
    @property
717
    def raw_content(self) -> str:
718
        return self.content.description
719
720
    @property
721
    def author(self) -> UserInContext:
722
        return UserInContext(
723
            dbsession=self.dbsession,
724
            config=self.config,
725
            user=self.content.first_revision.owner
726
        )
727
728
    @property
729
    def current_revision_id(self) -> int:
730
        return self.content.revision_id
731
732
    @property
733
    def created(self) -> datetime:
734
        return self.content.created
735
736
    @property
737
    def modified(self) -> datetime:
738
        return self.updated
739
740
    @property
741
    def updated(self) -> datetime:
742
        return self.content.updated
743
744
    @property
745
    def last_modifier(self) -> UserInContext:
746
        return UserInContext(
747
            dbsession=self.dbsession,
748
            config=self.config,
749
            user=self.content.last_revision.owner
750
        )
751
752
    # Context-related
753
    @property
754
    def show_in_ui(self) -> bool:
755
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
756
        # if false, then do not show content in the treeview.
757
        # This may his maybe used for specific contents or for sub-contents.
758
        # Default is True.
759
        # In first version of the API, this field is always True
760
        return True
761
762
    @property
763
    def slug(self) -> str:
764
        return slugify(self.content.label)
765
766
    @property
767
    def read_by_user(self) -> bool:
768
        assert self._user
769
        return not self.content.has_new_information_for(self._user)
770
771
    @property
772
    def frontend_url(self) -> str:
773
        root_frontend_url = get_root_frontend_url(self.config)
774
        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
775
            workspace_id=self.workspace_id,
776
            content_type=self.content_type,
777
            content_id=self.content_id,
778
        )
779
        return root_frontend_url + content_frontend_url
780
781
    # file specific
782
    @property
783
    def page_nb(self) -> typing.Optional[int]:
784
        """
785
        :return: page_nb of content if available, None if unavailable
786
        """
787
        if self.content.depot_file:
788
            from tracim_backend.lib.core.content import ContentApi
789
            content_api = ContentApi(
790
                current_user=self._user,
791
                session=self.dbsession,
792
                config=self.config
793
            )
794
            return content_api.get_preview_page_nb(self.content.revision_id)
795
        else:
796
            return None
797
798
    @property
799
    def mimetype(self) -> str:
800
        """
801
        :return: mimetype of content if available, None if unavailable
802
        """
803
        return self.content.file_mimetype
804
805
    @property
806
    def size(self) -> typing.Optional[int]:
807
        """
808
        :return: size of content if available, None if unavailable
809
        """
810
        if self.content.depot_file:
811
            return self.content.depot_file.file.content_length
812
        else:
813
            return None
814
815
    @property
816
    def pdf_available(self) -> bool:
817
        """
818
        :return: bool about if pdf version of content is available
819
        """
820
        if self.content.depot_file:
821
            from tracim_backend.lib.core.content import ContentApi
822
            content_api = ContentApi(
823
                current_user=self._user,
824
                session=self.dbsession,
825
                config=self.config
826
            )
827
            return content_api.has_pdf_preview(self.content.revision_id)
828
        else:
829
            return False
830
831
832
class RevisionInContext(object):
833
    """
834
    Interface to get Content data and Content data related to context.
835
    """
836
837
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
838
        assert content_revision is not None
839
        self.revision = content_revision
840
        self.dbsession = dbsession
841
        self.config = config
842
        self._user = user
843
844
    # Default
845
    @property
846
    def content_id(self) -> int:
847
        return self.revision.content_id
848
849
    @property
850
    def parent_id(self) -> int:
851
        """
852
        Return parent_id of the content
853
        """
854
        return self.revision.parent_id
855
856
    @property
857
    def workspace_id(self) -> int:
858
        return self.revision.workspace_id
859
860
    @property
861
    def label(self) -> str:
862
        return self.revision.label
863
864
    @property
865
    def revision_type(self) -> str:
866
        return self.revision.revision_type
867
868
    @property
869
    def content_type(self) -> str:
870
        return CONTENT_TYPES.get_one_by_slug(self.revision.type).slug
871
872
    @property
873
    def sub_content_types(self) -> typing.List[str]:
874
        return [_type.slug for _type
875
                in self.revision.node.get_allowed_content_types()]
876
877
    @property
878
    def status(self) -> str:
879
        return self.revision.status
880
881
    @property
882
    def is_archived(self) -> bool:
883
        return self.revision.is_archived
884
885
    @property
886
    def is_deleted(self) -> bool:
887
        return self.revision.is_deleted
888
889
    @property
890
    def raw_content(self) -> str:
891
        return self.revision.description
892
893
    @property
894
    def author(self) -> UserInContext:
895
        return UserInContext(
896
            dbsession=self.dbsession,
897
            config=self.config,
898
            user=self.revision.owner
899
        )
900
901
    @property
902
    def revision_id(self) -> int:
903
        return self.revision.revision_id
904
905
    @property
906
    def created(self) -> datetime:
907
        return self.updated
908
909
    @property
910
    def modified(self) -> datetime:
911
        return self.updated
912
913
    @property
914
    def updated(self) -> datetime:
915
        return self.revision.updated
916
917
    @property
918
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
919
        """
920
        Get next revision (later revision)
921
        :return: next_revision
922
        """
923
        next_revision = None
924
        revisions = self.revision.node.revisions
925
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
926
        # current one
927
        next_revisions = [
928
            revision for revision in revisions
929
            if revision.revision_id > self.revision.revision_id
930
        ]
931
        if next_revisions:
932
            # INFO - G.M - 2018-06-177 -sort revisions by date
933
            sorted_next_revisions = sorted(
934
                next_revisions,
935
                key=lambda revision: revision.updated
936
            )
937
            # INFO - G.M - 2018-06-177 - return only next revision
938
            return sorted_next_revisions[0]
939
        else:
940
            return None
941
942
    @property
943
    def comment_ids(self) -> typing.List[int]:
944
        """
945
        Get list of ids of all current revision related comments
946
        :return: list of comments ids
947
        """
948
        comments = self.revision.node.get_comments()
949
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
950
        revision_comments = [
951
            comment for comment in comments
952
            if comment.created > self.revision.updated
953
        ]
954
        if self.next_revision:
955
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
956
            # than current remove comments from theses rev (comments older
957
            # than next_revision.)
958
            revision_comments = [
959
                comment for comment in revision_comments
960
                if comment.created < self.next_revision.updated
961
            ]
962
        sorted_revision_comments = sorted(
963
            revision_comments,
964
            key=lambda revision: revision.created
965
        )
966
        comment_ids = []
967
        for comment in sorted_revision_comments:
968
            comment_ids.append(comment.content_id)
969
        return comment_ids
970
971
    # Context-related
972
    @property
973
    def show_in_ui(self) -> bool:
974
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
975
        # if false, then do not show content in the treeview.
976
        # This may his maybe used for specific contents or for sub-contents.
977
        # Default is True.
978
        # In first version of the API, this field is always True
979
        return True
980
981
    @property
982
    def slug(self) -> str:
983
        return slugify(self.revision.label)
984
985
    # file specific
986
    @property
987
    def page_nb(self) -> typing.Optional[int]:
988
        """
989
        :return: page_nb of content if available, None if unavailable
990
        """
991
        if self.revision.depot_file:
992
            # TODO - G.M - 2018-09-05 - Fix circular import better
993
            from tracim_backend.lib.core.content import ContentApi
994
            content_api = ContentApi(
995
                current_user=self._user,
996
                session=self.dbsession,
997
                config=self.config
998
            )
999
            return content_api.get_preview_page_nb(self.revision.revision_id)
1000
        else:
1001
            return None
1002
1003
    @property
1004
    def mimetype(self) -> str:
1005
        """
1006
        :return: mimetype of content if available, None if unavailable
1007
        """
1008
        return self.revision.file_mimetype
1009
1010
    @property
1011
    def size(self) -> typing.Optional[int]:
1012
        """
1013
        :return: size of content if available, None if unavailable
1014
        """
1015
        if self.revision.depot_file:
1016
            return self.revision.depot_file.file.content_length
1017
        else:
1018
            return None
1019
1020
    @property
1021
    def pdf_available(self) -> bool:
1022
        """
1023
        :return: bool about if pdf version of content is available
1024
        """
1025
        if self.revision.depot_file:
1026
            from tracim_backend.lib.core.content import ContentApi
1027
            content_api = ContentApi(
1028
                current_user=self._user,
1029
                session=self.dbsession,
1030
                config=self.config
1031
            )
1032
            return content_api.has_pdf_preview(self.revision.revision_id)
1033
        else:
1034
            return False
1035