HackSoft logo
  • Approach
  • Case Studies
  • Team
  • Company
  • Services
      Custom Software Development ConsultingAI Integrations
  • Solutions
  • Open Source
  • Blog

Need help with your Django project?

Check our django services

Quick and Sweet Django - Print queryset in a table format, for shell purposes

Ivaylo Donchev
Jan 9, 2025
Categories:DjangoPython

The use case

When working with Django, one of the most common tasks we encounter is querying data via the Django ORM.

And quite often, we want to test a query or debug something, and the usual place for this is the Django shell (or shell_plus).

Yet, the default representation of a QuerySet is not very useful and it's limited by the __str__ implementation of our models:

>>> User.objects.all()


<UsersQuerySet [<User: Name: self.name, Email: admin@admin.admin>, <User: Name: self.name, Email: admin2@admin.admin>, <User: Name: self.name, Email: admin3@admin.admin>, <User: Name: self.name, Email: admin4@admin.admin>, <User: Name: self.name, Email: admin5@admin.admin>, <User: Name: self.name, Email: admin6@admin.admin>, <User: Name: self.name, Email: admin7@admin.admin>, <User: Name: self.name, Email: admin8@admin.admin>, <User: Name: self.name, Email: admin9@admin.admin>, <User: Name: self.name, Email: admin10@admin.admin>, <User: Name: self.name, Email: admin11@admin.admin>, <User: Name: self.name, Email: admin12@admin.admin>, <User: Name: self.name, Email: admin13@admin.admin>, <User: Name: self.name, Email: admin14@admin.admin>, <User: Name: self.name, Email: admin15@admin.admin>, <User: Name: self.name, Email: admin16@admin.admin>, <User: Name: self.name, Email: admin17@admin.admin>, <User: Name: self.name, Email: admin18@admin.admin>, <User: Name: self.name, Email: admin19@admin.admin>, <User: Name: self.name, Email: admin20@admin.admin>, '...(remaining elements truncated)...']>

To get a better visibility of the data, what we usually do something like this:


 >>> for user in User.objects.all():
...     print(user.id, user.name, user.email)

2 User 1 admin@admin.admin
3 User 2 admin2@admin.admin
4 User 3 admin3@admin.admin
5 User 4 admin4@admin.admin
6 User 5 admin5@admin.admin
7 User 6 admin6@admin.admin
8 User 7 admin7@admin.admin
9 User 8 admin8@admin.admin
10 User 9 admin9@admin.admin
11 User 10 admin10@admin.admin
12 User 11 admin11@admin.admin
13 User 12 admin12@admin.admin
14 User 13 admin13@admin.admin
15 User 14 admin14@admin.admin
16 User 15 admin15@admin.admin
17 User 16 admin16@admin.admin
18 User 17 admin17@admin.admin
19 User 18 admin18@admin.admin
20 User 19 admin19@admin.admin
21 User 20 admin20@admin.admin
22 User 21 admin21@admin.admin
23 User 22 admin22@admin.admin
24 User 23 admin23@admin.admin
25 User 24 admin24@admin.admin
26 User 25 admin25@admin.admin

But this can get really annoying and involves a lot of typing.

What if we can show the objects in a more "natural" representation - a table?

Fortunately, the Python ecosystem has just the right tool for this!

Quick and Sweet queryset table representation

Our approach is going to be very simple and involve the python-tabulate package.

1. Install the python-tabulate package

pip install tabulate

2. Define a quick utility function your project's utils.py files, or wherever you see this fit.

from tabulate import tabulate


def tabulate_qs(queryset, *, fields: list[str] | None = None, exclude: list[str] | None = None) -> str:
    # Make sure the table won't be empty
    if not fields:
        fields = [field.name for field in queryset.model._meta.fields]

    if not exclude:
        exclude = []

    fields = [field for field in fields if field not in exclude]

    return tabulate(
        tabular_data=queryset.values_list(*fields),
        headers=fields,
        tablefmt="github",
    )


def print_qs(queryset, *, fields: list[str] | None = None, exclude: list[str] | None = None) -> None:
    print(tabulate_qs(queryset, fields=fields, exclude=exclude))

As you can see, we've even taken few more steps, defining a tabulate_qs function, which returns a string, and a print_qs function, which prints the result, as a side effect.

3. And here we go - we can print our queryset as a table:

In [1]: print_qs(User.objects.all())

|   id | name    | email               | password   | created_at                       | updated_at                       | is_active   |
|------|---------|---------------------|------------|----------------------------------|----------------------------------|-------------|
|    2 | User 1  | admin@admin.admin   | asd        | 2024-06-20 12:53:10.707902+00:00 | 2024-06-20 12:53:10.707969+00:00 | True        |
|    3 | User 2  | admin2@admin.admin  | asd        | 2024-06-20 12:53:18.024159+00:00 | 2024-06-20 12:53:18.024203+00:00 | True        |
|    4 | User 3  | admin3@admin.admin  | asd        | 2024-06-20 12:53:23.863903+00:00 | 2024-06-20 12:53:23.863960+00:00 | True        |
|    5 | User 4  | admin4@admin.admin  | asd        | 2024-06-20 12:53:54.588907+00:00 | 2024-06-20 12:53:54.588965+00:00 | True        |
...


In [2]: print_qs(User.objects.all(), fields=['id', 'name'])

|   id | name    |
|------|---------|
|    2 | User 1  |
|    3 | User 2  |
|    4 | User 3  |
|    5 | User 4  |
...

In [3]: print_qs(
    User.objects.all(),
    exclude=['password', 'created_at', 'updated_at']
)

|   id | name    | email               | is_active   |
|------|---------|---------------------|-------------|
|    2 | User 1  | admin@admin.admin   | True        |
|    3 | User 2  | admin2@admin.admin  | True        |
|    4 | User 3  | admin3@admin.admin  | True        |
|    5 | User 4  | admin4@admin.admin  | True        |
...

Extra step: Combine with shell_plus

If you use django-extensions package and the shell_plus command you can automatically include this utils when starting the shell:

SHELL_PLUS_IMPORTS = [
    "from styleguide_example.blog_examples.print_qs_in_shell.utils import print_qs"
]

Now when you run python manage.py shell_plus you can simply use print_qs, without the imports overhead.

We hope you find this useful!

Need help with your Django project?

Check our django services
HackSoft logo
Your development partner beyond code.