Passed
Pull Request — develop (#77)
by inkhey
01:38
created

UserRoleWorkspaceInContext.user_id()   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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