How can I make a Generic Class Based Create View for a Model?

Lockjaw picture Lockjaw · Apr 9, 2011 · Viewed 15.2k times · Source

What I'm trying to do is Django boilerplate for functional views. Any help here is very much appreciated, as the docs show examples for the template view and list view, but I've found very little for the model-based generic views. Am I missing an example in the docs?

I have a model that represents an entry in a calendar. There's a foreign key to another object (not a user) that owns the entry. What I want to do is simply to create the entry, ensuring that the entry's foreign key is properly set and then return the user to the appropriate calendar page.

I don't know, though, how class-based generic views receive their URL arguments and I'm not clear on how to set the success_url so that it reuses the id that was originally passed to the creation URL. Again, thank you in advance for your help.

What I'm asking, essentially, is, what is the class-based generic view equivalent of the following:

def create_course_entry(request, class_id):
'''Creates a general calendar entry.'''
if request.method == 'POST':
    form = CourseEntryForm(request.POST)
    if form.is_valid():
        new_entry = form.save(commit=False)
        new_entry.course = Class.objects.get(pk=class_id)
        new_entry.full_clean()
        new_entry.save()
        return HttpResponseRedirect('/class/%s/calendar/' % class_id)
else:
    form = CourseEntryForm()

return render_to_response('classes/course_entry_create.html',
        { 'class_id': class_id, 'form': form, },
        context_instance=RequestContext(request))

Answer

Matt Austin picture Matt Austin · May 8, 2011

You could subclass the edit.CreateView generic view, set the class/course in the dispatch() method, and save this by overriding the form_valid() method:

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.views.generic.edit import CreateView


class CourseEntryCreateView(CreateView):
    form_class = CourseEntryForm
    model = CourseEntry

    def dispatch(self, *args, **kwargs):
        self.course = get_object_or_404(Class, pk=kwargs['class_id'])
        return super(CourseEntryCreateView, self).dispatch(*args, **kwargs)

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.course = self.course
        self.object.save()
        return HttpResponseRedirect(self.get_success_url())

If you're not customising the CourseEntryForm ModelForm, then you can leave out the form_class property.

Unfortunately, it is not possible to call super() in the form_valid() method - due to the way it has been written would mean the object would be saved again.

If you need the Class (course?) instance in the template context, then you can add this in the get_context_data() method:

    def get_context_data(self, *args, **kwargs):
        context_data = super(CourseEntryCreateView, self).get_context_data(
            *args, **kwargs)
        context_data.update({'course': self.course})
        return context_data