Passed
Push — develop ( 6e2a24...07d4c2 )
by Bastien
01:54 queued 23s
created

WorkspaceUpdate.__init__()   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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