import re from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import Http404 from django.utils.encoding import smart_str 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 def get_object(self, queryset=None): """ 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. pk = self.kwargs.get('pk', None) slug = self.kwargs.get('slug', None) 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 smart_str(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): self.object = self.get_object() 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 /_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()`. """