mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-23 18:13:57 +03:00
258 lines
8.0 KiB
Python
258 lines
8.0 KiB
Python
"""
|
|
The `compat` module provides support for backwards compatibility with older
|
|
versions of Django/Python, and compatibility wrappers around optional packages.
|
|
"""
|
|
|
|
# flake8: noqa
|
|
from __future__ import unicode_literals
|
|
|
|
import django
|
|
from django.conf import settings
|
|
from django.db import connection, transaction
|
|
from django.utils import six
|
|
from django.views.generic import View
|
|
|
|
try:
|
|
import importlib # Available in Python 3.1+
|
|
except ImportError:
|
|
from django.utils import importlib # Will be removed in Django 1.9
|
|
|
|
|
|
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:
|
|
return 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
|
|
|
|
|
|
def unicode_http_header(value):
|
|
# Coerce HTTP header value to unicode.
|
|
if isinstance(value, six.binary_type):
|
|
return value.decode('iso-8859-1')
|
|
return value
|
|
|
|
|
|
def total_seconds(timedelta):
|
|
# TimeDelta.total_seconds() is only available in Python 2.7
|
|
if hasattr(timedelta, 'total_seconds'):
|
|
return timedelta.total_seconds()
|
|
else:
|
|
return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0)
|
|
|
|
|
|
def distinct(queryset, base):
|
|
if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
|
|
# distinct analogue for Oracle users
|
|
return base.filter(pk__in=set(queryset.values_list('pk', flat=True)))
|
|
return queryset.distinct()
|
|
|
|
|
|
# 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 SortedDict.
|
|
try:
|
|
from collections import OrderedDict
|
|
except ImportError:
|
|
from django.utils.datastructures import SortedDict as OrderedDict
|
|
|
|
|
|
# unittest.SkipUnless only available in Python 2.7.
|
|
try:
|
|
import unittest
|
|
unittest.skipUnless
|
|
except (ImportError, AttributeError):
|
|
from django.utils import unittest
|
|
|
|
|
|
# contrib.postgres only supported from 1.8 onwards.
|
|
try:
|
|
from django.contrib.postgres import fields as postgres_fields
|
|
except ImportError:
|
|
postgres_fields = None
|
|
|
|
|
|
# Apps only exists from 1.7 onwards.
|
|
try:
|
|
from django.apps import apps
|
|
get_model = apps.get_model
|
|
except ImportError:
|
|
from django.db.models import get_model
|
|
|
|
|
|
# Import path changes from 1.7 onwards.
|
|
try:
|
|
from django.contrib.contenttypes.fields import (
|
|
GenericForeignKey, GenericRelation
|
|
)
|
|
except ImportError:
|
|
from django.contrib.contenttypes.generic import (
|
|
GenericForeignKey, GenericRelation
|
|
)
|
|
|
|
# django-filter is optional
|
|
try:
|
|
import django_filters
|
|
except ImportError:
|
|
django_filters = None
|
|
|
|
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
|
|
|
|
|
|
# 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+
|
|
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)
|
|
|
|
|
|
# PATCH method is not implemented by Django
|
|
if 'patch' not in View.http_method_names:
|
|
View.http_method_names = View.http_method_names + ['patch']
|
|
|
|
|
|
# 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 = (', ', ': ')
|
|
INDENT_SEPARATORS = (',', ': ')
|
|
else:
|
|
SHORT_SEPARATORS = (b',', b':')
|
|
LONG_SEPARATORS = (b', ', b': ')
|
|
INDENT_SEPARATORS = (b',', b': ')
|
|
|
|
if django.VERSION >= (1, 8):
|
|
from django.db.models import DurationField
|
|
from django.utils.dateparse import parse_duration
|
|
from django.utils.duration import duration_string
|
|
else:
|
|
DurationField = duration_string = parse_duration = None
|
|
|
|
|
|
def set_rollback():
|
|
if hasattr(transaction, 'set_rollback'):
|
|
if connection.settings_dict.get('ATOMIC_REQUESTS', False):
|
|
# If running in >=1.6 then mark a rollback as required,
|
|
# and allow it to be handled by Django.
|
|
if connection.in_atomic_block:
|
|
transaction.set_rollback(True)
|
|
elif transaction.is_managed():
|
|
# Otherwise handle it explicitly if in managed mode.
|
|
if transaction.is_dirty():
|
|
transaction.rollback()
|
|
transaction.leave_transaction_management()
|
|
else:
|
|
# transaction not managed
|
|
pass
|