Passed
Pull Request — develop (#42)
by inkhey
02:42
created

UserInContext.avatar_url()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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