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

UserCreation.__init__()   A

Complexity

Conditions 1

Size

Total Lines 19
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 19
rs 9.6
c 0
b 0
f 0
cc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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