2010-10-18 21:34:47 +08:00
|
|
|
import re
|
|
|
|
|
|
|
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
|
|
|
from django.http import Http404
|
|
|
|
from django.views.generic.base import TemplateResponseMixin, View
|
|
|
|
|
|
|
|
|
|
|
|
class SingleObjectMixin(object):
|
|
|
|
"""
|
|
|
|
Provides the ability to retrieve a single object for further manipulation.
|
|
|
|
"""
|
|
|
|
model = None
|
|
|
|
queryset = None
|
|
|
|
slug_field = 'slug'
|
|
|
|
context_object_name = None
|
|
|
|
|
2010-10-20 08:21:47 +08:00
|
|
|
def get_object(self, queryset=None):
|
2010-10-18 21:34:47 +08:00
|
|
|
"""
|
|
|
|
Returns the object the view is displaying.
|
|
|
|
|
|
|
|
By default this requires `self.queryset` and a `pk` or `slug` argument
|
|
|
|
in the URLconf, but subclasses can override this to return any object.
|
|
|
|
"""
|
|
|
|
# Use a custom queryset if provided; this is required for subclasses
|
|
|
|
# like DateDetailView
|
|
|
|
if queryset is None:
|
|
|
|
queryset = self.get_queryset()
|
|
|
|
|
|
|
|
# Next, try looking up by primary key.
|
2010-10-20 08:21:47 +08:00
|
|
|
pk = self.kwargs.get('pk', None)
|
|
|
|
slug = self.kwargs.get('slug', None)
|
2010-10-18 21:34:47 +08:00
|
|
|
if pk is not None:
|
|
|
|
queryset = queryset.filter(pk=pk)
|
|
|
|
|
|
|
|
# Next, try looking up by slug.
|
|
|
|
elif slug is not None:
|
|
|
|
slug_field = self.get_slug_field()
|
|
|
|
queryset = queryset.filter(**{slug_field: slug})
|
|
|
|
|
|
|
|
# If none of those are defined, it's an error.
|
|
|
|
else:
|
|
|
|
raise AttributeError(u"Generic detail view %s must be called with "
|
|
|
|
u"either an object id or a slug."
|
|
|
|
% self.__class__.__name__)
|
|
|
|
|
|
|
|
try:
|
|
|
|
obj = queryset.get()
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
raise Http404(u"No %s found matching the query" %
|
|
|
|
(queryset.model._meta.verbose_name))
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
"""
|
|
|
|
Get the queryset to look an object up against. May not be called if
|
|
|
|
`get_object` is overridden.
|
|
|
|
"""
|
|
|
|
if self.queryset is None:
|
|
|
|
if self.model:
|
|
|
|
return self.model._default_manager.all()
|
|
|
|
else:
|
|
|
|
raise ImproperlyConfigured(u"%(cls)s is missing a queryset. Define "
|
|
|
|
u"%(cls)s.model, %(cls)s.queryset, or override "
|
|
|
|
u"%(cls)s.get_object()." % {
|
|
|
|
'cls': self.__class__.__name__
|
|
|
|
})
|
|
|
|
return self.queryset._clone()
|
|
|
|
|
|
|
|
def get_slug_field(self):
|
|
|
|
"""
|
|
|
|
Get the name of a slug field to be used to look up by slug.
|
|
|
|
"""
|
|
|
|
return self.slug_field
|
|
|
|
|
|
|
|
def get_context_object_name(self, obj):
|
|
|
|
"""
|
|
|
|
Get the name to use for the object.
|
|
|
|
"""
|
|
|
|
if self.context_object_name:
|
|
|
|
return self.context_object_name
|
|
|
|
elif hasattr(obj, '_meta'):
|
|
|
|
return re.sub('[^a-zA-Z0-9]+', '_',
|
|
|
|
obj._meta.verbose_name.lower())
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = kwargs
|
|
|
|
context_object_name = self.get_context_object_name(self.object)
|
|
|
|
if context_object_name:
|
|
|
|
context[context_object_name] = self.object
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class BaseDetailView(SingleObjectMixin, View):
|
|
|
|
def get(self, request, **kwargs):
|
2010-10-20 08:21:47 +08:00
|
|
|
self.object = self.get_object()
|
2010-10-18 21:34:47 +08:00
|
|
|
context = self.get_context_data(object=self.object)
|
|
|
|
return self.render_to_response(context)
|
|
|
|
|
|
|
|
|
|
|
|
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
|
|
|
|
template_name_field = None
|
|
|
|
template_name_suffix = '_detail'
|
|
|
|
|
|
|
|
def get_template_names(self):
|
|
|
|
"""
|
|
|
|
Return a list of template names to be used for the request. Must return
|
|
|
|
a list. May not be called if get_template is overridden.
|
|
|
|
"""
|
|
|
|
names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
|
|
|
|
|
|
|
|
# If self.template_name_field is set, grab the value of the field
|
|
|
|
# of that name from the object; this is the most specific template
|
|
|
|
# name, if given.
|
|
|
|
if self.object and self.template_name_field:
|
|
|
|
name = getattr(self.object, self.template_name_field, None)
|
|
|
|
if name:
|
|
|
|
names.insert(0, name)
|
|
|
|
|
|
|
|
# The least-specific option is the default <app>/<model>_detail.html;
|
|
|
|
# only use this if the object in question is a model.
|
|
|
|
if hasattr(self.object, '_meta'):
|
|
|
|
names.append("%s/%s%s.html" % (
|
|
|
|
self.object._meta.app_label,
|
|
|
|
self.object._meta.object_name.lower(),
|
|
|
|
self.template_name_suffix
|
|
|
|
))
|
|
|
|
elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
|
|
|
|
names.append("%s/%s%s.html" % (
|
|
|
|
self.model._meta.app_label,
|
|
|
|
self.model._meta.object_name.lower(),
|
|
|
|
self.template_name_suffix
|
|
|
|
))
|
|
|
|
return names
|
|
|
|
|
|
|
|
|
|
|
|
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
|
|
|
|
"""
|
|
|
|
Render a "detail" view of an object.
|
|
|
|
|
|
|
|
By default this is a model instance looked up from `self.queryset`, but the
|
|
|
|
view will support display of *any* object by overriding `self.get_object()`.
|
|
|
|
"""
|