UserCreation.__init__()   A
last analyzed

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_frontend_ui_base_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: str = None,
406
        user_public_name: str = None,
407
        role: str = None,
408
    ) -> None:
409
        self.role = role
410
        self.user_email = user_email
411
        self.user_public_name = user_public_name
412
        self.user_id = user_id
413
414
415
class WorkspaceUpdate(object):
416
    """
417
    Update workspace
418
    """
419
    def __init__(
420
        self,
421
        label: str,
422
        description: str,
423
    ) -> None:
424
        self.label = label
425
        self.description = description
426
427
428
class ContentCreation(object):
429
    """
430
    Content creation model
431
    """
432
    def __init__(
433
        self,
434
        label: str,
435
        content_type: str,
436
        parent_id: typing.Optional[int] = None,
437
    ) -> None:
438
        self.label = label
439
        self.content_type = content_type
440
        self.parent_id = parent_id or None
441
442
443
class CommentCreation(object):
444
    """
445
    Comment creation model
446
    """
447
    def __init__(
448
        self,
449
        raw_content: str,
450
    ) -> None:
451
        self.raw_content = raw_content
452
453
454
class SetContentStatus(object):
455
    """
456
    Set content status
457
    """
458
    def __init__(
459
        self,
460
        status: str,
461
    ) -> None:
462
        self.status = status
463
464
465
class TextBasedContentUpdate(object):
466
    """
467
    TextBasedContent update model
468
    """
469
    def __init__(
470
        self,
471
        label: str,
472
        raw_content: str,
473
    ) -> None:
474
        self.label = label
475
        self.raw_content = raw_content
476
477
478
class FolderContentUpdate(object):
479
    """
480
    Folder Content update model
481
    """
482
    def __init__(
483
        self,
484
        label: str,
485
        raw_content: str,
486
        sub_content_types: typing.List[str],
487
    ) -> None:
488
        self.label = label
489
        self.raw_content = raw_content
490
        self.sub_content_types = sub_content_types
491
492
493
class TypeUser(Enum):
494
    """Params used to find user"""
495
    USER_ID = 'found_id'
496
    EMAIL = 'found_email'
497
    PUBLIC_NAME = 'found_public_name'
498
499
500
class UserInContext(object):
501
    """
502
    Interface to get User data and User data related to context.
503
    """
504
505
    def __init__(self, user: User, dbsession: Session, config: CFG) -> None:
506
        self.user = user
507
        self.dbsession = dbsession
508
        self.config = config
509
510
    # Default
511
512
    @property
513
    def email(self) -> str:
514
        return self.user.email
515
516
    @property
517
    def user_id(self) -> int:
518
        return self.user.user_id
519
520
    @property
521
    def public_name(self) -> str:
522
        return self.display_name
523
524
    @property
525
    def display_name(self) -> str:
526
        return self.user.display_name
527
528
    @property
529
    def created(self) -> datetime:
530
        return self.user.created
531
532
    @property
533
    def is_active(self) -> bool:
534
        return self.user.is_active
535
536
    @property
537
    def timezone(self) -> str:
538
        return self.user.timezone
539
540
    @property
541
    def lang(self) -> str:
542
        return self.user.lang
543
544
    @property
545
    def profile(self) -> Profile:
546
        return self.user.profile.name
547
548
    @property
549
    def is_deleted(self) -> bool:
550
        return self.user.is_deleted
551
552
    # Context related
553
554
    @property
555
    def calendar_url(self) -> typing.Optional[str]:
556
        # TODO - G-M - 20-04-2018 - [Calendar] Replace calendar code to get
557
        # url calendar url.
558
        #
559
        # from tracim.lib.calendar import CalendarManager
560
        # calendar_manager = CalendarManager(None)
561
        # return calendar_manager.get_workspace_calendar_url(self.workspace_id)
562
        return None
563
564
    @property
565
    def avatar_url(self) -> typing.Optional[str]:
566
        # TODO - G-M - 20-04-2018 - [Avatar] Add user avatar feature
567
        return None
568
569
570
class WorkspaceInContext(object):
571
    """
572
    Interface to get Workspace data and Workspace data related to context.
573
    """
574
575
    def __init__(self, workspace: Workspace, dbsession: Session, config: CFG) -> None:  # nopep8
576
        self.workspace = workspace
577
        self.dbsession = dbsession
578
        self.config = config
579
580
    @property
581
    def workspace_id(self) -> int:
582
        """
583
        numeric id of the workspace.
584
        """
585
        return self.workspace.workspace_id
586
587
    @property
588
    def id(self) -> int:
589
        """
590
        alias of workspace_id
591
        """
592
        return self.workspace_id
593
594
    @property
595
    def label(self) -> str:
596
        """
597
        get workspace label
598
        """
599
        return self.workspace.label
600
601
    @property
602
    def description(self) -> str:
603
        """
604
        get workspace description
605
        """
606
        return self.workspace.description
607
608
    @property
609
    def slug(self) -> str:
610
        """
611
        get workspace slug
612
        """
613
        return slugify(self.workspace.label)
614
615
    @property
616
    def is_deleted(self) -> bool:
617
        """
618
        Is the workspace deleted ?
619
        """
620
        return self.workspace.is_deleted
621
622
    @property
623
    def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
624
        """
625
        get sidebar entries, those depends on activated apps.
626
        """
627
        # TODO - G.M - 22-05-2018 - Rework on this in
628
        # order to not use hardcoded list
629
        # list should be able to change (depending on activated/disabled
630
        # apps)
631
        app_api = ApplicationApi(
632
            app_list
633
        )
634
        return app_api.get_default_workspace_menu_entry(self.workspace)
635
636
    @property
637
    def frontend_url(self):
638
        root_frontend_url = get_frontend_ui_base_url(self.config)
639
        workspace_frontend_url = WORKSPACE_FRONTEND_URL_SCHEMA.format(
640
            workspace_id=self.workspace_id,
641
        )
642
        return root_frontend_url + workspace_frontend_url
643
644
645
class UserRoleWorkspaceInContext(object):
646
    """
647
    Interface to get UserRoleInWorkspace data and related content
648
649
    """
650
    def __init__(
651
            self,
652
            user_role: UserRoleInWorkspace,
653
            dbsession: Session,
654
            config: CFG,
655
            # Extended params
656
            newly_created: bool = None,
657
            email_sent: bool = None
658
    )-> None:
659
        self.user_role = user_role
660
        self.dbsession = dbsession
661
        self.config = config
662
        # Extended params
663
        self.newly_created = newly_created
664
        self.email_sent = email_sent
665
666
    @property
667
    def user_id(self) -> int:
668
        """
669
        User who has the role has this id
670
        :return: user id as integer
671
        """
672
        return self.user_role.user_id
673
674
    @property
675
    def workspace_id(self) -> int:
676
        """
677
        This role apply only on the workspace with this workspace_id
678
        :return: workspace id as integer
679
        """
680
        return self.user_role.workspace_id
681
682
    # TODO - G.M - 23-05-2018 - Check the API spec for this this !
683
684
    @property
685
    def role_id(self) -> int:
686
        """
687
        role as int id, each value refer to a different role.
688
        """
689
        return self.user_role.role
690
691
    @property
692
    def role(self) -> str:
693
        return self.role_slug
694
695
    @property
696
    def role_slug(self) -> str:
697
        """
698
        simple name of the role of the user.
699
        can be anything from UserRoleInWorkspace SLUG, like
700
        'not_applicable', 'reader',
701
        'contributor', 'content-manager', 'workspace-manager'
702
        :return: user workspace role as slug.
703
        """
704
        return WorkspaceRoles.get_role_from_level(self.user_role.role).slug
705
706
    @property
707
    def is_active(self) -> bool:
708
        return self.user.is_active
709
710
    @property
711
    def do_notify(self) -> bool:
712
        return self.user_role.do_notify
713
714
    @property
715
    def user(self) -> UserInContext:
716
        """
717
        User who has this role, with context data
718
        :return: UserInContext object
719
        """
720
        return UserInContext(
721
            self.user_role.user,
722
            self.dbsession,
723
            self.config
724
        )
725
726
    @property
727
    def workspace(self) -> WorkspaceInContext:
728
        """
729
        Workspace related to this role, with his context data
730
        :return: WorkspaceInContext object
731
        """
732
        return WorkspaceInContext(
733
            self.user_role.workspace,
734
            self.dbsession,
735
            self.config
736
        )
737
738
739
class ContentInContext(object):
740
    """
741
    Interface to get Content data and Content data related to context.
742
    """
743
744
    def __init__(self, content: Content, dbsession: Session, config: CFG, user: User=None) -> None:  # nopep8
745
        self.content = content
746
        self.dbsession = dbsession
747
        self.config = config
748
        self._user = user
749
750
    # Default
751
    @property
752
    def content_id(self) -> int:
753
        return self.content.content_id
754
755
    @property
756
    def parent_id(self) -> int:
757
        """
758
        Return parent_id of the content
759
        """
760
        return self.content.parent_id
761
762
    @property
763
    def workspace_id(self) -> int:
764
        return self.content.workspace_id
765
766
    @property
767
    def label(self) -> str:
768
        return self.content.label
769
770
    @property
771
    def content_type(self) -> str:
772
        content_type = content_type_list.get_one_by_slug(self.content.type)
773
        return content_type.slug
774
775
    @property
776
    def sub_content_types(self) -> typing.List[str]:
777
        return [_type.slug for _type in self.content.get_allowed_content_types()]  # nopep8
778
779
    @property
780
    def status(self) -> str:
781
        return self.content.status
782
783
    @property
784
    def is_archived(self) -> bool:
785
        return self.content.is_archived
786
787
    @property
788
    def is_deleted(self) -> bool:
789
        return self.content.is_deleted
790
791
    @property
792
    def is_editable(self) -> bool:
793
        from tracim_backend.lib.core.content import ContentApi
794
        content_api = ContentApi(
795
            current_user=self._user,
796
            session=self.dbsession,
797
            config=self.config,
798
            show_deleted=True,
799
            show_archived=True,
800
            show_active=True,
801
            show_temporary=True,
802
        )
803
        return content_api.is_editable(self.content)
804
805
    @property
806
    def raw_content(self) -> str:
807
        return self.content.description
808
809
    @property
810
    def author(self) -> UserInContext:
811
        return UserInContext(
812
            dbsession=self.dbsession,
813
            config=self.config,
814
            user=self.content.first_revision.owner
815
        )
816
817
    @property
818
    def current_revision_id(self) -> int:
819
        return self.content.revision_id
820
821
    @property
822
    def created(self) -> datetime:
823
        return self.content.created
824
825
    @property
826
    def modified(self) -> datetime:
827
        return self.updated
828
829
    @property
830
    def updated(self) -> datetime:
831
        return self.content.updated
832
833
    @property
834
    def last_modifier(self) -> UserInContext:
835
        return UserInContext(
836
            dbsession=self.dbsession,
837
            config=self.config,
838
            user=self.content.last_revision.owner
839
        )
840
841
    # Context-related
842
    @property
843
    def show_in_ui(self) -> bool:
844
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
845
        # if false, then do not show content in the treeview.
846
        # This may his maybe used for specific contents or for sub-contents.
847
        # Default is True.
848
        # In first version of the API, this field is always True
849
        return True
850
851
    @property
852
    def slug(self) -> str:
853
        return slugify(self.content.label)
854
855
    @property
856
    def read_by_user(self) -> bool:
857
        assert self._user
858
        return not self.content.has_new_information_for(self._user)
859
860
    @property
861
    def frontend_url(self) -> str:
862
        root_frontend_url = get_frontend_ui_base_url(self.config)
863
        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
864
            workspace_id=self.workspace_id,
865
            content_type=self.content_type,
866
            content_id=self.content_id,
867
        )
868
        return root_frontend_url + content_frontend_url
869
870
    # file specific
871
    @property
872
    def page_nb(self) -> typing.Optional[int]:
873
        """
874
        :return: page_nb of content if available, None if unavailable
875
        """
876
        if self.content.depot_file:
877
            from tracim_backend.lib.core.content import ContentApi
878
            content_api = ContentApi(
879
                current_user=self._user,
880
                session=self.dbsession,
881
                config=self.config,
882
                show_deleted=True,
883
                show_archived=True,
884
                show_active=True,
885
                show_temporary=True,
886
            )
887
            return content_api.get_preview_page_nb(
888
                self.content.revision_id,
889
                file_extension=self.content.file_extension
890
            )
891
        else:
892
            return None
893
894
    @property
895
    def mimetype(self) -> str:
896
        """
897
        :return: mimetype of content if available, None if unavailable
898
        """
899
        return self.content.file_mimetype
900
901
    @property
902
    def size(self) -> typing.Optional[int]:
903
        """
904
        :return: size of content if available, None if unavailable
905
        """
906
        if self.content.depot_file:
907
            return self.content.depot_file.file.content_length
908
        else:
909
            return None
910
911
    @property
912
    def has_pdf_preview(self) -> bool:
913
        """
914
        :return: bool about if pdf version of content is available
915
        """
916
        if not self.content.depot_file:
917
            return False
918
919
        from tracim_backend.lib.core.content import ContentApi
920
        content_api = ContentApi(
921
            current_user=self._user,
922
            session=self.dbsession,
923
            config=self.config,
924
            show_deleted=True,
925
            show_archived=True,
926
            show_active=True,
927
            show_temporary=True,
928
        )
929
        return content_api.has_pdf_preview(
930
            self.content.revision_id,
931
            file_extension=self.content.file_extension
932
            )
933
934
    @property
935
    def has_jpeg_preview(self) -> bool:
936
        """
937
        :return: bool about if jpeg version of content is available
938
        """
939
        if not self.content.depot_file:
940
            return False
941
942
        from tracim_backend.lib.core.content import ContentApi
943
        content_api = ContentApi(
944
            current_user=self._user,
945
            session=self.dbsession,
946
            config=self.config,
947
            show_deleted=True,
948
            show_archived=True,
949
            show_active=True,
950
            show_temporary=True,
951
        )
952
        return content_api.has_jpeg_preview(
953
            self.content.revision_id,
954
            file_extension=self.content.file_extension
955
        )
956
957
958
    @property
959
    def file_extension(self) -> str:
960
        """
961
        :return: file extension with "." at the beginning, example : .txt
962
        """
963
        return self.content.file_extension
964
965
    @property
966
    def filename(self) -> str:
967
        """
968
        :return: complete filename with both label and file extension part
969
        """
970
        return self.content.file_name
971
972
973
class RevisionInContext(object):
974
    """
975
    Interface to get Content data and Content data related to context.
976
    """
977
978
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
979
        assert content_revision is not None
980
        self.revision = content_revision
981
        self.dbsession = dbsession
982
        self.config = config
983
        self._user = user
984
985
    # Default
986
    @property
987
    def content_id(self) -> int:
988
        return self.revision.content_id
989
990
    @property
991
    def parent_id(self) -> int:
992
        """
993
        Return parent_id of the content
994
        """
995
        return self.revision.parent_id
996
997
    @property
998
    def workspace_id(self) -> int:
999
        return self.revision.workspace_id
1000
1001
    @property
1002
    def label(self) -> str:
1003
        return self.revision.label
1004
1005
    @property
1006
    def revision_type(self) -> str:
1007
        return self.revision.revision_type
1008
1009
    @property
1010
    def content_type(self) -> str:
1011
        return content_type_list.get_one_by_slug(self.revision.type).slug
1012
1013
    @property
1014
    def sub_content_types(self) -> typing.List[str]:
1015
        return [_type.slug for _type
1016
                in self.revision.node.get_allowed_content_types()]
1017
1018
    @property
1019
    def status(self) -> str:
1020
        return self.revision.status
1021
1022
    @property
1023
    def is_archived(self) -> bool:
1024
        return self.revision.is_archived
1025
1026
    @property
1027
    def is_deleted(self) -> bool:
1028
        return self.revision.is_deleted
1029
1030
    @property
1031
    def is_editable(self) -> bool:
1032
        from tracim_backend.lib.core.content import ContentApi
1033
        content_api = ContentApi(
1034
            current_user=self._user,
1035
            session=self.dbsession,
1036
            config=self.config,
1037
            show_deleted=True,
1038
            show_archived=True,
1039
            show_active=True,
1040
            show_temporary=True,
1041
        )
1042
        # INFO - G.M - 2018-11-02 - check if revision is last one and if it is,
1043
        # return editability of content.
1044
        content = content_api.get_one(
1045
            content_id=self.revision.content_id,
1046
            content_type=content_type_list.Any_SLUG
1047
        )
1048
        if content.revision_id == self.revision_id:
1049
            return content_api.is_editable(content)
1050
        # INFO - G.M - 2018-11-02 - old revision are not editable
1051
        return False
1052
1053
    @property
1054
    def raw_content(self) -> str:
1055
        return self.revision.description
1056
1057
    @property
1058
    def author(self) -> UserInContext:
1059
        return UserInContext(
1060
            dbsession=self.dbsession,
1061
            config=self.config,
1062
            user=self.revision.owner
1063
        )
1064
1065
    @property
1066
    def revision_id(self) -> int:
1067
        return self.revision.revision_id
1068
1069
    @property
1070
    def created(self) -> datetime:
1071
        return self.updated
1072
1073
    @property
1074
    def modified(self) -> datetime:
1075
        return self.updated
1076
1077
    @property
1078
    def updated(self) -> datetime:
1079
        return self.revision.updated
1080
1081
    @property
1082
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
1083
        """
1084
        Get next revision (later revision)
1085
        :return: next_revision
1086
        """
1087
        next_revision = None
1088
        revisions = self.revision.node.revisions
1089
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
1090
        # current one
1091
        next_revisions = [
1092
            revision for revision in revisions
1093
            if revision.revision_id > self.revision.revision_id
1094
        ]
1095
        if next_revisions:
1096
            # INFO - G.M - 2018-06-177 -sort revisions by date
1097
            sorted_next_revisions = sorted(
1098
                next_revisions,
1099
                key=lambda revision: revision.updated
1100
            )
1101
            # INFO - G.M - 2018-06-177 - return only next revision
1102
            return sorted_next_revisions[0]
1103
        else:
1104
            return None
1105
1106
    @property
1107
    def comment_ids(self) -> typing.List[int]:
1108
        """
1109
        Get list of ids of all current revision related comments
1110
        :return: list of comments ids
1111
        """
1112
        comments = self.revision.node.get_comments()
1113
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
1114
        revision_comments = [
1115
            comment for comment in comments
1116
            if comment.created > self.revision.updated or
1117
               comment.revision_id > self.revision.revision_id
1118
        ]
1119
        if self.next_revision:
1120
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
1121
            # than current remove comments from theses rev (comments older
1122
            # than next_revision.)
1123
            revision_comments = [
1124
                comment for comment in revision_comments
1125
                if comment.created < self.next_revision.updated or
1126
                   comment.revision_id < self.next_revision.revision_id
1127
            ]
1128
        sorted_revision_comments = sorted(
1129
            revision_comments,
1130
            key=lambda revision: revision.revision_id
1131
        )
1132
        comment_ids = []
1133
        for comment in sorted_revision_comments:
1134
            comment_ids.append(comment.content_id)
1135
        return comment_ids
1136
1137
    # Context-related
1138
    @property
1139
    def show_in_ui(self) -> bool:
1140
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
1141
        # if false, then do not show content in the treeview.
1142
        # This may his maybe used for specific contents or for sub-contents.
1143
        # Default is True.
1144
        # In first version of the API, this field is always True
1145
        return True
1146
1147
    @property
1148
    def slug(self) -> str:
1149
        return slugify(self.revision.label)
1150
1151
    # file specific
1152
    @property
1153
    def page_nb(self) -> typing.Optional[int]:
1154
        """
1155
        :return: page_nb of content if available, None if unavailable
1156
        """
1157
        if self.revision.depot_file:
1158
            # TODO - G.M - 2018-09-05 - Fix circular import better
1159
            from tracim_backend.lib.core.content import ContentApi
1160
            content_api = ContentApi(
1161
                current_user=self._user,
1162
                session=self.dbsession,
1163
                config=self.config,
1164
                show_deleted=True,
1165
                show_archived=True,
1166
                show_active=True,
1167
                show_temporary=True,
1168
            )
1169
            return content_api.get_preview_page_nb(
1170
                self.revision.revision_id,
1171
                file_extension=self.revision.file_extension
1172
            )
1173
        else:
1174
            return None
1175
1176
    @property
1177
    def mimetype(self) -> str:
1178
        """
1179
        :return: mimetype of content if available, None if unavailable
1180
        """
1181
        return self.revision.file_mimetype
1182
1183
    @property
1184
    def size(self) -> typing.Optional[int]:
1185
        """
1186
        :return: size of content if available, None if unavailable
1187
        """
1188
        if self.revision.depot_file:
1189
            return self.revision.depot_file.file.content_length
1190
        else:
1191
            return None
1192
1193
    @property
1194
    def has_pdf_preview(self) -> bool:
1195
        """
1196
        :return: bool about if pdf version of content is available
1197
        """
1198
        if not self.revision.depot_file:
1199
            return False
1200
1201
        from tracim_backend.lib.core.content import ContentApi
1202
        content_api = ContentApi(
1203
            current_user=self._user,
1204
            session=self.dbsession,
1205
            config=self.config,
1206
            show_deleted=True,
1207
            show_archived=True,
1208
            show_active=True,
1209
            show_temporary=True,
1210
        )
1211
        return content_api.has_pdf_preview(
1212
            self.revision.revision_id,
1213
            file_extension=self.revision.file_extension,
1214
        )
1215
1216
    @property
1217
    def has_jpeg_preview(self) -> bool:
1218
        """
1219
        :return: bool about if jpeg version of content is available
1220
        """
1221
        if not self.revision.depot_file:
1222
            return False
1223
1224
        from tracim_backend.lib.core.content import ContentApi
1225
        content_api = ContentApi(
1226
            current_user=self._user,
1227
            session=self.dbsession,
1228
            config=self.config,
1229
            show_deleted=True,
1230
            show_archived=True,
1231
            show_active=True,
1232
            show_temporary=True,
1233
        )
1234
        return content_api.has_jpeg_preview(
1235
            self.revision.revision_id,
1236
            file_extension=self.revision.file_extension
1237
        )
1238
1239
    @property
1240
    def file_extension(self) -> str:
1241
        """
1242
        :return: file extension with "." at the beginning, example : .txt
1243
        """
1244
        return self.revision.file_extension
1245
1246
    @property
1247
    def filename(self) -> str:
1248
        """
1249
        :return: complete filename with both label and file extension part
1250
        """
1251
        return self.revision.file_name