Completed
Push — develop ( 9a78f1...d1dc2b )
by Tracim
28s queued 12s
created

RevisionInContext.size()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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