CNK's Blog

Django for Rails developers

My group at $WORK has decided to start using Django as our web development framework so I am working my way through the tutorial. First off, yeah! there is actually a tutorial! Nice to be using a mature technology with good documentation.

The overview does a nice job of showing off some basics - models (including relationships), routing/urls, views, and templates (including template composition and reuse). Which brings me to my first mental mapping. One basic design pattern for web apps is known as MVC - Model View Controller. So Rails calls three of the directories under ‘app’ models, views, and controllers. There is a fourth directory named helpers, which is an awkward catch-all that tries to take the logic out of the view layer, with variable success. And the urls or routes are not defined within ‘app’ at all and instead are defined in the config directory in ‘config/routes.rb’. Rails reuses the names from MVC but in so doing sometimes ends up with some odd compromises. The Django breakdown, on the other hand, seems to do a nicer job of splitting the controller and view logic into the logic part (named views) and the display part, in templates.

One of the best parts of Ruby is rake and Rails makes extensive use of it for setting up and managing apps. In the Django ecosystem, many similar tasks are performed via the ‘manager.py’ script.

Command Rails Django
Populate the database rake db:migrate manage.py syncdb
Shell access to models rails console manage.py shell

Global

Django: “reusable apps” Rails: “engines” (and to a lesser degree, plugins)

Rails: built in sessions but no authentication or authorization. Django: sessions + authentication. Not sure how much authorization infrastructure comes built in.

Troubleshooting

rails console - great for models. Now better than it was for controllers, routes, helpers. Not sure about views. Find a recent blog post and read up / link.

Django shell:

manage.py shell - great for models. To poke around your views layer (anything that needs the response object), you need to add some power:

    >>> from django.test.utils import setup_test_environment
    >>> setup_test_environment()
    >>> from django.test.client import Client
    >>> client = Client()
    >>> from django.core.urlresolvers import reverse
    >>> response = client.get(reverse('polls:index'))
    >>> response.status_code
    200
    >>> response.content

Models

Both Rails and Django make handling associations a breeze. In Django, for one to many associations, all you need to do is create declare an attribute on the many side (the side that gets the mapping column) that is a ForeignKey, e.g.:

    class Article(models.Model):
        pub_date = models.DateField()
        headline = models.CharField(max_length=200)
        content = models.TextField()
        reporter = models.ForeignKey(Reporter)

And Django sets up the mapping for you. Then you can assign a reporter to an article by saying my_article.reporter = some_reporter. And you can chain methods to get to the reporter’s name: my_article.reporter.full_name. From the reporter side, you can get a list of all her articles by asking for murrow.article_set.all(). Rails is similar except that you don’t use foreign key relationships (not my favorite Rails decision) but instead you declare the relationships explicitly in both models:

    class Aritcle << ActiveRecord::Base
        belongs_to :reporter

    class Reporter << ActiveRecord::Base
        has_many :articles

The Django database query interface is a bit like the new ActiveModel syntax - but with ‘get’ instead of ‘find’.

    # Django provides a rich database lookup API.
    >>> Reporter.objects.get(id=1)
    <Reporter: John Smith>
    >>> Reporter.objects.get(full_name__startswith='John')
    <Reporter: John Smith>
    >>> Reporter.objects.get(full_name__contains='mith')
    <Reporter: John Smith>
    >>> Reporter.objects.all()
    [<Reporter: John Smith>]

Even in the first part of the tutorial, I run up against Python’s ‘explicit is better than implicit’ philosophy. Rails will use the class name to infer what instances of your model should be called. You can get to that value using the human_attribute_name method and you can either override that method in your model (old school) or use Rails internationalization capabilities to override the value in your locale config files. ActiveRecord also provides a default display of each instance which includes all the attributes of an instance. By contrast, in Django you need to define a __unicode__() (or in Python 3 a __str__()) method for each model class so that the shell and the admin interface know what to call things. There doesn’t appear to be a default representation that shows all the data values; I suspect you are expected to use Python’s dir() function, e.g. print dir(my_poll).


Models

In Rails the many side of the relationship is accessed as the plural of it’s name: my_poll.choices. In Django you use type + _set: my_poll.choice_set

Rails scopes (formerly known as named_scopes) let’s you make something that looks like a method that can be used to constructed queries - especially useful if you want to chain several of these conditions together but in a readable way.

Poking around in Django it appears to me that there isn’t such a thing

  • unless the people answering questions on StackOverflow are as ignorant as I am about Django’s query interface. The answer appears to be, if it’s simple, just put the logic for the method directly into your filter. If it is hairier, possibly write the SQL yourself. ???

Validations? Cool part of Rails. Is there a Django equivalent? or do you do that validation in the view (aka controller)?

Django admin

Wow! Just wow! Rails scaffold gives you OK CRUD functionality and there are some plugins that help make things somewhat fancier. But the out of the box admin functionality in Django is fabulous: sorting, searching, filtering. The closest thing I know in the Rails ecosystem is ActiveAdmin, which is nice, but deviates somewhat from the Rails standard.

One thing I really like a lot is the “History” tab. I end up wanting to add auditing to a lot of things I write but in several cases I have added acts_as_versioned to tracked the data - but didn’t provide an admin interface for displaying the changes. So, of course, now I want more. Does the Django admin interface provide a way to revert to a specific version? And can it diff within a text area?

Controllers / Views

Similar render and redirect options. Django provides two really handy shortcuts get_object_or_404 and [get_list_or_404]((https://docs.djangoproject.com/en/1.8/topics/http/shortcuts/#get-list-or-404). Handy enough I should create equivalents instead of the controller code I have.

Routing

Rails’ own interpretation of RESTful urls is strongly favored - in code and by the community. URLs all defined in a single routes.rb config file - unless you are using an engine. (For a while engines didn’t really have a mechanism for creating their routes other than by having a generator write something into the routes file at install. But now I think the routing infrastructure will look for routes files in engines that are mounted into your Rails project.

Django’s routing has a master file, urls.py but it is expected that each of your site’s apps will define their own urls and that you will just include those urls into your top level urls.py with a line like url(r'^polls/', include('polls.urls')) - with or without a namespace. The point of a namespace is to avoid name conflicts in the global set of url names. In with the include above, the names in polls.urls will be global - so the url named details in polls.urls will be the details url for the entire app. If you say url(r'^polls/', include('polls.urls', namespace='polls')), then they will not be global and instead can be accessed as polls:details.

Both frameworks encourage you to use the url lookup mechanism for creating links instead of hard coding them. The big win for this is that if your url need to change, you only have to change the appropriate routes.rb or urls.py file. The references to the urls can all stay the same.

    
    Django: <a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</a>
    Rails: <%= link_to poll.question, poll_url(poll.id) %>
   

In both systems, some people like having even shorter syntax for defining redirects - redirecting to the object. In Rails it is, as usual, ‘convention over configuration’. If you have defined your routes using ‘resources’, then you can just say redirect_to object

In Django, you can get a very similar redirect syntax: return redirect(object) But to make it work, you need to define a get_absolute_url method on your model class. For example:

    class List(models.Model):

        def get_absolute_url(self):
            return reverse('view_list', args=[self.id])

Templates

Every web framework provides some way of creating web pages from modular pieces. In fact defining your design and navigation elements in one place is sometimes the major reason for using a framework in the first place. I still need to read ‘The Rails View’ to see if I am doing this right, but I think the Django views may be a bit more flexible than the normal Rails view processing.

Rails - one big layout file - chose which main layout file by setting the name in the controller. ‘yield some_name’ and ‘content_for some_name’. Helpers - methods to munge display variables.

Django general “extends” mechanism. Named blocks. Filters attached to data using unix-style pipe, “|”.

Helpers

One of the fabulous things about Rails is all the useful helper methods available to you. Some were created for internal use but are super handy for display purposes, e.g. humanize, pluralize, etc. Others are specifically for display. Many other frameworks either had their own versions, or borrowed liberally from Rails.

Concept Rails Django Timezone handling django.utils.timezone

Forms

Code generation is nice - but there is a lot less typing involved in declaring the form using the Django admin tool. Field sets are quite nice - esp with the automatic ‘collapse’ option. I think this is fairly similar to Rails’ ActiveAdmin. (Sad to see that AA uses Devise for it’s authenitcation tool.)

Testing

Both frameworks come from a background of TDD and have pretty good tools for writing tests. Ruby actually has an embaarssment of riches since it seems that everyone has written their own testing DSL.

Default task runs all tests, or you can run parts. In Rails there are tests for each layer. For Django, the tests are broken out per app: manage.py test polls. View tests can assert against the content of the page with ‘contains’ and if you don’t find what you are looking for, you can print out the web page content:

    response = self.client.get(reverse('polls:index'))
    print response.content
    self.assertContains(response, "No polls are available.")


     get :show, :url_parts => @nested_page.url_parts
     puts response.body
     assert_select 'form[action*=page/update]'

Granular testing. Easy to do in rspec_rails. Possible but not so easy in test_unit/minitest. Easy in Django - just append the name of a specifit test to your command line:

    $ ./manage.py test animals # runs all tests in the animals app
    $ ./manage.py test animals.AnimalTestCase # runs tests in one class
    $ ./manage.py test animals.AnimalTestCase.test_animals_can_speak # runs one test method

Database for running tests

Djano: Aside from using a separate database, the test runner will otherwise use all of the same database settings you have in your settings file: ENGINE, USER, HOST, etc. The test database is created by the user specified by USER, so you’ll need to make sure that the given user account has sufficient privileges to create a new database on the system.

Both offer fixtures for setting up data. Rails community mainly moved to factories and the Django tutorial has you create a tiny factory as a helper methods. Are there libraries for factories in wide use?

Overriding settings - esp urls for apps that could be mounted anywhere; common option - use a decorator on the test method on or the entire class: @override_settings(LOGIN_URL=’/other/login/’)

Some useful looking Django test methods: SimpleTestCase.assertFieldOutput TestCase.assertFormError

Django integration with Selenium: LiveServerTestCase