소스 검색

Merge branch 'master' of github.com:tracim/tracim

Come 8 년 전
부모
커밋
b22248e431
31개의 변경된 파일917개의 추가작업 그리고 454개의 파일을 삭제
  1. 26 0
      tracim/migration/versions/2cd20ff3d23a_user_timezone.py
  2. 15 3
      tracim/tracim/command/user.py
  3. 1 2
      tracim/tracim/controllers/admin/user.py
  4. 9 14
      tracim/tracim/controllers/admin/workspace.py
  5. 13 7
      tracim/tracim/controllers/user.py
  6. BIN
      tracim/tracim/i18n/fr/LC_MESSAGES/tracim.mo
  7. 509 356
      tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po
  8. 0 1
      tracim/tracim/lib/app_globals.py
  9. 43 0
      tracim/tracim/lib/calendar.py
  10. 1 1
      tracim/tracim/lib/content.py
  11. 37 13
      tracim/tracim/lib/helpers.py
  12. 2 0
      tracim/tracim/lib/radicale/auth.py
  13. 35 4
      tracim/tracim/lib/user.py
  14. 35 18
      tracim/tracim/lib/workspace.py
  15. 6 2
      tracim/tracim/model/__init__.py
  16. 3 2
      tracim/tracim/model/auth.py
  17. 5 5
      tracim/tracim/model/data.py
  18. 7 3
      tracim/tracim/model/serializers.py
  19. 2 3
      tracim/tracim/templates/admin/workspace_getone.mak
  20. 4 2
      tracim/tracim/templates/file/getone.mak
  21. 2 1
      tracim/tracim/templates/folder/getone.mak
  22. 2 1
      tracim/tracim/templates/page/getone.mak
  23. 2 1
      tracim/tracim/templates/search/display.mak
  24. 2 1
      tracim/tracim/templates/thread/getone.mak
  25. 14 0
      tracim/tracim/templates/user_workspace_forms.mak
  26. 6 3
      tracim/tracim/templates/user_workspace_widgets.mak
  27. 2 1
      tracim/tracim/templates/workspace/getone.mak
  28. 3 2
      tracim/tracim/tests/__init__.py
  29. 109 0
      tracim/tracim/tests/functional/test_calendar.py
  30. 1 0
      tracim/tracim/tests/functional/test_ldap_restrictions.py
  31. 21 8
      tracim/tracim/tests/library/test_helpers.py

+ 26 - 0
tracim/migration/versions/2cd20ff3d23a_user_timezone.py 파일 보기

1
+"""user_timezone
2
+
3
+Revision ID: 2cd20ff3d23a
4
+Revises: b4b8d57b54e5
5
+Create Date: 2016-11-08 11:32:00.903232
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = '2cd20ff3d23a'
11
+down_revision = 'b4b8d57b54e5'
12
+
13
+from alembic import op
14
+import sqlalchemy as sa
15
+
16
+
17
+def upgrade():
18
+    ### commands auto generated by Alembic - please adjust! ###
19
+    op.add_column('users', sa.Column('timezone', sa.Unicode(length=255), server_default='', nullable=False))
20
+    ### end Alembic commands ###
21
+
22
+
23
+def downgrade():
24
+    ### commands auto generated by Alembic - please adjust! ###
25
+    op.drop_column('users', 'timezone')
26
+    ### end Alembic commands ###

+ 15 - 3
tracim/tracim/command/user.py 파일 보기

3
 from sqlalchemy.exc import IntegrityError
3
 from sqlalchemy.exc import IntegrityError
4
 from tg import config
4
 from tg import config
5
 
5
 
6
-from tracim.command import AppContextCommand, Extender
6
+from tracim.command import AppContextCommand
7
+from tracim.command import Extender
7
 from tracim.lib.auth.ldap import LDAPAuth
8
 from tracim.lib.auth.ldap import LDAPAuth
9
+from tracim.lib.daemons import DaemonsManager
10
+from tracim.lib.daemons import RadicaleDaemon
8
 from tracim.lib.email import get_email_manager
11
 from tracim.lib.email import get_email_manager
9
-from tracim.lib.exception import AlreadyExistError, CommandAbortedError
12
+from tracim.lib.exception import AlreadyExistError
13
+from tracim.lib.exception import CommandAbortedError
10
 from tracim.lib.group import GroupApi
14
 from tracim.lib.group import GroupApi
11
 from tracim.lib.user import UserApi
15
 from tracim.lib.user import UserApi
12
-from tracim.model import DBSession, User
16
+from tracim.model import DBSession
17
+from tracim.model import User
13
 
18
 
14
 
19
 
15
 class UserCommand(AppContextCommand):
20
 class UserCommand(AppContextCommand):
109
             user.update_webdav_digest_auth(password)
114
             user.update_webdav_digest_auth(password)
110
             self._session.add(user)
115
             self._session.add(user)
111
             self._session.flush()
116
             self._session.flush()
117
+
118
+            # We need to enable radicale if it not already done
119
+            daemons = DaemonsManager()
120
+            daemons.run('radicale', RadicaleDaemon)
121
+
122
+            user_api = UserApi(user)
123
+            user_api.execute_created_user_actions(user)
112
         except IntegrityError:
124
         except IntegrityError:
113
             self._session.rollback()
125
             self._session.rollback()
114
             raise AlreadyExistError()
126
             raise AlreadyExistError()

+ 1 - 2
tracim/tracim/controllers/admin/user.py 파일 보기

313
             is_tracim_manager = False
313
             is_tracim_manager = False
314
             is_tracim_admin = False
314
             is_tracim_admin = False
315
 
315
 
316
-
317
         api = UserApi(current_user)
316
         api = UserApi(current_user)
318
 
317
 
319
         if api.user_with_email_exists(email):
318
         if api.user_with_email_exists(email):
348
             email_manager = get_email_manager()
347
             email_manager = get_email_manager()
349
             email_manager.notify_created_account(user, password=password)
348
             email_manager.notify_created_account(user, password=password)
350
 
349
 
350
+        api.execute_created_user_actions(user)
351
         tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
351
         tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
352
         tg.redirect(self.url())
352
         tg.redirect(self.url())
353
 
353
 
354
-
355
     @tg.expose('tracim.templates.admin.user_getone')
354
     @tg.expose('tracim.templates.admin.user_getone')
356
     def get_one(self, user_id):
355
     def get_one(self, user_id):
357
         current_user = tmpl_context.current_user
356
         current_user = tmpl_context.current_user

+ 9 - 14
tracim/tracim/controllers/admin/workspace.py 파일 보기

5
 from tg.i18n import ugettext as _
5
 from tg.i18n import ugettext as _
6
 
6
 
7
 from tracim.controllers import TIMRestController
7
 from tracim.controllers import TIMRestController
8
-from tracim.controllers import TIMRestPathContextSetup
9
 
8
 
10
 
9
 
11
 from tracim.lib import CST
10
 from tracim.lib import CST
13
 from tracim.lib.helpers import on_off_to_boolean
12
 from tracim.lib.helpers import on_off_to_boolean
14
 from tracim.lib.user import UserApi
13
 from tracim.lib.user import UserApi
15
 from tracim.lib.userworkspace import RoleApi
14
 from tracim.lib.userworkspace import RoleApi
16
-from tracim.lib.content import ContentApi
17
 from tracim.lib.workspace import WorkspaceApi
15
 from tracim.lib.workspace import WorkspaceApi
18
-from tracim.model import DBSession
19
 
16
 
20
 from tracim.model.auth import Group
17
 from tracim.model.auth import Group
21
-from tracim.model.data import NodeTreeItem
22
-from tracim.model.data import Content
23
-from tracim.model.data import ContentType
24
-from tracim.model.data import Workspace
25
 from tracim.model.data import UserRoleInWorkspace
18
 from tracim.model.data import UserRoleInWorkspace
26
 
19
 
27
 from tracim.model.serializers import Context, CTX, DictLikeClass
20
 from tracim.model.serializers import Context, CTX, DictLikeClass
28
 
21
 
29
-from tracim.controllers.content import UserWorkspaceFolderRestController
30
-
31
-
32
-
33
 
22
 
34
 class RoleInWorkspaceRestController(TIMRestController, BaseController):
23
 class RoleInWorkspaceRestController(TIMRestController, BaseController):
35
 
24
 
198
         workspace_api_controller = WorkspaceApi(user)
187
         workspace_api_controller = WorkspaceApi(user)
199
         calendar_enabled = on_off_to_boolean(calendar_enabled)
188
         calendar_enabled = on_off_to_boolean(calendar_enabled)
200
 
189
 
201
-        workspace = workspace_api_controller.create_workspace(name, description)
202
-        workspace.calendar_enabled = calendar_enabled
203
-        DBSession.flush()
190
+        workspace = workspace_api_controller.create_workspace(
191
+            name,
192
+            description,
193
+            calendar_enabled=calendar_enabled,
194
+            save_now=True,
195
+        )
204
 
196
 
205
         tg.flash(_('{} workspace created.').format(workspace.label), CST.STATUS_OK)
197
         tg.flash(_('{} workspace created.').format(workspace.label), CST.STATUS_OK)
206
         tg.redirect(self.url())
198
         tg.redirect(self.url())
228
         workspace.calendar_enabled = calendar_enabled
220
         workspace.calendar_enabled = calendar_enabled
229
         workspace_api_controller.save(workspace)
221
         workspace_api_controller.save(workspace)
230
 
222
 
223
+        if calendar_enabled:
224
+            workspace_api_controller.ensure_calendar_exist(workspace)
225
+
231
         tg.flash(_('{} workspace updated.').format(workspace.label), CST.STATUS_OK)
226
         tg.flash(_('{} workspace updated.').format(workspace.label), CST.STATUS_OK)
232
         tg.redirect(self.url(workspace.workspace_id))
227
         tg.redirect(self.url(workspace.workspace_id))
233
         return
228
         return

+ 13 - 7
tracim/tracim/controllers/user.py 파일 보기

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
-
2
+import pytz
3
+from webob.exc import HTTPForbidden
3
 import tg
4
 import tg
4
 from tg import tmpl_context
5
 from tg import tmpl_context
5
 from tg.i18n import ugettext as _
6
 from tg.i18n import ugettext as _
6
-from webob.exc import HTTPForbidden
7
 
7
 
8
 from tracim.controllers import TIMRestController
8
 from tracim.controllers import TIMRestController
9
 from tracim.lib.user import UserApi
9
 from tracim.lib.user import UserApi
10
 from tracim.lib.workspace import WorkspaceApi
10
 from tracim.lib.workspace import WorkspaceApi
11
-
12
-from tracim.model.serializers import Context, CTX, DictLikeClass
11
+from tracim.model.serializers import Context
12
+from tracim.model.serializers import CTX
13
+from tracim.model.serializers import DictLikeClass
13
 from tracim import model as pm
14
 from tracim import model as pm
14
 
15
 
15
 
16
 
157
 
158
 
158
         dictified_user = Context(CTX.USER).toDict(current_user, 'user')
159
         dictified_user = Context(CTX.USER).toDict(current_user, 'user')
159
         fake_api = DictLikeClass(next_url=next_url)
160
         fake_api = DictLikeClass(next_url=next_url)
160
-        return DictLikeClass(result=dictified_user, fake_api=fake_api)
161
+        return DictLikeClass(
162
+            result=dictified_user,
163
+            fake_api=fake_api,
164
+            timezones=pytz.all_timezones,
165
+        )
161
 
166
 
162
     @tg.expose('tracim.templates.workspace.edit')
167
     @tg.expose('tracim.templates.workspace.edit')
163
-    def put(self, user_id, name, email, next_url=None):
168
+    def put(self, user_id, name, email, timezone, next_url=None):
164
         user_id = tmpl_context.current_user.user_id
169
         user_id = tmpl_context.current_user.user_id
165
         current_user = tmpl_context.current_user
170
         current_user = tmpl_context.current_user
166
         assert user_id==current_user.user_id
171
         assert user_id==current_user.user_id
168
         # Only keep allowed field update
173
         # Only keep allowed field update
169
         updated_fields = self._clean_update_fields({
174
         updated_fields = self._clean_update_fields({
170
             'name': name,
175
             'name': name,
171
-            'email': email
176
+            'email': email,
177
+            'timezone': timezone,
172
         })
178
         })
173
 
179
 
174
         api = UserApi(tmpl_context.current_user)
180
         api = UserApi(tmpl_context.current_user)

BIN
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.mo 파일 보기


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 509 - 356
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po


+ 0 - 1
tracim/tracim/lib/app_globals.py 파일 보기

23
         pass
23
         pass
24
 
24
 
25
     VERSION_NUMBER = '1.0.3'
25
     VERSION_NUMBER = '1.0.3'
26
-    LONG_DATE_FORMAT = '%A, the %d of %B %Y at %H:%M'
27
     SHORT_DATE_FORMAT = l_('%B %d at %I:%M%p')
26
     SHORT_DATE_FORMAT = l_('%B %d at %I:%M%p')
28
 
27
 
29
 
28
 

+ 43 - 0
tracim/tracim/lib/calendar.py 파일 보기

1
+import caldav
1
 import os
2
 import os
2
 
3
 
3
 import re
4
 import re
5
 
6
 
6
 from icalendar import Event as iCalendarEvent
7
 from icalendar import Event as iCalendarEvent
7
 from sqlalchemy.orm.exc import NoResultFound
8
 from sqlalchemy.orm.exc import NoResultFound
9
+from tg import tmpl_context
8
 from tg.i18n import ugettext as _
10
 from tg.i18n import ugettext as _
9
 
11
 
10
 from tracim.lib.content import ContentApi
12
 from tracim.lib.content import ContentApi
316
         :return: True if given collection path is an discover path
318
         :return: True if given collection path is an discover path
317
         """
319
         """
318
         return path in ('user', 'workspace')
320
         return path in ('user', 'workspace')
321
+
322
+    def create_then_remove_fake_event(
323
+            self,
324
+            calendar_class,
325
+            related_object_id,
326
+    ) -> None:
327
+        radicale_base_url = self.get_base_url()
328
+        client = caldav.DAVClient(
329
+            radicale_base_url,
330
+            username=self._user.email,
331
+            password=self._user.auth_token,
332
+        )
333
+        if calendar_class == WorkspaceCalendar:
334
+            calendar_url = self.get_workspace_calendar_url(related_object_id)
335
+        elif calendar_class == UserCalendar:
336
+            calendar_url = self.get_user_calendar_url(related_object_id)
337
+        else:
338
+            raise Exception('Unknown calendar type {0}'.format(calendar_class))
339
+
340
+        user_calendar = caldav.Calendar(
341
+            parent=client,
342
+            client=client,
343
+            url=calendar_url
344
+        )
345
+
346
+        event_ics = """BEGIN:VCALENDAR
347
+VERSION:2.0
348
+PRODID:-//Example Corp.//CalDAV Client//EN
349
+BEGIN:VEVENT
350
+UID:{uid}
351
+DTSTAMP:20100510T182145Z
352
+DTSTART:20100512T170000Z
353
+DTEND:20100512T180000Z
354
+SUMMARY:This is an event
355
+LOCATION:Here
356
+END:VEVENT
357
+END:VCALENDAR
358
+""".format(uid='{0}FAKEEVENT'.format(related_object_id))
359
+        event = user_calendar.add_event(event_ics)
360
+        event.delete()
361
+

+ 1 - 1
tracim/tracim/lib/content.py 파일 보기

184
         if workspace:
184
         if workspace:
185
             result = result.filter(Content.workspace_id==workspace.workspace_id)
185
             result = result.filter(Content.workspace_id==workspace.workspace_id)
186
 
186
 
187
-        if self._user and not self._disable_user_workspaces_filter:
187
+        if self._user and workspace and not self._disable_user_workspaces_filter:
188
             user = DBSession.query(User).get(self._user_id)
188
             user = DBSession.query(User).get(self._user_id)
189
             # Filter according to user workspaces
189
             # Filter according to user workspaces
190
             workspace_ids = [r.workspace_id for r in user.roles \
190
             workspace_ids = [r.workspace_id for r in user.roles \

+ 37 - 13
tracim/tracim/lib/helpers.py 파일 보기

6
 
6
 
7
 import datetime
7
 import datetime
8
 
8
 
9
+import pytz
9
 import slugify
10
 import slugify
10
-from babel.dates import format_date, format_time
11
+from babel.dates import format_date
12
+from babel.dates import format_time
11
 from markupsafe import Markup
13
 from markupsafe import Markup
12
 
14
 
13
 import tg
15
 import tg
16
+from tg import tmpl_context
14
 from tg.i18n import ugettext as _
17
 from tg.i18n import ugettext as _
15
 
18
 
16
 from tracim.lib import app_globals as plag
19
 from tracim.lib import app_globals as plag
20
 from tracim.lib.content import ContentApi
23
 from tracim.lib.content import ContentApi
21
 from tracim.lib.userworkspace import RoleApi
24
 from tracim.lib.userworkspace import RoleApi
22
 from tracim.lib.workspace import WorkspaceApi
25
 from tracim.lib.workspace import WorkspaceApi
23
-from tracim.model import User
24
 
26
 
25
 from tracim.model.data import ContentStatus
27
 from tracim.model.data import ContentStatus
26
 from tracim.model.data import Content
28
 from tracim.model.data import Content
28
 from tracim.model.data import UserRoleInWorkspace
30
 from tracim.model.data import UserRoleInWorkspace
29
 from tracim.model.data import Workspace
31
 from tracim.model.data import Workspace
30
 
32
 
33
+
34
+def get_with_timezone(
35
+        datetime_object: datetime.datetime,
36
+        to_timezone: str='',
37
+        default_from_timezone: str='UTC',
38
+) -> datetime.datetime:
39
+    """
40
+    Change timezone of a date
41
+    :param datetime_object: datetime to update
42
+    :param to_timezone: timezone name, if equal to '',
43
+    try to grap current user timezone. If no given timezone name and no
44
+    current user timezone, return original date time
45
+    :param default_from_timezone: datetime original timezone if datetime
46
+    object is naive
47
+    :return: datetime updated
48
+    """
49
+    # If no to_timezone, try to grab from current user
50
+    if not to_timezone and tmpl_context.current_user:
51
+        to_timezone = tmpl_context.current_user.timezone
52
+
53
+    # If no to_timezone, return original datetime
54
+    if not to_timezone:
55
+        return datetime_object
56
+
57
+    # If datetime_object have not timezone, set new from default_from_timezone
58
+    if not datetime_object.tzinfo:
59
+        from_tzinfo = pytz.timezone(default_from_timezone)
60
+        datetime_object = from_tzinfo.localize(datetime_object)
61
+
62
+    new_tzinfo = pytz.timezone(to_timezone)
63
+    return datetime_object.astimezone(new_tzinfo)
64
+
65
+
31
 def date_time_in_long_format(datetime_object, format=''):
66
 def date_time_in_long_format(datetime_object, format=''):
32
 
67
 
33
     current_locale = tg.i18n.get_lang()[0]
68
     current_locale = tg.i18n.get_lang()[0]
63
   now = datetime.datetime.now()
98
   now = datetime.datetime.now()
64
   return now.strftime('%Y')
99
   return now.strftime('%Y')
65
 
100
 
66
-def formatLongDateAndTime(datetime_object, format=''):
67
-    """ OBSOLETE
68
-    :param datetime_object:
69
-    :param format:
70
-    :return:
71
-    """
72
-    if not format:
73
-        format = plag.Globals.LONG_DATE_FORMAT
74
-    return datetime_object.strftime(format)
75
-
76
-
77
 
101
 
78
 def icon(icon_name, white=False):
102
 def icon(icon_name, white=False):
79
     if (white):
103
     if (white):

+ 2 - 0
tracim/tracim/lib/radicale/auth.py 파일 보기

1
 from tg import config
1
 from tg import config
2
 
2
 
3
 from tracim.lib.user import UserApi
3
 from tracim.lib.user import UserApi
4
+from tracim.model import DBSession
4
 
5
 
5
 
6
 
6
 class Auth(object):
7
 class Auth(object):
33
     """
34
     """
34
     see tracim.lib.radicale.auth.Auth#is_authenticated
35
     see tracim.lib.radicale.auth.Auth#is_authenticated
35
     """
36
     """
37
+    DBSession.expire_all()
36
     return Auth.is_authenticated(user, password)
38
     return Auth.is_authenticated(user, password)

+ 35 - 4
tracim/tracim/lib/user.py 파일 보기

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
-
3
-__author__ = 'damien'
2
+import transaction
4
 
3
 
5
 import tg
4
 import tg
6
 
5
 
8
 
7
 
9
 from tracim.model import auth as pbma
8
 from tracim.model import auth as pbma
10
 from tracim.model import DBSession
9
 from tracim.model import DBSession
11
-import tracim.model.data as pmd
10
+
11
+__author__ = 'damien'
12
+
12
 
13
 
13
 class UserApi(object):
14
 class UserApi(object):
14
 
15
 
30
     def get_one_by_id(self, id: int) -> User:
31
     def get_one_by_id(self, id: int) -> User:
31
         return self._base_query().filter(User.user_id==id).one()
32
         return self._base_query().filter(User.user_id==id).one()
32
 
33
 
33
-    def update(self, user: User, name: str=None, email: str=None, do_save=True):
34
+    def update(
35
+            self,
36
+            user: User,
37
+            name: str=None,
38
+            email: str=None,
39
+            do_save=True,
40
+            timezone: str='',
41
+    ):
34
         if name is not None:
42
         if name is not None:
35
             user.display_name = name
43
             user.display_name = name
36
 
44
 
37
         if email is not None:
45
         if email is not None:
38
             user.email = email
46
             user.email = email
39
 
47
 
48
+        user.timezone = timezone
49
+
40
         if do_save:
50
         if do_save:
41
             self.save(user)
51
             self.save(user)
42
 
52
 
70
     def save(self, user: User):
80
     def save(self, user: User):
71
         DBSession.flush()
81
         DBSession.flush()
72
 
82
 
83
+    def execute_created_user_actions(self, created_user: User) -> None:
84
+        """
85
+        Execute actions when user just been created
86
+        :return:
87
+        """
88
+        # NOTE: Cyclic import
89
+        from tracim.lib.calendar import CalendarManager
90
+        from tracim.model.organisational import UserCalendar
91
+
92
+        created_user.ensure_auth_token()
93
+
94
+        # Ensure database is up-to-date
95
+        DBSession.flush()
96
+        transaction.commit()
97
+
98
+        calendar_manager = CalendarManager(created_user)
99
+        calendar_manager.create_then_remove_fake_event(
100
+            calendar_class=UserCalendar,
101
+            related_object_id=created_user.user_id,
102
+        )
103
+
73
 
104
 
74
 class UserStaticApi(object):
105
 class UserStaticApi(object):
75
 
106
 

+ 35 - 18
tracim/tracim/lib/workspace.py 파일 보기

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
-
3
-__author__ = 'damien'
4
-
5
-import os
6
-from datetime import datetime
7
-from hashlib import sha256
8
-
9
-from sqlalchemy import Table, ForeignKey, Column
10
-from sqlalchemy.types import Unicode, Integer, DateTime, Text
11
-from sqlalchemy.orm import relation, synonym, contains_eager
12
-from sqlalchemy.orm import joinedload_all
13
-import sqlalchemy.orm as sqlao
14
-import sqlalchemy as sqla
15
-
16
-import tg
2
+import transaction
17
 
3
 
18
 from tracim.lib.userworkspace import RoleApi
4
 from tracim.lib.userworkspace import RoleApi
19
 from tracim.model.auth import Group
5
 from tracim.model.auth import Group
20
 from tracim.model.auth import User
6
 from tracim.model.auth import User
21
 from tracim.model.data import Workspace
7
 from tracim.model.data import Workspace
22
 from tracim.model.data import UserRoleInWorkspace
8
 from tracim.model.data import UserRoleInWorkspace
23
-
24
-from tracim.model import auth as pbma
25
 from tracim.model import DBSession
9
 from tracim.model import DBSession
26
 
10
 
11
+__author__ = 'damien'
12
+
27
 
13
 
28
 class WorkspaceApi(object):
14
 class WorkspaceApi(object):
29
 
15
 
39
             filter(UserRoleInWorkspace.user_id==self._user.user_id).\
25
             filter(UserRoleInWorkspace.user_id==self._user.user_id).\
40
             filter(Workspace.is_deleted==False)
26
             filter(Workspace.is_deleted==False)
41
 
27
 
42
-    def create_workspace(self, label: str, description: str='', save_now:bool=False) -> Workspace:
28
+    def create_workspace(
29
+            self,
30
+            label: str,
31
+            description: str='',
32
+            calendar_enabled: bool=False,
33
+            save_now: bool=False,
34
+    ) -> Workspace:
43
         workspace = Workspace()
35
         workspace = Workspace()
44
         workspace.label = label
36
         workspace.label = label
45
         workspace.description = description
37
         workspace.description = description
38
+        workspace.calendar_enabled = calendar_enabled
46
 
39
 
47
         # By default, we force the current user to be the workspace manager
40
         # By default, we force the current user to be the workspace manager
48
         # And to receive email notifications
41
         # And to receive email notifications
56
         if save_now:
49
         if save_now:
57
             DBSession.flush()
50
             DBSession.flush()
58
 
51
 
52
+        if calendar_enabled:
53
+            self.execute_created_workspace_actions(workspace)
54
+
59
         return workspace
55
         return workspace
60
 
56
 
61
     def get_one(self, id):
57
     def get_one(self, id):
125
 
121
 
126
         return workspace
122
         return workspace
127
 
123
 
124
+    def execute_created_workspace_actions(self, workspace: Workspace) -> None:
125
+        self.ensure_calendar_exist(workspace)
126
+
127
+    def ensure_calendar_exist(self, workspace: Workspace) -> None:
128
+        # Note: Cyclic imports
129
+        from tracim.lib.calendar import CalendarManager
130
+        from tracim.model.organisational import WorkspaceCalendar
131
+
132
+        if workspace.calendar_enabled:
133
+            self._user.ensure_auth_token()
134
+
135
+            # Ensure database is up-to-date
136
+            DBSession.flush()
137
+            transaction.commit()
138
+
139
+            calendar_manager = CalendarManager(self._user)
140
+            calendar_manager.create_then_remove_fake_event(
141
+                calendar_class=WorkspaceCalendar,
142
+                related_object_id=workspace.workspace_id,
143
+            )
144
+
128
 
145
 
129
 class UnsafeWorkspaceApi(WorkspaceApi):
146
 class UnsafeWorkspaceApi(WorkspaceApi):
130
     def _base_query(self):
147
     def _base_query(self):

+ 6 - 2
tracim/tracim/model/__init__.py 파일 보기

39
 
39
 
40
 # Global session manager: DBSession() returns the Thread-local
40
 # Global session manager: DBSession() returns the Thread-local
41
 # session object appropriate for the current web request.
41
 # session object appropriate for the current web request.
42
-maker = sessionmaker(autoflush=True, autocommit=False,
43
-                     extension=ZopeTransactionExtension())
42
+maker = sessionmaker(
43
+    autoflush=True,
44
+    autocommit=False,
45
+    extension=ZopeTransactionExtension(),
46
+    expire_on_commit=False,
47
+)
44
 DBSession = scoped_session(maker)
48
 DBSession = scoped_session(maker)
45
 
49
 
46
 # Base class for all of our model classes: By default, the data model is
50
 # Base class for all of our model classes: By default, the data model is

+ 3 - 2
tracim/tracim/model/auth.py 파일 보기

68
     group_id = Column(Integer, Sequence('seq__groups__group_id'), autoincrement=True, primary_key=True)
68
     group_id = Column(Integer, Sequence('seq__groups__group_id'), autoincrement=True, primary_key=True)
69
     group_name = Column(Unicode(16), unique=True, nullable=False)
69
     group_name = Column(Unicode(16), unique=True, nullable=False)
70
     display_name = Column(Unicode(255))
70
     display_name = Column(Unicode(255))
71
-    created = Column(DateTime, default=datetime.now)
71
+    created = Column(DateTime, default=datetime.utcnow)
72
 
72
 
73
     users = relationship('User', secondary=user_group_table, backref='groups')
73
     users = relationship('User', secondary=user_group_table, backref='groups')
74
 
74
 
121
     email = Column(Unicode(255), unique=True, nullable=False)
121
     email = Column(Unicode(255), unique=True, nullable=False)
122
     display_name = Column(Unicode(255))
122
     display_name = Column(Unicode(255))
123
     _password = Column('password', Unicode(128))
123
     _password = Column('password', Unicode(128))
124
-    created = Column(DateTime, default=datetime.now)
124
+    created = Column(DateTime, default=datetime.utcnow)
125
     is_active = Column(Boolean, default=True, nullable=False)
125
     is_active = Column(Boolean, default=True, nullable=False)
126
     imported_from = Column(Unicode(32), nullable=True)
126
     imported_from = Column(Unicode(32), nullable=True)
127
+    timezone = Column(Unicode(255), nullable=False, server_default='')
127
     _webdav_left_digest_response_hash = Column('webdav_left_digest_response_hash', Unicode(128))
128
     _webdav_left_digest_response_hash = Column('webdav_left_digest_response_hash', Unicode(128))
128
     auth_token = Column(Unicode(255))
129
     auth_token = Column(Unicode(255))
129
     auth_token_created = Column(DateTime)
130
     auth_token_created = Column(DateTime)

+ 5 - 5
tracim/tracim/model/data.py 파일 보기

580
             column_value = getattr(revision, column_name)
580
             column_value = getattr(revision, column_name)
581
             setattr(new_rev, column_name, column_value)
581
             setattr(new_rev, column_name, column_value)
582
 
582
 
583
-        new_rev.updated = datetime.now()
583
+        new_rev.updated = datetime.utcnow()
584
 
584
 
585
         return new_rev
585
         return new_rev
586
 
586
 
1009
 
1009
 
1010
     def created_as_delta(self, delta_from_datetime:datetime=None):
1010
     def created_as_delta(self, delta_from_datetime:datetime=None):
1011
         if not delta_from_datetime:
1011
         if not delta_from_datetime:
1012
-            delta_from_datetime = datetime.now()
1012
+            delta_from_datetime = datetime.utcnow()
1013
 
1013
 
1014
         return format_timedelta(delta_from_datetime - self.created,
1014
         return format_timedelta(delta_from_datetime - self.created,
1015
                                 locale=tg.i18n.get_lang()[0])
1015
                                 locale=tg.i18n.get_lang()[0])
1017
     def datetime_as_delta(self, datetime_object,
1017
     def datetime_as_delta(self, datetime_object,
1018
                           delta_from_datetime:datetime=None):
1018
                           delta_from_datetime:datetime=None):
1019
         if not delta_from_datetime:
1019
         if not delta_from_datetime:
1020
-            delta_from_datetime = datetime.now()
1020
+            delta_from_datetime = datetime.utcnow()
1021
         return format_timedelta(delta_from_datetime - datetime_object,
1021
         return format_timedelta(delta_from_datetime - datetime_object,
1022
                                 locale=tg.i18n.get_lang()[0])
1022
                                 locale=tg.i18n.get_lang()[0])
1023
 
1023
 
1232
 
1232
 
1233
     def created_as_delta(self, delta_from_datetime:datetime=None):
1233
     def created_as_delta(self, delta_from_datetime:datetime=None):
1234
         if not delta_from_datetime:
1234
         if not delta_from_datetime:
1235
-            delta_from_datetime = datetime.now()
1235
+            delta_from_datetime = datetime.utcnow()
1236
         return format_timedelta(delta_from_datetime - self.created,
1236
         return format_timedelta(delta_from_datetime - self.created,
1237
                                 locale=tg.i18n.get_lang()[0])
1237
                                 locale=tg.i18n.get_lang()[0])
1238
 
1238
 
1240
         aff = ''
1240
         aff = ''
1241
 
1241
 
1242
         if not delta_from_datetime:
1242
         if not delta_from_datetime:
1243
-            delta_from_datetime = datetime.now()
1243
+            delta_from_datetime = datetime.utcnow()
1244
 
1244
 
1245
         delta = delta_from_datetime - self.created
1245
         delta = delta_from_datetime - self.created
1246
         
1246
         

+ 7 - 3
tracim/tracim/model/serializers.py 파일 보기

621
     last_activity_date = content.get_last_activity_date()
621
     last_activity_date = content.get_last_activity_date()
622
     last_activity_date_formatted = format_datetime(last_activity_date,
622
     last_activity_date_formatted = format_datetime(last_activity_date,
623
                                                    locale=tg.i18n.get_lang()[0])
623
                                                    locale=tg.i18n.get_lang()[0])
624
-    last_activity_label = format_timedelta(datetime.now() - last_activity_date,
625
-                                           locale=tg.i18n.get_lang()[0])
624
+    last_activity_label = format_timedelta(
625
+        datetime.utcnow() - last_activity_date,
626
+        locale=tg.i18n.get_lang()[0],
627
+    )
626
     last_activity_label = last_activity_label.replace(' ', '\u00A0') # espace insécable
628
     last_activity_label = last_activity_label.replace(' ', '\u00A0') # espace insécable
627
 
629
 
628
     return DictLikeClass(
630
     return DictLikeClass(
645
     last_activity_date = content.get_last_activity_date()
647
     last_activity_date = content.get_last_activity_date()
646
     last_activity_date_formatted = format_datetime(last_activity_date,
648
     last_activity_date_formatted = format_datetime(last_activity_date,
647
                                                    locale=tg.i18n.get_lang()[0])
649
                                                    locale=tg.i18n.get_lang()[0])
648
-    last_activity_label = format_timedelta(datetime.now() - last_activity_date,
650
+    last_activity_label = format_timedelta(datetime.utcnow() - last_activity_date,
649
                                            locale=tg.i18n.get_lang()[0])
651
                                            locale=tg.i18n.get_lang()[0])
650
     last_activity_label = last_activity_label.replace(' ', '\u00A0') # espace insécable
652
     last_activity_label = last_activity_label.replace(' ', '\u00A0') # espace insécable
651
 
653
 
858
     result['enabled'] = user.is_active
860
     result['enabled'] = user.is_active
859
     result['profile'] = user.profile
861
     result['profile'] = user.profile
860
     result['has_password'] = user.password!=None
862
     result['has_password'] = user.password!=None
863
+    result['timezone'] = user.timezone
861
     return result
864
     return result
862
 
865
 
863
 
866
 
880
     result['enabled'] = user.is_active
883
     result['enabled'] = user.is_active
881
     result['profile'] = user.profile
884
     result['profile'] = user.profile
882
     result['calendar_url'] = user.calendar_url
885
     result['calendar_url'] = user.calendar_url
886
+    result['timezone'] = user.timezone
883
 
887
 
884
     return result
888
     return result
885
 
889
 

+ 2 - 3
tracim/tracim/templates/admin/workspace_getone.mak 파일 보기

24
 <%def name="TITLE_ROW()">
24
 <%def name="TITLE_ROW()">
25
     <div class="row-fluid">
25
     <div class="row-fluid">
26
         <div>
26
         <div>
27
-            <%
28
-                subtitle = _('created on {}').format(h.formatLongDateAndTime(result.workspace.created))
29
-            %>
27
+            <% created_localized = h.get_with_timezone(result.workspace.created) %>
28
+            <% subtitle = _('workspace created on {date} at {time}').format(date=h.date(created_localized), time=h.time(created_localized)) %>
30
             ${ROW.TITLE_ROW(_('Workspace {}').format(result.workspace.label), 'fa-bank', 'col-md-offset-3 col-md-7', 't-user-color', subtitle)}
29
             ${ROW.TITLE_ROW(_('Workspace {}').format(result.workspace.label), 'fa-bank', 'col-md-offset-3 col-md-7', 't-user-color', subtitle)}
31
         </div>
30
         </div>
32
     </div>
31
     </div>

+ 4 - 2
tracim/tracim/templates/file/getone.mak 파일 보기

48
         </h1>
48
         </h1>
49
 
49
 
50
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
50
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
51
-          <p>${_('file created on {date} at {time} by <b>{author}</b>').format(date=h.date(result.file.created), time=h.time(result.file.created), author=result.file.owner.name)|n}</p>
51
+            <% created_localized = h.get_with_timezone(result.file.created) %>
52
+          <p>${_('file created on {date} at {time} by <b>{author}</b>').format(date=h.date(created_localized), time=h.time(created_localized), author=result.file.owner.name)|n}</p>
52
         </div>
53
         </div>
53
     </div>
54
     </div>
54
 </div>
55
 </div>
107
                 </tr>
108
                 </tr>
108
                 <tr>
109
                 <tr>
109
                     <td class="tracim-title">${_('Modified')}</td>
110
                     <td class="tracim-title">${_('Modified')}</td>
110
-                    <td>${h.format_short(result.file.created)|n} ${_('by {}').format(result.file.owner.name)}</td>
111
+                    <% created_localized = h.get_with_timezone(result.file.created) %>
112
+                    <td>${h.format_short(created_localized)|n} ${_('by {}').format(result.file.owner.name)}</td>
111
                 </tr>
113
                 </tr>
112
             </table>
114
             </table>
113
         </div>
115
         </div>

+ 2 - 1
tracim/tracim/templates/folder/getone.mak 파일 보기

46
         </h1>
46
         </h1>
47
 
47
 
48
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
48
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
49
-          <p>${_('folder created on {date} at {time} by <b>{author}</b>').format(date=h.date(result.folder.created), time=h.time(result.folder.created), author=result.folder.owner.name)|n}</p>
49
+            <% created_localized = h.get_with_timezone(result.folder.created) %>
50
+          <p>${_('folder created on {date} at {time} by <b>{author}</b>').format(date=h.date(created_localized), time=h.time(created_localized), author=result.folder.owner.name)|n}</p>
50
         </div>
51
         </div>
51
     </div>
52
     </div>
52
 </div>
53
 </div>

+ 2 - 1
tracim/tracim/templates/page/getone.mak 파일 보기

47
         </h1>
47
         </h1>
48
 
48
 
49
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
49
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
50
-          <p>${_('page created on {date} at {time} by <b>{author}</b>').format(date=h.date(result.page.created), time=h.time(result.page.created), author=result.page.owner.name)|n}</p>
50
+            <% created_localized = h.get_with_timezone(result.page.created) %>
51
+          <p>${_('page created on {date} at {time} by <b>{author}</b>').format(date=h.date(created_localized), time=h.time(created_localized), author=result.page.owner.name)|n}</p>
51
         </div>
52
         </div>
52
     </div>
53
     </div>
53
 </div>
54
 </div>

+ 2 - 1
tracim/tracim/templates/search/display.mak 파일 보기

42
         </h1>
42
         </h1>
43
 
43
 
44
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
44
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
45
-##          <p>${_('folder created on {date} at {time} by <b>{author}</b>').format(date=h.date(result.folder.created), time=h.time(result.folder.created), author=result.folder.owner.name)|n}</p>
45
+            <% created_localized = h.get_with_timezone(result.folder.created) %>
46
+##          <p>${_('folder created on {date} at {time} by <b>{author}</b>').format(date=h.date(created_localized), time=h.time(created_localized), author=result.folder.owner.name)|n}</p>
46
         </div>
47
         </div>
47
     </div>
48
     </div>
48
 </div>
49
 </div>

+ 2 - 1
tracim/tracim/templates/thread/getone.mak 파일 보기

48
         </h1>
48
         </h1>
49
 
49
 
50
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
50
         <div style="margin: -1.5em auto -1.5em auto;" class="tracim-less-visible">
51
-          <p>${_('page created on {date} at {time} by <b>{author}</b>').format(date=h.date(result.thread.created), time=h.time(result.thread.created), author=result.thread.owner.name)|n}</p>
51
+            <% created_localized = h.get_with_timezone(result.thread.created) %>
52
+          <p>${_('page created on {date} at {time} by <b>{author}</b>').format(date=h.date(created_localized), time=h.time(created_localized), author=result.thread.owner.name)|n}</p>
52
         </div>
53
         </div>
53
     </div>
54
     </div>
54
 </div>
55
 </div>

+ 14 - 0
tracim/tracim/templates/user_workspace_forms.mak 파일 보기

115
                 <span class="info readonly">${_('This calendar URL will work with CalDav compatibles clients')}</span>
115
                 <span class="info readonly">${_('This calendar URL will work with CalDav compatibles clients')}</span>
116
                 <input id="calendar" type="text" class="form-control"  disabled="disabled" value="${user.calendar_url}" />
116
                 <input id="calendar" type="text" class="form-control"  disabled="disabled" value="${user.calendar_url}" />
117
             </div>
117
             </div>
118
+            <div class="form-group">
119
+                <label for="timezone">${_('Timezone')}</label>
120
+                <span class="info readonly">${_('Dates will be displayed with this timezone')}</span>
121
+                <select id="timezone" name="timezone" class="form-control">
122
+                    <option value=""></option>
123
+                    % for timezone in timezones:
124
+                        % if timezone == user.timezone:
125
+                            <option value="${timezone}" selected>${timezone}</option>
126
+                        % else:
127
+                            <option value="${timezone}">${timezone}</option>
128
+                        % endif
129
+                    % endfor
130
+                </select>
131
+            </div>
118
         </div>
132
         </div>
119
         <div class="modal-footer">
133
         <div class="modal-footer">
120
             <span class="pull-right t-modal-form-submit-button">
134
             <span class="pull-right t-modal-form-submit-button">

+ 6 - 3
tracim/tracim/templates/user_workspace_widgets.mak 파일 보기

303
 </%def>
303
 </%def>
304
 
304
 
305
 <%def name="SECURED_TIMELINE_ITEM(user, item)">
305
 <%def name="SECURED_TIMELINE_ITEM(user, item)">
306
+##     <% created_localized = h.get_with_timezone(item.created) %>
306
 ##     <div class="row t-odd-or-even t-hacky-thread-comment-border-top">
307
 ##     <div class="row t-odd-or-even t-hacky-thread-comment-border-top">
307
 ##         <div class="col-sm-7 col-sm-offset-3">
308
 ##         <div class="col-sm-7 col-sm-offset-3">
308
 ##             <div class="t-timeline-item">
309
 ##             <div class="t-timeline-item">
312
 ##                 <h5 style="margin: 0;">
313
 ##                 <h5 style="margin: 0;">
313
 ##                     <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(item.owner.name)|n}</span>
314
 ##                     <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(item.owner.name)|n}</span>
314
 ##
315
 ##
315
-##                     <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(item.created)|n}">
316
+##                     <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(created_localized)|n}">
316
 ##                         ${_('{delta} ago').format(delta=item.created_as_delta)}
317
 ##                         ${_('{delta} ago').format(delta=item.created_as_delta)}
317
 ##
318
 ##
318
 ##                         % if h.is_item_still_editable(item) and item.owner.id==user.id:
319
 ##                         % if h.is_item_still_editable(item) and item.owner.id==user.id:
336
 </%def>
337
 </%def>
337
 
338
 
338
 <%def name="SECURED_HISTORY_VIRTUAL_EVENT(user, event)">
339
 <%def name="SECURED_HISTORY_VIRTUAL_EVENT(user, event)">
340
+    <% created_localized = h.get_with_timezone(event.created) %>
339
     <% is_new_css_class = 't-is-new-content' if event.is_new else '' %>
341
     <% is_new_css_class = 't-is-new-content' if event.is_new else '' %>
340
 
342
 
341
     <div class="row t-odd-or-even t-hacky-thread-comment-border-top ${is_new_css_class}">
343
     <div class="row t-odd-or-even t-hacky-thread-comment-border-top ${is_new_css_class}">
353
                         <span class="tracim-less-visible">${_('{} by <strong>{}</strong>').format(event.label, event.owner.name)|n}</span>
355
                         <span class="tracim-less-visible">${_('{} by <strong>{}</strong>').format(event.label, event.owner.name)|n}</span>
354
                     % endif
356
                     % endif
355
 
357
 
356
-                    <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(event.created)|n}">
358
+                    <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(created_localized)|n}">
357
                         ${_('{delta} ago').format(delta=event.created_as_delta)}
359
                         ${_('{delta} ago').format(delta=event.created_as_delta)}
358
 
360
 
359
                         % if h.is_item_still_editable(CFG, event) and event.owner.id==user.id:
361
                         % if h.is_item_still_editable(CFG, event) and event.owner.id==user.id:
374
 </%def>
376
 </%def>
375
 
377
 
376
 <%def name="SECURED_HISTORY_VIRTUAL_EVENT_AS_TABLE_ROW(user, event, current_revision_id)">
378
 <%def name="SECURED_HISTORY_VIRTUAL_EVENT_AS_TABLE_ROW(user, event, current_revision_id)">
379
+    <% created_localized = h.get_with_timezone(event.created) %>
377
     <%
380
     <%
378
         warning_or_not = ('', 'warning')[current_revision_id==event.id]
381
         warning_or_not = ('', 'warning')[current_revision_id==event.id]
379
         row_css = 't-is-new-content' if event.is_new else warning_or_not
382
         row_css = 't-is-new-content' if event.is_new else warning_or_not
382
         <td class="t-less-visible">
385
         <td class="t-less-visible">
383
             <span class="label label-default">${ICON.FA_FW(event.type.icon)} ${event.type.label}</span>
386
             <span class="label label-default">${ICON.FA_FW(event.type.icon)} ${event.type.label}</span>
384
         </td>
387
         </td>
385
-        <td title="${h.date_time(event.created)|n}">${_('{delta} ago').format(delta=event.created_as_delta)}</td>
388
+        <td title="${h.date_time(created_localized)|n}">${_('{delta} ago').format(delta=event.created_as_delta)}</td>
386
         <td>${event.owner.name}</td>
389
         <td>${event.owner.name}</td>
387
 ## FIXME - REMOVE                            <td>${event}</td>
390
 ## FIXME - REMOVE                            <td>${event}</td>
388
 
391
 

+ 2 - 1
tracim/tracim/templates/workspace/getone.mak 파일 보기

44
         </h1>
44
         </h1>
45
 
45
 
46
         <div style="margin: -1.5em auto -1.5em auto;" class="t-less-visible">
46
         <div style="margin: -1.5em auto -1.5em auto;" class="t-less-visible">
47
-          <p>${_('workspace created on {date} at {time}').format(date=h.date(result.workspace.created), time=h.time(result.workspace.created))|n}</p>
47
+            <% created_localized = h.get_with_timezone(result.workspace.created) %>
48
+          <p>${_('workspace created on {date} at {time}').format(date=h.date(created_localized), time=h.time(created_localized))|n}</p>
48
         </div>
49
         </div>
49
     </div>
50
     </div>
50
 </div>
51
 </div>

+ 3 - 2
tracim/tracim/tests/__init__.py 파일 보기

51
         try:
51
         try:
52
             super()._check_status(status, res)
52
             super()._check_status(status, res)
53
         except AppError as exc:
53
         except AppError as exc:
54
-            dump_file_path = "/tmp/debug_%d_%s.html" % (time.time() * 1000, res.request.path_qs[1:])
54
+            escaped_page_name = res.request.path_qs[1:].replace('/', '')
55
+            dump_file_path = "/tmp/debug_%d_%s.html" % (time.time() * 1000, escaped_page_name)
55
             if os.path.exists("/tmp"):
56
             if os.path.exists("/tmp"):
56
                 with open(dump_file_path, 'w') as dump_file:
57
                 with open(dump_file_path, 'w') as dump_file:
57
                     print(res.ubody, file=dump_file)
58
                     print(res.ubody, file=dump_file)
354
         return thread
355
         return thread
355
 
356
 
356
 
357
 
357
-class TestCalendar(TestController):
358
+class TestCalendar(TracimTestController):
358
     fixtures = [BaseFixture, TestFixture]
359
     fixtures = [BaseFixture, TestFixture]
359
     application_under_test = 'radicale'
360
     application_under_test = 'radicale'
360
 
361
 

+ 109 - 0
tracim/tracim/tests/functional/test_calendar.py 파일 보기

1
+import os
1
 import time
2
 import time
2
 
3
 
3
 import caldav
4
 import caldav
4
 import transaction
5
 import transaction
5
 from caldav.lib.error import AuthorizationError
6
 from caldav.lib.error import AuthorizationError
7
+from collections import OrderedDict
6
 from nose.tools import eq_
8
 from nose.tools import eq_
7
 from nose.tools import ok_
9
 from nose.tools import ok_
8
 from nose.tools import raises
10
 from nose.tools import raises
9
 import requests
11
 import requests
10
 from requests.exceptions import ConnectionError
12
 from requests.exceptions import ConnectionError
11
 from sqlalchemy.orm.exc import NoResultFound
13
 from sqlalchemy.orm.exc import NoResultFound
14
+from tg import config
12
 
15
 
13
 from tracim.config.app_cfg import daemons
16
 from tracim.config.app_cfg import daemons
14
 from tracim.lib.calendar import CalendarManager
17
 from tracim.lib.calendar import CalendarManager
18
 from tracim.tests import not_raises
21
 from tracim.tests import not_raises
19
 from tracim.model.auth import User
22
 from tracim.model.auth import User
20
 from tracim.model.data import Content
23
 from tracim.model.data import Content
24
+from tracim.model.data import Workspace
21
 
25
 
22
 
26
 
23
 class TestCalendar(BaseTestCalendar):
27
 class TestCalendar(BaseTestCalendar):
203
         eq_(event.properties['location'], 'Here')
207
         eq_(event.properties['location'], 'Here')
204
         eq_(event.properties['start'], '2010-05-12 18:00:00+0000')
208
         eq_(event.properties['start'], '2010-05-12 18:00:00+0000')
205
         eq_(event.properties['end'], '2010-05-12 17:00:00+0000')
209
         eq_(event.properties['end'], '2010-05-12 17:00:00+0000')
210
+
211
+    def test_created_user_radicale_calendar(self):
212
+        self._connect_user(
213
+            'admin@admin.admin',
214
+            'admin@admin.admin',
215
+        )
216
+
217
+        user_count = DBSession.query(User)\
218
+            .filter(User.email == 'an-other-email@test.local').count()
219
+        eq_(0, user_count, 'User should not exist yet')
220
+
221
+        radicale_users_folder = '{0}/user'\
222
+            .format(config.get('radicale.server.filesystem.folder'))
223
+        eq_(
224
+            False,
225
+            os.path.isdir(radicale_users_folder),
226
+            'Radicale users folder should not exist yet',
227
+        )
228
+
229
+        # Create a new user, his calendar should be created to
230
+        try_post_user = self.app.post(
231
+            '/admin/users',
232
+            OrderedDict([
233
+                ('name', 'TEST'),
234
+                ('email', 'an-other-email@test.local'),
235
+                ('password', 'an-other-email@test.local'),
236
+                ('is_tracim_manager', 'off'),
237
+                ('is_tracim_admin', 'off'),
238
+                ('send_email', 'off'),
239
+            ])
240
+        )
241
+
242
+        eq_(try_post_user.status_code, 302,
243
+            "Code should be 302, but is %d" % try_post_user.status_code)
244
+
245
+        users_calendars = len([
246
+            name for name in os.listdir(radicale_users_folder)
247
+            if name.endswith('.ics')
248
+        ])
249
+
250
+        user = DBSession.query(User) \
251
+            .filter(User.email == 'an-other-email@test.local').one()
252
+
253
+        eq_(1, users_calendars, 'Radicale user path should list 1 calendar')
254
+        user_calendar = '{0}/{1}.ics'.format(
255
+            radicale_users_folder,
256
+            user.user_id,
257
+        )
258
+        user_calendar_exist = os.path.isfile(user_calendar)
259
+        eq_(True, user_calendar_exist, 'User calendar should be created')
260
+
261
+    def test_created_workspace_radicale_calendar(self):
262
+        self._connect_user(
263
+            'admin@admin.admin',
264
+            'admin@admin.admin',
265
+        )
266
+
267
+        workspaces_count = DBSession.query(Workspace)\
268
+            .filter(Workspace.label == 'WTESTCAL').count()
269
+        eq_(0, workspaces_count, 'Workspace should not exist yet !')
270
+
271
+        radicale_workspaces_folder = '{0}/workspace'\
272
+            .format(config.get('radicale.server.filesystem.folder'))
273
+        eq_(
274
+            False,
275
+            os.path.isdir(radicale_workspaces_folder),
276
+            'Radicale workskpaces folder should not exist yet',
277
+        )
278
+
279
+        # Create a new workspace, his calendar should be created to
280
+        try_post_workspace = self.app.post(
281
+            '/admin/workspaces',
282
+            OrderedDict([
283
+                ('name', 'WTESTCAL'),
284
+                ('description', 'WTESTCALDESCR'),
285
+                ('calendar_enabled', 'on'),
286
+            ])
287
+        )
288
+
289
+        eq_(try_post_workspace.status_code, 302,
290
+            "Code should be 302, but is %d" % try_post_workspace.status_code)
291
+
292
+        workspaces_calendars = len([
293
+            name for name in os.listdir(radicale_workspaces_folder)
294
+            if name.endswith('.ics')
295
+        ])
296
+
297
+        workspace = DBSession.query(Workspace) \
298
+            .filter(Workspace.label == 'WTESTCAL').one()
299
+
300
+        eq_(
301
+            1,
302
+            workspaces_calendars,
303
+            'Radicale workspace path should list 1 calendar',
304
+        )
305
+        workspace_calendar = '{0}/{1}.ics'.format(
306
+            radicale_workspaces_folder,
307
+            workspace.workspace_id,
308
+        )
309
+        workspace_calendar_exist = os.path.isfile(workspace_calendar)
310
+        eq_(
311
+            True,
312
+            workspace_calendar_exist,
313
+            'Workspace calendar should be created',
314
+        )

+ 1 - 0
tracim/tracim/tests/functional/test_ldap_restrictions.py 파일 보기

68
             OrderedDict([
68
             OrderedDict([
69
                 ('name', 'Lawrence Lessig YEAH'),
69
                 ('name', 'Lawrence Lessig YEAH'),
70
                 ('email', 'An-other-email@fsf.org'),
70
                 ('email', 'An-other-email@fsf.org'),
71
+                ('timezone', ''),
71
             ])
72
             ])
72
         )
73
         )
73
 
74
 

+ 21 - 8
tracim/tracim/tests/library/test_helpers.py 파일 보기

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 
2
 
3
 import datetime
3
 import datetime
4
+from unittest.mock import MagicMock
4
 
5
 
6
+import pytz
7
+from babel.dates import get_timezone
5
 from nose.tools import eq_
8
 from nose.tools import eq_
6
-from nose.tools import ok_
9
+from tg.request_local import TurboGearsContextMember
10
+from tg.util.webtest import test_context
11
+from tg import tmpl_context
7
 
12
 
8
 import tracim.lib.helpers as h
13
 import tracim.lib.helpers as h
9
 from tracim.config.app_cfg import CFG
14
 from tracim.config.app_cfg import CFG
10
-from tracim.model.data import Content
11
-from tracim.model.data import ContentType
12
-from tracim.model.data import Workspace
13
-
14
-from tracim.model.serializers import Context
15
-from tracim.model.serializers import CTX
16
 from tracim.model.serializers import DictLikeClass
15
 from tracim.model.serializers import DictLikeClass
17
-
18
 from tracim.tests import TestStandard
16
 from tracim.tests import TestStandard
19
 
17
 
20
 
18
 
45
         config.DATA_UPDATE_ALLOWED_DURATION = 8
43
         config.DATA_UPDATE_ALLOWED_DURATION = 8
46
         item.created = datetime.datetime.now() - datetime.timedelta(0, 10)
44
         item.created = datetime.datetime.now() - datetime.timedelta(0, 10)
47
         eq_(False, h.is_item_still_editable(config, item))
45
         eq_(False, h.is_item_still_editable(config, item))
46
+
47
+    def test_unit__change_datetime_timezone__ok__with_naive_and_current_user(self):  # nopep8
48
+        user_mock = MagicMock(timezone='America/Guadeloupe')
49
+
50
+        with test_context(self.app):
51
+            tmpl_context.current_user = user_mock
52
+            naive_datetime = datetime.datetime(2000, 1, 1, 0, 0, 0)
53
+
54
+            new_datetime = h.get_with_timezone(
55
+                datetime_object=naive_datetime,
56
+                default_from_timezone='UTC',
57
+                to_timezone='',  # user_mock.timezone should be used
58
+            )
59
+
60
+            eq_(str(new_datetime), '1999-12-31 20:00:00-04:00')