""" 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 inspect import django from django.apps import apps from django.conf import settings from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import \ MaxLengthValidator as DjangoMaxLengthValidator from django.core.validators import MaxValueValidator as DjangoMaxValueValidator from django.core.validators import \ MinLengthValidator as DjangoMinLengthValidator from django.core.validators import MinValueValidator as DjangoMinValueValidator from django.db import connection, models, transaction from django.template import Context, RequestContext, Template from django.utils import six from django.views.generic import View try: from django.urls import ( NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve ) except ImportError: from django.core.urlresolvers import ( # Will be removed in Django 2.0 NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve ) try: import urlparse # Python 2.x except ImportError: import urllib.parse as urlparse 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() # Obtaining manager instances and names from model options differs after 1.10. def get_names_and_managers(options): if django.VERSION >= (1, 10): # Django 1.10 onwards provides a `.managers` property on the Options. return [ (manager.name, manager) for manager in options.managers ] # For Django 1.8 and 1.9, use the three-tuple information provided # by .concrete_managers and .abstract_managers return [ (manager_info[1], manager_info[2]) for manager_info in (options.concrete_managers + options.abstract_managers) ] # field.rel is deprecated from 1.9 onwards def get_remote_field(field, **kwargs): if 'default' in kwargs: if django.VERSION < (1, 9): return getattr(field, 'rel', kwargs['default']) return getattr(field, 'remote_field', kwargs['default']) if django.VERSION < (1, 9): return field.rel return field.remote_field def _resolve_model(obj): """ Resolve supplied `obj` to a Django model class. `obj` must be a Django model class itself, or a string representation of one. Useful in situations like GH #1225 where Django may not have resolved a string-based reference to a model in another model's foreign key definition. String representations should have the format: 'appname.ModelName' """ if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: app_name, model_name = obj.split('.') resolved_model = apps.get_model(app_name, model_name) if resolved_model is None: msg = "Django did not return a model for {0}.{1}" raise ImproperlyConfigured(msg.format(app_name, model_name)) return resolved_model elif inspect.isclass(obj) and issubclass(obj, models.Model): return obj raise ValueError("{0} is not a Django model".format(obj)) def is_authenticated(user): if django.VERSION < (1, 10): return user.is_authenticated() return user.is_authenticated def is_anonymous(user): if django.VERSION < (1, 10): return user.is_anonymous() return user.is_anonymous def get_related_model(field): if django.VERSION < (1, 9): return _resolve_model(field.rel.to) return field.remote_field.model def value_from_object(field, obj): if django.VERSION < (1, 9): return field._get_val_from_obj(obj) return field.value_from_object(obj) # contrib.postgres only supported from 1.8 onwards. try: from django.contrib.postgres import fields as postgres_fields except ImportError: postgres_fields = None # JSONField is only supported from 1.9 onwards try: from django.contrib.postgres.fields import JSONField except ImportError: JSONField = None # coreapi is optional (Note that uritemplate is a dependency of coreapi) try: import coreapi import uritemplate except (ImportError, SyntaxError): # SyntaxError is possible under python 3.2 coreapi = None uritemplate = None # coreschema is optional try: import coreschema except ImportError: coreschema = None # django-crispy-forms is optional try: import crispy_forms except ImportError: crispy_forms = None # requests is optional try: import requests except ImportError: requests = None # 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 try: if 'guardian' in settings.INSTALLED_APPS: import guardian except ImportError: pass # 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 if markdown.version <= '2.2': HEADERID_EXT_PATH = 'headerid' LEVEL_PARAM = 'level' elif markdown.version < '2.6': HEADERID_EXT_PATH = 'markdown.extensions.headerid' LEVEL_PARAM = 'level' else: HEADERID_EXT_PATH = 'markdown.extensions.toc' LEVEL_PARAM = 'baselevel' def apply_markdown(text): """ Simple wrapper around :func:`markdown.markdown` to set the base level of '#' style headers to

. """ extensions = [HEADERID_EXT_PATH] extension_configs = { HEADERID_EXT_PATH: { LEVEL_PARAM: '2' } } md = markdown.Markdown( extensions=extensions, extension_configs=extension_configs ) return md.convert(text) except ImportError: apply_markdown = None markdown = None try: import pygments from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter def pygments_highlight(text, lang, style): lexer = get_lexer_by_name(lang, stripall=False) formatter = HtmlFormatter(nowrap=True, style=style) return pygments.highlight(text, lexer, formatter) def pygments_css(style): formatter = HtmlFormatter(style=style) return formatter.get_style_defs('.highlight') except ImportError: pygments = None def pygments_highlight(text, lang, style): return text def pygments_css(style): return None try: import pytz from pytz.exceptions import InvalidTimeError except ImportError: InvalidTimeError = Exception # `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': ') try: # DecimalValidator is unavailable in Django < 1.9 from django.core.validators import DecimalValidator except ImportError: DecimalValidator = None class CustomValidatorMessage(object): """ We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`. https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297 Ref: https://github.com/encode/django-rest-framework/pull/5452 """ def __init__(self, *args, **kwargs): self.message = kwargs.pop('message', self.message) super(CustomValidatorMessage, self).__init__(*args, **kwargs) class MinValueValidator(CustomValidatorMessage, DjangoMinValueValidator): pass class MaxValueValidator(CustomValidatorMessage, DjangoMaxValueValidator): pass class MinLengthValidator(CustomValidatorMessage, DjangoMinLengthValidator): pass class MaxLengthValidator(CustomValidatorMessage, DjangoMaxLengthValidator): pass 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 def template_render(template, context=None, request=None): """ Passing Context or RequestContext to Template.render is deprecated in 1.9+, see https://github.com/django/django/pull/3883 and https://github.com/django/django/blob/1.9/django/template/backends/django.py#L82-L84 :param template: Template instance :param context: dict :param request: Request instance :return: rendered template as SafeText instance """ if isinstance(template, Template): if request: context = RequestContext(request, context) else: context = Context(context) return template.render(context) # backends template, e.g. django.template.backends.django.Template else: return template.render(context, request=request) def set_many(instance, field, value): if django.VERSION < (1, 10): setattr(instance, field, value) else: field = getattr(instance, field) field.set(value) def include(module, namespace=None, app_name=None): from django.conf.urls import include if django.VERSION < (1,9): return include(module, namespace, app_name) else: return include((module, app_name), namespace)