Completed
Push — develop ( 1af38c...877f7e )
by Bastien
21s queued 10s
created

FileCreation.__init__()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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