Thursday, September 15, 2011

Simplify with Django

As a legacy from a previous job, I had continued using Ruby on Rails as a web front-end to display test regression results and other data. There is a lot about Ruby on Rails that I never really understood (such as 'routes'), and the sheer number of folders and files created by a new Rails project always intimidated me. After all, I am not doing much with it, so why should I have all this extra stuff I never even seem to need? Yet, I did grow fond of Ruby itself, so I didn't mind the Rails wilderness too much, as long as I could get my little stuff to work.


In a subsequent job, I needed to learn Python, and I found the transition from Ruby to be pretty easy. There are a few Ruby-isms I missed but for the most part I was fine with Python. I still used a variant on the old Rails project, but decided to explore Django, since it's Python as well, and it's always better to simplify, if you can. What I didn't expect, though, was just how much simpler Django was going to be than Rails.


My projects, as I said, are very simple. Regression test results (and other data, such as apache benchmarks and server performance monitoring data) are stored in simply MySQL databases. I want access to these results through a browser. Most of the results are presented in HTML tables. Others are displayed in Google Charts.


in Rails I needed separate files for each database table's corresponding controllers, models and and views. I also touched the databse.yml file, the routes.rb and migrate files. I don't really have anything in the app/helpers or lib or log or public or script or test or tmp or vendor directories, yet there they are! In Rails, the views directories for each table contain index, new, show, edit and delete html files. In sum, there are more than 40 files to edit and maintain in my bare-bones Rails project.


In the Django version, there are the main 3 files (manage,py, settings.py and urls.py), and then one file for all models (models.py) and one file for all views (views.py). I have html template files for each table, so that's another 5 - for a total of 10 files. There are no unused folders, no other clutter.


And that's not all. In Rails, I had to write MySQL scripts to extract the data and Ruby code to pass the results up through the controller to the view, stuff like this:


def self.find_history
    date = (Date.today-30).to_s
    stmt = "select *
            from (select server, date
                   from ads f1
                   group by server
                   order by date desc) f1
            left join ads f2 on f1.server=f2.server and f2.date > '#{date}'"
            
     result = find_by_sql(stmt)
     @server = Array.new
     @mean1 = Array.new
     @mean2 = Array.new
     @mean3 = Array.new
     @date = Array.new
     
     result.each do | a |
        @server << a.server
        @mean1 << a.mean1
        @mean2 << a.mean2
        @mean3 << a.mean3
        p = a.date.to_s.split(" ")
        @date << p[0]
      end
      
      return result
  end
In Django, no. I just define the db in the models file:


class Benchmarks(models.Model):
    server = models.CharField(max_length=128)
    users = models.CharField(max_length=32)
    mean1 = models.CharField(max_length=32)
    mean2 = models.CharField(max_length=32)
    mean3 = models.CharField(max_length=32)
    date = models.DateTimeField()



And in the views file I use a built-in method to get the data from MySQL, then use a 'context' to pass it along to a 'template' file which will be rendered by the browser


def benchmark_index(request):
    all_entries = Regression.objects.all()
    t = loader.get_template('benchmark/index.html')
    c = Context({
        'all_entries': all_entries,
    })
    return HttpResponse(t.render(c))
The template is similar in look-and-feel to embedded Ruby. You put Python code in between {% and %} markers, and the rest is html:


table here:
tr
th>Server
th>Users
th>Mean Response Time 1
th>Mean Response Time 2
th>Mean Response Time 3
/tr

{% if all_entries %}
{% for a in all_entries %}
tr>
td>{{ a.server }}
td>{{ a.users }}
td>{{ a.mean1 }}
td>{{ a.mean2 }}
td>{{ a.mean3 }}
td>{{ a.date }}
{% endfor %}
{% endif %}

/tr>




Notice that 'all_entries' - the variable used in the template, was explicitly defined in the views file and passed into the context. Also, the urls used by Django are explicitly defined, formed by regular expressions, and stored all together in a file called urls.py:


urlpatterns = patterns('',
    # Examples:
    (r'^benchmarks/$', 'regression.views.benchmark_index'),


)



It makes it clear that the url is ROOT/benchmarks, and when you go there, you invoke the method 'benchmark_index' in the views.py file in the 'regression' application. It's all quite explicit and easy to track.


This is most of the project in a nutshell. A simple db with values easily retrieved and displayed in an HTML table in a browser.


bonus coverage: The google charts aspect was also straightforward. 1) Install the Python module: sudo easy_install -U GChartWrapper 2) embed some google chart code in your template file 3) create and pass the data to the chart from the views.py file


Building on the example above, add to the benchmark_index and template file:



def benchmark_index(request):
    all_entries = Benchmarks.objects.all()
    data = []
    max_val = 0
    for a in all_entries:
        if a.mean3 > max_val:
            max_val = a.mean3
        data.append(a.mean3)
    #print max_val
    mid_val = float(max_val) / 2
    t = loader.get_template('benchmark/index.html')
    c = Context({
        'all_entries': all_entries,
        'data': data,
        'max_val': max_val,
        'mid_val': mid_val,
    })
    return HttpResponse(t.render(c))



// in the template, below the sample code above



{% load charts %}
{% chart Line data %}
{% title 'Max Mean Response Times' 0000FF 36 %}
{% color 3072F3 %}
{% line 3 2 0 %}
{% size 600 200 %}
{% axes type xy %}
{% scale 0 max_val %}
{% marker 's' 'blue' 0 -1,5 %}%}
{% legend 'Mean Response Times' %}
{% axes range 1 0,max_val %}
{% axes label 0 %}
{% axes label 1 0 mid_val max_val %}
{% img alt=DataScaling height=200 id=img title=DataScaling %}
{% endchart %}



The result:


Nothing fancy, but there you have it. Why complicate your life? Simplify with Django and Python.