Django ORM for the Rails Developer Part 1
- 9 minsQuerying with the Django ORM: how do we do it? What journeys lie ahead of us? Which queries are more efficient and put less load on a database? Coming from an ActiveRecord and Rails background, the transition and answers to these questions are still an ongoing journey. Below we will discuss some fun queries in Rails and how those might translate to Django; Django ORM for people who aren’t familiar with the Django ORM. Photographed above, meet our household’s #1, Django the cat.
For the purposes of this post, let’s say we are working on a blog. We will be working side by side with a Rails application with a PostgreSQL database and a Django application with a PostgreSQL database.
Entering the Console
Before we get started we will need to enter the console of our applications
To enter the Rails console we will be using the command
bundle exec rails console
To enter the Django console we will be using the command
./manage.py shell
Querying for a Single Object
One of the cornerstones of web development is the ability to read and return data from a database.
So let’s get to it, for our first task we want to return a single record in our database: we want to return one post
that has an id=1
.
Rails - ActiveRecord
find
irb(main):003:0> Post.find(1)
[2019-03-04T00:01:56.094388 #4] DEBUG -- : Post Load (1.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", "1"], ["LIMIT", 1]]
=> <#Post id: "1", author: blogger-jim, live_at: "2018-12-11 16:00:34", created_at: "2018-12-11 04:13:54", updated_at: "2018-12-11 16:00:34", heading: "Just a regular old blog post">
In the console, ActiveRecord will show us what SQL
it has run to execute the given query.
Now if we query for a post
with an id=7
that does not exist in our database, ActiveRecord will let us know by alerting us with a handy ActiveRecord::RecordNotFound
error:
irb(main):003:0> Post.find(7)
Post Load (0.8ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", nil], ["LIMIT", 1]]
Traceback (most recent call last):
1: from (irb):1
ActiveRecord::RecordNotFound (Couldnt find Post with 'id'=7)
Sound the alarm friends, there is no such object here!
Django ORM
get
Now, let’s try the same thing in our Django blog, we want to return one post
that has an id=1
.
In [1]: from posts.models import Post
In [2]: Post.objects.get(pk=1)
Out[2]: <Post: Post author: blogger-jim, created: 2018-11-28 14:32:19.956328+00:00>
Right off the bat, three things are sticking out to me in my Rails brain, we need to import models into the console?, .objects
?, and what is a pk
?
-
What is this importing all about?
Unlike the Rails console that loads the the majority of the application upon loading, the Django shell requires us toimport
the models, functions, and anything else we want to interact with during our session in the shell. -
What is this
.objects
deal?
TLDR; When using Django,objects
is the Manager of the model, we won’t get into too much detail on managers now, but think of a manager as a class method.
For our purposes, when you see the .objects
call on the Post
model, we are querying the instances of the objects within that class.
- What in tarnation is a
pk
?
pk
is the object’s primary key, aka theid
of the object in Rails, aka the database id for the object.
In Django, the convention prefers the use of pk
over id
.
If we run the same query using looking for a pk
that does not exist in our database, we will get an error similar to this:
DoesNotExist Traceback (most recent call last)
<ipython-input-2-eb4715ec5410> in <module>
----> 1 Post.objects.get(pk=1)
~/.virtualenvs/django-api/lib/python3.6/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
80 def create_method(name, method):
81 def manager_method(self, *args, **kwargs):
---> 82 return getattr(self.get_queryset(), name)(*args, **kwargs)
83 manager_method.__name__ = method.__name__
84 manager_method.__doc__ = method.__doc__
~/.virtualenvs/django-api/lib/python3.6/site-packages/django/db/models/query.py in get(self, *args, **kwargs)
397 raise self.model.DoesNotExist(
398 "%s matching query does not exist. %"
--> 399 self.model._meta.object_name
400 )
401 raise self.model.MultipleObjectsReturned()
DoesNotExist: Post matching query does not exist.
Querying for a Single Object - Without Raising Errors and Exploding Everywhere
Now, our first few examples were all fine and dandy but there will be instances when if we don’t find a record in our database we don’t want an exception to be raised.
Rails - ActiveRecord
where
irb(main):004:0> Post.where(id: 1)
[2019-03-04T00:07:53.498783 #4] DEBUG -- : Post Load (0.9ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", "1"], ["LIMIT", 11]]
=> <#ActiveRecord::Relation [<#Post id: 1, author: blogger-jim, live_at: "2018-12-11 16:00:34", created_at: "2018-12-11 04:13:54", updated_at: "2018-12-11 16:00:34", heading: "Just a regular old blog post">]>
Something special to note here, take a look at the output we are getting from this query. When using the where
clause, ActiveRecord will return an ActiveRecord::Relation
object, which for the purposes of this blog post, is like a special ActiveRecord array.
Arrays, eh? So what happens if we write a .where
query that returns nothing?
irb(main):007:0> Post.where(id: 100)
[2019-03-04T00:14:49.323594 #4] DEBUG -- : Post Load (1.0ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" IS NULL LIMIT $1 [["LIMIT", 11]]
=> <#ActiveRecord::Relation []>
Note we still get an ActiveRecord::Relation
object back, but we can see here it returns an empty array if nothing on the database side matches this query. In our code, we will just need to handle an empty array and can skip the error parsing that the .find
queries will get you.
Unlike with .find
, using where
means we get to query by all sorts of attributes not just id
:
irb(main):004:0> Post.where(author: "blogger-jim")
[2019-03-04T00:07:53.498783] DEBUG -- : Post Load (0.9ms) SELECT "posts".* FROM "posts" WHERE "posts"."author" = $1 LIMIT $2 [["author", "blogger-jim"], ["LIMIT", 11]]
=> <#ActiveRecord::Relation [<#Post id: 1, author: "blogger-jim", live_at: "2018-12-11 16:00:34", created_at: "2018-12-11 04:13:54", updated_at: "2018-12-11 16:00:34", heading: "Just Breathe">]>
where
may also return multiple objects from the database, while our find friend will only ever return a single object.
Django ORM
filter
To mimic the behavior we get with .where
in Rails, we can use the .filter
function in the Django ORM.
In [1]: from posts.models import *
In [2]: Post.objects.filter(pk=1)
Out[2]: <QuerySet [<Post: Post author: blogger-jim, created: 2018-11-28 14:32:19.956328+00:00>]>
Check it out! Similar to the where
method, filter
will return a special type of object, in Django it is called a QuerySet
and again, for the purposes of this post, we can just treat this as an array of objects.
And just like .where
, by using .filter
in Django we can query by different attributes and not just by primary key.
If we run a .filter
query that doesn’t return any results, we will get an empty QuerySet
back:
In [4]: Post.objects.filter(pk=500)
Out[4]: <QuerySet []>
You, my friend, are well on your way to becoming an ex-rails-django-querying master! Stay tuned for more complicated querying in Django and discussions around query optimizations.