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

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