You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

215 lines
7.8 KiB

  1. """
  2. This module collects helper functions and classes that "span" multiple levels
  3. of MVC. In other words, these functions/classes introduce controlled coupling
  4. for convenience's sake.
  5. """
  6. import warnings
  7. from django.core import urlresolvers
  8. from django.db.models.base import ModelBase
  9. from django.db.models.manager import Manager
  10. from django.db.models.query import QuerySet
  11. from django.http import (
  12. Http404, HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect,
  13. )
  14. from django.template import RequestContext, loader
  15. from django.template.context import _current_app_undefined
  16. from django.template.engine import (
  17. _context_instance_undefined, _dictionary_undefined, _dirs_undefined,
  18. )
  19. from django.utils import six
  20. from django.utils.deprecation import RemovedInDjango110Warning
  21. from django.utils.encoding import force_text
  22. from django.utils.functional import Promise
  23. def render_to_response(template_name, context=None,
  24. context_instance=_context_instance_undefined,
  25. content_type=None, status=None, dirs=_dirs_undefined,
  26. dictionary=_dictionary_undefined, using=None):
  27. """
  28. Returns a HttpResponse whose content is filled with the result of calling
  29. django.template.loader.render_to_string() with the passed arguments.
  30. """
  31. if (context_instance is _context_instance_undefined
  32. and dirs is _dirs_undefined
  33. and dictionary is _dictionary_undefined):
  34. # No deprecated arguments were passed - use the new code path
  35. content = loader.render_to_string(template_name, context, using=using)
  36. else:
  37. # Some deprecated arguments were passed - use the legacy code path
  38. content = loader.render_to_string(
  39. template_name, context, context_instance, dirs, dictionary,
  40. using=using)
  41. return HttpResponse(content, content_type, status)
  42. def render(request, template_name, context=None,
  43. context_instance=_context_instance_undefined,
  44. content_type=None, status=None, current_app=_current_app_undefined,
  45. dirs=_dirs_undefined, dictionary=_dictionary_undefined,
  46. using=None):
  47. """
  48. Returns a HttpResponse whose content is filled with the result of calling
  49. django.template.loader.render_to_string() with the passed arguments.
  50. Uses a RequestContext by default.
  51. """
  52. if (context_instance is _context_instance_undefined
  53. and current_app is _current_app_undefined
  54. and dirs is _dirs_undefined
  55. and dictionary is _dictionary_undefined):
  56. # No deprecated arguments were passed - use the new code path
  57. # In Django 1.10, request should become a positional argument.
  58. content = loader.render_to_string(
  59. template_name, context, request=request, using=using)
  60. else:
  61. # Some deprecated arguments were passed - use the legacy code path
  62. if context_instance is not _context_instance_undefined:
  63. if current_app is not _current_app_undefined:
  64. raise ValueError('If you provide a context_instance you must '
  65. 'set its current_app before calling render()')
  66. else:
  67. context_instance = RequestContext(request)
  68. if current_app is not _current_app_undefined:
  69. warnings.warn(
  70. "The current_app argument of render is deprecated. "
  71. "Set the current_app attribute of request instead.",
  72. RemovedInDjango110Warning, stacklevel=2)
  73. request.current_app = current_app
  74. # Directly set the private attribute to avoid triggering the
  75. # warning in RequestContext.__init__.
  76. context_instance._current_app = current_app
  77. content = loader.render_to_string(
  78. template_name, context, context_instance, dirs, dictionary,
  79. using=using)
  80. return HttpResponse(content, content_type, status)
  81. def redirect(to, *args, **kwargs):
  82. """
  83. Returns an HttpResponseRedirect to the appropriate URL for the arguments
  84. passed.
  85. The arguments could be:
  86. * A model: the model's `get_absolute_url()` function will be called.
  87. * A view name, possibly with arguments: `urlresolvers.reverse()` will
  88. be used to reverse-resolve the name.
  89. * A URL, which will be used as-is for the redirect location.
  90. By default issues a temporary redirect; pass permanent=True to issue a
  91. permanent redirect
  92. """
  93. if kwargs.pop('permanent', False):
  94. redirect_class = HttpResponsePermanentRedirect
  95. else:
  96. redirect_class = HttpResponseRedirect
  97. return redirect_class(resolve_url(to, *args, **kwargs))
  98. def _get_queryset(klass):
  99. """
  100. Returns a QuerySet from a Model, Manager, or QuerySet. Created to make
  101. get_object_or_404 and get_list_or_404 more DRY.
  102. Raises a ValueError if klass is not a Model, Manager, or QuerySet.
  103. """
  104. if isinstance(klass, QuerySet):
  105. return klass
  106. elif isinstance(klass, Manager):
  107. manager = klass
  108. elif isinstance(klass, ModelBase):
  109. manager = klass._default_manager
  110. else:
  111. if isinstance(klass, type):
  112. klass__name = klass.__name__
  113. else:
  114. klass__name = klass.__class__.__name__
  115. raise ValueError("Object is of type '%s', but must be a Django Model, "
  116. "Manager, or QuerySet" % klass__name)
  117. return manager.all()
  118. def get_object_or_404(klass, *args, **kwargs):
  119. """
  120. Uses get() to return an object, or raises a Http404 exception if the object
  121. does not exist.
  122. klass may be a Model, Manager, or QuerySet object. All other passed
  123. arguments and keyword arguments are used in the get() query.
  124. Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
  125. object is found.
  126. """
  127. queryset = _get_queryset(klass)
  128. try:
  129. return queryset.get(*args, **kwargs)
  130. except queryset.model.DoesNotExist:
  131. raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
  132. def get_list_or_404(klass, *args, **kwargs):
  133. """
  134. Uses filter() to return a list of objects, or raise a Http404 exception if
  135. the list is empty.
  136. klass may be a Model, Manager, or QuerySet object. All other passed
  137. arguments and keyword arguments are used in the filter() query.
  138. """
  139. queryset = _get_queryset(klass)
  140. obj_list = list(queryset.filter(*args, **kwargs))
  141. if not obj_list:
  142. raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
  143. return obj_list
  144. def resolve_url(to, *args, **kwargs):
  145. """
  146. Return a URL appropriate for the arguments passed.
  147. The arguments could be:
  148. * A model: the model's `get_absolute_url()` function will be called.
  149. * A view name, possibly with arguments: `urlresolvers.reverse()` will
  150. be used to reverse-resolve the name.
  151. * A URL, which will be returned as-is.
  152. """
  153. # If it's a model, use get_absolute_url()
  154. if hasattr(to, 'get_absolute_url'):
  155. return to.get_absolute_url()
  156. if isinstance(to, Promise):
  157. # Expand the lazy instance, as it can cause issues when it is passed
  158. # further to some Python functions like urlparse.
  159. to = force_text(to)
  160. if isinstance(to, six.string_types):
  161. # Handle relative URLs
  162. if to.startswith(('./', '../')):
  163. return to
  164. # Next try a reverse URL resolution.
  165. try:
  166. return urlresolvers.reverse(to, args=args, kwargs=kwargs)
  167. except urlresolvers.NoReverseMatch:
  168. # If this is a callable, re-raise.
  169. if callable(to):
  170. raise
  171. # If this doesn't "feel" like a URL, re-raise.
  172. if '/' not in to and '.' not in to:
  173. raise
  174. # Finally, fall back and assume it's a URL
  175. return to