Browse Source

Merge pull request #209 from herawo/dev/_/185/button_to_read_all

Skylsmoi 8 years ago
parent
commit
c7409a3d81

+ 35 - 1
tracim/tracim/controllers/content.py View File

7
 from cgi import FieldStorage
7
 from cgi import FieldStorage
8
 import tg
8
 import tg
9
 from tg import tmpl_context
9
 from tg import tmpl_context
10
+from tg import require
11
+from tg import predicates
10
 from tg.i18n import ugettext as _
12
 from tg.i18n import ugettext as _
11
 from tg.predicates import not_anonymous
13
 from tg.predicates import not_anonymous
12
 from sqlalchemy.orm.exc import NoResultFound
14
 from sqlalchemy.orm.exc import NoResultFound
13
 from tg import abort
15
 from tg import abort
14
 
16
 
15
 from tracim.controllers import TIMRestController
17
 from tracim.controllers import TIMRestController
18
+from tracim.controllers import StandardController
16
 from tracim.controllers import TIMRestPathContextSetup
19
 from tracim.controllers import TIMRestPathContextSetup
17
 from tracim.controllers import TIMRestControllerWithBreadcrumb
20
 from tracim.controllers import TIMRestControllerWithBreadcrumb
18
 from tracim.controllers import TIMWorkspaceContentRestController
21
 from tracim.controllers import TIMWorkspaceContentRestController
28
 from tracim.lib.predicates import current_user_is_contributor
31
 from tracim.lib.predicates import current_user_is_contributor
29
 from tracim.lib.predicates import current_user_is_content_manager
32
 from tracim.lib.predicates import current_user_is_content_manager
30
 from tracim.lib.predicates import require_current_user_is_owner
33
 from tracim.lib.predicates import require_current_user_is_owner
31
-from tracim.model.serializers import Context, CTX, DictLikeClass
34
+from tracim.model.serializers import Context
35
+from tracim.model.serializers import CTX
36
+from tracim.model.serializers import DictLikeClass
32
 from tracim.model.data import ActionDescription
37
 from tracim.model.data import ActionDescription
33
 from tracim.model import new_revision
38
 from tracim.model import new_revision
34
 from tracim.model import DBSession
39
 from tracim.model import DBSession
1074
             msg = _('{} not un-deleted: {}').format(self._item_type_label, str(e))
1079
             msg = _('{} not un-deleted: {}').format(self._item_type_label, str(e))
1075
             tg.flash(msg, CST.STATUS_ERROR)
1080
             tg.flash(msg, CST.STATUS_ERROR)
1076
             tg.redirect(back_url)
1081
             tg.redirect(back_url)
1082
+
1083
+
1084
+class ContentController(StandardController):
1085
+
1086
+    '''
1087
+    Class of controllers used for example in home to mark read the unread 
1088
+    contents via mark_all_read()
1089
+    '''
1090
+
1091
+    @classmethod
1092
+    def current_item_id_key_in_context(cls) -> str:
1093
+        return''
1094
+
1095
+    @tg.expose()
1096
+    def index(self):
1097
+        return dict()
1098
+
1099
+    @require(predicates.not_anonymous())
1100
+    @tg.expose()
1101
+    def mark_all_read(self):
1102
+        '''
1103
+        Mark as read all the content that hasn't been read
1104
+        redirects the user to "/home"
1105
+        '''
1106
+        user = tg.tmpl_context.current_user
1107
+        content_api = ContentApi(user)
1108
+        content_api.mark_read__all()
1109
+
1110
+        tg.redirect("/home")

+ 4 - 0
tracim/tracim/controllers/root.py View File

12
 
12
 
13
 from tg.i18n import ugettext as _
13
 from tg.i18n import ugettext as _
14
 from tracim.controllers.api import APIController
14
 from tracim.controllers.api import APIController
15
+from tracim.controllers.content import ContentController
15
 
16
 
16
 from tracim.lib import CST
17
 from tracim.lib import CST
17
 from tracim.lib.base import logger
18
 from tracim.lib.base import logger
57
     debug = DebugController()
58
     debug = DebugController()
58
     error = ErrorController()
59
     error = ErrorController()
59
 
60
 
61
+
60
     # Rest controllers
62
     # Rest controllers
61
     workspaces = UserWorkspaceRestController()
63
     workspaces = UserWorkspaceRestController()
62
     user = UserRestController()
64
     user = UserRestController()
63
 
65
 
66
+    content = ContentController()
67
+
64
     # api
68
     # api
65
     api = APIController()
69
     api = APIController()
66
 
70
 

+ 23 - 2
tracim/tracim/controllers/workspace.py View File

42
     def get_all(self, *args, **kw):
42
     def get_all(self, *args, **kw):
43
         tg.redirect(tg.url('/home'))
43
         tg.redirect(tg.url('/home'))
44
 
44
 
45
+    @tg.expose()
46
+    def mark_read(self, workspace_id, **kwargs):
47
+
48
+        user = tmpl_context.current_user
49
+        workspace_api = WorkspaceApi(user)
50
+        workspace = workspace_api.get_one(workspace_id)
51
+
52
+        content_api = ContentApi(user)
53
+        content_api.mark_read__workspace(workspace)
54
+
55
+        tg.redirect('/workspaces/{}'.format(workspace_id))
56
+        return DictLikeClass(fake_api=fake_api)
57
+
45
     @tg.expose('tracim.templates.workspace.getone')
58
     @tg.expose('tracim.templates.workspace.getone')
46
     def get_one(self, workspace_id, **kwargs):
59
     def get_one(self, workspace_id, **kwargs):
47
         """
60
         """
55
         show_archived = str_as_bool(kwargs.get('show_archived', ''))
68
         show_archived = str_as_bool(kwargs.get('show_archived', ''))
56
         user = tmpl_context.current_user
69
         user = tmpl_context.current_user
57
 
70
 
71
+        workspace_api = WorkspaceApi(user)
72
+        workspace = workspace_api.get_one(workspace_id)
73
+
74
+        unread_contents = ContentApi(user).get_last_unread(None,
75
+                                                           ContentType.Any,
76
+                                                           workspace=workspace)
58
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
77
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
59
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
78
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
60
 
79
 
61
-        workspace_api = WorkspaceApi(user)
62
-        workspace = workspace_api.get_one(workspace_id)
80
+
63
 
81
 
64
         dictified_current_user = Context(CTX.CURRENT_USER).toDict(user)
82
         dictified_current_user = Context(CTX.CURRENT_USER).toDict(user)
65
         dictified_folders = self.folders.get_all_fake(workspace).result
83
         dictified_folders = self.folders.get_all_fake(workspace).result
66
         fake_api = DictLikeClass(
84
         fake_api = DictLikeClass(
85
+            last_unread=Context(CTX.CONTENT_LIST).toDict(unread_contents,
86
+                                                         'contents',
87
+                                                         'nb'),
67
             current_user=dictified_current_user,
88
             current_user=dictified_current_user,
68
             current_workspace_folders=dictified_folders,
89
             current_workspace_folders=dictified_folders,
69
             current_user_workspace_role=workspace.get_user_role(user)
90
             current_user_workspace_role=workspace.get_user_role(user)

+ 25 - 1
tracim/tracim/lib/content.py View File

26
 from tracim.lib import cmp_to_key
26
 from tracim.lib import cmp_to_key
27
 from tracim.lib.notifications import NotifierFactory
27
 from tracim.lib.notifications import NotifierFactory
28
 from tracim.lib.utils import SameValueError
28
 from tracim.lib.utils import SameValueError
29
-from tracim.model import DBSession, new_revision
29
+from tracim.model import DBSession
30
+from tracim.model import new_revision
30
 from tracim.model.auth import User
31
 from tracim.model.auth import User
31
 from tracim.model.data import ActionDescription
32
 from tracim.model.data import ActionDescription
32
 from tracim.model.data import BreadcrumbItem
33
 from tracim.model.data import BreadcrumbItem
872
         content.is_deleted = False
873
         content.is_deleted = False
873
         content.revision_type = ActionDescription.UNDELETION
874
         content.revision_type = ActionDescription.UNDELETION
874
 
875
 
876
+    def mark_read__all(self,
877
+                       read_datetime: datetime=None,
878
+                       do_flush: bool=True,
879
+                       recursive: bool=True
880
+                       ):
881
+
882
+        itemset = self.get_last_unread(None, ContentType.Any)
883
+
884
+        for item in itemset:
885
+            self.mark_read(item, read_datetime, do_flush, recursive)
886
+
887
+    def mark_read__workspace(self,
888
+                       workspace : Workspace,
889
+                       read_datetime: datetime=None,
890
+                       do_flush: bool=True,
891
+                       recursive: bool=True
892
+                       ):
893
+
894
+        itemset = self.get_last_unread(None, ContentType.Any, workspace)
895
+
896
+        for item in itemset:
897
+            self.mark_read(item, read_datetime, do_flush, recursive)
898
+
875
     def mark_read(self, content: Content,
899
     def mark_read(self, content: Content,
876
                   read_datetime: datetime=None,
900
                   read_datetime: datetime=None,
877
                   do_flush: bool=True, recursive: bool=True) -> Content:
901
                   do_flush: bool=True, recursive: bool=True) -> Content:

+ 1 - 0
tracim/tracim/lib/webdav/sql_resources.py View File

3
 
3
 
4
 import os
4
 import os
5
 
5
 
6
+import tg
6
 import transaction
7
 import transaction
7
 import re
8
 import re
8
 from datetime import datetime
9
 from datetime import datetime

+ 2 - 0
tracim/tracim/templates/home.mak View File

90
 
90
 
91
     ## NOT READ
91
     ## NOT READ
92
     <div class="" id="unread-content-panel">
92
     <div class="" id="unread-content-panel">
93
+
93
         <div class="panel panel-success">
94
         <div class="panel panel-success">
94
             <div class="panel-body">
95
             <div class="panel-body">
95
                 % if fake_api.last_unread.nb <= 0:
96
                 % if fake_api.last_unread.nb <= 0:
96
                     ${P.EMPTY_CONTENT(_('No new content.'))}
97
                     ${P.EMPTY_CONTENT(_('No new content.'))}
97
                 % else:
98
                 % else:
99
+                    <a href="${tg.url('/content/mark_all_read')}" class="btn btn-default"> ${_('Mark everything as read')} </a>
98
                     <table class="table table-hover">
100
                     <table class="table table-hover">
99
                         % for item in fake_api.last_unread.contents:
101
                         % for item in fake_api.last_unread.contents:
100
                             <tr>
102
                             <tr>

+ 6 - 0
tracim/tracim/templates/workspace/getone.mak View File

54
 
54
 
55
 
55
 
56
 <div class="workspace__detail__wrapper">
56
 <div class="workspace__detail__wrapper">
57
+     %if fake_api.last_unread.nb > 0:
58
+         <% workspace_id = result.workspace.id %>
59
+         <a href="${tg.url('/workspaces/{ws_id}/mark_read'.format(ws_id = workspace_id))}" class="btn btn-default"> ${_('Mark this workspace as read')} </a>
60
+
61
+     %endif
62
+
57
     ${TITLE.H3(_('Detail'), 'fa-align-justify', 'workspace-members')}
63
     ${TITLE.H3(_('Detail'), 'fa-align-justify', 'workspace-members')}
58
     % if result.workspace.description:
64
     % if result.workspace.description:
59
         <p>${result.workspace.description}</p>
65
         <p>${result.workspace.description}</p>

+ 155 - 0
tracim/tracim/tests/library/test_content_api.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+import datetime
2
 
3
 
3
 from nose.tools import eq_, ok_
4
 from nose.tools import eq_, ok_
4
 from nose.tools import raises
5
 from nose.tools import raises
347
         eq_(ActionDescription.COMMENT, c.revision_type)
348
         eq_(ActionDescription.COMMENT, c.revision_type)
348
 
349
 
349
 
350
 
351
+    def test_mark_read__workspace(self):
352
+        uapi = UserApi(None)
353
+        groups = [GroupApi(None).get_one(Group.TIM_USER),
354
+                  GroupApi(None).get_one(Group.TIM_MANAGER),
355
+                  GroupApi(None).get_one(Group.TIM_ADMIN)]
356
+        user_a = uapi.create_user(email='this.is@user',
357
+                                  groups=groups, save_now=True)
358
+        user_b = uapi.create_user(email='this.is@another.user',
359
+                                  groups=groups, save_now=True)
360
+
361
+        wapi = WorkspaceApi(user_a)
362
+        workspace1 = wapi.create_workspace(
363
+            'test workspace n°1',
364
+            save_now=True)
365
+        workspace2 = wapi.create_workspace(
366
+            'test workspace n°2',
367
+            save_now=True)
368
+
369
+        role_api1 = RoleApi(user_a)
370
+        role_api1.create_one(user_b, workspace1, UserRoleInWorkspace.READER,
371
+                            False)
372
+
373
+        role_api2 = RoleApi(user_a)
374
+        role_api2.create_one(user_b, workspace2, UserRoleInWorkspace.READER,
375
+                             False)
376
+
377
+        cont_api_a = ContentApi(user_a)
378
+        cont_api_b = ContentApi(user_b)
379
+
380
+
381
+        # Creates page_1 & page_2 in workspace 1
382
+        #     and page_3 & page_4 in workspace 2
383
+        page_1 = cont_api_a.create(ContentType.Page, workspace1, None,
384
+                                   'this is a page', do_save=True)
385
+        page_2 = cont_api_a.create(ContentType.Page, workspace1, None,
386
+                                   'this is page1', do_save=True)
387
+        page_3 = cont_api_a.create(ContentType.Thread, workspace2, None,
388
+                                   'this is page2', do_save=True)
389
+        page_4 = cont_api_a.create(ContentType.File, workspace2, None,
390
+                                   'this is page3', do_save=True)
391
+
392
+        for rev in page_1.revisions:
393
+            eq_(user_b not in rev.read_by.keys(), True)
394
+        for rev in page_2.revisions:
395
+            eq_(user_b not in rev.read_by.keys(), True)
396
+        for rev in page_3.revisions:
397
+            eq_(user_b not in rev.read_by.keys(), True)
398
+        for rev in page_4.revisions:
399
+            eq_(user_b not in rev.read_by.keys(), True)
400
+
401
+        # Set as read the workspace n°1
402
+        cont_api_b.mark_read__workspace(workspace=workspace1)
403
+
404
+        for rev in page_1.revisions:
405
+            eq_(user_b in rev.read_by.keys(), True)
406
+        for rev in page_2.revisions:
407
+            eq_(user_b in rev.read_by.keys(), True)
408
+        for rev in page_3.revisions:
409
+            eq_(user_b not in rev.read_by.keys(), True)
410
+        for rev in page_4.revisions:
411
+            eq_(user_b not in rev.read_by.keys(), True)
412
+
413
+        # Set as read the workspace n°2
414
+        cont_api_b.mark_read__workspace(workspace=workspace2)
415
+
416
+        for rev in page_1.revisions:
417
+            eq_(user_b in rev.read_by.keys(), True)
418
+        for rev in page_2.revisions:
419
+            eq_(user_b in rev.read_by.keys(), True)
420
+        for rev in page_3.revisions:
421
+            eq_(user_b in rev.read_by.keys(), True)
422
+        for rev in page_4.revisions:
423
+            eq_(user_b in rev.read_by.keys(), True)
424
+
425
+    def test_mark_read(self):
426
+        uapi = UserApi(None)
427
+        groups = [GroupApi(None).get_one(Group.TIM_USER),
428
+                  GroupApi(None).get_one(Group.TIM_MANAGER),
429
+                  GroupApi(None).get_one(Group.TIM_ADMIN)]
430
+        user_a = uapi.create_user(email='this.is@user',
431
+                                 groups=groups, save_now=True)
432
+        user_b = uapi.create_user(email='this.is@another.user',
433
+                                 groups=groups, save_now=True)
434
+
435
+        wapi = WorkspaceApi(user_a)
436
+        workspace = wapi.create_workspace(
437
+            'test workspace',
438
+            save_now=True)
439
+
440
+        role_api = RoleApi(user_a)
441
+        role_api.create_one(user_b, workspace, UserRoleInWorkspace.READER, False)
442
+        cont_api_a = ContentApi(user_a)
443
+        cont_api_b = ContentApi(user_b)
444
+
445
+        page_1 = cont_api_a.create(ContentType.Page, workspace, None,
446
+                                   'this is a page', do_save=True)
447
+
448
+        for rev in page_1.revisions:
449
+            eq_(user_b not in rev.read_by.keys(), True)
450
+
451
+        cont_api_b.mark_read(page_1)
452
+
453
+        for rev in page_1.revisions:
454
+            eq_(user_b in rev.read_by.keys(), True)
455
+
456
+
457
+    def test_mark_read__all(self):
458
+        uapi = UserApi(None)
459
+        groups = [GroupApi(None).get_one(Group.TIM_USER),
460
+                  GroupApi(None).get_one(Group.TIM_MANAGER),
461
+                  GroupApi(None).get_one(Group.TIM_ADMIN)]
462
+        user_a = uapi.create_user(email='this.is@user',
463
+                                 groups=groups, save_now=True)
464
+        user_b = uapi.create_user(email='this.is@another.user',
465
+                                 groups=groups, save_now=True)
466
+
467
+        wapi = WorkspaceApi(user_a)
468
+        workspace = wapi.create_workspace(
469
+            'test workspace',
470
+            save_now=True)
471
+
472
+        role_api = RoleApi(user_a)
473
+        role_api.create_one(user_b, workspace, UserRoleInWorkspace.READER, False)
474
+        cont_api_a = ContentApi(user_a)
475
+        cont_api_b = ContentApi(user_b)
476
+
477
+        page_2 = cont_api_a.create(ContentType.Page, workspace, None, 'this is page1', do_save=True)
478
+        page_3 = cont_api_a.create(ContentType.Thread, workspace, None, 'this is page2', do_save=True)
479
+        page_4 = cont_api_a.create(ContentType.File, workspace, None, 'this is page3', do_save=True)
480
+
481
+        for rev in page_2.revisions:
482
+            eq_(user_b not in rev.read_by.keys(), True)
483
+        for rev in page_3.revisions:
484
+            eq_(user_b not in rev.read_by.keys(), True)
485
+        for rev in page_4.revisions:
486
+            eq_(user_b not in rev.read_by.keys(), True)
487
+
488
+        DBSession.refresh(page_2)
489
+        DBSession.refresh(page_3)
490
+        DBSession.refresh(page_4)
491
+
492
+        cont_api_b.mark_read__all()
493
+
494
+        for rev in page_2.revisions:
495
+            eq_(user_b in rev.read_by.keys(), True)
496
+        for rev in page_3.revisions:
497
+            eq_(user_b in rev.read_by.keys(), True)
498
+        for rev in page_4.revisions:
499
+            eq_(user_b in rev.read_by.keys(), True)
500
+
501
+
502
+
503
+
504
+
350
     def test_update(self):
505
     def test_update(self):
351
         uapi = UserApi(None)
506
         uapi = UserApi(None)
352
         groups = [GroupApi(None).get_one(Group.TIM_USER),
507
         groups = [GroupApi(None).get_one(Group.TIM_USER),