Skip to content

Commit

Permalink
Massive refactoring with view that can only be instatiated once. Only…
Browse files Browse the repository at this point in the history
… the base view works.
  • Loading branch information
bfirsh committed May 29, 2010
1 parent 0816ad5 commit 5424e16
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 596 deletions.
179 changes: 115 additions & 64 deletions class_based_views/base.py
@@ -1,52 +1,117 @@
from django import http
from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse, HttpResponseNotAllowed
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _

class View(object):
"""
Parent class for all views.
"""

def __init__(self, **kwargs):
self._load_config_values(kwargs,
context_processors = None,
mimetype = 'text/html',
template_loader = None,
template_name = None,
decorators = None,
)
if kwargs:
raise TypeError("__init__() got an unexpected keyword argument '%s'" % iter(kwargs).next())


template_name = None
context_processors = None
template_loader = None
decorators = None
allowed_methods = ['GET', 'POST']
strict_allowed_methods = False
allowed_formats = ['html']
format_mimetypes = {
'html': 'text/html'
}
default_format = 'html'

def __init__(self, *args, **kwargs):
self._has_been_called = False
super(View, self).__init__(*args, **kwargs)

def __call__(self, request, *args, **kwargs):
method = getattr(self, request.method.lower(), 'get')
if self.decorators is not None:
for decorator in reversed(self.decorators):
method = decorator(method)
return method(request, *args, **kwargs)
self._check_has_been_called()
self.request = request
callback = self.get_callback()
if callback:
return callback(*args, **kwargs)
allowed_methods = [m for m in self.allowed_methods if hasattr(self, m)]
return http.HttpResponseNotAllowed(allowed_methods)

def get_callback(self):
"""
Based on the request's HTTP method, get the callback on this class that
returns a response. If the method isn't allowed, None is returned.
"""
method = self.request.method.upper()
if method not in self.allowed_methods:
if self.strict_allowed_methods:
return None
else:
method = 'GET'
callback = getattr(self, method, getattr(self, 'GET', None))
if callback:
if self.decorators is not None:
for decorator in self.decorators:
callback = decorator(callback)
return callback

def GET(self, *args, **kwargs):
content = self.get_content(*args, **kwargs)
mimetype = self.get_mimetype()
return self.get_response(content, mimetype=mimetype)

def get_response(self, content, **httpresponse_kwargs):
"""
Construct an `HttpResponse` object.
"""
return http.HttpResponse(content, **httpresponse_kwargs)

def get(self, request, *args, **kwargs):
obj = self.get_object(request, *args, **kwargs)
template = self.get_template(request, obj)
context = self.get_context(request, obj)
mimetype = self.get_mimetype(request, obj)
response = self.get_response(request, obj, template, context, mimetype=mimetype)
return response
def get_content(self):
"""
Get the content to go in the response.
"""
format = self.get_format()
resource = self.get_resource()
return getattr(self, 'render_%s' % format)(resource)

def get_object(self, request, *args, **kwargs):
return None
def get_resource(self, *args, **kwargs):
"""
Get a dictionary representing the resource for this view.
"""
return {}

def get_template(self, request, obj):
def get_mimetype(self):
"""
Get the mimetype to be used for the response.
"""
return self.format_mimetypes[self.get_format()]

def get_format(self):
"""
Get the format for the content, defaulting to ``default_format``.
The format is usually a short string to identify the format of the
content in the response. For example, 'html' or 'json'.
"""
format = self.request.GET.get('format', self.default_format)
if format not in self.allowed_formats:
format = self.default_format
return format

def render_html(self, resource):
"""
Render a template with a given resource
"""
return self.get_template().render(self.get_context())

def get_template(self):
"""
Get a ``Template`` object for the given request.
"""
names = self.get_template_names(request, obj)
names = self.get_template_names()
if not names:
raise ImproperlyConfigured("'%s' must provide template_name." % self.__class__.__name__)
return self.load_template(request, obj, names)
raise ImproperlyConfigured("'%s' must provide template_name."
% self.__class__.__name__)
return self.load_template(names)

def get_template_names(self, request, obj):
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.
Expand All @@ -58,54 +123,40 @@ def get_template_names(self, request, obj):
else:
return self.template_name

def load_template(self, request, obj, names=[]):
def load_template(self, names=[]):
"""
Load a template, using self.template_loader or the default.
"""
return self.get_template_loader(request, obj).select_template(names)
return self.get_template_loader().select_template(names)

def get_template_loader(self, request, obj):
def get_template_loader(self):
"""
Get the template loader to be used for this request. Defaults to
``django.template.loader``.
"""
import django.template.loader
return self.template_loader or django.template.loader

def get_context(self, request, obj, context=None):
def get_context(self, *args, **kwargs):
"""
Get the context. Must return a Context (or subclass) instance.
Get the template context. Must return a Context (or subclass) instance.
"""
processors = self.get_context_processors(request, obj)
if context is None:
context = {}
return RequestContext(request, context, processors)
resource = self.get_resource(*args, **kwargs)
context_processors = self.get_context_processors()
return RequestContext(self.request, resource, context_processors)

def get_context_processors(self, request, obj):
def get_context_processors(self):
"""
Get the context processors to be used for the given request.
Get the template context processors to be used.
"""
return self.context_processors

def get_mimetype(self, request, obj):
"""
Get the mimetype to be used for the given request.
"""
return self.mimetype

def get_response(self, request, obj, template, context, **httpresponse_kwargs):
"""
Construct an `HttpResponse` object given the template and context.
"""
return HttpResponse(template.render(context), **httpresponse_kwargs)

def _load_config_values(self, initkwargs, **defaults):
"""
Set on self some config values possibly taken from __init__, or
attributes on self.__class__, or some default.
"""
for k in defaults:
default = getattr(self.__class__, k, defaults[k])
value = initkwargs.pop(k, default)
setattr(self, k, value)
def _check_has_been_called(self):
if self._has_been_called:
raise ImproperlyConfigured("'%(class)s' has been instantiated in "
"the URLconf. Class-based views should only be passed as "
"classes. Try changing '%(class)s()' to '%(class)s'." % {
'class': self.__class__.__name__
})
self._has_been_called = True

14 changes: 5 additions & 9 deletions class_based_views/detail.py
Expand Up @@ -10,15 +10,11 @@ class DetailView(View):
By default this is a model instance lookedup from `self.queryset`, but the
view will support display of *any* object by overriding `get_object()`.
"""

def __init__(self, **kwargs):
self._load_config_values(kwargs,
queryset = None,
slug_field = 'slug',
template_object_name = None,
template_name_field = None,
)
super(DetailView, self).__init__(**kwargs)

queryset = None
slug_field = 'slug'
template_object_name = None
template_name_field = None

def get_object(self, request, pk=None, slug=None, object_id=None, queryset=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion class_based_views/edit.py
Expand Up @@ -2,7 +2,7 @@
from class_based_views import View, ListView, DetailView

class FormView(View):
def post(self, request, *args, **kwargs):
def POST(self, request, *args, **kwargs):
obj = self.get_object(request, *args, **kwargs)
form = self.get_form(request, obj, *args, **kwargs)
if form.is_valid():
Expand Down
58 changes: 10 additions & 48 deletions class_based_views/list.py
Expand Up @@ -10,18 +10,14 @@ class ListView(View):
`self.items`, but if it's a queryset set on `self.queryset` then the
queryset will be handled correctly.
"""

def __init__(self, **kwargs):
self._load_config_values(kwargs,
paginate_by = None,
allow_empty = True,
template_object_name = None,
queryset = None,
items = None,
)
super(ListView, self).__init__(**kwargs)

def get(self, request, *args, **kwargs):

paginate_by = None
allow_empty = True
template_object_name = None
queryset = None
items = None

def GET(self, request, *args, **kwargs):
page = kwargs.get('page', None)
paginator, page, items = self.get_items(request, page)
template = self.get_template(request, items)
Expand All @@ -40,7 +36,7 @@ def get_items(self, request, page):
elif hasattr(self, 'items') and self.items is not None:
items = self.items
else:
raise ImproperlyConfigured("'%s' must define 'queryset' or 'items'" \
raise ImproperlyConfigured("'%s' must define 'queryset' or 'items'"
% self.__class__.__name__)

return self.paginate_items(request, items, page)
Expand Down Expand Up @@ -118,21 +114,6 @@ def get_context(self, request, items, paginator, page, context=None):
template_obj_name = self.get_template_object_name(request, items)
if template_obj_name:
context[template_obj_name] = items

# If we're paginated, populate the context with legacy pagination
# stuff. In 1.2 `legacy_context` will default to False, and in 1.3
# these context variables will be removed.
if paginator is not None and getattr(self, 'legacy_context', True):
import warnings
warnings.warn(
"'%(cls)s' is using legacy context variables which will be "\
"removed in a future version of Django. Set "\
"'%(cls)s.legacy_content' to False to stop using these "\
"context variables." % {'cls': self.__class__.__name__},
PendingDeprecationWarning
)
context.update(self._get_legacy_paginated_context(paginator, page))

return context

def get_template_object_name(self, request, items):
Expand All @@ -145,23 +126,4 @@ def get_template_object_name(self, request, items):
return smart_str(items.model._meta.verbose_name_plural)
else:
return None

def _get_legacy_paginated_context(self, paginator, page):
"""
Legacy template context stuff. New templates should use page_obj
to access this instead.
"""
return {
'is_paginated': page.has_other_pages(),
'results_per_page': paginator.per_page,
'has_next': page.has_next(),
'has_previous': page.has_previous(),
'page': page.number,
'next': page.next_page_number(),
'previous': page.previous_page_number(),
'first_on_page': page.start_index(),
'last_on_page': page.end_index(),
'pages': paginator.num_pages,
'hits': paginator.count,
'page_range': paginator.page_range,
}

1 change: 1 addition & 0 deletions class_based_views/tests/templates/views/about.html
@@ -0,0 +1 @@
<h1>About</h1>

0 comments on commit 5424e16

Please sign in to comment.