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)
|
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.
#
That's exactly the approach of django-modelviews (see documentation and code). Do not hesitate to contact me if you want to participate!
#