detail.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. from __future__ import unicode_literals
  2. from django.core.exceptions import ImproperlyConfigured
  3. from django.db import models
  4. from django.http import Http404
  5. from django.utils.translation import ugettext as _
  6. from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
  7. class SingleObjectMixin(ContextMixin):
  8. """
  9. Provides the ability to retrieve a single object for further manipulation.
  10. """
  11. model = None
  12. queryset = None
  13. slug_field = 'slug'
  14. context_object_name = None
  15. slug_url_kwarg = 'slug'
  16. pk_url_kwarg = 'pk'
  17. def get_object(self, queryset=None):
  18. """
  19. Returns the object the view is displaying.
  20. By default this requires `self.queryset` and a `pk` or `slug` argument
  21. in the URLconf, but subclasses can override this to return any object.
  22. """
  23. # Use a custom queryset if provided; this is required for subclasses
  24. # like DateDetailView
  25. if queryset is None:
  26. queryset = self.get_queryset()
  27. # Next, try looking up by primary key.
  28. pk = self.kwargs.get(self.pk_url_kwarg, None)
  29. slug = self.kwargs.get(self.slug_url_kwarg, None)
  30. if pk is not None:
  31. queryset = queryset.filter(pk=pk)
  32. # Next, try looking up by slug.
  33. elif slug is not None:
  34. slug_field = self.get_slug_field()
  35. queryset = queryset.filter(**{slug_field: slug})
  36. # If none of those are defined, it's an error.
  37. else:
  38. raise AttributeError("Generic detail view %s must be called with "
  39. "either an object pk or a slug."
  40. % self.__class__.__name__)
  41. try:
  42. # Get the single item from the filtered queryset
  43. obj = queryset.get()
  44. except queryset.model.DoesNotExist:
  45. raise Http404(_("No %(verbose_name)s found matching the query") %
  46. {'verbose_name': queryset.model._meta.verbose_name})
  47. return obj
  48. def get_queryset(self):
  49. """
  50. Return the `QuerySet` that will be used to look up the object.
  51. Note that this method is called by the default implementation of
  52. `get_object` and may not be called if `get_object` is overridden.
  53. """
  54. if self.queryset is None:
  55. if self.model:
  56. return self.model._default_manager.all()
  57. else:
  58. raise ImproperlyConfigured(
  59. "%(cls)s is missing a QuerySet. Define "
  60. "%(cls)s.model, %(cls)s.queryset, or override "
  61. "%(cls)s.get_queryset()." % {
  62. 'cls': self.__class__.__name__
  63. }
  64. )
  65. return self.queryset.all()
  66. def get_slug_field(self):
  67. """
  68. Get the name of a slug field to be used to look up by slug.
  69. """
  70. return self.slug_field
  71. def get_context_object_name(self, obj):
  72. """
  73. Get the name to use for the object.
  74. """
  75. if self.context_object_name:
  76. return self.context_object_name
  77. elif isinstance(obj, models.Model):
  78. return obj._meta.model_name
  79. else:
  80. return None
  81. def get_context_data(self, **kwargs):
  82. """
  83. Insert the single object into the context dict.
  84. """
  85. context = {}
  86. if self.object:
  87. context['object'] = self.object
  88. context_object_name = self.get_context_object_name(self.object)
  89. if context_object_name:
  90. context[context_object_name] = self.object
  91. context.update(kwargs)
  92. return super(SingleObjectMixin, self).get_context_data(**context)
  93. class BaseDetailView(SingleObjectMixin, View):
  94. """
  95. A base view for displaying a single object
  96. """
  97. def get(self, request, *args, **kwargs):
  98. self.object = self.get_object()
  99. context = self.get_context_data(object=self.object)
  100. return self.render_to_response(context)
  101. class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
  102. template_name_field = None
  103. template_name_suffix = '_detail'
  104. def get_template_names(self):
  105. """
  106. Return a list of template names to be used for the request. May not be
  107. called if render_to_response is overridden. Returns the following list:
  108. * the value of ``template_name`` on the view (if provided)
  109. * the contents of the ``template_name_field`` field on the
  110. object instance that the view is operating upon (if available)
  111. * ``<app_label>/<model_name><template_name_suffix>.html``
  112. """
  113. try:
  114. names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
  115. except ImproperlyConfigured:
  116. # If template_name isn't specified, it's not a problem --
  117. # we just start with an empty list.
  118. names = []
  119. # If self.template_name_field is set, grab the value of the field
  120. # of that name from the object; this is the most specific template
  121. # name, if given.
  122. if self.object and self.template_name_field:
  123. name = getattr(self.object, self.template_name_field, None)
  124. if name:
  125. names.insert(0, name)
  126. # The least-specific option is the default <app>/<model>_detail.html;
  127. # only use this if the object in question is a model.
  128. if isinstance(self.object, models.Model):
  129. names.append("%s/%s%s.html" % (
  130. self.object._meta.app_label,
  131. self.object._meta.model_name,
  132. self.template_name_suffix
  133. ))
  134. elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model):
  135. names.append("%s/%s%s.html" % (
  136. self.model._meta.app_label,
  137. self.model._meta.model_name,
  138. self.template_name_suffix
  139. ))
  140. # If we still haven't managed to find any template names, we should
  141. # re-raise the ImproperlyConfigured to alert the user.
  142. if not names:
  143. raise
  144. return names
  145. class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
  146. """
  147. Render a "detail" view of an object.
  148. By default this is a model instance looked up from `self.queryset`, but the
  149. view will support display of *any* object by overriding `self.get_object()`.
  150. """