Accessing Related Data using Django ORM

In the previous lesson we have covered all the basic stuff needed to interact with the database using Django ORM. We have created, modified and deleted many objects. The type of the objects we handled till now were simple objects that can exists on their own. In this lesson we will learn how to access related data, but before we do that we must first populate some data in Category, Post and Tag's table first .

If you are following these lessons closely your Author table i.e blog_author should be empty. Before we proceed, lets add some Author objects to the blog_author table. Open Django shell by executing the shell command and import all necessary models from the blog app.

(env) C:\Users\Q\TGDB\django_project>python manage.py shell
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 20:20:57) [MSC v.1600 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blog.models import Category, Tag, Author, Post
>>>

Author.objects.bulk_create([
    Author(name='tom', email='tom@mail.com'),
    Author(name='jerry', email='jerry@mail.com'),
    Author(name='spike', email='spike@mail.com'),
    Author(name='tyke', email='tyke@mail.com'),
])

>>>
>>> Author.objects.bulk_create([
...     Author(name='tom', email='tom@mail.com'),
...     Author(name='jerry', email='jerry@mail.com'),
...     Author(name='spike', email='spike@mail.com'),
...     Author(name='tyke', email='tyke@mail.com'),
... ])
[<Author: tom:tom@mail.com>, <Author: jerry:jerry@mail.com>, <Author: spike:spik
e@mail.com>, <Author: tyke:tyke@mail.com>]
>>>

Let's now shift our attention to the Category model. Here is how the Category model is defined:

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True)
    author = models.ForeignKey(Author)

And this is how blog_category table looks like:

[]

Important point to notice here is that Category model has one-to-many relatioship with the Author's model. As a result a Category object must be associated with an Author object.

Let's try creating an Category object and see what happens:

>>>
>>>
>>> c = Category(name='python', slug='python')
>>>
>>> c.save()
>>>
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\K\TGDB\env\lib\site-packages\django\db\models\base.py", line 79
6, in save
>>>...
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: NOT NULL constraint failed: blog_category.author
_id
>>>

Calling save method raises an exception of type IntegrityError. You would get the same exception, even if you call create() method of objects manager.

So what's the problem ?

The problem is we are trying to create a Category object without an Author.

Okay, let's try creating Category object one more time, but this time we will provide an Author.

>>>
>>> Author.objects.values_list("id", "name")
<QuerySet [(7, 'tom'), (8, 'jerry'), (9, 'spike'), (10, 'tyke')]>
>>>>
>>>>
>>> a1 = Author.objects.get(pk=7)
>>> a1
<Author: tom:tom@mail.com>
>>>
>>> a2 = Author.objects.get(pk=8) 
>>> a2
<Author: jerry:jerry@mail.com>
>>>
>>> c1 = Category(name='python', slug='python', author=a1)
>>> c1.save()
>>>

As you can see this time operation succeeded. Instead of calling Category() constructor method you could also use objects manager create() method like this:

>>>
>>> c1 = Category.objects.create(name='python', slug='python', author=a1)
>>>

We can now use c1 variable to get the information about the Category object as well as Author who created this object using the dot(.) operator.

>>> c1.name
python
>>>
>>> c1.slug
python
>>> c1.author
<Author: tom:tom@mail.com>
>>>
>>> c1.author.name
tom
>>> c1.author.email
tom@mail.com
>>>

Notice that in the last two commands we are accessing data stored in a different table i.e blog_author without writing SQL join query. That's the power of the Django ORM.

Instead of passing Author object you could also pass primary key of the author, but then you would have to assign it to the author_id keyword argument instead of Author.

>>>
>>> c2 = Category(name='java', slug='java', author_id=a2.pk)
>>> c2.save()
>>>

Similarly, you can create Tag objects as follows:

>>>
>>> t1 = Tag.objects.create(name="django", slug="django", author=a1)
>>>
>>> t2 = Tag.objects.create(name="flask", slug="flask", author=a2)
>>>
>>>

Now we know how to create Category and Tag objects let's shift our attention to the Post model. This is how the Post model is defined.

class Post(models.Model):
    title = models.CharField(max_length=50)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    pub_date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(Author)
    category = models.ForeignKey(Category)
    tags = models.ManyToManyField(Tag)

A Post object has following relationships:

1) One-to-many relationship between Post and Category object.
2) One-to-many relationship between Post and Author object.
3) Many-to-many relationship between Post and Tag object.

This is how Post table (blog_post) looks like:

As you see to create a Post object we must provide an Author and Category.

Let's create some Post objects

>>>
>>>
>>> p1 = Post.objects.create(title="Post 1", slug="post-1", content="post 1 content", author=a1
, category=c1)
>>>
>>> p1
<Post: Post 1>
>>>
>>>

Important thing to notice here is that we are not passing any tags to the create() method. This is because a Post object has many-to-many relationship between Tag object. As a result relationship between them will be stored in a different table (blog_post_tags in this case). Once a Post object is saved in the database, we can add tags related to it later (we will learn how to do that in the upcoming sections).

The variables a1 and c1 must point to a valid Author and Category object respectively otherwise you will get an error.

This is how a Post object looks like in the blog_post table.

Note: Your author_id and category_id may be different than mine.

Just as with Category or Tag object, we can use a Post object to find the any relevant information about the author and category associated with it.

>>>
>>>
>>> p.author.name
'tom'
>>> p.author.email
'tom@mail.com'
>>> p.author.active
True
>>> p.category.name
'python'
>>> p.category.slug
'python'
>>>
>>>

Let's finish this section by creating another Post object.

>>>
>>>
>>> p2 = Post.objects.create(title="Post 2", slug="post-2", content="post 2 content", author=a2
, category=c2)
>>>
>>>

More managers

objects is not the only the manager available in Django. It turns out that when dealing with many-to-many relationship Django uses a manager to connect data. In the Post model tags field is one such manager. You can verify this by typing the following command in the Django shell.

>>>
>>> type(p2.tags)
<class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_
manager.<locals>.ManyRelatedManager'>
>>>

As tags is manager you can use all manager methods on it that we have learned in the last chapter.

To view all tags associated with the p1 and p2 post type the following code:

>>>
>>> p1.tags.all()
<QuerySet []>
>>>
>>>

>>>
>>> p2.tags.all()
<QuerySet []>
>>>

Currently, posts p1 and p2 are not associated with any tags that's why an empty QuerySet object is returned.

So how we add tags to existing Post objects ?

All relationship managers comes with add() method which can be used to connect objects.

>>>
>>> t = Tag.objects.get(name__contains='django')
>>> t
<Tag: django>
>>>

>>>
>>> p2.tags.add(t2)
>>>

Now post p2 is associated with django tag. The above command adds the following record in the blog_post_tags table.

[img]

>>>
>>> p2.tags.all()
<QuerySet [<Tag: django>]>
>>>

You can also associate multiple tags to a Post object by passing multiple arguments to add().

>>>
>>> p1.tags.add(t1, t2)
>>>
>>>
>>> p1.tags.all()
<QuerySet [<Tag: django>, <Tag: flask>]>
>>>
>>>
>>> p1.tags.all().order_by("-name")
<QuerySet [<Tag: flask>, <Tag: django>]>
>>>

We know that once we have a Post object we can find all the information related to it like category, author, tags etc. But, How do we access data other way around ? Simply put how do we find all post related to a particular tag or all the post published by a particular author ?

Let p, a, c, t be a Post, Author, Category and Tag object respectively, then

all the posts published by author a is given by - a.post_set.all()
Author of post p is given by - p.author.name

all the posts published under category c is given by - c.post_set.all()
Category of post p is given by - p.category.name

all the posts filed under tag t is given by - t.post_set.all()
all tags related to post p is given by -  p.tags.all()

In case you are wondering what is post_set ?

post_set is just another relation manager that Django has created dynamically for you to make database interaction easy. As post_set is a manager you can use all the manager methods on it. You can verify this by executing the following commands.

>>>
>>> c = Category.objects.all()[0]
>>> c
<Category: python>
>>> type(c.post_set)
<class 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_m
anager.<locals>.RelatedManager'>
>>>

>>>
>>> t = Tag.objects.all()[0]
>>> t
<Tag: django>
>>> type(t.post_set)
<class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_
manager.<locals>.ManyRelatedManager'>
>>>

Creating objects the other way around

So far we have been creating objects either by Model's constructor method i.e Post(), Category() etc or objects manager create() method. The other way to create objects is to use related model class and then use the manager's create() method on it. For example, Post and Category model have one-to-many relationship between them, so instead of creating Post object using Post constructor or create() method, we can use Category object to create post. Here are are some examples:

>>>
>>>
>>> Category.objects.values_list("id", "name")
<QuerySet [(2, 'java'), (1, 'python')]>
>>>
>>>
>>> c2 = Category.objects.get(pk=2)
>>> c2
<Category: java>
>>>
>>>
>>> c2.post_set.create(
... title = "Post from Java Category",
... slug = "post-from-java-category",
... content = "This is content is for java category",
... author = a
... )
<Post: Post from Java Category>
>>>
>>>

Notice that we are not passing an object of type Category while creating Post object because we are calling create() method on the object of type Category .

Similarly you can create Post object from a Tag object or Author object.

>>>
>>> Tag.objects.values_list("id", "name")
<QuerySet [(1, 'django'), (2, 'flask')]>
>>>
>>>
>>> t1 = Tag.objects.get(pk=1)
>>>
>>> t1
<Tag: django>
>>>
>>> t1.post_set.create(
... title = "Post from tag object",
... slug = "Post-from-tag-object",
... content = "This is content for title Post from tag object",
... author = a,
... category = c
... )
<Post: Post from tag object>
>>>
>>>
>>> t1.post_set.all()
<QuerySet [<Post: Post 1>, <Post: Post 2>, <Post: Post from tag object>]>
>>>
>>>

Fetching records using related fields

Let's say you want to find all the post belong to the category 'java'. Here is the one way to find all such posts.

>>>
>>>
>>> Category.objects.values_list("id", "name")
<QuerySet [(2, 'java'), (1, 'python')]>
>>>
>>> c = Category.objects.get(name='java')
>>> c
<Category: python>
>>>
>>> posts = Post.objects.filter(category=c)
>>> posts
<QuerySet [<Post: Post 1>, <Post: Post 2>, <Post: Post from Java Category>, <Pos
t: Post from tag object>]>
>>>
>>> posts[0].category.name
'java'
>>> posts[2].category.name
'java'
>>>
>>>

We can achieve the same thing using the following code.

>>>
>>> posts = Post.objects.filter(category__name='python')
>>> posts
<QuerySet [<Post: this is title>, <Post: this is title 2>]>
>>>

category__name consists of two underscores.

Similarly we can find all the post belongs to the author "tom".

>>>
>>> posts = Post.objects.filter(author__name="tom")
>>> posts
<QuerySet [<Post: Post 1>, <Post: Post 2>, <Post: Post from Java Category>, <Pos
t: Post from tag object>]>
>>>
>>>

We can also apply lookups to category__name, author__name.

>>>
>>> posts = Post.objects.filter(category__name__contains='java')
>>>
>>> posts
<QuerySet [<Post: Post 1>, <Post: Post 2>, <Post: Post from Java Category>, <Pos
t: Post from tag object>]>
>>>

>>>
>>>
>>> posts = Post.objects.filter(author__name__contains='to')
>>> posts
<QuerySet [<Post: Post 1>, <Post: Post 2>, <Post: Post from Java Category>, <Pos
t: Post from tag object>]>
>>>
>>>