Django and Object Relational Mapping

The next step in my home automation project is to build the front end to view the data, and for that I'm going to use a web application framework.

Since I'm sending data from SmartThings to Zato I could just store the data in the Redis database and build some APIs that I could call from client-side Javascript to display it. All I'd need then is a webserver to serve the static content - a basic webpage and the Javascript files themselves.

Using a web application framework is much easier though. A good framework handles a lot of the work - authentication, page templates, data management - that is fiddly and easy to screw up if you roll your own.

I've chosen Django. It's Python based, like Zato itself, so I won't be constantly having to switch languages, and it's the most comprehensive and well documented of the python web frameworks.

So, to recap, the goal is to get temperature data from SmartThings into a database and display the results.

Let's start with the data. Django uses ORM - Object Relational Mapping - to link code to data. That's a bit of a new idea for old school programmers. Instead of defining tables directly using SQL, we'll define them in code and let the framework do the job of translating that into the database.

That's a big win. It means the data structures are only defined in one place and changes to the data structures in code are automatically reflected in the database. There's no chance of the two getting out of sync.

The best way to illustrate this is with an example. Let's say we want to have a table for our temperature sensors and a table for our readings. Assuming we've set up a new Django app called temperature, we'll put the following in our models.py file:
from __future__ import unicode_literals

from django.db import models

class Sensor(models.Model):
  name = models.CharField(max_length = 200)

  def __str__(self):
    return self.name

class Reading(models.Model):
  sensor = models.ForeignKey(Sensor, 
                             on_delete = models.CASCADE)
  time = models.DateTimeField('Time')
  value = models.IntegerField(default = 0)

  def __str__(self):
    return str(self.time) + " " + str(self.value)
And run the commands to apply the model to the database:

python manage.py makemigrations temperature
python manage.py migrate

So what has this done? Let's log on to the database and take a look.

MariaDB [website]> desc temperature_sensor
    -> ;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(200) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+

MariaDB [website]> desc temperature_reading;

+-----------+----------+------+-----+---------+----------------+
| Field     | Type     | Null | Key | Default | Extra          |
+-----------+----------+------+-----+---------+----------------+
| id        | int(11)  | NO   | PRI | NULL    | auto_increment |
| time      | datetime | NO   |     | NULL    |                |
| value     | int(11)  | NO   |     | NULL    |                |
| sensor_id | int(11)  | NO   | MUL | NULL    |                |
+-----------+----------+------+-----+---------+----------------+

It's automatically created two tables. The table names are the Django app name linked to the object class name with an underscore and the records have automatically been give auto-incrementing IDs as unique primary keys. Foreign keys are automatically named by the table name and the primary key.

The Django model definitions also allow for the creation of secondary indexes, many-to-many relationships and all of the things you'd expect to need while ensuring consistent naming conventions.

Django also gives you a default admin site to maintain the data - you just have to tell it what objects you want to manage in your app's admin.py:
from django.contrib import admin


from .models import Reading, Sensor


admin.site.register(Reading)
admin.site.register(Sensor)
And now you get a nice admin screen:



This won't be the screen you present to users but it's a handy feature while you're developing.

Note also that we set the "on_delete = models.CASCADE" option for our foreign key in the reading object. This means that if we delete a sensor, all of the readings associated with it will be deleted automatically as long as we do it through Django code, the API or the admin screen:



So that's step one - we have a database ready to take our temperature sensor readings. Step two is getting the actual data in from the sensors.



Labels: , , ,