File: __init__.py

package info (click to toggle)
django-webtest 1.9.13-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 268 kB
  • sloc: python: 1,115; sh: 16; makefile: 8
file content (368 lines) | stat: -rw-r--r-- 14,252 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# -*- coding: utf-8 -*-
import copy

from django.conf import settings
from django.test.signals import template_rendered
from django.core.handlers.wsgi import WSGIHandler
from django.test import TestCase, TransactionTestCase
from django.test.client import store_rendered_templates

from functools import partial

try:
    from importlib import import_module
except ImportError:
    from django.utils.importlib import import_module

from django.core import signals
try:
    from django.db import close_old_connections
except ImportError:
    from django.db import close_connection
    close_old_connections = None
try:
    from django.core.servers.basehttp import (
            AdminMediaHandler as StaticFilesHandler)
except ImportError:
    from django.contrib.staticfiles.handlers import StaticFilesHandler

from webtest import TestApp
try:
    from webtest.utils import NoDefault
except ImportError:
    NoDefault = ''

from django_webtest.response import DjangoWebtestResponse
from django_webtest.compat import to_string, to_wsgi_safe_string


# sentinel to differentiate user=None / user param not given
_notgiven = object()


class DjangoTestApp(TestApp):
    response_class = DjangoWebtestResponse

    def __init__(self, *args, **kwargs):
        extra_environ = (kwargs.get('extra_environ') or {}).copy()
        extra_environ.setdefault('HTTP_HOST', 'testserver')
        kwargs['extra_environ'] = extra_environ
        super(DjangoTestApp, self).__init__(self.get_wsgi_handler(), *args, **kwargs)

    def get_wsgi_handler(self):
        return StaticFilesHandler(WSGIHandler())

    def set_user(self, user):
        """Update the user used by the app globall; pass None to unset."""
        if user is None and 'WEBTEST_USER' in self.extra_environ:
            del self.extra_environ['WEBTEST_USER']
        if user is not None:
            self.extra_environ = self._update_environ(self.extra_environ, user)

    def _update_environ(self, environ, user=_notgiven):
        environ = environ or {}

        if user is not _notgiven:
            if user is None:
                # We can't just delete the key here, the test request is built
                # from self.extra_environ + this environ, so the header defined
                # by set_user will be found in self.extra_environ.
                environ['WEBTEST_USER'] = ''
            else:
                username = _get_username(user)
                environ['WEBTEST_USER'] = to_wsgi_safe_string(username)

        return environ

    def do_request(self, req, status, expect_errors):
        # Django closes the database connection after every request;
        # this breaks the use of transactions in your tests.
        if close_old_connections is not None:  # Django 1.6+
            signals.request_started.disconnect(close_old_connections)
            signals.request_finished.disconnect(close_old_connections)
        else:  # Django < 1.6
            signals.request_finished.disconnect(close_connection)

        try:
            req.environ.setdefault('REMOTE_ADDR', '127.0.0.1')

            # is this a workaround for
            # https://code.djangoproject.com/ticket/11111 ?
            req.environ['REMOTE_ADDR'] = to_string(req.environ['REMOTE_ADDR'])
            req.environ['PATH_INFO'] = to_string(req.environ['PATH_INFO'])

            # Curry a data dictionary into an instance of the template renderer
            # callback function.
            data = {}
            on_template_render = partial(store_rendered_templates, data)
            template_rendered.connect(on_template_render)

            response = super(DjangoTestApp, self).do_request(req, status,
                                                             expect_errors)

            # Add any rendered template detail to the response.
            # If there was only one template rendered (the most likely case),
            # flatten the list to a single element.
            def flattend(detail):
                if len(data[detail]) == 1:
                    return data[detail][0]
                return data[detail]

            response.context = None
            response.template = None
            response.templates = data.get('templates', None)

            if data.get('context'):
                response.context = flattend('context')

            if data.get('template'):
                response.template = flattend('template')
            elif data.get('templates'):
                response.template = flattend('templates')

            response.__class__ = self.response_class
            return response
        finally:
            if close_old_connections:  # Django 1.6+
                signals.request_started.connect(close_old_connections)
                signals.request_finished.connect(close_old_connections)
            else:  # Django < 1.6
                signals.request_finished.connect(close_connection)

    def get(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        auto_follow = kwargs.pop('auto_follow', False)

        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        response = super(DjangoTestApp, self).get(url, *args, **kwargs)

        def is_redirect(r):
            return r.status_int >= 300 and r.status_int < 400
        while auto_follow and is_redirect(response):
            response = response.follow(**kwargs)

        return response

    def post(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).post(url, *args, **kwargs)

    def put(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).put(url, *args, **kwargs)

    def patch(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).patch(url, *args, **kwargs)

    def head(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).head(url, *args, **kwargs)

    def options(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).options(url, *args, **kwargs)

    def delete(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).delete(url, *args, **kwargs)

    def post_json(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).post_json(url, *args, **kwargs)

    def put_json(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).put_json(url, *args, **kwargs)

    def patch_json(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).patch_json(url, *args, **kwargs)

    def delete_json(self, url, *args, **kwargs):
        extra_environ = kwargs.get('extra_environ')
        user = kwargs.pop('user', _notgiven)
        kwargs['extra_environ'] = self._update_environ(extra_environ, user)
        return super(DjangoTestApp, self).delete_json(url, *args, **kwargs)

    @property
    def session(self):
        """
        Obtains the current session variables.
        """
        engine = import_module(settings.SESSION_ENGINE)
        cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
        if cookie:
            return engine.SessionStore(cookie)
        else:
            return {}

    def set_cookie(self, *args, **kwargs):
        self.extra_environ = self._update_environ(self.extra_environ)
        return super(DjangoTestApp, self).set_cookie(*args, **kwargs)


class WebTestMixin(object):
    extra_environ = {}
    csrf_checks = True
    setup_auth = True
    app_class = DjangoTestApp

    def _patch_settings(self):
        '''
        Patches settings to add support for django-webtest authorization
        and (optional) to disable CSRF checks.
        '''
        self._DEBUG_PROPAGATE_EXCEPTIONS = settings.DEBUG_PROPAGATE_EXCEPTIONS
        self._MIDDLEWARE = self.settings_middleware[:]
        self._AUTHENTICATION_BACKENDS = settings.AUTHENTICATION_BACKENDS[:]
        self._REST_FRAMEWORK = getattr(
            settings, 'REST_FRAMEWORK', {'DEFAULT_AUTHENTICATION_CLASSES': []})

        self.settings_middleware = list(self.settings_middleware)
        settings.AUTHENTICATION_BACKENDS = list(
            settings.AUTHENTICATION_BACKENDS)
        settings.REST_FRAMEWORK = copy.deepcopy(self._REST_FRAMEWORK)
        settings.DEBUG_PROPAGATE_EXCEPTIONS = True

        if not self.csrf_checks:
            self._disable_csrf_checks()

        if self.setup_auth:
            self._setup_auth()

    def _unpatch_settings(self):
        ''' Restores settings to before-patching state '''
        self.settings_middleware = self._MIDDLEWARE
        settings.AUTHENTICATION_BACKENDS = self._AUTHENTICATION_BACKENDS
        settings.DEBUG_PROPAGATE_EXCEPTIONS = self._DEBUG_PROPAGATE_EXCEPTIONS
        settings.REST_FRAMEWORK = self._REST_FRAMEWORK

    def _setup_auth(self):
        ''' Setups django-webtest authorization '''
        self._setup_auth_middleware()
        self._setup_auth_backend()
        self._setup_auth_class()

    def _disable_csrf_checks(self):
        disable_csrf_middleware = (
            'django_webtest.middleware.DisableCSRFCheckMiddleware')
        if disable_csrf_middleware not in self.settings_middleware:
            self.settings_middleware.insert(0, disable_csrf_middleware)

    def _setup_auth_middleware(self):
        webtest_auth_middleware = (
            'django_webtest.middleware.WebtestUserMiddleware')
        django_auth_middleware = (
            'django.contrib.auth.middleware.AuthenticationMiddleware')

        if django_auth_middleware not in self.settings_middleware:
            # There can be a custom AuthenticationMiddleware subclass or
            # replacement, we can't compute its index so just put our auth
            # middleware to the end.  If appending causes problems
            # _setup_auth_middleware method can be overriden by a subclass.
            self.settings_middleware.append(webtest_auth_middleware)
        elif webtest_auth_middleware not in self.settings_middleware:
            index = self.settings_middleware.index(django_auth_middleware)
            self.settings_middleware.insert(index + 1, webtest_auth_middleware)

    def _setup_auth_backend(self):
        backend_name = getattr(
            settings, 'WEBTEST_AUTHENTICATION_BACKEND',
            'django_webtest.backends.WebtestUserBackend')
        settings.AUTHENTICATION_BACKENDS.insert(0, backend_name)

    def _setup_auth_class(self):
        class_name = 'django_webtest.rest_framework_auth.WebtestAuthentication'
        drf_settings = settings.REST_FRAMEWORK
        try:
            classes = drf_settings['DEFAULT_AUTHENTICATION_CLASSES']
        except KeyError:
            classes = []
        if class_name not in classes:
            if isinstance(classes, tuple):
                classes = list(classes)
            classes.append(class_name)
            drf_settings['DEFAULT_AUTHENTICATION_CLASSES'] = classes

    @property
    def middleware_setting_name(self):
        try:
            return self._middleware_setting_name
        except AttributeError:
            if getattr(settings, 'MIDDLEWARE', None) is not None:
                name = 'MIDDLEWARE'
            else:
                name = 'MIDDLEWARE_CLASSES'
            self._middleware_setting_name = name
            return name

    @property
    def settings_middleware(self):
        return getattr(settings, self.middleware_setting_name)

    @settings_middleware.setter
    def settings_middleware(self, value):
        setattr(settings, self.middleware_setting_name, value)

    def renew_app(self):
        """
        Resets self.app (drops the stored state): cookies, etc.
        Note: this renews only self.app, not the responses fetched by self.app.
        """
        self.app = self.app_class(extra_environ=self.extra_environ)

    def __call__(self, result=None):
        self._patch_settings()
        self.renew_app()
        res = super(WebTestMixin, self).__call__(result)
        self._unpatch_settings()
        return res


class WebTest(WebTestMixin, TestCase):
    pass


class TransactionWebTest(WebTestMixin, TransactionTestCase):
    pass


def _get_username(user):
    """
    Return user's username. ``user`` can be standard Django User
    instance, a custom user model or just an username (as string).
    """
    value = None
    # custom user, django 1.5+
    get_username = getattr(user, 'get_username', None)
    if get_username is not None:
        value = get_username()
    if value is None:
        # standard User
        username = getattr(user, 'username', None)
        if username is not None:
            value = username
        else:
            # assume user is just an username
            value = user
    return value