django-rest-framework/rest_framework/compat.py

201 lines
6.7 KiB
Python
Raw Normal View History

"""
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-10-27 13:32:49 +04:00
# flake8: noqa
2012-11-22 03:20:49 +04:00
from __future__ import unicode_literals
import inspect
from django.core.exceptions import ImproperlyConfigured
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
from django.utils import six
import django
# 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
except ImportError:
from django.utils.datastructures import SortedDict as OrderedDict
# 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
# django-filter is optional
try:
import django_filters
except ImportError:
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
# 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
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
2013-09-25 13:30:04 +04:00
# View._allowed_methods only present from 1.5 onwards
2013-04-09 21:22:39 +04:00
if django.VERSION >= (1, 5):
from django.views.generic import View
else:
2013-09-25 13:30:04 +04:00
from django.views.generic import View as DjangoView
class View(DjangoView):
2013-04-09 21:22:39 +04:00
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
if django.VERSION >= (1, 8):
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.validators import MinLengthValidator, MaxLengthValidator
else:
from django.core.validators import MinValueValidator as DjangoMinValueValidator
from django.core.validators import MaxValueValidator as DjangoMaxValueValidator
from django.core.validators import MinLengthValidator as DjangoMinLengthValidator
from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator
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)
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)
# 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)
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']
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
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
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 = {
'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': force_text(parsed[4]),
'REQUEST_METHOD': six.text_type(method),
2013-06-28 20:17:39 +04:00
}
if data:
r.update({
'CONTENT_LENGTH': len(data),
'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
# 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)']
safe_mode = False
md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
return md.convert(text)
except ImportError:
apply_markdown = None
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767
if six.PY3:
SHORT_SEPARATORS = (',', ':')
LONG_SEPARATORS = (', ', ': ')
else:
SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ')