Passed
Pull Request — develop (#79)
by inkhey
01:35
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 typing
3
from datetime import datetime
4
from enum import Enum
5
6
from slugify import slugify
7
from sqlalchemy.orm import Session
8
from tracim_backend.config import CFG
9
from tracim_backend.config import PreviewDim
10
from tracim_backend.extensions import app_list
11
from tracim_backend.lib.core.application import ApplicationApi
12
from tracim_backend.lib.utils.utils import get_root_frontend_url
13
from tracim_backend.lib.utils.utils import password_generator
14
from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
15
from tracim_backend.lib.utils.utils import WORKSPACE_FRONTEND_URL_SCHEMA
16
from tracim_backend.models import User
17
from tracim_backend.models.auth import Profile
18
from tracim_backend.models.auth import Group
19
from tracim_backend.models.data import Content
20
from tracim_backend.models.data import ContentRevisionRO
21
from tracim_backend.models.data import Workspace
22
from tracim_backend.models.data import UserRoleInWorkspace
23
from tracim_backend.models.roles import WorkspaceRoles
24
from tracim_backend.app_models.workspace_menu_entries import WorkspaceMenuEntry
25
from tracim_backend.app_models.contents import CONTENT_TYPES
26
27
28
class AboutModel(object):
29
30
    def __init__(
31
        self,
32
        name: str,
33
        version: typing.Optional[str],
34
        datetime: datetime,
35
        website: str,
36
    ):
37
        self.name = name
38
        self.version = version
39
        self.datetime = datetime
40
        self.website = website
41
42
43
class ConfigModel(object):
44
45
    def __init__(
46
        self,
47
        email_notification_activated: bool
48
    ):
49
        self.email_notification_activated = email_notification_activated
50
51
52
class PreviewAllowedDim(object):
53
54
    def __init__(
55
            self,
56
            restricted: bool,
57
            dimensions: typing.List[PreviewDim]
58
    ) -> None:
59
        self.restricted = restricted
60
        self.dimensions = dimensions
61
62
63
class MoveParams(object):
64
    """
65
    Json body params for move action model
66
    """
67
    def __init__(self, new_parent_id: str, new_workspace_id: str = None) -> None:  # nopep8
68
        self.new_parent_id = new_parent_id
69
        self.new_workspace_id = new_workspace_id
70
71
72
class LoginCredentials(object):
73
    """
74
    Login credentials model for login model
75
    """
76
77
    def __init__(self, email: str, password: str) -> None:
78
        self.email = email
79
        self.password = password
80
81
82
class ResetPasswordRequest(object):
83
    """
84
    Reset password : request to reset password of user
85
    """
86
    def __init__(self, email: str) -> None:
87
        self.email = email
88
89
90
class ResetPasswordCheckToken(object):
91
    """
92
    Reset password : check reset password token
93
    """
94
    def __init__(
95
        self,
96
        reset_password_token: str,
97
        email: str,
98
    ) -> None:
99
        self.email = email
100
        self.reset_password_token = reset_password_token
101
102
103
class ResetPasswordModify(object):
104
    """
105
    Reset password : modification step
106
    """
107
    def __init__(
108
        self,
109
        reset_password_token: str,
110
        email: str,
111
        new_password: str,
112
        new_password2: str
113
    ) -> None:
114
        self.email = email
115
        self.reset_password_token = reset_password_token
116
        self.new_password = new_password
117
        self.new_password2 = new_password2
118
119
120
class SetEmail(object):
121
    """
122
    Just an email and password
123
    """
124
    def __init__(self, loggedin_user_password: str, email: str) -> None:
125
        self.loggedin_user_password = loggedin_user_password
126
        self.email = email
127
128
129
class SetPassword(object):
130
    """
131
    Just an password
132
    """
133
    def __init__(
134
        self,
135
        loggedin_user_password: str,
136
        new_password: str,
137
        new_password2: str
138
    ) -> None:
139
        self.loggedin_user_password = loggedin_user_password
140
        self.new_password = new_password
141
        self.new_password2 = new_password2
142
143
144
class UserInfos(object):
145
    """
146
    Just some user infos
147
    """
148
    def __init__(self, timezone: str, public_name: str, lang: str) -> None:
149
        self.timezone = timezone
150
        self.public_name = public_name
151
        self.lang = lang
152
153
154
class UserProfile(object):
155
    """
156
    Just some user infos
157
    """
158
    def __init__(self, profile: str) -> None:
159
        self.profile = profile
160
161
162
class UserCreation(object):
163
    """
164
    Just some user infos
165
    """
166
    def __init__(
167
            self,
168
            email: str,
169
            password: str = None,
170
            public_name: str = None,
171
            timezone: str = None,
172
            profile: str = None,
173
            lang: str = None,
174
            email_notification: bool = True,
175
    ) -> None:
176
        self.email = email
177
        # INFO - G.M - 2018-08-16 - cleartext password, default value
178
        # is auto-generated.
179
        self.password = password or password_generator()
180
        self.public_name = public_name or None
181
        self.timezone = timezone or ''
182
        self.lang = lang or None
183
        self.profile = profile or Group.TIM_USER_GROUPNAME
184
        self.email_notification = email_notification
185
186
187
class WorkspaceAndContentPath(object):
188
    """
189
    Paths params with workspace id and content_id model
190
    """
191
    def __init__(self, workspace_id: int, content_id: int) -> None:
192
        self.content_id = content_id
193
        self.workspace_id = workspace_id
194
195
196
class WorkspaceAndContentRevisionPath(object):
197
    """
198
    Paths params with workspace id and content_id model
199
    """
200
    def __init__(self, workspace_id: int, content_id: int, revision_id) -> None:
201
        self.content_id = content_id
202
        self.revision_id = revision_id
203
        self.workspace_id = workspace_id
204
205
206
class ContentPreviewSizedPath(object):
207
    """
208
    Paths params with workspace id and content_id, width, heigth
209
    """
210
    def __init__(self, workspace_id: int, content_id: int, width: int, height: int) -> None:  # nopep8
211
        self.content_id = content_id
212
        self.workspace_id = workspace_id
213
        self.width = width
214
        self.height = height
215
216
217
class RevisionPreviewSizedPath(object):
218
    """
219
    Paths params with workspace id and content_id, revision_id width, heigth
220
    """
221
    def __init__(self, workspace_id: int, content_id: int, revision_id: int, width: int, height: int) -> None:  # nopep8
222
        self.content_id = content_id
223
        self.revision_id = revision_id
224
        self.workspace_id = workspace_id
225
        self.width = width
226
        self.height = height
227
228
229
class WorkspaceAndUserPath(object):
230
    """
231
    Paths params with workspace id and user_id
232
    """
233
    def __init__(self, workspace_id: int, user_id: int):
234
        self.workspace_id = workspace_id
235
        self.user_id = user_id
236
237
238
class UserWorkspaceAndContentPath(object):
239
    """
240
    Paths params with user_id, workspace id and content_id model
241
    """
242
    def __init__(self, user_id: int, workspace_id: int, content_id: int) -> None:  # nopep8
243
        self.content_id = content_id
244
        self.workspace_id = workspace_id
245
        self.user_id = user_id
246
247
248
class CommentPath(object):
249
    """
250
    Paths params with workspace id and content_id and comment_id model
251
    """
252
    def __init__(
253
        self,
254
        workspace_id: int,
255
        content_id: int,
256
        comment_id: int
257
    ) -> None:
258
        self.content_id = content_id
259
        self.workspace_id = workspace_id
260
        self.comment_id = comment_id
261
262
263
class AutocompleteQuery(object):
264
    """
265
    Autocomplete query model
266
    """
267
    def __init__(self, acp: str):
268
        self.acp = acp
269
270
271
class FileQuery(object):
272
    """
273
    File query model
274
    """
275
    def __init__(
276
        self,
277
        force_download: int = 0,
278
    ):
279
        self.force_download = force_download
280
281
282
class PageQuery(object):
283
    """
284
    Page query model
285
    """
286
    def __init__(
287
            self,
288
            force_download: int = 0,
289
            page: int = 1
290
    ):
291
        self.force_download = force_download
292
        self.page = page
293
294
295
class ContentFilter(object):
296
    """
297
    Content filter model
298
    """
299
    def __init__(
300
            self,
301
            workspace_id: int = None,
302
            parent_id: int = None,
303
            show_archived: int = 0,
304
            show_deleted: int = 0,
305
            show_active: int = 1,
306
            content_type: str = None,
307
            label: str = None,
308
            offset: int = None,
309
            limit: int = None,
310
    ) -> None:
311
        self.parent_id = parent_id
312
        self.workspace_id = workspace_id
313
        self.show_archived = bool(show_archived)
314
        self.show_deleted = bool(show_deleted)
315
        self.show_active = bool(show_active)
316
        self.limit = limit
317
        self.offset = offset
318
        self.label = label
319
        self.content_type = content_type
320
321
322
class ActiveContentFilter(object):
323
    def __init__(
324
            self,
325
            limit: int = None,
326
            before_content_id: datetime = None,
327
    ):
328
        self.limit = limit
329
        self.before_content_id = before_content_id
330
331
332
class ContentIdsQuery(object):
333
    def __init__(
334
            self,
335
            contents_ids: typing.List[int] = None,
336
    ):
337
        self.contents_ids = contents_ids
338
339
340
class RoleUpdate(object):
341
    """
342
    Update role
343
    """
344
    def __init__(
345
        self,
346
        role: str,
347
    ):
348
        self.role = role
349
350
351
class WorkspaceMemberInvitation(object):
352
    """
353
    Workspace Member Invitation
354
    """
355
    def __init__(
356
        self,
357
        user_id: int = None,
358
        user_email_or_public_name: str = None,
359
        role: str = None,
360
    ):
361
        self.role = role
362
        self.user_email_or_public_name = user_email_or_public_name
363
        self.user_id = user_id
364
365
366
class WorkspaceUpdate(object):
367
    """
368
    Update workspace
369
    """
370
    def __init__(
371
        self,
372
        label: str,
373
        description: str,
374
    ):
375
        self.label = label
376
        self.description = description
377
378
379
class ContentCreation(object):
380
    """
381
    Content creation model
382
    """
383
    def __init__(
384
        self,
385
        label: str,
386
        content_type: str,
387
        parent_id: typing.Optional[int] = None,
388
    ) -> None:
389
        self.label = label
390
        self.content_type = content_type
391
        self.parent_id = parent_id or None
392
393
394
class CommentCreation(object):
395
    """
396
    Comment creation model
397
    """
398
    def __init__(
399
        self,
400
        raw_content: str,
401
    ) -> None:
402
        self.raw_content = raw_content
403
404
405
class SetContentStatus(object):
406
    """
407
    Set content status
408
    """
409
    def __init__(
410
        self,
411
        status: str,
412
    ) -> None:
413
        self.status = status
414
415
416
class TextBasedContentUpdate(object):
417
    """
418
    TextBasedContent update model
419
    """
420
    def __init__(
421
        self,
422
        label: str,
423
        raw_content: str,
424
    ) -> None:
425
        self.label = label
426
        self.raw_content = raw_content
427
428
429
class FolderContentUpdate(object):
430
    """
431
    Folder Content update model
432
    """
433
    def __init__(
434
        self,
435
        label: str,
436
        raw_content: str,
437
        sub_content_types: typing.List[str],
438
    ) -> None:
439
        self.label = label
440
        self.raw_content = raw_content
441
        self.sub_content_types = sub_content_types
442
443
444
class TypeUser(Enum):
445
    """Params used to find user"""
446
    USER_ID = 'found_id'
447
    EMAIL = 'found_email'
448
    PUBLIC_NAME = 'found_public_name'
449
450
451
class UserInContext(object):
452
    """
453
    Interface to get User data and User data related to context.
454
    """
455
456
    def __init__(self, user: User, dbsession: Session, config: CFG):
457
        self.user = user
458
        self.dbsession = dbsession
459
        self.config = config
460
461
    # Default
462
463
    @property
464
    def email(self) -> str:
465
        return self.user.email
466
467
    @property
468
    def user_id(self) -> int:
469
        return self.user.user_id
470
471
    @property
472
    def public_name(self) -> str:
473
        return self.display_name
474
475
    @property
476
    def display_name(self) -> str:
477
        return self.user.display_name
478
479
    @property
480
    def created(self) -> datetime:
481
        return self.user.created
482
483
    @property
484
    def is_active(self) -> bool:
485
        return self.user.is_active
486
487
    @property
488
    def timezone(self) -> str:
489
        return self.user.timezone
490
491
    @property
492
    def lang(self) -> str:
493
        return self.user.lang
494
495
    @property
496
    def profile(self) -> Profile:
497
        return self.user.profile.name
498
499
    @property
500
    def is_deleted(self) -> bool:
501
        return self.user.is_deleted
502
503
    # Context related
504
505
    @property
506
    def calendar_url(self) -> typing.Optional[str]:
507
        # TODO - G-M - 20-04-2018 - [Calendar] Replace calendar code to get
508
        # url calendar url.
509
        #
510
        # from tracim.lib.calendar import CalendarManager
511
        # calendar_manager = CalendarManager(None)
512
        # return calendar_manager.get_workspace_calendar_url(self.workspace_id)
513
        return None
514
515
    @property
516
    def avatar_url(self) -> typing.Optional[str]:
517
        # TODO - G-M - 20-04-2018 - [Avatar] Add user avatar feature
518
        return None
519
520
521
class WorkspaceInContext(object):
522
    """
523
    Interface to get Workspace data and Workspace data related to context.
524
    """
525
526
    def __init__(self, workspace: Workspace, dbsession: Session, config: CFG):
527
        self.workspace = workspace
528
        self.dbsession = dbsession
529
        self.config = config
530
531
    @property
532
    def workspace_id(self) -> int:
533
        """
534
        numeric id of the workspace.
535
        """
536
        return self.workspace.workspace_id
537
538
    @property
539
    def id(self) -> int:
540
        """
541
        alias of workspace_id
542
        """
543
        return self.workspace_id
544
545
    @property
546
    def label(self) -> str:
547
        """
548
        get workspace label
549
        """
550
        return self.workspace.label
551
552
    @property
553
    def description(self) -> str:
554
        """
555
        get workspace description
556
        """
557
        return self.workspace.description
558
559
    @property
560
    def slug(self) -> str:
561
        """
562
        get workspace slug
563
        """
564
        return slugify(self.workspace.label)
565
566
    @property
567
    def is_deleted(self) -> bool:
568
        """
569
        Is the workspace deleted ?
570
        """
571
        return self.workspace.is_deleted
572
573
    @property
574
    def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
575
        """
576
        get sidebar entries, those depends on activated apps.
577
        """
578
        # TODO - G.M - 22-05-2018 - Rework on this in
579
        # order to not use hardcoded list
580
        # list should be able to change (depending on activated/disabled
581
        # apps)
582
        app_api = ApplicationApi(
583
            app_list
584
        )
585
        return app_api.get_default_workspace_menu_entry(self.workspace)
586
587
    @property
588
    def frontend_url(self):
589
        root_frontend_url = get_root_frontend_url(self.config)
590
        workspace_frontend_url = WORKSPACE_FRONTEND_URL_SCHEMA.format(
591
            workspace_id=self.workspace_id,
592
        )
593
        return root_frontend_url + workspace_frontend_url
594
595
596
class UserRoleWorkspaceInContext(object):
597
    """
598
    Interface to get UserRoleInWorkspace data and related content
599
600
    """
601
    def __init__(
602
            self,
603
            user_role: UserRoleInWorkspace,
604
            dbsession: Session,
605
            config: CFG,
606
            # Extended params
607
            newly_created: bool = None,
608
            email_sent: bool = None
609
    )-> None:
610
        self.user_role = user_role
611
        self.dbsession = dbsession
612
        self.config = config
613
        # Extended params
614
        self.newly_created = newly_created
615
        self.email_sent = email_sent
616
617
    @property
618
    def user_id(self) -> int:
619
        """
620
        User who has the role has this id
621
        :return: user id as integer
622
        """
623
        return self.user_role.user_id
624
625
    @property
626
    def workspace_id(self) -> int:
627
        """
628
        This role apply only on the workspace with this workspace_id
629
        :return: workspace id as integer
630
        """
631
        return self.user_role.workspace_id
632
633
    # TODO - G.M - 23-05-2018 - Check the API spec for this this !
634
635
    @property
636
    def role_id(self) -> int:
637
        """
638
        role as int id, each value refer to a different role.
639
        """
640
        return self.user_role.role
641
642
    @property
643
    def role(self) -> str:
644
        return self.role_slug
645
646
    @property
647
    def role_slug(self) -> str:
648
        """
649
        simple name of the role of the user.
650
        can be anything from UserRoleInWorkspace SLUG, like
651
        'not_applicable', 'reader',
652
        'contributor', 'content-manager', 'workspace-manager'
653
        :return: user workspace role as slug.
654
        """
655
        return WorkspaceRoles.get_role_from_level(self.user_role.role).slug
656
657
    @property
658
    def is_active(self) -> bool:
659
        return self.user.is_active
660
661
    @property
662
    def do_notify(self) -> bool:
663
        return self.user_role.do_notify
664
665
    @property
666
    def user(self) -> UserInContext:
667
        """
668
        User who has this role, with context data
669
        :return: UserInContext object
670
        """
671
        return UserInContext(
672
            self.user_role.user,
673
            self.dbsession,
674
            self.config
675
        )
676
677
    @property
678
    def workspace(self) -> WorkspaceInContext:
679
        """
680
        Workspace related to this role, with his context data
681
        :return: WorkspaceInContext object
682
        """
683
        return WorkspaceInContext(
684
            self.user_role.workspace,
685
            self.dbsession,
686
            self.config
687
        )
688
689
690
class ContentInContext(object):
691
    """
692
    Interface to get Content data and Content data related to context.
693
    """
694
695
    def __init__(self, content: Content, dbsession: Session, config: CFG, user: User=None):  # nopep8
696
        self.content = content
697
        self.dbsession = dbsession
698
        self.config = config
699
        self._user = user
700
701
    # Default
702
    @property
703
    def content_id(self) -> int:
704
        return self.content.content_id
705
706
    @property
707
    def parent_id(self) -> int:
708
        """
709
        Return parent_id of the content
710
        """
711
        return self.content.parent_id
712
713
    @property
714
    def workspace_id(self) -> int:
715
        return self.content.workspace_id
716
717
    @property
718
    def label(self) -> str:
719
        return self.content.label
720
721
    @property
722
    def content_type(self) -> str:
723
        content_type = CONTENT_TYPES.get_one_by_slug(self.content.type)
724
        return content_type.slug
725
726
    @property
727
    def sub_content_types(self) -> typing.List[str]:
728
        return [_type.slug for _type in self.content.get_allowed_content_types()]  # nopep8
729
730
    @property
731
    def status(self) -> str:
732
        return self.content.status
733
734
    @property
735
    def is_archived(self) -> bool:
736
        return self.content.is_archived
737
738
    @property
739
    def is_deleted(self) -> bool:
740
        return self.content.is_deleted
741
742
    @property
743
    def raw_content(self) -> str:
744
        return self.content.description
745
746
    @property
747
    def author(self) -> UserInContext:
748
        return UserInContext(
749
            dbsession=self.dbsession,
750
            config=self.config,
751
            user=self.content.first_revision.owner
752
        )
753
754
    @property
755
    def current_revision_id(self) -> int:
756
        return self.content.revision_id
757
758
    @property
759
    def created(self) -> datetime:
760
        return self.content.created
761
762
    @property
763
    def modified(self) -> datetime:
764
        return self.updated
765
766
    @property
767
    def updated(self) -> datetime:
768
        return self.content.updated
769
770
    @property
771
    def last_modifier(self) -> UserInContext:
772
        return UserInContext(
773
            dbsession=self.dbsession,
774
            config=self.config,
775
            user=self.content.last_revision.owner
776
        )
777
778
    # Context-related
779
    @property
780
    def show_in_ui(self) -> bool:
781
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
782
        # if false, then do not show content in the treeview.
783
        # This may his maybe used for specific contents or for sub-contents.
784
        # Default is True.
785
        # In first version of the API, this field is always True
786
        return True
787
788
    @property
789
    def slug(self) -> str:
790
        return slugify(self.content.label)
791
792
    @property
793
    def read_by_user(self) -> bool:
794
        assert self._user
795
        return not self.content.has_new_information_for(self._user)
796
797
    @property
798
    def frontend_url(self) -> str:
799
        root_frontend_url = get_root_frontend_url(self.config)
800
        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
801
            workspace_id=self.workspace_id,
802
            content_type=self.content_type,
803
            content_id=self.content_id,
804
        )
805
        return root_frontend_url + content_frontend_url
806
807
    # file specific
808
    @property
809
    def page_nb(self) -> typing.Optional[int]:
810
        """
811
        :return: page_nb of content if available, None if unavailable
812
        """
813
        if self.content.depot_file:
814
            from tracim_backend.lib.core.content import ContentApi
815
            content_api = ContentApi(
816
                current_user=self._user,
817
                session=self.dbsession,
818
                config=self.config
819
            )
820
            return content_api.get_preview_page_nb(self.content.revision_id)
821
        else:
822
            return None
823
824
    @property
825
    def mimetype(self) -> str:
826
        """
827
        :return: mimetype of content if available, None if unavailable
828
        """
829
        return self.content.file_mimetype
830
831
    @property
832
    def size(self) -> typing.Optional[int]:
833
        """
834
        :return: size of content if available, None if unavailable
835
        """
836
        if self.content.depot_file:
837
            return self.content.depot_file.file.content_length
838
        else:
839
            return None
840
841
    @property
842
    def pdf_available(self) -> bool:
843
        """
844
        :return: bool about if pdf version of content is available
845
        """
846
        if self.content.depot_file:
847
            from tracim_backend.lib.core.content import ContentApi
848
            content_api = ContentApi(
849
                current_user=self._user,
850
                session=self.dbsession,
851
                config=self.config
852
            )
853
            return content_api.has_pdf_preview(self.content.revision_id)
854
        else:
855
            return False
856
857
    @property
858
    def file_extension(self) -> str:
859
        """
860
        :return: file extension with "." at the beginning, example : .txt
861
        """
862
        return self.content.file_extension
863
864
865
class RevisionInContext(object):
866
    """
867
    Interface to get Content data and Content data related to context.
868
    """
869
870
    def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG,  user: User=None) -> None:  # nopep8
871
        assert content_revision is not None
872
        self.revision = content_revision
873
        self.dbsession = dbsession
874
        self.config = config
875
        self._user = user
876
877
    # Default
878
    @property
879
    def content_id(self) -> int:
880
        return self.revision.content_id
881
882
    @property
883
    def parent_id(self) -> int:
884
        """
885
        Return parent_id of the content
886
        """
887
        return self.revision.parent_id
888
889
    @property
890
    def workspace_id(self) -> int:
891
        return self.revision.workspace_id
892
893
    @property
894
    def label(self) -> str:
895
        return self.revision.label
896
897
    @property
898
    def revision_type(self) -> str:
899
        return self.revision.revision_type
900
901
    @property
902
    def content_type(self) -> str:
903
        return CONTENT_TYPES.get_one_by_slug(self.revision.type).slug
904
905
    @property
906
    def sub_content_types(self) -> typing.List[str]:
907
        return [_type.slug for _type
908
                in self.revision.node.get_allowed_content_types()]
909
910
    @property
911
    def status(self) -> str:
912
        return self.revision.status
913
914
    @property
915
    def is_archived(self) -> bool:
916
        return self.revision.is_archived
917
918
    @property
919
    def is_deleted(self) -> bool:
920
        return self.revision.is_deleted
921
922
    @property
923
    def raw_content(self) -> str:
924
        return self.revision.description
925
926
    @property
927
    def author(self) -> UserInContext:
928
        return UserInContext(
929
            dbsession=self.dbsession,
930
            config=self.config,
931
            user=self.revision.owner
932
        )
933
934
    @property
935
    def revision_id(self) -> int:
936
        return self.revision.revision_id
937
938
    @property
939
    def created(self) -> datetime:
940
        return self.updated
941
942
    @property
943
    def modified(self) -> datetime:
944
        return self.updated
945
946
    @property
947
    def updated(self) -> datetime:
948
        return self.revision.updated
949
950
    @property
951
    def next_revision(self) -> typing.Optional[ContentRevisionRO]:
952
        """
953
        Get next revision (later revision)
954
        :return: next_revision
955
        """
956
        next_revision = None
957
        revisions = self.revision.node.revisions
958
        # INFO - G.M - 2018-06-177 - Get revisions more recent that
959
        # current one
960
        next_revisions = [
961
            revision for revision in revisions
962
            if revision.revision_id > self.revision.revision_id
963
        ]
964
        if next_revisions:
965
            # INFO - G.M - 2018-06-177 -sort revisions by date
966
            sorted_next_revisions = sorted(
967
                next_revisions,
968
                key=lambda revision: revision.updated
969
            )
970
            # INFO - G.M - 2018-06-177 - return only next revision
971
            return sorted_next_revisions[0]
972
        else:
973
            return None
974
975
    @property
976
    def comment_ids(self) -> typing.List[int]:
977
        """
978
        Get list of ids of all current revision related comments
979
        :return: list of comments ids
980
        """
981
        comments = self.revision.node.get_comments()
982
        # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
983
        revision_comments = [
984
            comment for comment in comments
985
            if comment.created > self.revision.updated
986
        ]
987
        if self.next_revision:
988
            # INFO - G.M - 2018-06-177 - if there is a revision more recent
989
            # than current remove comments from theses rev (comments older
990
            # than next_revision.)
991
            revision_comments = [
992
                comment for comment in revision_comments
993
                if comment.created < self.next_revision.updated
994
            ]
995
        sorted_revision_comments = sorted(
996
            revision_comments,
997
            key=lambda revision: revision.created
998
        )
999
        comment_ids = []
1000
        for comment in sorted_revision_comments:
1001
            comment_ids.append(comment.content_id)
1002
        return comment_ids
1003
1004
    # Context-related
1005
    @property
1006
    def show_in_ui(self) -> bool:
1007
        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
1008
        # if false, then do not show content in the treeview.
1009
        # This may his maybe used for specific contents or for sub-contents.
1010
        # Default is True.
1011
        # In first version of the API, this field is always True
1012
        return True
1013
1014
    @property
1015
    def slug(self) -> str:
1016
        return slugify(self.revision.label)
1017
1018
    # file specific
1019
    @property
1020
    def page_nb(self) -> typing.Optional[int]:
1021
        """
1022
        :return: page_nb of content if available, None if unavailable
1023
        """
1024
        if self.revision.depot_file:
1025
            # TODO - G.M - 2018-09-05 - Fix circular import better
1026
            from tracim_backend.lib.core.content import ContentApi
1027
            content_api = ContentApi(
1028
                current_user=self._user,
1029
                session=self.dbsession,
1030
                config=self.config
1031
            )
1032
            return content_api.get_preview_page_nb(self.revision.revision_id)
1033
        else:
1034
            return None
1035
1036
    @property
1037
    def mimetype(self) -> str:
1038
        """
1039
        :return: mimetype of content if available, None if unavailable
1040
        """
1041
        return self.revision.file_mimetype
1042
1043
    @property
1044
    def size(self) -> typing.Optional[int]:
1045
        """
1046
        :return: size of content if available, None if unavailable
1047
        """
1048
        if self.revision.depot_file:
1049
            return self.revision.depot_file.file.content_length
1050
        else:
1051
            return None
1052
1053
    @property
1054
    def pdf_available(self) -> bool:
1055
        """
1056
        :return: bool about if pdf version of content is available
1057
        """
1058
        if self.revision.depot_file:
1059
            from tracim_backend.lib.core.content import ContentApi
1060
            content_api = ContentApi(
1061
                current_user=self._user,
1062
                session=self.dbsession,
1063
                config=self.config
1064
            )
1065
            return content_api.has_pdf_preview(self.revision.revision_id)
1066
        else:
1067
            return False
1068
1069
    @property
1070
    def file_extension(self) -> str:
1071
        """
1072
        :return: file extension with "." at the beginning, example : .txt
1073
        """
1074
        return self.revision.file_extension
1075