Here's an example of writing generic views in an object-oriented style, which allows for very fine-grained customization via subclassing. The snippet includes generic create and update views which are backwards compatible with Django's versions.
To use one of these generic views, it should be wrapped in a function that creates a new instance of the view object and calls it:
def create_object(request, *args, **kwargs):
return CreateObjectView()(request, *args, **kwargs)
If an instance of one of these views is placed directly in the URLconf without such a wrapper, it will not be thread-safe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | from django.template import RequestContext, loader
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.core.exceptions import ObjectDoesNotExist
from django import forms
class CreateObjectView(object):
"""
Generic view to create instances of a model.
"""
def __call__(self, request, model=None, form_class=None,
template_name=None, extra_context=None,
post_save_redirect=None):
"""
Create a new object using a ModelForm. Accepts arguments:
``request``
The HttpRequest object.
``model``
Model type to create (either this or form_class is
required)
``form_class``
ModelForm subclass to use (either this or model is
required)
``template_name``
name of template to use, or list of templates - defaults
to <app_label>/<model_name>_form.html
``extra_context``
dictionary of items and/or callables to add to template
context.
``post_save_redirect``
URL to redirect to after successful object save. If
post_save_redirect is None or an empty string, default is
to send to the instances get_absolute_url method or in its
absence, the site root.
"""
self.request = request
(self.model, self.form_class) = self.get_model_and_form_class(
model, form_class)
form = self.get_form(request, self.form_class)
if request.method == 'POST' and form.is_valid():
return self.get_redirect(post_save_redirect, self.save_form(form))
c = self.apply_extra_context(extra_context,
self.get_context(request,
{'form': form}))
t = self.get_template(self.model, template_name)
return self.get_response(t, c)
def get_model_and_form_class(self, _model, form_class):
"""
Return a model and form class based on model or form_class
argument.
"""
if _model is None:
try:
_model = form_class._meta.model
except AttributeError:
raise ValueError("%s requires either model or form_class" %
(self.__class__.__name__,))
if form_class is None:
class Meta:
model = _model
class_name = _model.__name__ + 'Form'
form_class = forms.models.ModelFormMetaclass(
class_name, (forms.ModelForm,), {'Meta': Meta})
return (_model, form_class)
def apply_extra_context(self, extra_context, context):
"""
Add items from extra_context dict to the given context,
calling any callables in extra_context. Return the updated
context.
"""
extra_context = extra_context or {}
for key, value in extra_context.iteritems():
if callable(value):
context[key] = value()
else:
context[key] = value
return context
def get_form_kwargs(self, request):
"""
Get dictionary of arguments to construct the appropriate
``form_class`` instance.
"""
if request.method == 'POST':
return {'data': request.POST, 'files': request.FILES}
return {}
def get_form(self, request, form_class):
"""
Return the appropriate ``form_class`` instance based on the
``request``.
"""
return form_class(**self.get_form_kwargs(request))
def save_instance(self, obj):
"""
Save and return model instance.
"""
obj.save()
return obj
def save_form(self, form):
"""
Save form, returning saved object.
"""
return self.save_instance(form.save(commit=False))
def get_redirect(self, post_save_redirect, obj):
"""
Return a HttpResponseRedirect based on ``post_save_redirect``
argument and just-saved object ``obj``.
"""
if not post_save_redirect:
if hasattr(obj, 'get_absolute_url'):
post_save_redirect = obj.get_absolute_url()
else:
post_save_redirect = "/"
return HttpResponseRedirect(post_save_redirect)
def get_template(self, model, template_name):
"""
Return a template to use based on ``template_name`` and ``model``.
"""
template_name = template_name or "%s/%s_form.html" % (
model._meta.app_label, model._meta.object_name.lower())
if isinstance(template_name, (list, tuple)):
return loader.select_template(template_name)
else:
return loader.get_template(template_name)
def get_context(self, request, dictionary):
"""
Return a context instance with data in ``dictionary``.
"""
return RequestContext(request, dictionary)
def get_response(self, template, context_instance):
"""
Return a HttpResponse object based on given request, template,
and context.
"""
return HttpResponse(template.render(context_instance))
class UpdateObjectView(CreateObjectView):
"""
Generic view to update instances of a model.
"""
def __call__(self, request, object_id=None, slug=None, slug_field='slug',
*args, **kwargs):
"""
Update an existing object using a ModelForm. Accepts same
arguments as CreateObjectView, and also:
``object_id``
id of object to update (either this or slug+slug_field is
required)
``slug``
slug of object to update (either this or object_id is
required)
``slug_field``
field to look up slug in (defaults to ``slug``)
"""
self.object_id = object_id
self.slug = slug
self.slug_field = slug_field
return super(UpdateObjectView, self).__call__(request, *args, **kwargs)
def get_model_and_form_class(self, *args, **kwargs):
"""
Wrap parent ``get_model_and_form_class`` and save the model
class so we can get to it in get_form_args.
"""
ret = super(UpdateObjectView, self).get_model_and_form_class(*args,
**kwargs)
self.model = ret[0]
return ret
def get_form_kwargs(self, request):
instance = self.lookup_object(self.model, self.object_id,
self.slug, self.slug_field)
kwargs = super(UpdateObjectView, self).get_form_kwargs(request)
kwargs['instance'] = instance
return kwargs
def lookup_object(self, model, object_id, slug, slug_field):
"""
Find and return an object of type ``model`` based on either
the given ``object_id`` or ``slug`` and ``slug_field``.
"""
lookup_kwargs = {}
if object_id:
lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
raise ValueError("%s requires object_id or slug+slug_field"
% (self.__class__.__name__,))
try:
return model.objects.get(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404, "No %s found for %s" % (model._meta.verbose_name,
lookup_kwargs)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
I'm a big fan of this approach, but I don't think you're taking advantage of subclassing enough in this code. Instead of passing configuration parameters to the _call_ method I suggest having them as class properties, and then requiring that people subclass your generic views to customise them.
For example, your code could look like this:
Then your users would just have to do this:
#
Thanks Simon. You're totally right, of course. I briefly thought of doing that, but for some reason I had in mind as a goal to preserve backwards-compatibility with Django's generic views. Now I can't actually think of a single good reason to do that.
#
Why have separate classes for Create, Update etc - each with their own __call__?
If you just have one class (say, Views), you could have create, update methods etc on it.
The URL dispatcher can call a 'instance.verb' method just as well as it can __call__ a 'verb_instance' class.
Firstly, you only have to instantiate one class for the URL dispatcher, and secondly (as Simon does), the class properties are only provided once, regardless of how many view verbs you have.
#
Oh - and now (v1.1) that the URL dispatcher include() can take sequences of patterns, the same class can even generate standardised URLs to tie to its own views.
#
quite efficient,cuts out boiler-plate code,@simon thanks alot for the insight:-)
#
Please login first before commenting.