00:00:16.320
I'll make a start. My name's George, and I work for Thoughtbot. We're a web and mobile consultancy. Most of the web stuff we do is with Rails, but I haven't always worked for Thoughtbot, and I haven't always worked with Rails. I spent more than a year writing Django for a couple of different startups. I found moving from Rails to Django and then back to Rails again to be really fascinating. There were lots of interesting comparisons and differences that came up in that process. That's why I wanted to talk to you all about Django today.
00:00:42.840
Before we start properly, I want to clarify something. I've titled this "Sleeping with the Enemy." Django people aren't really the enemy; they're actually lovely if you get to know them. This talk will be a comparison, focusing on places where I think Django has made better decisions. That's only because this is RailsConf, and that's where we can learn the most. If this were DjangoCon, then I would be discussing where Rails has made better decisions. There's no favoritism here; it's just a comparison and a way we can learn.
00:01:06.000
But if I'm not trying to pick a winner or say which framework is best, then why are we doing this? Well, Rails and Django solve similar problems, but in different ways and different languages. This is probably literally true for this audience. I say tomato, and most of you say tomato. It's interesting seeing those differences in language; the same idea can be expressed in different ways. Similarly, when we look at Rails and Django, we see the same problems. We see all the boring, repetitive, or tricky bits of web development being abstracted for us, but we see it done in different ways.
00:01:36.320
We're going to look at Django, and we'll see unfamiliar solutions to very familiar problems, which hopefully can help us understand those problems better and improve Rails and our own apps. I mentioned in my abstract that I was going to do some live coding, but everyone said, "What? That's a terrible idea!" So I've chickened out on that.
00:01:59.760
All the code you're going to see is from a GitHub repository, which I'll link to at the end. This is a working application. The real reason for not doing the live coding is that there's quite a bit of configuration to do when you first set up a Django app. Watching me type database configuration for 10 minutes really isn't what anyone's here for, so we'll just look at the interesting bits of the code. You can verify that it all works by looking at GitHub later.
00:02:33.440
Now, let's start with the anatomy of a Django project. The project that I have on GitHub is called 'Demo,' and it's a simple blog. First of all, we have a directory called 'demo,' which contains project-level settings. This is where all that database config that I had to type lives. There's a URLs file which is like the routes file in a Rails app, and there's a WSGI file which is like the config.ru file in a Rails app. Then, we have some apps. 'Apps' is where the meat of a Django project lives; this is where the models and the views are organized, but they're grouped together into different apps. This project is just a blog, so it only has one app, called 'blog.' Inside that app, there's a blog post model and any other models we might need.
00:03:05.040
We also have manage.py, which is the command line interface for a Django app. In Rails, you type 'rails server'; in Django, you type 'python manage.py runserver.' It's the same idea. Any rake tasks we would have in Rails or custom extensions would all be done through this. Then we have a requirements file which is the Python equivalent of your gem file; it's just a list of dependencies and their versions. Finally, we have some templates, which are just the chunks of HTML that Django is going to insert things into and use to render our pages.
00:03:41.440
We'll start from the database and work our way up, so let's begin with the models. Here is a blog post. We're declaring a class called 'BlogPost.' The parentheses here in Python mean inheritance, so it's inheriting from models.Model. This is the Django equivalent of inheriting from ActiveRecord in a Rails app. This class is a model tied to a database table. We can save instances to that database table and retrieve them later.
00:04:12.560
Now, something that might be unfamiliar if you've used ActiveRecord, but if you've used DataMapper, you might recognize this pattern: we define the fields that the model has directly in the model class. The schema isn't defined separately; it's defined within this object. The assignments here aren't local variables; these are attributes on the class. For instance, I could access blogPost.title to get the title object. The title field is an instance of models.CharField.
00:04:45.120
In Ruby, we’d typically use CharField.new to create an instance of a character field, but in Python, we just use parentheses to call it as if it’s a function. We pass in a maximum length and define other fields such as body, published, and publish date. However, this is just setting the scene. A website is much more than models; what we really need to do is display blog posts.
00:05:03.680
To make this interesting, we need another concept in Django called a view. Now, a view in Django is nothing like a view in Rails. I'll try to keep these two things separate as I speak. If I talk about Django views, they're much more like Rails actions. Therefore, in Rails, you would have a show action that displays a single blog post. In Django, you would have a detail view that shows the detailed view of a blog post.
00:05:36.000
Views use quite a simple mechanism. Django instantiates a request object and passes it to the view, which is just some kind of callable. It could be a lambda, a function, or a callable object, which is quite a nice feature of Python. The view returns a response that Django gives back to the user. However, such code can get very boring and repetitive. We've all seen show actions in Rails controllers that look the same: something like 'blogPost = BlogPost.find(params[:id])'. It's a very repetitive task.
00:06:15.760
So how does Django abstract away this repetition? Rails has conventional structures, including RESTful resources, which help us avoid writing the same code repeatedly. In contrast, Django provides a lot of help through a mechanism called generic views. A view handles the request and returns a response. Our blog post detail view, which we're declaring here, inherits from DetailView, a generic view that handles everything you might want to do with showing a single instance of a model.
00:06:44.800
We configure it by telling it which model to use. We set an attribute on it to denote the model class, and that's it. This will now work. Yes, there's a line in the URLs file that connects a URL to this view, but when you send a GET request to that URL, you get back a page showing the blog post. Notice all the code that isn’t here. There's no code to pull the ID out of the URL; there's no code to load the blog post from the database; there's no code to assign it to a variable to pass down to the template layer. All of that is handled by the DetailView superclass, abstracting everything into the framework.
00:07:26.000
The first time I saw this, I was a bit concerned; I thought that was too much convention. Convention is nice, but this seemed to take it too far. They’d taken so many details away. All I did was say which model to use, and I assumed this setup would be brittle and hard to change. So let's change something and see what breaks. In our BlogPost model, we have a 'published' field that determines whether the post is published.
00:08:03.440
We might want our detail view to display only published blog posts. The code we need to implement this isn’t as extensive as I expected. I originally thought I'd have to re-implement a lot of this stuff. Instead, we just assign another attribute on our class called 'queryset.' In Django, a queryset is the equivalent of an ActiveRecord relation; it describes a query that you can add filtering to later or ask for results. In this case, the queryset we're using filters blog posts where published equals true.
00:08:42.559
The DetailView will now take this queryset and use it as the basis for its query. It will look inside the queryset for something with the ID we request. While that works, we are doing this at class definition time, meaning it's a bit brittle because this is a static queryset. It can't change based on request properties.
00:09:20.160
Let’s add previews to our blog. If you add '?preview=1' to your URL, you can see a blog post even if it is unpublished. Here's the code for that: we drop our queryset attribute, and instead, we have the 'get_queryset' method. The parameter 'self' is just how Python implements classes; you must include a self parameter in every method. Some people hate it, while others don’t mind it, but it must be included.
00:09:54.560
We pull the current request from 'self' and look at its GET dictionary; that's a hash of all the query parameters. We check if there's something called 'preview'. If it exists, we return all blog posts as a queryset; otherwise, we do the same filtering we had earlier. Now, we’ve transitioned from a typical conventional detail view that shows any blog post based on the ID provided in the URL to one that exhibits dynamic behavior based on the request.
00:10:30.320
However, we are still not loading a blog post here. We’re still not extracting the ID from the URL, and we're not passing this information down to the template. We wanted to alter the queryset, and that is all we had to change. In fact, there are many of these methods that we can override. We've overridden 'get_queryset,' but there's much more we can accomplish behind the scenes.
00:11:05.800
When the user makes a GET request, Django calls our view. Our view has checked the type of request and determined it was a GET request, leading to the call of the ‘get’ method. The 'get' method defines what should happen with this request, but internally that's also calling 'get_template_names' to figure out the name of the template that will be returned. This will give back a template name attribute if defined, or return a sensible default based on conventions.
00:11:35.920
The 'get_slug_field' method is responsible for looking up blog posts by slug instead of by ID. Furthermore, it has 'get_object,' which loads the entire model. This allows us to stop using Django's ORM entirely and just use a plain old Python object or pull something from another source, which can be achieved by overriding this method.
00:12:01.680
The 'get_context_object_name' method is a mouthful, but context consists of the data passed from the view to the template. This method determines the name our blog post should be given within the template. Again, we can either set a static name by assigning an attribute on the class or override this method for a dynamic name, or just return a sensible default.
00:12:30.320
Finally, the 'get_context_data' method collects everything that gets passed to the template. There are several methods being used by 'get' that we can override to alter the algorithm. If you're a fan of 'Gang of Four' design patterns or have read Sandy Metz's book, you may recognize this as the template method pattern, which you can tick off on your bingo card.
00:12:59.760
The template method pattern serves a purpose that acts as a seam between framework code and application code. It allows the framework to define a standard algorithm, which is initiated with a single method call. You invoke the template method, which then calls many smaller operations, while subclasses in application code can easily modify one of those operations. This results in flexible and clean code. The only code you write in a Django view pertains to aspects outside the conventions.
00:13:35.760
This stands in contrast to a Rails action. If you use the scaffold, the generated code contains boilerplate and sections that define the conventions. I have been pleasantly surprised and impressed by how Django has taken the idea of convention over configuration to a new level by providing not just conventional structure but also conventional functionality.
00:14:03.920
However, this functionality is still simple to change with minimal code and remains flexible when moving outside the established patterns of convention. So, we can display blog posts now, but that doesn’t make a complete blog; we still have an empty database and need to create blog posts.
00:14:39.680
This example showcases a common pattern in web applications, where you have an HTML form. The user submits it, which results in a POST request. Some operations occur in the framework and application, ultimately leading to SQL execution where user data is funneled into the database. This process, while commonplace, carries inherent risks such as CSRF vulnerabilities, SQL injection attacks, and type conversion issues since HTML forms primarily handle strings while databases manage various data types.
00:15:15.440
There are potential drawbacks like the strong parameters problem. A user could add an extra field to the form in an attempt to exploit the system. Given the frequency of this occurrence, frameworks offer robust support to address such issues. So, how does Django handle this?
00:15:43.680
First, we create another view. Recall that views roughly correlate to Rails actions; in this case, we are using a create view, which is similar to both the new and create actions in a Rails RESTful controller. If you send a GET request, it shows a form; if you send a POST request, it processes that form. If successful, it redirects you somewhere, while failures yield descriptive error messages.
00:16:12.560
Thus, this action encapsulates both new and create functionalities. Additionally, there's a remarkable level of convention in action here, allowing us to function with minimal code.
00:16:36.800
Behind the scenes, however, there exists an object intermediary between the view and the model. This intermediary is called a Form, and it's possibly my favorite feature of Django. I have great enthusiasm for it! A form comprises a collection of fields, and these fields are aware of the data types they manage and related conversions.
00:17:00.960
For instance, for a date field, when provided with a date input, it converts it into a string that the form can utilize. Conversely, if given a string from the form, it converts it back into a date for use in the model. The widget is responsible for the user interface and manages HTML forms and HTTP concerns.
00:17:27.440
When we send a GET request to our view, the template instructs the form to render itself. The form iterates over its fields and tells each field, 'Render yourself'. Each widget then renders the appropriate HTML. When submitting the form, the data flows back in the opposite direction, working through the widgets and fields before reaching the form.
00:18:13.920
Next, let’s use a custom form in code for clarity. Here's our create view; we need to import the BlogPost form where it's defined. This view should utilize the specified form class to manage our blog post.
00:18:44.640
Defining the form class looks like this; while the top part of the code merely consists of imports, we create a BlogPost form that inherits from forms.ModelForm. Animal_model form has three noteworthy features. Firstly, it can automatically derive its list of fields from the model definition.
00:19:07.440
Secondly, if you provide a model, it can populate its fields with values from that model. Lastly, it has a save method, allowing you to say, 'Save yourself,' which instructs it to write values back to the model instance it’s associated with before the model saves itself to the database.
00:19:39.440
In this form class, we have a Meta class, which we can view as a collection of configuration options. Although there's more complexity involved, the essentials of this example focus on telling it which model to use. We could cease here, allowing the form to derive all its fields from the model.
00:20:10.880
However, we might want to exclude specific fields. This is where Django addresses similar issues as strong parameters and attr_accessible. This filtering mechanism enables us to specify a form that represents a blog post but excludes the published field, ensuring the user can only create drafts, not publish posts.
00:20:39.280
I believe this is a more practical approach to filtering. It doesn't reside within the controller or the model, but instead exists within a third entity. However, as the form object's responsibility encompasses both the creation of the user interface and the data processing, it mitigates the risk of errors.
00:21:11.680
If you unintentionally leave a field within the form, it's visible in the UI, making it easy to catch before a user might sneakily add data. Its purpose ensures that the process of constructing a form naturally leads to the inclusion or exclusion of the appropriate fields.
00:21:44.160
If the form lacks a field, it knows nothing of the model's structure; it merely recognizes its assigned fields. So if the model has a field that the form does not recognize, the form simply ignores it without generating errors.
00:22:12.960
Consequently, our form comprises a selection of fields. The final layer consists of widgets. While we could adhere to the default widgets that accompany the fields, the default date widget isn't particularly effective.
00:22:45.840
It's essentially a text field where users must enter ISO 8601 formatted dates, which can be challenging for users less familiar with such standards. Let's enhance the user experience by employing a select date widget.
00:23:15.840
We specify a dictionary, similar to a hash, indicating that the publish date should utilize an instance of the select date widget. At this stage, we are ready. Our view is now utilizing this custom form. Once we send a GET request, we expect to see a rendering of the blog post creation form.
00:23:44.560
However, it requires some improvements such as CSS, a submit button, and a title. There's still some work ahead, but this is a functioning interface.
00:24:17.680
Upon submission of this form, the process it undergoes is as follows: the HTML form generates an HTTP-encoded version of the fields, comprising data. I'm only displaying the date-related details here to streamline the example, but all other fields would also be included. This data traverses various paths—that may include the internet and web server infrastructure—before reaching Django and the routing processes.
00:25:04.640
Eventually, the data appears in a more useful format, being transformed from strings into a standard dictionary. However, these values currently pertain to the HTML form fields, not the fields in our model, with all types defaulting as strings.
00:25:48.320
Upon reaching our view, it passes the unmodified data to the form and states 'Hey, I got this from the browser; see what you can do with it.' The form will iterate over its fields, and field by field, it will provide the entire dictionary to the widgets associated with those fields.
00:26:37.440
The form isn’t concerned about which values correspond to which widgets; instead, it just states: here's all this data—extract the correct values. As a result, our select date widget will take the appropriate values, aggregate them, and convert them into a nicely formatted ISO 8601 string—the exact format that we want to simplify for the users.
00:27:26.720
After this, each field sends its values back to the form, which compiles them into a dictionary called 'cleaned_data' due to the validity of this data; it’s formatted accurately. This data is valid; it’s now the right types as all necessary conversions have occurred, which prepares it for subsequent usage.
00:28:10.560
Only then does it reach the model layer. Because we're using a model form, this data is allocated to the model, saved, and then forwarded to the database. I find this fascinating. To clarify the advantages, let's compare this to the analogous flow in Rails.
00:28:50.560
In Rails, we'd implement a 'new' action and a 'create' action within our blog post controller. The 'new' action generates the form view, and upon submission, the 'create' action would handle the posted parameters. These parameters embody the essential details we wish to extract; we process them and incorporate them into the corresponding blog post model.
00:29:22.879
This workflow seems quite similar to the Python example. However, it introduces complexities: Rails uses ActiveRecord attribute assignment, which directly assigns a hash to the model fields based on the incoming parameters.
00:29:56.040
If the model receives a hash containing potentially conflicting parameter keys—parenthetical keys indicating that these belong to the same group—it still needs to convert those values accurately before resulting in the targeted data types.
00:30:26.000
The resultant data still requires individual attention from components at both ends of the stack, leading them to communicate effectively by using the strong parameters approach. Unfortunately, this necessitates intricate coordination where modifications made require revisions across multiple classes.
00:30:49.760
Conversely, the Django paradigm emphasizes a clear understanding of individual components. Here, we ensure single responsibility by using distinct roles to manage layers of the application effectively. This modularity allows flexibility and clarity in understanding data flow, enabling you to modify or replace specific parts of the stack without overhauling the entire system.
00:31:20.760
I recount this to illustrate that Django embodies commendable conventions and effectively employs the single responsibility principle in contexts where Rails might not. Both frameworks possess their unique strengths, but Django's strengths inspire ideas that can enhance our work in Rails.
00:31:52.560
This collaboration acknowledges the great work happening within web development, and ongoing discourse between communities serves to enrich our collective understanding. For instance, the date select reliance on the database schema highlighted a limitation in utilizing Django conventions within Active Model.
00:32:34.000
I have initiated a pull request aiming to improve this aspect, inspired by my understanding of Django while working with Rails. It's not about self-promotion; it's about recognizing that various web frameworks and communities offer fruitful opportunities to refine each other's approaches.
00:33:15.440
Therefore, the next time you encounter a DjangoCon talk or consider a side project, explore different frameworks or languages—try something new. At worst, you might reaffirm your commitment to Rails; at best, you'll gain insights that can be brought back to our community—knowledge that could lead to improvement in Rails.
00:33:49.360
I hope this conversation has presented a worthwhile overview of Django and fostered new avenues for knowledge sharing and collaboration. If you have questions, feel free to reach out via email or ask them now; we've got about ten minutes left.