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.
Leave a Reply