""" 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

. """ 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