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.

shortcuts.py 5.5KB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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.http import (
  8. Http404, HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect,
  9. )
  10. from django.template import loader
  11. from django.urls import NoReverseMatch, reverse
  12. from django.utils.deprecation import RemovedInDjango30Warning
  13. from django.utils.functional import Promise
  14. def render_to_response(template_name, context=None, content_type=None, status=None, using=None):
  15. """
  16. Return a HttpResponse whose content is filled with the result of calling
  17. django.template.loader.render_to_string() with the passed arguments.
  18. """
  19. warnings.warn(
  20. 'render_to_response() is deprecated in favor of render(). It has the '
  21. 'same signature except that it also requires a request.',
  22. RemovedInDjango30Warning, stacklevel=2,
  23. )
  24. content = loader.render_to_string(template_name, context, using=using)
  25. return HttpResponse(content, content_type, status)
  26. def render(request, template_name, context=None, content_type=None, status=None, using=None):
  27. """
  28. Return a HttpResponse whose content is filled with the result of calling
  29. django.template.loader.render_to_string() with the passed arguments.
  30. """
  31. content = loader.render_to_string(template_name, context, request, using=using)
  32. return HttpResponse(content, content_type, status)
  33. def redirect(to, *args, permanent=False, **kwargs):
  34. """
  35. Return an HttpResponseRedirect to the appropriate URL for the arguments
  36. passed.
  37. The arguments could be:
  38. * A model: the model's `get_absolute_url()` function will be called.
  39. * A view name, possibly with arguments: `urls.reverse()` will be used
  40. to reverse-resolve the name.
  41. * A URL, which will be used as-is for the redirect location.
  42. Issues a temporary redirect by default; pass permanent=True to issue a
  43. permanent redirect.
  44. """
  45. redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
  46. return redirect_class(resolve_url(to, *args, **kwargs))
  47. def _get_queryset(klass):
  48. """
  49. Return a QuerySet or a Manager.
  50. Duck typing in action: any class with a `get()` method (for
  51. get_object_or_404) or a `filter()` method (for get_list_or_404) might do
  52. the job.
  53. """
  54. # If it is a model class or anything else with ._default_manager
  55. if hasattr(klass, '_default_manager'):
  56. return klass._default_manager.all()
  57. return klass
  58. def get_object_or_404(klass, *args, **kwargs):
  59. """
  60. Use get() to return an object, or raise a Http404 exception if the object
  61. does not exist.
  62. klass may be a Model, Manager, or QuerySet object. All other passed
  63. arguments and keyword arguments are used in the get() query.
  64. Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
  65. one object is found.
  66. """
  67. queryset = _get_queryset(klass)
  68. if not hasattr(queryset, 'get'):
  69. klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
  70. raise ValueError(
  71. "First argument to get_object_or_404() must be a Model, Manager, "
  72. "or QuerySet, not '%s'." % klass__name
  73. )
  74. try:
  75. return queryset.get(*args, **kwargs)
  76. except queryset.model.DoesNotExist:
  77. raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
  78. def get_list_or_404(klass, *args, **kwargs):
  79. """
  80. Use filter() to return a list of objects, or raise a Http404 exception if
  81. the list is empty.
  82. klass may be a Model, Manager, or QuerySet object. All other passed
  83. arguments and keyword arguments are used in the filter() query.
  84. """
  85. queryset = _get_queryset(klass)
  86. if not hasattr(queryset, 'filter'):
  87. klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
  88. raise ValueError(
  89. "First argument to get_list_or_404() must be a Model, Manager, or "
  90. "QuerySet, not '%s'." % klass__name
  91. )
  92. obj_list = list(queryset.filter(*args, **kwargs))
  93. if not obj_list:
  94. raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
  95. return obj_list
  96. def resolve_url(to, *args, **kwargs):
  97. """
  98. Return a URL appropriate for the arguments passed.
  99. The arguments could be:
  100. * A model: the model's `get_absolute_url()` function will be called.
  101. * A view name, possibly with arguments: `urls.reverse()` will be used
  102. to reverse-resolve the name.
  103. * A URL, which will be returned as-is.
  104. """
  105. # If it's a model, use get_absolute_url()
  106. if hasattr(to, 'get_absolute_url'):
  107. return to.get_absolute_url()
  108. if isinstance(to, Promise):
  109. # Expand the lazy instance, as it can cause issues when it is passed
  110. # further to some Python functions like urlparse.
  111. to = str(to)
  112. if isinstance(to, str):
  113. # Handle relative URLs
  114. if to.startswith(('./', '../')):
  115. return to
  116. # Next try a reverse URL resolution.
  117. try:
  118. return reverse(to, args=args, kwargs=kwargs)
  119. except NoReverseMatch:
  120. # If this is a callable, re-raise.
  121. if callable(to):
  122. raise
  123. # If this doesn't "feel" like a URL, re-raise.
  124. if '/' not in to and '.' not in to:
  125. raise
  126. # Finally, fall back and assume it's a URL
  127. return to