Need help with your Django project?
Check our django servicesThe 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!