Created
April 4, 2016 13:14
-
-
Save craigderington/9c84d0e738a72ff3aeb62de3017a2323 to your computer and use it in GitHub Desktop.
Django Generic Detail View
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.views.generic import DetailView | |
from .models import Customer | |
class CustomerDetailView(LoginRequiredMixin, DetailView): | |
def _allowed_methods(self): | |
return [m.upper() for m in self.http_method_names if hasattr(self, m)] | |
@classonlymethod | |
def as_view(cls, **initkwargs): | |
""" | |
Main entry point for a request-response process. | |
""" | |
for key in initkwargs: | |
if key in cls.http_method_names: | |
raise TypeError("You tried to pass in the %s method name as a " | |
"keyword argument to %s(). Don't do that." | |
% (key, cls.__name__)) | |
if not hasattr(cls, key): | |
raise TypeError("%s() received an invalid keyword %r. as_view " | |
"only accepts arguments that are already " | |
"attributes of the class." % (cls.__name__, key)) | |
def view(request, *args, **kwargs): | |
self = cls(**initkwargs) | |
if hasattr(self, 'get') and not hasattr(self, 'head'): | |
self.head = self.get | |
self.request = request | |
self.args = args | |
self.kwargs = kwargs | |
return self.dispatch(request, *args, **kwargs) | |
# take name and docstring from class | |
update_wrapper(view, cls, updated=()) | |
# and possible attributes set by decorators | |
# like csrf_exempt from dispatch | |
update_wrapper(view, cls.dispatch, assigned=()) | |
return view | |
def dispatch(self, request, *args, **kwargs): | |
# Try to dispatch to the right method; if a method doesn't exist, | |
# defer to the error handler. Also defer to the error handler if the | |
# request method isn't on the approved list. | |
if request.method.lower() in self.http_method_names: | |
handler = getattr(self, request.method.lower(), self.http_method_not_allowed) | |
else: | |
handler = self.http_method_not_allowed | |
return handler(request, *args, **kwargs) | |
def get(self, request, *args, **kwargs): | |
self.object = self.get_object() | |
context = self.get_context_data(object=self.object) | |
return self.render_to_response(context) | |
def get_context_data(self, **kwargs): | |
""" | |
Insert the single object into the context dict. | |
""" | |
context = {} | |
if self.object: | |
context['object'] = self.object | |
context_object_name = self.get_context_object_name(self.object) | |
if context_object_name: | |
context[context_object_name] = self.object | |
context.update(kwargs) | |
return super(SingleObjectMixin, self).get_context_data(**context) | |
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 isinstance(obj, models.Model): | |
return obj._meta.model_name | |
else: | |
return 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(self.pk_url_kwarg, None) | |
slug = self.kwargs.get(self.slug_url_kwarg, None) | |
if pk is not None: | |
queryset = queryset.filter(pk=pk) | |
# Next, try looking up by slug. | |
if slug is not None and (pk is None or self.query_pk_and_slug): | |
slug_field = self.get_slug_field() | |
queryset = queryset.filter(**{slug_field: slug}) | |
# If none of those are defined, it's an error. | |
if pk is None and slug is None: | |
raise AttributeError("Generic detail view %s must be called with " | |
"either an object pk or a slug." | |
% self.__class__.__name__) | |
try: | |
# Get the single item from the filtered queryset | |
obj = queryset.get() | |
except queryset.model.DoesNotExist: | |
raise Http404(_("No %(verbose_name)s found matching the query") % | |
{'verbose_name': queryset.model._meta.verbose_name}) | |
return obj | |
def get_queryset(self): | |
""" | |
Return the `QuerySet` that will be used to look up the object. | |
Note that this method is called by the default implementation of | |
`get_object` and 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( | |
"%(cls)s is missing a QuerySet. Define " | |
"%(cls)s.model, %(cls)s.queryset, or override " | |
"%(cls)s.get_queryset()." % { | |
'cls': self.__class__.__name__ | |
} | |
) | |
return self.queryset.all() | |
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_template_names(self): | |
""" | |
Return a list of template names to be used for the request. May not be | |
called if render_to_response is overridden. Returns the following list: | |
* the value of ``template_name`` on the view (if provided) | |
* the contents of the ``template_name_field`` field on the | |
object instance that the view is operating upon (if available) | |
* ``<app_label>/<model_name><template_name_suffix>.html`` | |
""" | |
try: | |
names = super(SingleObjectTemplateResponseMixin, self).get_template_names() | |
except ImproperlyConfigured: | |
# If template_name isn't specified, it's not a problem -- | |
# we just start with an empty list. | |
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 isinstance(self.object, models.Model): | |
names.append("%s/%s%s.html" % ( | |
self.object._meta.app_label, | |
self.object._meta.model_name, | |
self.template_name_suffix | |
)) | |
elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model): | |
names.append("%s/%s%s.html" % ( | |
self.model._meta.app_label, | |
self.model._meta.model_name, | |
self.template_name_suffix | |
)) | |
# If we still haven't managed to find any template names, we should | |
# re-raise the ImproperlyConfigured to alert the user. | |
if not names: | |
raise | |
return names | |
def http_method_not_allowed(self, request, *args, **kwargs): | |
logger.warning('Method Not Allowed (%s): %s', request.method, request.path, | |
extra={ | |
'status_code': 405, | |
'request': request | |
} | |
) | |
return http.HttpResponseNotAllowed(self._allowed_methods()) | |
def __init__(self, **kwargs): | |
""" | |
Constructor. Called in the URLconf; can contain helpful extra | |
keyword arguments, and other things. | |
""" | |
# Go through keyword arguments, and either save their values to our | |
# instance, or raise an error. | |
for key, value in six.iteritems(kwargs): | |
setattr(self, key, value) | |
def options(self, request, *args, **kwargs): | |
""" | |
Handles responding to requests for the OPTIONS HTTP verb. | |
""" | |
response = http.HttpResponse() | |
response['Allow'] = ', '.join(self._allowed_methods()) | |
response['Content-Length'] = '0' | |
return response | |
def render_to_response(self, context, **response_kwargs): | |
""" | |
Returns a response, using the `response_class` for this | |
view, with a template rendered with the given context. | |
If any keyword arguments are provided, they will be | |
passed to the constructor of the response class. | |
""" | |
response_kwargs.setdefault('content_type', self.content_type) | |
return self.response_class( | |
request=self.request, | |
template=self.get_template_names(), | |
context=context, | |
using=self.template_engine, | |
**response_kwargs | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment