compat.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. """
  2. The `compat` module provides support for backwards compatibility with older
  3. versions of Django/Python, and compatibility wrappers around optional packages.
  4. """
  5. import sys
  6. from django.conf import settings
  7. from django.views.generic import View
  8. try:
  9. from django.urls import ( # noqa
  10. URLPattern,
  11. URLResolver,
  12. )
  13. except ImportError:
  14. # Will be removed in Django 2.0
  15. from django.urls import ( # noqa
  16. RegexURLPattern as URLPattern,
  17. RegexURLResolver as URLResolver,
  18. )
  19. try:
  20. from django.core.validators import ProhibitNullCharactersValidator # noqa
  21. except ImportError:
  22. ProhibitNullCharactersValidator = None
  23. def get_original_route(urlpattern):
  24. """
  25. Get the original route/regex that was typed in by the user into the path(), re_path() or url() directive. This
  26. is in contrast with get_regex_pattern below, which for RoutePattern returns the raw regex generated from the path().
  27. """
  28. if hasattr(urlpattern, 'pattern'):
  29. # Django 2.0
  30. return str(urlpattern.pattern)
  31. else:
  32. # Django < 2.0
  33. return urlpattern.regex.pattern
  34. def get_regex_pattern(urlpattern):
  35. """
  36. Get the raw regex out of the urlpattern's RegexPattern or RoutePattern. This is always a regular expression,
  37. unlike get_original_route above.
  38. """
  39. if hasattr(urlpattern, 'pattern'):
  40. # Django 2.0
  41. return urlpattern.pattern.regex.pattern
  42. else:
  43. # Django < 2.0
  44. return urlpattern.regex.pattern
  45. def is_route_pattern(urlpattern):
  46. if hasattr(urlpattern, 'pattern'):
  47. # Django 2.0
  48. from django.urls.resolvers import RoutePattern
  49. return isinstance(urlpattern.pattern, RoutePattern)
  50. else:
  51. # Django < 2.0
  52. return False
  53. def make_url_resolver(regex, urlpatterns):
  54. try:
  55. # Django 2.0
  56. from django.urls.resolvers import RegexPattern
  57. return URLResolver(RegexPattern(regex), urlpatterns)
  58. except ImportError:
  59. # Django < 2.0
  60. return URLResolver(regex, urlpatterns)
  61. def unicode_http_header(value):
  62. # Coerce HTTP header value to unicode.
  63. if isinstance(value, bytes):
  64. return value.decode('iso-8859-1')
  65. return value
  66. def distinct(queryset, base):
  67. if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
  68. # distinct analogue for Oracle users
  69. return base.filter(pk__in=set(queryset.values_list('pk', flat=True)))
  70. return queryset.distinct()
  71. # django.contrib.postgres requires psycopg2
  72. try:
  73. from django.contrib.postgres import fields as postgres_fields
  74. except ImportError:
  75. postgres_fields = None
  76. # coreapi is required for CoreAPI schema generation
  77. try:
  78. import coreapi
  79. except ImportError:
  80. coreapi = None
  81. # uritemplate is required for OpenAPI and CoreAPI schema generation
  82. try:
  83. import uritemplate
  84. except ImportError:
  85. uritemplate = None
  86. # coreschema is optional
  87. try:
  88. import coreschema
  89. except ImportError:
  90. coreschema = None
  91. # pyyaml is optional
  92. try:
  93. import yaml
  94. except ImportError:
  95. yaml = None
  96. # requests is optional
  97. try:
  98. import requests
  99. except ImportError:
  100. requests = None
  101. # PATCH method is not implemented by Django
  102. if 'patch' not in View.http_method_names:
  103. View.http_method_names = View.http_method_names + ['patch']
  104. # Markdown is optional (version 3.0+ required)
  105. try:
  106. import markdown
  107. HEADERID_EXT_PATH = 'markdown.extensions.toc'
  108. LEVEL_PARAM = 'baselevel'
  109. def apply_markdown(text):
  110. """
  111. Simple wrapper around :func:`markdown.markdown` to set the base level
  112. of '#' style headers to <h2>.
  113. """
  114. extensions = [HEADERID_EXT_PATH]
  115. extension_configs = {
  116. HEADERID_EXT_PATH: {
  117. LEVEL_PARAM: '2'
  118. }
  119. }
  120. md = markdown.Markdown(
  121. extensions=extensions, extension_configs=extension_configs
  122. )
  123. md_filter_add_syntax_highlight(md)
  124. return md.convert(text)
  125. except ImportError:
  126. apply_markdown = None
  127. markdown = None
  128. try:
  129. import pygments
  130. from pygments.lexers import get_lexer_by_name, TextLexer
  131. from pygments.formatters import HtmlFormatter
  132. def pygments_highlight(text, lang, style):
  133. lexer = get_lexer_by_name(lang, stripall=False)
  134. formatter = HtmlFormatter(nowrap=True, style=style)
  135. return pygments.highlight(text, lexer, formatter)
  136. def pygments_css(style):
  137. formatter = HtmlFormatter(style=style)
  138. return formatter.get_style_defs('.highlight')
  139. except ImportError:
  140. pygments = None
  141. def pygments_highlight(text, lang, style):
  142. return text
  143. def pygments_css(style):
  144. return None
  145. if markdown is not None and pygments is not None:
  146. # starting from this blogpost and modified to support current markdown extensions API
  147. # https://zerokspot.com/weblog/2008/06/18/syntax-highlighting-in-markdown-with-pygments/
  148. from markdown.preprocessors import Preprocessor
  149. import re
  150. class CodeBlockPreprocessor(Preprocessor):
  151. pattern = re.compile(
  152. r'^\s*``` *([^\n]+)\n(.+?)^\s*```', re.M | re.S)
  153. formatter = HtmlFormatter()
  154. def run(self, lines):
  155. def repl(m):
  156. try:
  157. lexer = get_lexer_by_name(m.group(1))
  158. except (ValueError, NameError):
  159. lexer = TextLexer()
  160. code = m.group(2).replace('\t', ' ')
  161. code = pygments.highlight(code, lexer, self.formatter)
  162. code = code.replace('\n\n', '\n&nbsp;\n').replace('\n', '<br />').replace('\\@', '@')
  163. return '\n\n%s\n\n' % code
  164. ret = self.pattern.sub(repl, "\n".join(lines))
  165. return ret.split("\n")
  166. def md_filter_add_syntax_highlight(md):
  167. md.preprocessors.register(CodeBlockPreprocessor(), 'highlight', 40)
  168. return True
  169. else:
  170. def md_filter_add_syntax_highlight(md):
  171. return False
  172. # Django 1.x url routing syntax. Remove when dropping Django 1.11 support.
  173. try:
  174. from django.urls import include, path, re_path, register_converter # noqa
  175. except ImportError:
  176. from django.conf.urls import include, url # noqa
  177. path = None
  178. register_converter = None
  179. re_path = url
  180. # `separators` argument to `json.dumps()` differs between 2.x and 3.x
  181. # See: https://bugs.python.org/issue22767
  182. SHORT_SEPARATORS = (',', ':')
  183. LONG_SEPARATORS = (', ', ': ')
  184. INDENT_SEPARATORS = (',', ': ')
  185. # Version Constants.
  186. PY36 = sys.version_info >= (3, 6)