Completed
Push — develop ( f2955b...37a460 )
by
unknown
20s queued 12s
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: 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_root_frontend_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
        )
799
        return content_api.is_editable(self.content)
800
801
    @property
802
    def raw_content(self) -> str:
803
        return self.content.description
804
805
    @property
806
    def author(self) -> UserInContext:
807
        return UserInContext(
808
            dbsession=self.dbsession,
809
            config=self.config,
810
            user=self.content.first_revision.owner
811
        )
812
813
    @property
814
    def current_revision_id(self) -> int:
815
        return self.content.revision_id
816
817
    @property
818
    def created(self) -> datetime:
819
        return self.content.created
820
821
    @property
822
    def modified(self) -> datetime:
823
        return self.updated
824
825
    @property
826
    def updated(self) -> datetime:
827
        return self.content.updated
828
829
    @property
830
    def last_modifier(self) -> UserInContext:
831
        return UserInContext(
832
            dbsession=self.dbsession,
833
            config=self.config,
834
            user=self.content.last_revision.owner
835
        )
836
837
    # Context-related
838
    @property
839
    def show_in_ui(self) -> bool:
840
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
841
        # if false, then do not show content in the treeview.
842
        # This may his maybe used for specific contents or for sub-contents.
843
        # Default is True.
844
        # In first version of the API, this field is always True
845
        return True
846
847
    @property
848
    def slug(self) -> str:
849
        return slugify(self.content.label)
850
851
    @property
852
    def read_by_user(self) -> bool:
853
        assert self._user
854
        return not self.content.has_new_information_for(self._user)
855
856
    @property
857
    def frontend_url(self) -> str:
858
        root_frontend_url = get_root_frontend_url(self.config)
859
        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
860
            workspace_id=self.workspace_id,
861
            content_type=self.content_type,
862
            content_id=self.content_id,
863
        )
864
        return root_frontend_url + content_frontend_url
865
866
    # file specific
867
    @property
868
    def page_nb(self) -> typing.Optional[int]:
869
        """
870
        :return: page_nb of content if available, None if unavailable
871
        """
872
        if self.content.depot_file:
873
            from tracim_backend.lib.core.content import ContentApi
874
            content_api = ContentApi(
875
                current_user=self._user,
876
                session=self.dbsession,
877
                config=self.config
878
            )
879
            return content_api.get_preview_page_nb(
880
                self.content.revision_id,
881
                file_extension=self.content.file_extension
882
            )
883
        else:
884
            return None
885
886
    @property
887
    def mimetype(self) -> str:
888
        """
889
        :return: mimetype of content if available, None if unavailable
890
        """
891
        return self.content.file_mimetype
892
893
    @property
894
    def size(self) -> typing.Optional[int]:
895
        """
896
        :return: size of content if available, None if unavailable
897
        """
898
        if self.content.depot_file:
899
            return self.content.depot_file.file.content_length
900
        else:
901
            return None
902
903
    @property
904
    def pdf_available(self) -> bool:
905
        """
906
        :return: bool about if pdf version of content is available
907
        """
908
        if self.content.depot_file:
909
            from tracim_backend.lib.core.content import ContentApi
910
            content_api = ContentApi(
911
                current_user=self._user,
912
                session=self.dbsession,
913
                config=self.config
914
            )
915
            return content_api.has_pdf_preview(
916
                self.content.revision_id,
917
                file_extension=self.content.file_extension
918
            )
919
        else:
920
            return False
921
922
    @property
923
    def file_extension(self) -> str:
924
        """
925
        :return: file extension with "." at the beginning, example : .txt
926
        """
927
        return self.content.file_extension
928
929
    @property
930
    def filename(self) -> str:
931
        """
932
        :return: complete filename with both label and file extension part
933
        """
934
        return self.content.file_name
935
936
937
class RevisionInContext(object):
938
    """
939
    Interface to get Content data and Content data related to context.
940
    """
941
942
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
943
        assert content_revision is not None
944
        self.revision = content_revision
945
        self.dbsession = dbsession
946
        self.config = config
947
        self._user = user
948
949
    # Default
950
    @property
951
    def content_id(self) -> int:
952
        return self.revision.content_id
953
954
    @property
955
    def parent_id(self) -> int:
956
        """
957
        Return parent_id of the content
958
        """
959
        return self.revision.parent_id
960
961
    @property
962
    def workspace_id(self) -> int:
963
        return self.revision.workspace_id
964
965
    @property
966
    def label(self) -> str:
967
        return self.revision.label
968
969
    @property
970
    def revision_type(self) -> str:
971
        return self.revision.revision_type
972
973
    @property
974
    def content_type(self) -> str:
975
        return content_type_list.get_one_by_slug(self.revision.type).slug
976
977
    @property
978
    def sub_content_types(self) -> typing.List[str]:
979
        return [_type.slug for _type
980
                in self.revision.node.get_allowed_content_types()]
981
982
    @property
983
    def status(self) -> str:
984
        return self.revision.status
985
986
    @property
987
    def is_archived(self) -> bool:
988
        return self.revision.is_archived
989
990
    @property
991
    def is_deleted(self) -> bool:
992
        return self.revision.is_deleted
993
994
    @property
995
    def is_editable(self) -> bool:
996
        from tracim_backend.lib.core.content import ContentApi
997
        content_api = ContentApi(
998
            current_user=self._user,
999
            session=self.dbsession,
1000
            config=self.config
1001
        )
1002
        # INFO - G.M - 2018-11-02 - check if revision is last one and if it is,
1003
        # return editability of content.
1004
        content = content_api.get_one(
1005
            content_id=self.revision.content_id,
1006
            content_type=content_type_list.Any_SLUG
1007
        )
1008
        if content.revision_id == self.revision_id:
1009
            return content_api.is_editable(content)
1010
        # INFO - G.M - 2018-11-02 - old revision are not editable
1011
        return False
1012
1013
    @property
1014
    def raw_content(self) -> str:
1015
        return self.revision.description
1016
1017
    @property
1018
    def author(self) -> UserInContext:
1019
        return UserInContext(
1020
            dbsession=self.dbsession,
1021
            config=self.config,
1022
            user=self.revision.owner
1023
        )
1024
1025
    @property
1026
    def revision_id(self) -> int:
1027
        return self.revision.revision_id
1028
1029
    @property
1030
    def created(self) -> datetime:
1031
        return self.updated
1032
1033
    @property
1034
    def modified(self) -> datetime:
1035
        return self.updated
1036
1037
    @property
1038
    def updated(self) -> datetime:
1039
        return self.revision.updated
1040
1041
    @property
1042
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
1043
        """
1044
        Get next revision (later revision)
1045
        :return: next_revision
1046
        """
1047
        next_revision = None
1048
        revisions = self.revision.node.revisions
1049
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
1050
        # current one
1051
        next_revisions = [
1052
            revision for revision in revisions
1053
            if revision.revision_id > self.revision.revision_id
1054
        ]
1055
        if next_revisions:
1056
            # INFO - G.M - 2018-06-177 -sort revisions by date
1057
            sorted_next_revisions = sorted(
1058
                next_revisions,
1059
                key=lambda revision: revision.updated
1060
            )
1061
            # INFO - G.M - 2018-06-177 - return only next revision
1062
            return sorted_next_revisions[0]
1063
        else:
1064
            return None
1065
1066
    @property
1067
    def comment_ids(self) -> typing.List[int]:
1068
        """
1069
        Get list of ids of all current revision related comments
1070
        :return: list of comments ids
1071
        """
1072
        comments = self.revision.node.get_comments()
1073
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
1074
        revision_comments = [
1075
            comment for comment in comments
1076
            if comment.created > self.revision.updated or
1077
               comment.revision_id > self.revision.revision_id
1078
        ]
1079
        if self.next_revision:
1080
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
1081
            # than current remove comments from theses rev (comments older
1082
            # than next_revision.)
1083
            revision_comments = [
1084
                comment for comment in revision_comments
1085
                if comment.created < self.next_revision.updated or
1086
                   comment.revision_id < self.next_revision.revision_id
1087
            ]
1088
        sorted_revision_comments = sorted(
1089
            revision_comments,
1090
            key=lambda revision: revision.revision_id
1091
        )
1092
        comment_ids = []
1093
        for comment in sorted_revision_comments:
1094
            comment_ids.append(comment.content_id)
1095
        return comment_ids
1096
1097
    # Context-related
1098
    @property
1099
    def show_in_ui(self) -> bool:
1100
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
1101
        # if false, then do not show content in the treeview.
1102
        # This may his maybe used for specific contents or for sub-contents.
1103
        # Default is True.
1104
        # In first version of the API, this field is always True
1105
        return True
1106
1107
    @property
1108
    def slug(self) -> str:
1109
        return slugify(self.revision.label)
1110
1111
    # file specific
1112
    @property
1113
    def page_nb(self) -> typing.Optional[int]:
1114
        """
1115
        :return: page_nb of content if available, None if unavailable
1116
        """
1117
        if self.revision.depot_file:
1118
            # TODO - G.M - 2018-09-05 - Fix circular import better
1119
            from tracim_backend.lib.core.content import ContentApi
1120
            content_api = ContentApi(
1121
                current_user=self._user,
1122
                session=self.dbsession,
1123
                config=self.config
1124
            )
1125
            return content_api.get_preview_page_nb(
1126
                self.revision.revision_id,
1127
                file_extension=self.revision.file_extension
1128
            )
1129
        else:
1130
            return None
1131
1132
    @property
1133
    def mimetype(self) -> str:
1134
        """
1135
        :return: mimetype of content if available, None if unavailable
1136
        """
1137
        return self.revision.file_mimetype
1138
1139
    @property
1140
    def size(self) -> typing.Optional[int]:
1141
        """
1142
        :return: size of content if available, None if unavailable
1143
        """
1144
        if self.revision.depot_file:
1145
            return self.revision.depot_file.file.content_length
1146
        else:
1147
            return None
1148
1149
    @property
1150
    def pdf_available(self) -> bool:
1151
        """
1152
        :return: bool about if pdf version of content is available
1153
        """
1154
        if self.revision.depot_file:
1155
            from tracim_backend.lib.core.content import ContentApi
1156
            content_api = ContentApi(
1157
                current_user=self._user,
1158
                session=self.dbsession,
1159
                config=self.config
1160
            )
1161
            return content_api.has_pdf_preview(
1162
                self.revision.revision_id,
1163
                file_extension=self.revision.file_extension,
1164
            )
1165
        else:
1166
            return False
1167
1168
    @property
1169
    def file_extension(self) -> str:
1170
        """
1171
        :return: file extension with "." at the beginning, example : .txt
1172
        """
1173
        return self.revision.file_extension
1174
1175
    @property
1176
    def filename(self) -> str:
1177
        """
1178
        :return: complete filename with both label and file extension part
1179
        """
1180
        return self.revision.file_name