2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-27 13:32:49 +04:00
|
|
|
The `compat` module provides support for backwards compatibility with older
|
2012-11-14 22:36:29 +04:00
|
|
|
versions of django/python, and compatibility wrappers around optional packages.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2013-06-27 00:18:13 +04:00
|
|
|
|
2012-10-27 13:32:49 +04:00
|
|
|
# flake8: noqa
|
2012-11-22 03:20:49 +04:00
|
|
|
from __future__ import unicode_literals
|
2014-10-01 16:09:14 +04:00
|
|
|
|
2014-12-04 04:50:25 +03:00
|
|
|
import inspect
|
|
|
|
|
2013-05-01 01:24:20 +04:00
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2014-12-04 05:11:42 +03:00
|
|
|
from django.utils.encoding import force_text
|
2014-12-04 17:47:42 +03:00
|
|
|
from django.utils.six.moves.urllib import parse as urlparse
|
2013-06-28 20:17:39 +04:00
|
|
|
from django.conf import settings
|
2014-08-19 20:06:55 +04:00
|
|
|
from django.utils import six
|
2014-10-01 16:09:14 +04:00
|
|
|
import django
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
2014-12-15 14:55:17 +03:00
|
|
|
def unicode_repr(instance):
|
|
|
|
# Get the repr of an instance, but ensure it is a unicode string
|
|
|
|
# on both python 3 (already the case) and 2 (not the case).
|
|
|
|
if six.PY2:
|
|
|
|
repr(instance).decode('utf-8')
|
|
|
|
return repr(instance)
|
|
|
|
|
|
|
|
|
|
|
|
def unicode_to_repr(value):
|
|
|
|
# Coerce a unicode string to the correct repr return type, depending on
|
|
|
|
# the Python version. We wrap all our `__repr__` implementations with
|
|
|
|
# this and then use unicode throughout internally.
|
|
|
|
if six.PY2:
|
|
|
|
return value.encode('utf-8')
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
2014-11-06 15:00:30 +03:00
|
|
|
# OrderedDict only available in Python 2.7.
|
|
|
|
# This will always be the case in Django 1.7 and above, as these versions
|
|
|
|
# no longer support Python 2.6.
|
|
|
|
# For Django <= 1.6 and Python 2.6 fall back to OrderedDict.
|
|
|
|
try:
|
|
|
|
from collections import OrderedDict
|
2014-12-04 05:11:42 +03:00
|
|
|
except ImportError:
|
2014-11-06 15:00:30 +03:00
|
|
|
from django.utils.datastructures import SortedDict as OrderedDict
|
|
|
|
|
|
|
|
|
2013-06-22 01:42:04 +04:00
|
|
|
# HttpResponseBase only exists from 1.5 onwards
|
|
|
|
try:
|
|
|
|
from django.http.response import HttpResponseBase
|
|
|
|
except ImportError:
|
|
|
|
from django.http import HttpResponse as HttpResponseBase
|
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
2014-12-28 15:02:52 +03:00
|
|
|
# request only provides `resolver_match` from 1.5 onwards.
|
|
|
|
def get_resolver_match(request):
|
|
|
|
try:
|
|
|
|
return request.resolver_match
|
|
|
|
except AttributeError:
|
|
|
|
# Django < 1.5
|
|
|
|
from django.core.urlresolvers import resolve
|
|
|
|
return resolve(request.path_info)
|
|
|
|
|
|
|
|
|
2012-11-08 01:07:24 +04:00
|
|
|
# django-filter is optional
|
|
|
|
try:
|
|
|
|
import django_filters
|
2013-02-06 17:05:17 +04:00
|
|
|
except ImportError:
|
2012-11-08 01:07:24 +04:00
|
|
|
django_filters = None
|
|
|
|
|
2014-09-10 16:52:16 +04:00
|
|
|
if django.VERSION >= (1, 6):
|
|
|
|
def clean_manytomany_helptext(text):
|
|
|
|
return text
|
|
|
|
else:
|
|
|
|
# Up to version 1.5 many to many fields automatically suffix
|
|
|
|
# the `help_text` attribute with hardcoded text.
|
|
|
|
def clean_manytomany_helptext(text):
|
|
|
|
if text.endswith(' Hold down "Control", or "Command" on a Mac, to select more than one.'):
|
|
|
|
text = text[:-69]
|
|
|
|
return text
|
|
|
|
|
2014-07-28 09:37:30 +04:00
|
|
|
# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS
|
|
|
|
# Fixes (#1712). We keep the try/except for the test suite.
|
|
|
|
guardian = None
|
|
|
|
if 'guardian' in settings.INSTALLED_APPS:
|
|
|
|
try:
|
|
|
|
import guardian
|
|
|
|
import guardian.shortcuts # Fixes #1624
|
|
|
|
except ImportError:
|
|
|
|
pass
|
2013-09-06 20:01:31 +04:00
|
|
|
|
2012-11-08 01:07:24 +04:00
|
|
|
|
2013-09-23 19:48:25 +04:00
|
|
|
def get_model_name(model_cls):
|
|
|
|
try:
|
|
|
|
return model_cls._meta.model_name
|
|
|
|
except AttributeError:
|
|
|
|
# < 1.6 used module_name instead of model_name
|
|
|
|
return model_cls._meta.module_name
|
|
|
|
|
|
|
|
|
2014-09-10 19:57:22 +04:00
|
|
|
# View._allowed_methods only present from 1.5 onwards
|
|
|
|
if django.VERSION >= (1, 5):
|
|
|
|
from django.views.generic import View
|
|
|
|
else:
|
|
|
|
from django.views.generic import View as DjangoView
|
|
|
|
|
|
|
|
class View(DjangoView):
|
|
|
|
def _allowed_methods(self):
|
|
|
|
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
|
|
|
|
|
|
|
|
|
2014-10-09 18:11:19 +04:00
|
|
|
# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
|
2014-09-22 16:57:45 +04:00
|
|
|
if django.VERSION >= (1, 8):
|
|
|
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
2014-10-09 18:11:19 +04:00
|
|
|
from django.core.validators import MinLengthValidator, MaxLengthValidator
|
2014-09-22 16:57:45 +04:00
|
|
|
else:
|
|
|
|
from django.core.validators import MinValueValidator as DjangoMinValueValidator
|
|
|
|
from django.core.validators import MaxValueValidator as DjangoMaxValueValidator
|
2014-10-09 18:11:19 +04:00
|
|
|
from django.core.validators import MinLengthValidator as DjangoMinLengthValidator
|
|
|
|
from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator
|
2014-09-22 16:57:45 +04:00
|
|
|
|
|
|
|
class MinValueValidator(DjangoMinValueValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.message = kwargs.pop('message', self.message)
|
|
|
|
super(MinValueValidator, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
class MaxValueValidator(DjangoMaxValueValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.message = kwargs.pop('message', self.message)
|
|
|
|
super(MaxValueValidator, self).__init__(*args, **kwargs)
|
|
|
|
|
2014-10-09 18:11:19 +04:00
|
|
|
class MinLengthValidator(DjangoMinLengthValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.message = kwargs.pop('message', self.message)
|
|
|
|
super(MinLengthValidator, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
class MaxLengthValidator(DjangoMaxLengthValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.message = kwargs.pop('message', self.message)
|
|
|
|
super(MaxLengthValidator, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
|
2014-10-02 23:41:18 +04:00
|
|
|
# URLValidator only accepts `message` in 1.6+
|
2014-09-22 19:45:06 +04:00
|
|
|
if django.VERSION >= (1, 6):
|
|
|
|
from django.core.validators import URLValidator
|
|
|
|
else:
|
|
|
|
from django.core.validators import URLValidator as DjangoURLValidator
|
|
|
|
|
|
|
|
class URLValidator(DjangoURLValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.message = kwargs.pop('message', self.message)
|
|
|
|
super(URLValidator, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
# EmailValidator requires explicit regex prior to 1.6+
|
|
|
|
if django.VERSION >= (1, 6):
|
|
|
|
from django.core.validators import EmailValidator
|
|
|
|
else:
|
|
|
|
from django.core.validators import EmailValidator as DjangoEmailValidator
|
|
|
|
from django.core.validators import email_re
|
|
|
|
|
|
|
|
class EmailValidator(DjangoEmailValidator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(EmailValidator, self).__init__(email_re, *args, **kwargs)
|
|
|
|
|
2014-09-22 16:57:45 +04:00
|
|
|
|
2013-04-09 21:22:39 +04:00
|
|
|
# PATCH method is not implemented by Django
|
|
|
|
if 'patch' not in View.http_method_names:
|
|
|
|
View.http_method_names = View.http_method_names + ['patch']
|
|
|
|
|
2012-12-16 22:11:59 +04:00
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
# RequestFactory only provides `generic` from 1.5 onwards
|
2013-06-28 20:17:39 +04:00
|
|
|
from django.test.client import RequestFactory as DjangoRequestFactory
|
|
|
|
from django.test.client import FakePayload
|
2014-12-04 04:50:25 +03:00
|
|
|
|
2013-06-28 20:17:39 +04:00
|
|
|
try:
|
|
|
|
# In 1.5 the test client uses force_bytes
|
2014-01-28 18:30:46 +04:00
|
|
|
from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes
|
2013-06-28 20:17:39 +04:00
|
|
|
except ImportError:
|
2013-09-25 13:30:04 +04:00
|
|
|
# In 1.4 the test client just uses smart_str
|
2013-06-28 20:17:39 +04:00
|
|
|
from django.utils.encoding import smart_str as force_bytes_or_smart_bytes
|
|
|
|
|
2014-12-04 04:50:25 +03:00
|
|
|
|
2013-06-28 20:17:39 +04:00
|
|
|
class RequestFactory(DjangoRequestFactory):
|
|
|
|
def generic(self, method, path,
|
|
|
|
data='', content_type='application/octet-stream', **extra):
|
|
|
|
parsed = urlparse.urlparse(path)
|
|
|
|
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
|
|
|
|
r = {
|
2014-12-04 04:50:25 +03:00
|
|
|
'PATH_INFO': self._get_path(parsed),
|
|
|
|
'QUERY_STRING': force_text(parsed[4]),
|
2014-10-01 16:09:14 +04:00
|
|
|
'REQUEST_METHOD': six.text_type(method),
|
2013-06-28 20:17:39 +04:00
|
|
|
}
|
|
|
|
if data:
|
|
|
|
r.update({
|
|
|
|
'CONTENT_LENGTH': len(data),
|
2014-12-04 04:50:25 +03:00
|
|
|
'CONTENT_TYPE': six.text_type(content_type),
|
|
|
|
'wsgi.input': FakePayload(data),
|
2013-06-28 20:17:39 +04:00
|
|
|
})
|
|
|
|
r.update(extra)
|
|
|
|
return self.request(**r)
|
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
# Markdown is optional
|
|
|
|
try:
|
|
|
|
import markdown
|
|
|
|
|
|
|
|
def apply_markdown(text):
|
|
|
|
"""
|
|
|
|
Simple wrapper around :func:`markdown.markdown` to set the base level
|
|
|
|
of '#' style headers to <h2>.
|
|
|
|
"""
|
|
|
|
|
|
|
|
extensions = ['headerid(level=2)']
|
2012-11-09 19:54:23 +04:00
|
|
|
safe_mode = False
|
2012-09-20 16:06:27 +04:00
|
|
|
md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
|
|
|
|
return md.convert(text)
|
|
|
|
except ImportError:
|
|
|
|
apply_markdown = None
|
|
|
|
|
|
|
|
|
|
|
|
# Yaml is optional
|
|
|
|
try:
|
|
|
|
import yaml
|
|
|
|
except ImportError:
|
|
|
|
yaml = None
|
|
|
|
|
|
|
|
|
2013-02-22 17:17:22 +04:00
|
|
|
# XML is optional
|
2012-09-20 16:06:27 +04:00
|
|
|
try:
|
2013-02-22 17:17:22 +04:00
|
|
|
import defusedxml.ElementTree as etree
|
|
|
|
except ImportError:
|
|
|
|
etree = None
|
2013-02-26 14:22:21 +04:00
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
|
|
|
# OAuth2 is optional
|
2013-02-26 14:22:21 +04:00
|
|
|
try:
|
2013-03-07 13:01:53 +04:00
|
|
|
# Note: The `oauth2` package actually provides oauth1.0a support. Urg.
|
2013-02-26 14:22:21 +04:00
|
|
|
import oauth2 as oauth
|
|
|
|
except ImportError:
|
|
|
|
oauth = None
|
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
|
|
|
# OAuthProvider is optional
|
2013-02-26 14:22:21 +04:00
|
|
|
try:
|
|
|
|
import oauth_provider
|
2013-03-07 13:01:53 +04:00
|
|
|
from oauth_provider.store import store as oauth_provider_store
|
2013-12-14 01:57:07 +04:00
|
|
|
|
|
|
|
# check_nonce's calling signature in django-oauth-plus changes sometime
|
|
|
|
# between versions 2.0 and 2.2.1
|
|
|
|
def check_nonce(request, oauth_request, oauth_nonce, oauth_timestamp):
|
|
|
|
check_nonce_args = inspect.getargspec(oauth_provider_store.check_nonce).args
|
|
|
|
if 'timestamp' in check_nonce_args:
|
|
|
|
return oauth_provider_store.check_nonce(
|
|
|
|
request, oauth_request, oauth_nonce, oauth_timestamp
|
|
|
|
)
|
|
|
|
return oauth_provider_store.check_nonce(
|
|
|
|
request, oauth_request, oauth_nonce
|
|
|
|
)
|
|
|
|
|
2013-05-01 01:24:20 +04:00
|
|
|
except (ImportError, ImproperlyConfigured):
|
2013-03-07 13:01:53 +04:00
|
|
|
oauth_provider = None
|
|
|
|
oauth_provider_store = None
|
2013-12-14 01:57:07 +04:00
|
|
|
check_nonce = None
|
2013-03-01 05:08:58 +04:00
|
|
|
|
2013-09-25 13:30:04 +04:00
|
|
|
|
2013-03-01 05:08:58 +04:00
|
|
|
# OAuth 2 support is optional
|
|
|
|
try:
|
2014-03-05 21:15:52 +04:00
|
|
|
import provider as oauth2_provider
|
2013-03-10 17:08:29 +04:00
|
|
|
from provider import scope as oauth2_provider_scope
|
2013-03-12 23:07:30 +04:00
|
|
|
from provider import constants as oauth2_constants
|
2014-12-04 04:50:25 +03:00
|
|
|
|
2014-03-05 21:15:52 +04:00
|
|
|
if oauth2_provider.__version__ in ('0.2.3', '0.2.4'):
|
2013-06-27 00:18:13 +04:00
|
|
|
# 0.2.3 and 0.2.4 are supported version that do not support
|
|
|
|
# timezone aware datetimes
|
2013-06-27 23:29:52 +04:00
|
|
|
import datetime
|
2014-12-04 04:50:25 +03:00
|
|
|
|
2013-06-27 23:29:52 +04:00
|
|
|
provider_now = datetime.datetime.now
|
2013-06-27 00:18:13 +04:00
|
|
|
else:
|
|
|
|
# Any other supported version does use timezone aware datetimes
|
|
|
|
from django.utils.timezone import now as provider_now
|
2013-03-01 05:08:58 +04:00
|
|
|
except ImportError:
|
|
|
|
oauth2_provider = None
|
2013-03-10 17:08:29 +04:00
|
|
|
oauth2_provider_scope = None
|
2013-03-12 23:07:30 +04:00
|
|
|
oauth2_constants = None
|
2013-06-27 00:18:13 +04:00
|
|
|
provider_now = None
|
2013-05-18 20:04:17 +04:00
|
|
|
|
2014-12-04 04:50:25 +03:00
|
|
|
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
2014-10-30 19:53:12 +03:00
|
|
|
# See: http://bugs.python.org/issue22767
|
|
|
|
if six.PY3:
|
|
|
|
SHORT_SEPARATORS = (',', ':')
|
|
|
|
LONG_SEPARATORS = (', ', ': ')
|
|
|
|
else:
|
|
|
|
SHORT_SEPARATORS = (b',', b':')
|
|
|
|
LONG_SEPARATORS = (b', ', b': ')
|