Passed
Pull Request — develop (#123)
by inkhey
01:41
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_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 pdf_available(self) -> bool:
913
        """
914
        :return: bool about if pdf version of content is available
915
        """
916
        if self.content.depot_file:
917
            from tracim_backend.lib.core.content import ContentApi
918
            content_api = ContentApi(
919
                current_user=self._user,
920
                session=self.dbsession,
921
                config=self.config,
922
                show_deleted=True,
923
                show_archived=True,
924
                show_active=True,
925
                show_temporary=True,
926
            )
927
            return content_api.has_pdf_preview(
928
                self.content.revision_id,
929
                file_extension=self.content.file_extension
930
            )
931
        else:
932
            return False
933
934
    @property
935
    def jpeg_available(self) -> bool:
936
        """
937
        :return: bool about if jpeg version of content is available
938
        """
939
        if self.content.depot_file:
940
            from tracim_backend.lib.core.content import ContentApi
941
            content_api = ContentApi(
942
                current_user=self._user,
943
                session=self.dbsession,
944
                config=self.config,
945
                show_deleted=True,
946
                show_archived=True,
947
                show_active=True,
948
                show_temporary=True,
949
            )
950
            return content_api.has_jpeg_preview(
951
                self.content.revision_id,
952
                file_extension=self.content.file_extension
953
            )
954
        else:
955
            return False
956
957
    @property
958
    def file_extension(self) -> str:
959
        """
960
        :return: file extension with "." at the beginning, example : .txt
961
        """
962
        return self.content.file_extension
963
964
    @property
965
    def filename(self) -> str:
966
        """
967
        :return: complete filename with both label and file extension part
968
        """
969
        return self.content.file_name
970
971
972
class RevisionInContext(object):
973
    """
974
    Interface to get Content data and Content data related to context.
975
    """
976
977
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
978
        assert content_revision is not None
979
        self.revision = content_revision
980
        self.dbsession = dbsession
981
        self.config = config
982
        self._user = user
983
984
    # Default
985
    @property
986
    def content_id(self) -> int:
987
        return self.revision.content_id
988
989
    @property
990
    def parent_id(self) -> int:
991
        """
992
        Return parent_id of the content
993
        """
994
        return self.revision.parent_id
995
996
    @property
997
    def workspace_id(self) -> int:
998
        return self.revision.workspace_id
999
1000
    @property
1001
    def label(self) -> str:
1002
        return self.revision.label
1003
1004
    @property
1005
    def revision_type(self) -> str:
1006
        return self.revision.revision_type
1007
1008
    @property
1009
    def content_type(self) -> str:
1010
        return content_type_list.get_one_by_slug(self.revision.type).slug
1011
1012
    @property
1013
    def sub_content_types(self) -> typing.List[str]:
1014
        return [_type.slug for _type
1015
                in self.revision.node.get_allowed_content_types()]
1016
1017
    @property
1018
    def status(self) -> str:
1019
        return self.revision.status
1020
1021
    @property
1022
    def is_archived(self) -> bool:
1023
        return self.revision.is_archived
1024
1025
    @property
1026
    def is_deleted(self) -> bool:
1027
        return self.revision.is_deleted
1028
1029
    @property
1030
    def is_editable(self) -> bool:
1031
        from tracim_backend.lib.core.content import ContentApi
1032
        content_api = ContentApi(
1033
            current_user=self._user,
1034
            session=self.dbsession,
1035
            config=self.config,
1036
            show_deleted=True,
1037
            show_archived=True,
1038
            show_active=True,
1039
            show_temporary=True,
1040
        )
1041
        # INFO - G.M - 2018-11-02 - check if revision is last one and if it is,
1042
        # return editability of content.
1043
        content = content_api.get_one(
1044
            content_id=self.revision.content_id,
1045
            content_type=content_type_list.Any_SLUG
1046
        )
1047
        if content.revision_id == self.revision_id:
1048
            return content_api.is_editable(content)
1049
        # INFO - G.M - 2018-11-02 - old revision are not editable
1050
        return False
1051
1052
    @property
1053
    def raw_content(self) -> str:
1054
        return self.revision.description
1055
1056
    @property
1057
    def author(self) -> UserInContext:
1058
        return UserInContext(
1059
            dbsession=self.dbsession,
1060
            config=self.config,
1061
            user=self.revision.owner
1062
        )
1063
1064
    @property
1065
    def revision_id(self) -> int:
1066
        return self.revision.revision_id
1067
1068
    @property
1069
    def created(self) -> datetime:
1070
        return self.updated
1071
1072
    @property
1073
    def modified(self) -> datetime:
1074
        return self.updated
1075
1076
    @property
1077
    def updated(self) -> datetime:
1078
        return self.revision.updated
1079
1080
    @property
1081
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
1082
        """
1083
        Get next revision (later revision)
1084
        :return: next_revision
1085
        """
1086
        next_revision = None
1087
        revisions = self.revision.node.revisions
1088
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
1089
        # current one
1090
        next_revisions = [
1091
            revision for revision in revisions
1092
            if revision.revision_id > self.revision.revision_id
1093
        ]
1094
        if next_revisions:
1095
            # INFO - G.M - 2018-06-177 -sort revisions by date
1096
            sorted_next_revisions = sorted(
1097
                next_revisions,
1098
                key=lambda revision: revision.updated
1099
            )
1100
            # INFO - G.M - 2018-06-177 - return only next revision
1101
            return sorted_next_revisions[0]
1102
        else:
1103
            return None
1104
1105
    @property
1106
    def comment_ids(self) -> typing.List[int]:
1107
        """
1108
        Get list of ids of all current revision related comments
1109
        :return: list of comments ids
1110
        """
1111
        comments = self.revision.node.get_comments()
1112
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
1113
        revision_comments = [
1114
            comment for comment in comments
1115
            if comment.created > self.revision.updated or
1116
               comment.revision_id > self.revision.revision_id
1117
        ]
1118
        if self.next_revision:
1119
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
1120
            # than current remove comments from theses rev (comments older
1121
            # than next_revision.)
1122
            revision_comments = [
1123
                comment for comment in revision_comments
1124
                if comment.created < self.next_revision.updated or
1125
                   comment.revision_id < self.next_revision.revision_id
1126
            ]
1127
        sorted_revision_comments = sorted(
1128
            revision_comments,
1129
            key=lambda revision: revision.revision_id
1130
        )
1131
        comment_ids = []
1132
        for comment in sorted_revision_comments:
1133
            comment_ids.append(comment.content_id)
1134
        return comment_ids
1135
1136
    # Context-related
1137
    @property
1138
    def show_in_ui(self) -> bool:
1139
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
1140
        # if false, then do not show content in the treeview.
1141
        # This may his maybe used for specific contents or for sub-contents.
1142
        # Default is True.
1143
        # In first version of the API, this field is always True
1144
        return True
1145
1146
    @property
1147
    def slug(self) -> str:
1148
        return slugify(self.revision.label)
1149
1150
    # file specific
1151
    @property
1152
    def page_nb(self) -> typing.Optional[int]:
1153
        """
1154
        :return: page_nb of content if available, None if unavailable
1155
        """
1156
        if self.revision.depot_file:
1157
            # TODO - G.M - 2018-09-05 - Fix circular import better
1158
            from tracim_backend.lib.core.content import ContentApi
1159
            content_api = ContentApi(
1160
                current_user=self._user,
1161
                session=self.dbsession,
1162
                config=self.config,
1163
                show_deleted=True,
1164
                show_archived=True,
1165
                show_active=True,
1166
                show_temporary=True,
1167
            )
1168
            return content_api.get_preview_page_nb(
1169
                self.revision.revision_id,
1170
                file_extension=self.revision.file_extension
1171
            )
1172
        else:
1173
            return None
1174
1175
    @property
1176
    def mimetype(self) -> str:
1177
        """
1178
        :return: mimetype of content if available, None if unavailable
1179
        """
1180
        return self.revision.file_mimetype
1181
1182
    @property
1183
    def size(self) -> typing.Optional[int]:
1184
        """
1185
        :return: size of content if available, None if unavailable
1186
        """
1187
        if self.revision.depot_file:
1188
            return self.revision.depot_file.file.content_length
1189
        else:
1190
            return None
1191
1192
    @property
1193
    def pdf_available(self) -> bool:
1194
        """
1195
        :return: bool about if pdf version of content is available
1196
        """
1197
        if self.revision.depot_file:
1198
            from tracim_backend.lib.core.content import ContentApi
1199
            content_api = ContentApi(
1200
                current_user=self._user,
1201
                session=self.dbsession,
1202
                config=self.config,
1203
                show_deleted=True,
1204
                show_archived=True,
1205
                show_active=True,
1206
                show_temporary=True,
1207
            )
1208
            return content_api.has_pdf_preview(
1209
                self.revision.revision_id,
1210
                file_extension=self.revision.file_extension,
1211
            )
1212
        else:
1213
            return False
1214
1215
    @property
1216
    def jpeg_available(self) -> bool:
1217
        """
1218
        :return: bool about if jpeg version of content is available
1219
        """
1220
        if self.revision.depot_file:
1221
            from tracim_backend.lib.core.content import ContentApi
1222
            content_api = ContentApi(
1223
                current_user=self._user,
1224
                session=self.dbsession,
1225
                config=self.config,
1226
                show_deleted=True,
1227
                show_archived=True,
1228
                show_active=True,
1229
                show_temporary=True,
1230
            )
1231
            return content_api.has_jpeg_preview(
1232
                self.revision.revision_id,
1233
                file_extension=self.revision.file_extension
1234
            )
1235
        else:
1236
            return False
1237
1238
    @property
1239
    def file_extension(self) -> str:
1240
        """
1241
        :return: file extension with "." at the beginning, example : .txt
1242
        """
1243
        return self.revision.file_extension
1244
1245
    @property
1246
    def filename(self) -> str:
1247
        """
1248
        :return: complete filename with both label and file extension part
1249
        """
1250
        return self.revision.file_name