This talk is a comparison of Rails and Django. All of the code examples
come from a Django application which can be found here:
github.com/georgebrock/swte-demo
They’re not really the enemy!
I'm not going to be trying to pick a winner here.
I will focus on areas where I think Django does better, but only
because those are the places where we have the most to learn.
You say ‘tomato’,
I say ‘tomato’
Django and Rails are solving the same problem: Abstracting away the
common, boring bits of building a web site.
They do that in very different ways.
We're going to be looking at unfamiliar solutions to
very familiar problems.
Seeing alternatives is good for us, even when they're not directly
applicable to our day to day work.
demo is the name of the projects; the demo
directory mostly contains project level configuration.
apps are groups of associated models, views, and other
code. In this project blog is an example of an app.
manage.py is the command line interface to the Django
project, for example python manage.py runserver is the
equivalent of rails server.
templates contains HTML templates. The location of this
directory is configurable and may vary from project to project.
Models
from django.db import models
class BlogPost(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
published = models.BooleanField()
publish_date = models.DateTimeField()
BlogPost inherits from django.db.models.Model;
this is the equivalent of ActiveRecord::Base
The schema is derived from the model. This might be familiar if you've
used Data Mapper with Rails.
Displaying blog posts
Models on their own aren't very interesting: we need to display them
somehow. In Django, we do this using views
Views are like actions
Request
View
Response
A view is a callable which takes a Request and returns a
Response.
A URL pattern is routed to a single view. The view is responsible for
handling all requests to that URL, regardless of the HTTP method they
used (GET, POST, etc.)
Same old, same old
View code, like Rails action code, gets very repetitive. How Django
removes the repetition by providing generic class-based views; classes
we can use as the basis of our views.
from django.views.generic import DetailView
from apps.blog.models import BlogPost
class BlogPostDetailView(DetailView):
model = BlogPost
We have specified the model to use, the DetailView has
done the rest.
Django is providing a lot of conventions here:
Finding the BlogPost ID in the URL
Loading the BlogPost instance
Deciding what to call the variable used to pass the
BlogPost to the template.
Deciding which template to render.
Is this too much convention? It's much more than Rails does for us.
Let's see what happens when we want to customise the view.
from django.views.generic import DetailView
from apps.blog.models import BlogPost
class BlogPostDetailView(DetailView):
model = BlogPost
queryset = BlogPost.objects.filter(
published=True,
)
A QuerySet is equivalent to an
ActiveRecord::Relation: it describes a query, but doesn't
execute it until we ask it for results.
The DetailView uses its QuerySet as the
starting point for searching for the BlogPost to display.
This is set at class definition time. What if we want to vary the
QuerySet at runtime based on some property of the
request?
from django.views.generic import DetailView
from apps.blog.models import BlogPost
class BlogPostDetailView(DetailView):
model = BlogPost
def get_queryset(self):
if self.request.GET.get("preview"):
return BlogPost.objects.all()
else:
return BlogPost.objects.filter(
published=True,
)
We can create blog posts using another of Django's generic class-based
views: the CreateView.
The CreateView responds to a GET request by displaying
a form, and to a POST request by creating an object. It's like the
new and create actions in a Rails controller
rolled into one.
Django has a very capable admin system, which is configured in the
demo app to provide a comparison. The techniques I'm showing here
would be more commonly used for user created data, like comments. I'm
limiting us to one model here for the sake of simplicity.
Forms
Form
Field
Widget
A concrete example
Form
DateField
DateSelectWidget
CreateView doesn't interact directly with the model. It
goes through a Form.
The Form is responsible for rendering the form interface
(when the user makes a GET request) and for processing the submitted
data (when the user makes a POST request).
A Form is more or less just an ordered list of
Field instances.
A Field understands types and type conversion, e.g. a
DateField understands how to convert between strings
and dates.
A Widget understands HTML forms and the HTTP requests
they generate.
A CreateView will generate a form for us automatically,
but let's use a custom form to make it more clear what's going on.
from django.views.generic import CreateView
from apps.blog.models import BlogPost
from apps.blog.forms import BlogPostForm
class BlogPostCreateView(CreateView):
model = BlogPost
form_class = BlogPostForm
from django import forms
from django.forms.extras.widgets import \
SelectDateWidget
from apps.blog.models import BlogPost
class BlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
exclude = ("published", )
widgets = {
"publish_date": SelectDateWidget(),
}
The exclude attribute tells the form if there are any
fields in the model which should not be included in the form.
Because the form handles both the UI rendering and processes the
results, it's unlikely that a developer would forget to remove a
sensitive field here.
The widgets attribute lets us customise the widgets that
each field uses. Sensible defaults are provided for each field
type.