Browse Source

Closes #173: Update code to permit notifications email in wsgidav context

Bastien Sevajol (Algoo) 8 years ago
parent
commit
2a0fb4c6cf

+ 1 - 0
install/requirements.txt View File

@@ -64,3 +64,4 @@ zope.interface==4.1.3
64 64
 zope.sqlalchemy==0.7.6
65 65
 PyYAML
66 66
 redis==2.10.5
67
+typing==3.5.3.0

+ 1 - 1
tracim/tracim/config/app_cfg.py View File

@@ -25,7 +25,7 @@ from tgext.asyncjob.trackers.redisdb import RedisProgressTracker
25 25
 from tgext.pluggable import plug
26 26
 from tgext.pluggable import replace_template
27 27
 
28
-from tg.i18n import lazy_ugettext as l_
28
+from tracim.lib.utils import lazy_ugettext as l_
29 29
 
30 30
 import tracim
31 31
 from tracim import model

+ 2 - 2
tracim/tracim/controllers/__init__.py View File

@@ -4,6 +4,7 @@ from sqlalchemy.orm.exc import NoResultFound
4 4
 from tg import abort
5 5
 from tracim.lib.integrity import PathValidationManager
6 6
 from tracim.lib.integrity import render_invalid_integrity_chosen_path
7
+from tracim.lib.user import CurrentUserGetterApi
7 8
 from tracim.lib.workspace import WorkspaceApi
8 9
 
9 10
 import tg
@@ -28,7 +29,6 @@ from tracim.model.data import ContentType
28 29
 from tracim.model.data import Workspace
29 30
 
30 31
 from tracim.lib.content import ContentApi
31
-from tracim.lib.user import UserStaticApi
32 32
 from tracim.lib.utils import SameValueError
33 33
 
34 34
 from tracim.model.serializers import Context
@@ -38,7 +38,7 @@ class TIMRestPathContextSetup(object):
38 38
 
39 39
     @classmethod
40 40
     def current_user(cls) -> User:
41
-        user = UserStaticApi.get_current_user()
41
+        user = CurrentUserGetterApi.get_current_user()
42 42
         tmpl_context.current_user_id = user.user_id if user else None
43 43
         tmpl_context.current_user = user if user else None
44 44
         return user

+ 1 - 2
tracim/tracim/controllers/admin/user.py View File

@@ -11,7 +11,7 @@ from tw2 import forms as tw2f
11 11
 import tg
12 12
 from tg import predicates
13 13
 from tg import tmpl_context
14
-from tg.i18n import ugettext as _, lazy_ugettext as l_
14
+from tg.i18n import ugettext as _
15 15
 
16 16
 from sprox.widgets import PropertyMultipleSelectField
17 17
 from sprox._compat import unicode_text
@@ -28,7 +28,6 @@ from tracim.lib.base import logger
28 28
 from tracim.lib.email import get_email_manager
29 29
 from tracim.lib.user import UserApi
30 30
 from tracim.lib.group import GroupApi
31
-from tracim.lib.user import UserStaticApi
32 31
 from tracim.lib.userworkspace import RoleApi
33 32
 from tracim.lib.workspace import WorkspaceApi
34 33
 

+ 3 - 3
tracim/tracim/controllers/root.py View File

@@ -1,5 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2
-
2
+import tg
3 3
 from tg import expose
4 4
 from tg import flash
5 5
 from tg import lurl
@@ -15,7 +15,7 @@ from tracim.controllers.api import APIController
15 15
 
16 16
 from tracim.lib import CST
17 17
 from tracim.lib.base import logger
18
-from tracim.lib.user import UserStaticApi
18
+from tracim.lib.user import CurrentUserGetterApi
19 19
 from tracim.lib.content import ContentApi
20 20
 
21 21
 from tracim.controllers import StandardController
@@ -115,7 +115,7 @@ class RootController(StandardController):
115 115
             redirect(url('/login'),
116 116
                 params=dict(came_from=came_from, __logins=login_counter))
117 117
 
118
-        user = UserStaticApi.get_current_user()
118
+        user = CurrentUserGetterApi.get_current_user()
119 119
 
120 120
         flash(_('Welcome back, %s!') % user.get_display_name())
121 121
         redirect(came_from)

+ 1 - 1
tracim/tracim/lib/__init__.py View File

@@ -1,5 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2
-from tg.i18n import lazy_ugettext as l_
2
+
3 3
 
4 4
 class NotFoundError(Exception):
5 5
     pass

+ 1 - 1
tracim/tracim/lib/app_globals.py View File

@@ -3,7 +3,7 @@
3 3
 from markupsafe import escape_silent as escape
4 4
 
5 5
 import tg
6
-from tg.i18n import ugettext as _, lazy_ugettext as l_
6
+from tracim.lib.utils import lazy_ugettext as l_
7 7
 from tg.flash import TGFlash
8 8
 
9 9
 """The application's Globals object"""

+ 19 - 22
tracim/tracim/lib/notifications.py View File

@@ -8,15 +8,12 @@ from lxml.html.diff import htmldiff
8 8
 
9 9
 from mako.template import Template
10 10
 
11
-from tg.i18n import lazy_ugettext as l_
12
-from tg.i18n import ugettext as _
13
-
14 11
 from tracim.lib.base import logger
15 12
 from tracim.lib.email import SmtpConfiguration
16 13
 from tracim.lib.email import EmailSender
17 14
 from tracim.lib.user import UserApi
18 15
 from tracim.lib.workspace import WorkspaceApi
19
-
16
+from tracim.lib.utils import lazy_ugettext as l_
20 17
 from tracim.model.serializers import Context
21 18
 from tracim.model.serializers import CTX
22 19
 from tracim.model.serializers import DictLikeClass
@@ -319,72 +316,72 @@ class EmailNotifier(object):
319 316
 
320 317
         action = content.get_last_action().id
321 318
         if ActionDescription.COMMENT == action:
322
-            content_intro = _('<span id="content-intro-username">{}</span> added a comment:').format(actor.display_name)
319
+            content_intro = l_('<span id="content-intro-username">{}</span> added a comment:').format(actor.display_name)
323 320
             content_text = content.description
324
-            call_to_action_text = _('Answer')
321
+            call_to_action_text = l_('Answer')
325 322
 
326 323
         elif ActionDescription.CREATION == action:
327 324
 
328 325
             # Default values (if not overriden)
329 326
             content_text = content.description
330
-            call_to_action_text = _('View online')
327
+            call_to_action_text = l_('View online')
331 328
 
332 329
             if ContentType.Thread == content.type:
333
-                call_to_action_text = _('Answer')
334
-                content_intro = _('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
330
+                call_to_action_text = l_('Answer')
331
+                content_intro = l_('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
335 332
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
336 333
                                content.get_last_comment_from(actor).description
337 334
 
338 335
             elif ContentType.File == content.type:
339
-                content_intro = _('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
336
+                content_intro = l_('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
340 337
                 if content.description:
341 338
                     content_text = content.description
342 339
                 else:
343 340
                     content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
344 341
 
345 342
             elif ContentType.Page == content.type:
346
-                content_intro = _('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
343
+                content_intro = l_('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
347 344
                 content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
348 345
 
349 346
         elif ActionDescription.REVISION == action:
350 347
             content_text = content.description
351
-            call_to_action_text = _('View online')
348
+            call_to_action_text = l_('View online')
352 349
 
353 350
             if ContentType.File == content.type:
354
-                content_intro = _('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
351
+                content_intro = l_('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
355 352
                 content_text = ''
356 353
 
357 354
             elif ContentType.Page == content.type:
358
-                content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
355
+                content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
359 356
                 previous_revision = content.get_previous_revision()
360 357
                 title_diff = ''
361 358
                 if previous_revision.label != content.label:
362 359
                     title_diff = htmldiff(previous_revision.label, content.label)
363
-                content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
360
+                content_text = l_('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
364 361
                     title_diff + \
365 362
                     htmldiff(previous_revision.description, content.description)
366 363
 
367 364
             elif ContentType.Thread == content.type:
368
-                content_intro = _('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
365
+                content_intro = l_('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
369 366
                 previous_revision = content.get_previous_revision()
370 367
                 title_diff = ''
371 368
                 if previous_revision.label != content.label:
372 369
                     title_diff = htmldiff(previous_revision.label, content.label)
373
-                content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
370
+                content_text = l_('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
374 371
                     title_diff + \
375 372
                     htmldiff(previous_revision.description, content.description)
376 373
 
377 374
             # elif ContentType.Thread == content.type:
378
-            #     content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
375
+            #     content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
379 376
             #     previous_revision = content.get_previous_revision()
380
-            #     content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
377
+            #     content_text = l_('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
381 378
             #         htmldiff(previous_revision.description, content.description)
382 379
 
383 380
         elif ActionDescription.EDITION == action:
384
-            call_to_action_text = _('View online')
381
+            call_to_action_text = l_('View online')
385 382
 
386 383
             if ContentType.File == content.type:
387
-                content_intro = _('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
384
+                content_intro = l_('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
388 385
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
389 386
                     content.description
390 387
 
@@ -404,7 +401,7 @@ class EmailNotifier(object):
404 401
         from tracim.config.app_cfg import CFG
405 402
         body_content = template.render(
406 403
             base_url=self._global_config.WEBSITE_BASE_URL,
407
-            _=_,
404
+            _=l_,
408 405
             h=helpers,
409 406
             user_display_name=role.user.display_name,
410 407
             user_role_label=role.role_as_label(),

+ 1 - 1
tracim/tracim/lib/predicates.py View File

@@ -3,7 +3,7 @@
3 3
 from tg import abort
4 4
 from tg import request
5 5
 from tg import tmpl_context
6
-from tg.i18n import lazy_ugettext as l_
6
+from tracim.lib.utils import lazy_ugettext as l_
7 7
 from tg.i18n import ugettext as _
8 8
 from tg.predicates import Predicate
9 9
 

+ 55 - 17
tracim/tracim/lib/user.py View File

@@ -1,14 +1,16 @@
1 1
 # -*- coding: utf-8 -*-
2
-import transaction
2
+import threading
3 3
 
4
+import cherrypy
5
+import transaction
4 6
 import tg
7
+import typing as typing
5 8
 
6 9
 from tracim.model.auth import User
7
-
8
-from tracim.model import auth as pbma
9 10
 from tracim.model import DBSession
10 11
 
11
-__author__ = 'damien'
12
+CURRENT_USER_WEB = 'WEB'
13
+CURRENT_USER_WSGIDAV = 'WSGIDAV'
12 14
 
13 15
 
14 16
 class UserApi(object):
@@ -103,25 +105,61 @@ class UserApi(object):
103 105
         )
104 106
 
105 107
 
106
-class UserStaticApi(object):
108
+class CurrentUserGetterInterface(object):
109
+    def get_current_user(self) -> typing.Union[None, User]:
110
+        raise NotImplementedError()
107 111
 
108
-    @classmethod
109
-    def get_current_user(cls) -> User:
112
+
113
+class BaseCurrentUserGetter(CurrentUserGetterInterface):
114
+    def __init__(self) -> None:
115
+        self.api = UserApi(None)
116
+
117
+
118
+class WebCurrentUserGetter(BaseCurrentUserGetter):
119
+    def get_current_user(self) -> typing.Union[None, User]:
110 120
         # HACK - D.A. - 2015-09-02
111 121
         # In tests, the tg.request.identity may not be set
112 122
         # (this is a buggy case, but for now this is how the software is;)
113
-        if tg.request != None:
123
+        if tg.request is not None:
114 124
             if hasattr(tg.request, 'identity'):
115
-                if tg.request.identity != None:
116
-                    return cls._get_user(tg.request.identity['repoze.who.userid'])
125
+                if tg.request.identity is not None:
126
+                    return self.api.get_one_by_email(
127
+                        tg.request.identity['repoze.who.userid'],
128
+                    )
129
+
130
+        return None
131
+
132
+
133
+class WsgidavCurrentUserGetter(BaseCurrentUserGetter):
134
+    def get_current_user(self) -> typing.Union[None, User]:
135
+        if hasattr(cherrypy.request, 'current_user_email'):
136
+            return self.api.get_one_by_email(
137
+                cherrypy.request.current_user_email,
138
+            )
117 139
 
118 140
         return None
119 141
 
142
+
143
+class CurrentUserGetterApi(object):
144
+    thread_local = threading.local()
145
+    matches = {
146
+        CURRENT_USER_WEB: WebCurrentUserGetter,
147
+        CURRENT_USER_WSGIDAV: WsgidavCurrentUserGetter,
148
+    }
149
+    default = CURRENT_USER_WEB
150
+
120 151
     @classmethod
121
-    def _get_user(cls, email) -> User:
122
-        """
123
-        Do not use directly in your code.
124
-        :param email:
125
-        :return:
126
-        """
127
-        return pbma.User.by_email_address(email)
152
+    def get_current_user(cls) -> User:
153
+        try:
154
+            return cls.thread_local.getter.get_current_user()
155
+        except AttributeError:
156
+            return cls.factory(cls.default).get_current_user()
157
+
158
+    @classmethod
159
+    def set_thread_local_getter(cls, name) -> None:
160
+        if not hasattr(cls.thread_local, 'getter'):
161
+            cls.thread_local.getter = cls.factory(name)
162
+
163
+    @classmethod
164
+    def factory(cls, name: str) -> CurrentUserGetterInterface:
165
+        return cls.matches[name]()

+ 26 - 0
tracim/tracim/lib/utils.py View File

@@ -9,6 +9,10 @@ from tg import response
9 9
 from tg.controllers.util import abort
10 10
 from tg.appwrappers.errorpage import ErrorPageApplicationWrapper \
11 11
     as BaseErrorPageApplicationWrapper
12
+from tg.i18n import ugettext
13
+from tg.support.registry import StackedObjectProxy
14
+from tg.util import LazyString as BaseLazyString
15
+from tg.util import lazify
12 16
 
13 17
 from tracim.lib.base import logger
14 18
 from webob import Response
@@ -145,3 +149,25 @@ def str_as_bool(string: str) -> bool:
145 149
     if string == '0':
146 150
         return False
147 151
     return bool(string)
152
+
153
+
154
+class LazyString(BaseLazyString):
155
+    pass
156
+
157
+
158
+def _lazy_ugettext(text: str):
159
+    """
160
+    This function test if application context is available
161
+    :param text: String to traduce
162
+    :return: lazyfied string or string
163
+    """
164
+    try:
165
+        # Test if context is available,
166
+        # cf. https://github.com/tracim/tracim/issues/173
167
+        context = StackedObjectProxy(name="context")
168
+        context.translator
169
+        return ugettext(text)
170
+    except TypeError:
171
+        return text
172
+
173
+lazy_ugettext = lazify(_lazy_ugettext)

+ 12 - 1
tracim/tracim/lib/webdav/tracim_http_authenticator.py View File

@@ -1,6 +1,12 @@
1
+import os
2
+import re
3
+
1 4
 from wsgidav.http_authenticator import HTTPAuthenticator
2 5
 from wsgidav import util
3
-import re
6
+import cherrypy
7
+
8
+from tracim.lib.user import CurrentUserGetterApi
9
+from tracim.lib.user import CURRENT_USER_WSGIDAV
4 10
 
5 11
 _logger = util.getModuleLogger(__name__, True)
6 12
 HOTFIX_WINXP_AcceptRootShareLogin = True
@@ -131,6 +137,11 @@ class TracimHTTPAuthenticator(HTTPAuthenticator):
131 137
 
132 138
         environ["http_authenticator.realm"] = realmname
133 139
         environ["http_authenticator.username"] = req_username
140
+
141
+        # Set request current user email to be able to recognise him later
142
+        cherrypy.request.current_user_email = req_username
143
+        CurrentUserGetterApi.set_thread_local_getter(CURRENT_USER_WSGIDAV)
144
+
134 145
         return self._application(environ, start_response)
135 146
 
136 147
     def tracim_compute_digest_response(self, left_digest_response_hash, method, uri, nonce, cnonce, qop, nc):

+ 1 - 1
tracim/tracim/model/auth.py View File

@@ -15,7 +15,7 @@ from datetime import datetime
15 15
 import time
16 16
 from hashlib import sha256
17 17
 from sqlalchemy.ext.hybrid import hybrid_property
18
-from tg.i18n import lazy_ugettext as l_
18
+from tracim.lib.utils import lazy_ugettext as l_
19 19
 from hashlib import md5
20 20
 
21 21
 __all__ = ['User', 'Group', 'Permission']

+ 1 - 2
tracim/tracim/model/data.py View File

@@ -8,7 +8,6 @@ from datetime import datetime
8 8
 import tg
9 9
 from babel.dates import format_timedelta
10 10
 from bs4 import BeautifulSoup
11
-from slugify import slugify
12 11
 from sqlalchemy import Column, inspect, Index
13 12
 from sqlalchemy import ForeignKey
14 13
 from sqlalchemy import Sequence
@@ -25,8 +24,8 @@ from sqlalchemy.types import Integer
25 24
 from sqlalchemy.types import LargeBinary
26 25
 from sqlalchemy.types import Text
27 26
 from sqlalchemy.types import Unicode
28
-from tg.i18n import lazy_ugettext as l_, ugettext as _
29 27
 
28
+from tracim.lib.utils import lazy_ugettext as l_
30 29
 from tracim.lib.exception import ContentRevisionUpdateError
31 30
 from tracim.model import DeclarativeBase, RevisionsIntegrity
32 31
 from tracim.model.auth import User

+ 11 - 4
tracim/tracim/model/serializers.py View File

@@ -1,4 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2
+import cherrypy
3
+import os
4
+
2 5
 import types
3 6
 
4 7
 from bs4 import BeautifulSoup
@@ -10,8 +13,7 @@ import tg
10 13
 from tg.i18n import ugettext as _
11 14
 from tg.util import LazyString
12 15
 from tracim.lib.base import logger
13
-from tracim.lib.user import UserStaticApi
14
-from tracim.lib.utils import exec_time_monitor
16
+from tracim.lib.user import CurrentUserGetterApi
15 17
 from tracim.model.auth import Profile
16 18
 from tracim.model.auth import User
17 19
 from tracim.model.data import BreadcrumbItem, ActionDescription
@@ -154,12 +156,17 @@ class Context(object):
154 156
         self.context_string = context_string
155 157
         self._current_user = current_user  # Allow to define the current user if any
156 158
         if not current_user:
157
-            self._current_user = UserStaticApi.get_current_user()
159
+            self._current_user = CurrentUserGetterApi.get_current_user()
158 160
 
159 161
         self._base_url = base_url # real root url like http://mydomain.com:8080
160 162
 
161 163
     def url(self, base_url='/', params=None, qualified=False) -> str:
162
-        url = tg.url(base_url, params)
164
+        # HACK (REF WSGIDAV.CONTEXT.TG.URL) This is a temporary hack who
165
+        # permit to know we are in WSGIDAV context.
166
+        if not hasattr(cherrypy.request, 'current_user_email'):
167
+            url = tg.url(base_url, params)
168
+        else:
169
+            url = base_url
163 170
 
164 171
         if self._base_url:
165 172
             url = '{}{}'.format(self._base_url, url)