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

WorkspaceInContext.label()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nop 1
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