Simple Django Class based CRUD views

Class based CRUD views in django have been around for a while, but I haven’t been using them because every time I tried I ended up with more code that seemed no better than the old way. I just started a small project from scratch and decided to give it a go again.

The basic idea is pretty simple. There are default views like DetailView, ListView, UpdateView etc. you can build your views from. Then in urls.py you just say MyDerivedView.as_view() for the view function.

So before it might have been:

url (
    regex = '^detail/(?P<pk>\d+)/$',
    view = 'scrud.view.detail_view',
    name = 'scrud_detail'
),

Now its

url (
    regex = '^detail/(?P<pk>\d+)/$',
    view = ScrudDetailView.as_view(),
    name = 'scrud_detail'
),

The trick comes in how we define these views. The defaults are fine if you aren’t doing anything fancy. There are lots of nice parameters to the base classes you can modify as shown in the docs.

urlpatterns = patterns('',
    (r'^publishers/$', ListView.as_view(
        model=Publisher,
        context_object_name="publisher_list",
    )),
)

Personally, I don’t like gumming up my urls.py with that stuff so I go ahead and make a views.py and define new classes there.

Either way lets try to see how this works for a simple model.

class Scrud(models.Model):
    foo = models.IntegerField(default=0)
    bar = models.CharField(max_length=20)
    owned_by = models.ForeignKey(User, blank=True)
    last_change = models.DateTimeField(auto_now=True)
 
    def get_absolute_url(self):
        return reverse('scrud_detail', args=[self.pk])
 
    def __unicode__(self):
        return u"Foo:%d Bar:%s  Owner:%s Date:%s" %
        (self.foo, self.bar, self.owned_by, self.last_change)

The get_absoulte_url() method is going to be what some of the views use by default.

To make things easier lets just create a Mixin class we can use for all the views we want.

class ScrudMixin(object):
    model = Scrud
    def get_success_url(self):
        return reverse('scrud_list')
    def get_queryset(self):
        return Scrud.objects.filter(owned_by=self.request.user)

This is nice because it will limit the queryset to objects owned by the user as well as set up the defaults needed by each of the generic CRUD (and List) views.

We can force login by changing ScrudMixin(object) to ScrudMixin(LoginRequiredMixin) and add something like:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
 
class LoginRequiredMixin(object):
    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)

Now we can define views like:

class ScrudListView(ScrudMixin, ListView):
    pass
class ScrudDetailView(ScrudMixin, DetailView):
    pass
class ScrudCreateView(ScrudMixin, CreateView):
    pass
class ScrudDeleteView(ScrudMixin, DeleteView):
    pass
class ScrudUpdateView(ScrudMixin, UpdateView):
    pass

and use them in urls.py like:

urlpatterns = patterns('',
       url (
           regex = '^list/$',
           view =  ScrudListView.as_view(),
           name = 'scrud_list'
       ),
 
       url (
           regex = '^detail/(?P\d+)/$',
           view =  ScrudDetailView.as_view(),
           name = 'scrud_detail'
       ),
       url (
           regex = '^create/$',
           view =  ScrudCreateView.as_view(),
           name = 'scrud_create'
       ),
 
       url (
           regex = '^delete/(?P\d+)/$',
           view =  ScrudDeleteView.as_view(),
           name = 'scrud_delete'
       ),
       url (
           regex = '^update/(?P\d+)/$',
           view =  ScrudUpdateView.as_view(),
           name = 'scrud_update'
       ),
)

Oh yeah. We are going to need some templates. You can override the names if you like but you’ll need something like:
scrud_list.html

{% for object in object_list %}
  <a href="{{ object.get_absolute_url }}">{{ object }}</a>
{% endfor %}

scrud_detail.html

{{ object }}

scrud_form.html

<form action="." method="POST">{{ form }}{% csrf_token %}
   <input type="submit" value="Go" />
</form>

scrud_confirm_delete.html

<h1>Delete This?</h1>
{{ object }}
<form action="." method="post">{% csrf_token %}
    <input type="submit" value="Delete" />
</form>

Now there should be a fully working crud system for your object. Read some hackernews to celebrate.

When you come back you will realize that you probably want something a little different. Like for example. Maybe you want the owned_by field to be set automatically based on the user. You don’t want it in the default form. No problem you can override the form with your own custom model form.

class ScrudForm(forms.ModelForm):
    class Meta:
        model = Scrud
        exclude = ('owned_by')

The top of your ScrudMixin is now looking like:

class ScrudMixin(LoginRequiredMixin):
    model = Scrud
    form_class = ScrudForm

Which takes care of not letting the user change the owner but doesn’t actually set it. For that you need to add one last thing your ScrudMixin class, a form_valid override. This changes what happens when the form is valid so that the field can be set correctly.

from django.views.generic.edit import ModelFormMixin
def form_valid(self, form):
    # save but don't commit the model form
    self.object = form.save(commit=False)
    # set the owner to be the current user
    self.object.owned_by = self.request.user
    #
    # Here you can make any other adjustments to the model
    #
    self.object.save()
    # ok now call the base class and we are done.
    return super(ModelFormMixin, self).form_valid(form)

I hope this at least helps some people get started using class based CRUD views. Better yet, I hope someone will tell me an even better way.

4 Replies to “Simple Django Class based CRUD views”

Leave a Reply

Your email address will not be published. Required fields are marked *