Passed
Pull Request — develop (#79)
by inkhey
01:35
created

ContentInContext.__init__()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nop 5
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
        website: str,
36
    ):
37
        self.name = name
38
        self.version = version
39
        self.datetime = datetime
40
        self.website = website
41
42
43
class ConfigModel(object):
44
45
    def __init__(
46
        self,
47
        email_notification_activated: bool
48
    ):
49
        self.email_notification_activated = email_notification_activated
50
51
52
class PreviewAllowedDim(object):
53
54
    def __init__(
55
            self,
56
            restricted: bool,
57
            dimensions: typing.List[PreviewDim]
58
    ) -> None:
59
        self.restricted = restricted
60
        self.dimensions = dimensions
61
62
63
class MoveParams(object):
64
    """
65
    Json body params for move action model
66
    """
67
    def __init__(self, new_parent_id: str, new_workspace_id: str = None) -> None:  # nopep8
68
        self.new_parent_id = new_parent_id
69
        self.new_workspace_id = new_workspace_id
70
71
72
class LoginCredentials(object):
73
    """
74
    Login credentials model for login model
75
    """
76
77
    def __init__(self, email: str, password: str) -> None:
78
        self.email = email
79
        self.password = password
80
81
82
class ResetPasswordRequest(object):
83
    """
84
    Reset password : request to reset password of user
85
    """
86
    def __init__(self, email: str) -> None:
87
        self.email = email
88
89
90
class ResetPasswordCheckToken(object):
91
    """
92
    Reset password : check reset password token
93
    """
94
    def __init__(
95
        self,
96
        reset_password_token: str,
97
        email: str,
98
    ) -> None:
99
        self.email = email
100
        self.reset_password_token = reset_password_token
101
102
103
class ResetPasswordModify(object):
104
    """
105
    Reset password : modification step
106
    """
107
    def __init__(
108
        self,
109
        reset_password_token: str,
110
        email: str,
111
        new_password: str,
112
        new_password2: str
113
    ) -> None:
114
        self.email = email
115
        self.reset_password_token = reset_password_token
116
        self.new_password = new_password
117
        self.new_password2 = new_password2
118
119
120
class SetEmail(object):
121
    """
122
    Just an email and password
123
    """
124
    def __init__(self, loggedin_user_password: str, email: str) -> None:
125
        self.loggedin_user_password = loggedin_user_password
126
        self.email = email
127
128
129
class SetPassword(object):
130
    """
131
    Just an password
132
    """
133
    def __init__(
134
        self,
135
        loggedin_user_password: str,
136
        new_password: str,
137
        new_password2: str
138
    ) -> None:
139
        self.loggedin_user_password = loggedin_user_password
140
        self.new_password = new_password
141
        self.new_password2 = new_password2
142
143
144
class UserInfos(object):
145
    """
146
    Just some user infos
147
    """
148
    def __init__(self, timezone: str, public_name: str, lang: str) -> None:
149
        self.timezone = timezone
150
        self.public_name = public_name
151
        self.lang = lang
152
153
154
class UserProfile(object):
155
    """
156
    Just some user infos
157
    """
158
    def __init__(self, profile: str) -> None:
159
        self.profile = profile
160
161
162
class UserCreation(object):
163
    """
164
    Just some user infos
165
    """
166
    def __init__(
167
            self,
168
            email: str,
169
            password: str = None,
170
            public_name: str = None,
171
            timezone: str = None,
172
            profile: str = None,
173
            lang: str = None,
174
            email_notification: bool = True,
175
    ) -> None:
176
        self.email = email
177
        # INFO - G.M - 2018-08-16 - cleartext password, default value
178
        # is auto-generated.
179
        self.password = password or password_generator()
180
        self.public_name = public_name or None
181
        self.timezone = timezone or ''
182
        self.lang = lang or None
183
        self.profile = profile or Group.TIM_USER_GROUPNAME
184
        self.email_notification = email_notification
185
186
187
class WorkspaceAndContentPath(object):
188
    """
189
    Paths params with workspace id and content_id model
190
    """
191
    def __init__(self, workspace_id: int, content_id: int) -> None:
192
        self.content_id = content_id
193
        self.workspace_id = workspace_id
194
195
196
class WorkspaceAndContentRevisionPath(object):
197
    """
198
    Paths params with workspace id and content_id model
199
    """
200
    def __init__(self, workspace_id: int, content_id: int, revision_id) -> None:
201
        self.content_id = content_id
202
        self.revision_id = revision_id
203
        self.workspace_id = workspace_id
204
205
206
class ContentPreviewSizedPath(object):
207
    """
208
    Paths params with workspace id and content_id, width, heigth
209
    """
210
    def __init__(self, workspace_id: int, content_id: int, width: int, height: int) -> None:  # nopep8
211
        self.content_id = content_id
212
        self.workspace_id = workspace_id
213
        self.width = width
214
        self.height = height
215
216
217
class RevisionPreviewSizedPath(object):
218
    """
219
    Paths params with workspace id and content_id, revision_id width, heigth
220
    """
221
    def __init__(self, workspace_id: int, content_id: int, revision_id: int, width: int, height: int) -> None:  # nopep8
222
        self.content_id = content_id
223
        self.revision_id = revision_id
224
        self.workspace_id = workspace_id
225
        self.width = width
226
        self.height = height
227
228
229
class WorkspaceAndUserPath(object):
230
    """
231
    Paths params with workspace id and user_id
232
    """
233
    def __init__(self, workspace_id: int, user_id: int):
234
        self.workspace_id = workspace_id
235
        self.user_id = user_id
236
237
238
class UserWorkspaceAndContentPath(object):
239
    """
240
    Paths params with user_id, workspace id and content_id model
241
    """
242
    def __init__(self, user_id: int, workspace_id: int, content_id: int) -> None:  # nopep8
243
        self.content_id = content_id
244
        self.workspace_id = workspace_id
245
        self.user_id = user_id
246
247
248
class CommentPath(object):
249
    """
250
    Paths params with workspace id and content_id and comment_id model
251
    """
252
    def __init__(
253
        self,
254
        workspace_id: int,
255
        content_id: int,
256
        comment_id: int
257
    ) -> None:
258
        self.content_id = content_id
259
        self.workspace_id = workspace_id
260
        self.comment_id = comment_id
261
262
263
class AutocompleteQuery(object):
264
    """
265
    Autocomplete query model
266
    """
267
    def __init__(self, acp: str):
268
        self.acp = acp
269
270
271
class FileQuery(object):
272
    """
273
    File query model
274
    """
275
    def __init__(
276
        self,
277
        force_download: int = 0,
278
    ):
279
        self.force_download = force_download
280
281
282
class PageQuery(object):
283
    """
284
    Page query model
285
    """
286
    def __init__(
287
            self,
288
            force_download: int = 0,
289
            page: int = 1
290
    ):
291
        self.force_download = force_download
292
        self.page = page
293
294
295
class ContentFilter(object):
296
    """
297
    Content filter model
298
    """
299
    def __init__(
300
            self,
301
            workspace_id: int = None,
302
            parent_id: int = None,
303
            show_archived: int = 0,
304
            show_deleted: int = 0,
305
            show_active: int = 1,
306
            content_type: str = None,
307
            label: str = None,
308
            offset: int = None,
309
            limit: int = None,
310
    ) -> None:
311
        self.parent_id = parent_id
312
        self.workspace_id = workspace_id
313
        self.show_archived = bool(show_archived)
314
        self.show_deleted = bool(show_deleted)
315
        self.show_active = bool(show_active)
316
        self.limit = limit
317
        self.offset = offset
318
        self.label = label
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 = None,
358
        user_email_or_public_name: str = None,
359
        role: str = None,
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
    @property
858
    def file_extension(self) -> str:
859
        """
860
        :return: file extension with "." at the beginning, example : .txt
861
        """
862
        return self.content.file_extension
863
864
865
class RevisionInContext(object):
866
    """
867
    Interface to get Content data and Content data related to context.
868
    """
869
870
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
871
        assert content_revision is not None
872
        self.revision = content_revision
873
        self.dbsession = dbsession
874
        self.config = config
875
        self._user = user
876
877
    # Default
878
    @property
879
    def content_id(self) -> int:
880
        return self.revision.content_id
881
882
    @property
883
    def parent_id(self) -> int:
884
        """
885
        Return parent_id of the content
886
        """
887
        return self.revision.parent_id
888
889
    @property
890
    def workspace_id(self) -> int:
891
        return self.revision.workspace_id
892
893
    @property
894
    def label(self) -> str:
895
        return self.revision.label
896
897
    @property
898
    def revision_type(self) -> str:
899
        return self.revision.revision_type
900
901
    @property
902
    def content_type(self) -> str:
903
        return CONTENT_TYPES.get_one_by_slug(self.revision.type).slug
904
905
    @property
906
    def sub_content_types(self) -> typing.List[str]:
907
        return [_type.slug for _type
908
                in self.revision.node.get_allowed_content_types()]
909
910
    @property
911
    def status(self) -> str:
912
        return self.revision.status
913
914
    @property
915
    def is_archived(self) -> bool:
916
        return self.revision.is_archived
917
918
    @property
919
    def is_deleted(self) -> bool:
920
        return self.revision.is_deleted
921
922
    @property
923
    def raw_content(self) -> str:
924
        return self.revision.description
925
926
    @property
927
    def author(self) -> UserInContext:
928
        return UserInContext(
929
            dbsession=self.dbsession,
930
            config=self.config,
931
            user=self.revision.owner
932
        )
933
934
    @property
935
    def revision_id(self) -> int:
936
        return self.revision.revision_id
937
938
    @property
939
    def created(self) -> datetime:
940
        return self.updated
941
942
    @property
943
    def modified(self) -> datetime:
944
        return self.updated
945
946
    @property
947
    def updated(self) -> datetime:
948
        return self.revision.updated
949
950
    @property
951
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
952
        """
953
        Get next revision (later revision)
954
        :return: next_revision
955
        """
956
        next_revision = None
957
        revisions = self.revision.node.revisions
958
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
959
        # current one
960
        next_revisions = [
961
            revision for revision in revisions
962
            if revision.revision_id > self.revision.revision_id
963
        ]
964
        if next_revisions:
965
            # INFO - G.M - 2018-06-177 -sort revisions by date
966
            sorted_next_revisions = sorted(
967
                next_revisions,
968
                key=lambda revision: revision.updated
969
            )
970
            # INFO - G.M - 2018-06-177 - return only next revision
971
            return sorted_next_revisions[0]
972
        else:
973
            return None
974
975
    @property
976
    def comment_ids(self) -> typing.List[int]:
977
        """
978
        Get list of ids of all current revision related comments
979
        :return: list of comments ids
980
        """
981
        comments = self.revision.node.get_comments()
982
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
983
        revision_comments = [
984
            comment for comment in comments
985
            if comment.created > self.revision.updated
986
        ]
987
        if self.next_revision:
988
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
989
            # than current remove comments from theses rev (comments older
990
            # than next_revision.)
991
            revision_comments = [
992
                comment for comment in revision_comments
993
                if comment.created < self.next_revision.updated
994
            ]
995
        sorted_revision_comments = sorted(
996
            revision_comments,
997
            key=lambda revision: revision.created
998
        )
999
        comment_ids = []
1000
        for comment in sorted_revision_comments:
1001
            comment_ids.append(comment.content_id)
1002
        return comment_ids
1003
1004
    # Context-related
1005
    @property
1006
    def show_in_ui(self) -> bool:
1007
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
1008
        # if false, then do not show content in the treeview.
1009
        # This may his maybe used for specific contents or for sub-contents.
1010
        # Default is True.
1011
        # In first version of the API, this field is always True
1012
        return True
1013
1014
    @property
1015
    def slug(self) -> str:
1016
        return slugify(self.revision.label)
1017
1018
    # file specific
1019
    @property
1020
    def page_nb(self) -> typing.Optional[int]:
1021
        """
1022
        :return: page_nb of content if available, None if unavailable
1023
        """
1024
        if self.revision.depot_file:
1025
            # TODO - G.M - 2018-09-05 - Fix circular import better
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.get_preview_page_nb(self.revision.revision_id)
1033
        else:
1034
            return None
1035
1036
    @property
1037
    def mimetype(self) -> str:
1038
        """
1039
        :return: mimetype of content if available, None if unavailable
1040
        """
1041
        return self.revision.file_mimetype
1042
1043
    @property
1044
    def size(self) -> typing.Optional[int]:
1045
        """
1046
        :return: size of content if available, None if unavailable
1047
        """
1048
        if self.revision.depot_file:
1049
            return self.revision.depot_file.file.content_length
1050
        else:
1051
            return None
1052
1053
    @property
1054
    def pdf_available(self) -> bool:
1055
        """
1056
        :return: bool about if pdf version of content is available
1057
        """
1058
        if self.revision.depot_file:
1059
            from tracim_backend.lib.core.content import ContentApi
1060
            content_api = ContentApi(
1061
                current_user=self._user,
1062
                session=self.dbsession,
1063
                config=self.config
1064
            )
1065
            return content_api.has_pdf_preview(self.revision.revision_id)
1066
        else:
1067
            return False
1068
1069
    @property
1070
    def file_extension(self) -> str:
1071
        """
1072
        :return: file extension with "." at the beginning, example : .txt
1073
        """
1074
        return self.revision.file_extension
1075