Skip to content
Ethan Soergel edited this page May 19, 2013 · 1 revision

This will be about using class-based views that are closely coupled to a database. First off, you should be working inside a particular app. I'm working in "cities." The database models are already constructed, but you need to add them to the admin site to put in some sample data. Make an "admin.py" file in your app. To start, just register the models (you can get a lot fancier with this, but here's the most basic way (this is my entire admin.py file):

from django.contrib import admin

from .models import City, Cohort

admin.site.register(City)
admin.site.register(Cohort)

Now login to the admin site and add some sample data to work with.

You should be able to map urls to views already, if not, look over the examples in apps/cities/urls.py and make sure you understand it. Notice that for CBVs, you have to call ViewName.as_view() in your urlconf. If you want to make CBVs feel less like magic, that's a good place to start. Absolutely have a tab open to ccbv.co.uk while building CBVs, it's my favorite way to remember specifics about each view. The simplest complete view in there is TemplateView, so if you want to understand it, walk through the execution, starting with .as_view().

Basically each CBV has a bunch of attributes defined at the top to control stuff like which template to use, what model, and other specifics like that. This is about as simple as a CBV can be:

class ClassyDummyView(TemplateView):
    template_name = "dummy.html"

template_name here is an attribute that you override to direct the view to use whatever template you want. The power of CBVs is that you can take the default functionality and override stuff. Each CBV inherits from other CBVs and mixins, both of which are just python classes with attributes and methods. From ccbv, you can see that TemplateView has three ancestors, View, TemplateResponseMixin, and ContextMixin. Each of these brings along a set of methods and attributes, so you can mix and match what you want to create your view. The methods are all pretty distinct, which is nice.

ContextMixin for example, provides no attributes, and one method - get_context_data(). Any view that is going to pass context to the templates will use this method, so just add ContextMixin (or any of its descendants) to your view definition to get that method.

Back to the entry point - as_view. This returns a function called view(), which returns dispatch(). Dispatch returns another function - whichever handler is appropriate, probably get() for basic stuff. This goes on for a while, the methods call other ones as the request is processed.

Let's say you want to print the context data to the console while you're figuring out how the view works. ("What exactly gets passed to the template and how can I use it?"). Note that you should not leave print statements in your views beyond testing, and you should really be using logging, but for the sake of this example I'll use a print statement. So we want to do something with context, right? Go to the part of the view that deals with context - get_context_data() - and override it with your own version.

def get_context_data(self, **kwargs):
    "This is to print your context variables during testing ONLY"
    context = super(MyViewName, self).get_context_data(**kwargs)
    print context
    return context

This first line calls the higher up version of get_context_data, just change MyViewName to your view name, and "context" will store whatever get_context_data WOULD return if you hadn't overridden it. This way you don't lose the other versions of get_context_data. Then you print that context and return it. This is when it's essential to use the ccbv inspector to see what each method does (return a context dictionary, in this case) so your overriding it doesn't totally break that functionality unless you want it to.

Using database-based CBVs

I'm gonna walk through the use of DetailView to display the detail of a particular database object. (ListView shows a list of many objects, UpdateView will update a specific object, etc...)

Detail view needs to know a few things, what database model it's returning, and how to find a specific object that is an instance of that model. In my example, the model is City, and we're looking for the details of a particular city, say... Boston.

class City(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()
    organizers = models.ManyToManyField(UserProfile)

DetailView will render the template you specify with a context variable representing that object. By contrast, ListView will return a context variable with a list of a bunch of City objects. The minimum you need to do to make DetailView work is this:

class CityDetailView(DetailView):
    model = City

Okay, it knows to return a city, but how does it know WHICH city to return? This is a get request, so look at the get method:

def get(self, request, *args, **kwargs):
    self.object = self.get_object()
    context = self.get_context_data(object=self.object)
    return self.render_to_response(context) 

It gets the object using a method called get_object(), gets the context using get_context_data, and returns a rendered response using render_to_response. get_object clearly finds the appropriate city, so let's look at it. It's got a helpful docstring:

Returns the object the view is displaying.

By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.

Well how does the request tell you which object to return? Here's my urlconf:

url(
    r"^(?P<name>[\w-]+)/$",
    TemplateView.as_view(),
    name="city_detail"
),

The regex captures the name of the city, which you can look up in the database to find the appropriate object. In other examples, the url might capture the primary key ("pk" also known as the "id") of the object you want. Look at the code for get_object - it looks for a url kwarg (key word argument) called "pk", if that's not in there, it looks for "slug", if that's not in there, it raises an error.

Look at the view attributes - there are a few lines relevant to this:

pk_url_kwarg = 'pk'
slug_field = 'slug'
slug_url_kwarg = 'slug'

by overriding pk_url_kwarg in your view definition, you can tell it to look for a different url kwarg to find the primary key (there are cases where that might be necessary, but generally you should just call that pattern pk in your regex).

Back to my example, I'm looking up objects by city name, not pk. A slug is a unique field that can be used to identify an object in the database (the term comes from Django's roots as an online newspaper back-end, a slug is a short descriptive phrase describing an article). So the slug_field I'm using is "name", and in the url I'm capturing that pattern and calling it "name". Here's what the view looks like now:

class CityDetailView(DetailView):
    model = City
    slug_field = "name"
    slug_url_kwarg = "name"

Read through get_object to see how it uses these attributes. Okay, so now the view can find a particular object, but how do I know what it's going to be called in the template, furthermore, how does it know which template to render? Read the view. There's an attribute that says "context_object_name = None" and a method called get_context_object_name(). I'll leave it to you to see how those work (there are two possible paths), but it's pretty simple. Note that when get() calls get_context_data it passes in a kwarg (self.get_context_data(object=self.object)). get_context_data passes all kwargs through as part of the context, so you can actually access the object in your templates using the variable {{object}} in addition to whatever name get_context_object_name() returns. In practice, however, it's best to use the latter, as {{city}} is much clearer than {{object}}.

Lastly, as with the simpler views, you can simple define a template_name as a class attribute, but many of the more complex views (including DetailView and the other database based views) have logic to find find the template name so you don't need to manually define it. The default is /_detail.html as defined in get_template_names. In this case all I'd have to do is write a template called city_detail.html and put it in with all my other templates (which should be in apps/cities/templates/cities/) and DetailView will find it without me needing to specify a template name.

Anyways, hopefully this will give you an idea of how CBVs work, they mostly do what you expect, but if you're not sure ("How does FormView know which form to use?"), check the view attributes and methods and you'll probably be able to find where that happens ("oh, there's an attribute "form_class" and a method called get_form, it's probably in there"). The generic CBVs take a bunch of common functionalities, and break them up logically into different mixins and views. Mixins are basically just groupings of functionalities that you can piece together to make complete views. If for example, I want to use that custom version of get_context_data that prints the context, I can make a mixin and add it to views while I'm testing them out, without needing to copy-paste the code over and over again.

Clone this wiki locally