If you haven't yet tried or played around with Django's class-based views, I highly recommend them over traditional function-based views. At first, I was terrified of class-based views, not knowing how they worked or how to properly extend them without breaking their functionality. After doing a few projects using class-based views, I can safely say that I am now comfortable with using them in every future Django project as the main view type.
Firstly, don't rely on Django's documentation for class-based views, they are not too helpful when actually modifying their behavior to suit your particular scenario. To enable login_required, do so in your urls.py file, as it is much simpler and easier to locate at a later date which views are actually decorated. Creating a special ProtectedView as exampled in the Django documentation isn't your best route for CRUD views, it may work fine to extend TemplateView, but don't use it for CRUDs, it will only add to your overall development time.
I believe a simple example of how to use these class-based views to your advantage will well suit this article, here is a simple example of a TodoList with user ownership checks:
class TodoList(ListView): model = Todo all_items = False def get_queryset(self): queryset = super(TodoList, self).get_queryset().filter(user=self.request.user) if not self.all_items: return queryset.filter(complete=False) return queryset class TodoDetails(DetailView): model = Todo def get_queryset(self): return super(TodoDetails, self).get_queryset().filter(user=self.request.user) class TodoCreate(CreateView): model = Todo form_class = TodoForm def form_valid(self, form): self.object = form.save(commit=False) self.object.user = self.request.user self.object.save() return super(ModelFormMixin, self).form_valid(form)
This would be placed into your views.py, you may notice something interesting done with the TodoList. There is an extra model-level variable called all_items which is a boolean. Here is how you could use this in your urls.py:
urlpatterns = patterns('', url(r'^todo/$', login_required(TodoList.as_view()), name='todo-listl'), url(r'^todo/all/$', login_required(TodoList.as_view(all_items=True)), name='todo-list-all'), url(r'^todo/add/$', login_required(TodoCreate.as_view()), name='todo-add'), url(r'^todo/(?P<pk>\d+)/$', login_required(TodoDetails.as_view()), name='todo-view'), )
The trick here to extending, is to create a model-level variable which is assigned through your urls.py, these model-level variables cannot be assigned any other way from the request. You can of course create new variables at will, if need them throughout your view. Here are some class variables which are helpful to know about when extending class-view functionality. These variables aren't well documented, but they are there, and the default generic views use them:
- This is a standard dictionary accessed like self.kwargs['slug']. You can use this to access variables captured in the URL. In function-based views, these are normally based directly to the function as keyword parameters. Class-based views capture them in the same way.
- If your view access or creates an object, then it will live in self.object, you are free to reference it in most functions within your view class. In the Todo example, it is modified to add the current user to the object before saving.
- Ah, our good old request variable is of course available to us in every function in a view class, it can be accessed through self.request, it is used in the Todo example to obtain the current user object.
There are other class-specific variables as well, and I do believe that self.args is also available. Hopefully this short article helps developers new and old of Django better understand how class-based views work. I plan on writing additional articles on class-based views in the future.