Django Class Based View for both Create and Update

GrantU picture GrantU · Jun 19, 2013 · Viewed 17.3k times · Source

Say I want to create a Class Based View which both updates and creates an object. From a previous question I worked out I could do one of the following things:

1) Use 2 generic views CreateView and UpdateView which I think would mean having two URL's pointing to two different classes.

2) Use a class based view which inherits base View, which I think would mean having two URL's pointing to just 1 class (I created which inherits View).

I have two questions:

a) Which is better?

b) ccbv.co.uk shows a base View, but I don't see any get, post etc methods documented, is this correct?

Answer

scubabuddha picture scubabuddha · Jun 20, 2015

I ran into a situation where I wanted something like this. Here's what I came up with (do note that if you're trying to use it as an update view and it can't find the requested object, it'll behave as a create view rather than throwing a 404):

from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import ModelFormMixin, ProcessFormView

class CreateUpdateView(
    SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView
):

    def get_object(self, queryset=None):
        try:
            return super(CreateUpdateView,self).get_object(queryset)
        except AttributeError:
            return None

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(CreateUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(CreateUpdateView, self).post(request, *args, **kwargs)

It turns out that UpdateView and CreateView inherit from exactly the same classes and mixins. The only difference is in the get/post methods. Here's how they're defined in the Django source (1.8.2):

class BaseCreateView(ModelFormMixin, ProcessFormView):
    """
    Base view for creating an new object instance.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).post(request, *args, **kwargs)


class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
    """
    View for creating a new object instance,
    with a response rendered by template.
    """
    template_name_suffix = '_form'


class BaseUpdateView(ModelFormMixin, ProcessFormView):
    """
    Base view for updating an existing object.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).post(request, *args, **kwargs)


class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
    """
    View for updating an object,
    with a response rendered by template.
    """
    template_name_suffix = '_form'

As you can see, the CreateView get and post methods set self.object = None while the UpdateView sets it to self.get_object(). All I've done is combine those two in my CreateUpdateView.get_object method which attempts to call the parent class' get_object and returns None rather than raising an exception if there is no object.

To serve a 404 page when used as an update view, you could probably override as_view and pass it an update_only boolean argument. If update_only is True and the view can't find the object, then raise the 404.