DEV Community

Cover image for Djangonaut Diaries: Week 1, part 2 - Creating and debugging a Django project
rodbv
rodbv

Posted on

Djangonaut Diaries: Week 1, part 2 - Creating and debugging a Django project

Hi there!

In the previous post, we cloned a fork of Django locally, and ensure it's installed and running its tests. Now we can start using this local copy of Django to do whatever changes we want to do on Django, for a future contribution.

For some context, my goal is to eventually open a Pull Request (PR) to improve how the confirmation page in Admin for items we want to delete display these items. You can see a comment I added to this issue on Trac, which is Django's issue-tracking system.

To make the changes I want, I am planning to

  1. Create a sample Django project with some simple models (Blog, Post and Comments), pointing to the local installation of Django we did on the first post;
  2. Create a helper command to insert a few hundred blog posts and comments, to visualize the current behavior on Django Admin;
  3. Set up Visual Studio Code's debugger to be able to debug the application, including the code in Django core, to better understand what is going on the admin template I want to change, especially how data is sent to the template, so I can start experimenting some changes and creating some tests (which will be a future post).

Cool! Let's get to it.

Creating a new project and pointing to our local Django install

For the new project, I will use uv, which is an extremely fast Python package manager and integrates seamlessly with pyproject.toml to manage the packages we will need to install as we go along.

First I will create a directory sample_blog, and type the following in it

❯ uv init .
❯ uv add --editable ../django
Using CPython 3.14.3 interpreter at: /usr/bin/python3.14
Creating virtual environment at: .venv
Resolved 5 packages in 1.95s
      Built django @ file:///home/rodrigo/code/django-test/django
Prepared 1 package in 1.93s
Installed 3 packages in 5ms
 + asgiref==3.11.1
 + django==6.1.dev20260311170544 (from file:///home/rodrigo/code/django-test/django)
 + sqlparse==0.5.5

Enter fullscreen mode Exit fullscreen mode

As you can see, uv took care of creating a virtual env for me, which is great. We can activate it as usual with source command.

source .venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

We can now use it to create our sample project, as usual.

❯ django-admin startproject sample_blog .
Enter fullscreen mode Exit fullscreen mode

Nice! So we now have a local installation of Django and our own project, side-by-side. Next I will create an application, the models I want (Blog, Post, Comments). I will not cover every step here as it's the standard set of steps, basically what you'll find on Django's tutorial, but you can have a look at what I've done in this repository.

In summary what will be done is:

  • create an app called "blog"
  • create three models: Blog, Post and Comment
  • add these models to Admin, so I can manage them.

Once I make and run the migrations and create a superuser, we can see this by opening Admin

Django admin with the Blog, Post, Comment links

Adding sample data using commands

Back to the issue I want to send a PR, it's related to the number of items that we show when we confirm the deletion of one or more things.

Admin page for delete blog, showing a summary of the deletions and listing all blogs, posts and comments that will be deleted.

To see the issue, I would like to add about 1000 blog posts, which is boring to do manually. I could open Django shell and run an ad-hoc script with a loop, but then, when I decide to run it again in the future, this code will be gone.

It's better to create a command, which will be part of my application.

Mine will be called create_posts and will have accept an argument --quantity (or -q) with the number of posts. For simplicity sake we will just add them to the first blog we find, and create a new one if none exists yet - we can make this more complicated later, but it's fine by now ;)

Since we want some "lorem ipsum" for the posts, let's add a library called ... lorem, which is great for this job. Let's also use a nice little lib called tdqm which shows a progress bar while the command is running, good for long-running stuff

uv add lorem tdqm
Enter fullscreen mode Exit fullscreen mode

This is how the command looks like

# blog/management/commands/create_posts.py
import lorem
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from tqdm import tqdm

from blog.models import Blog, Post

User = get_user_model()


class Command(BaseCommand):
    help = "Creates posts for the first available blog"

    def add_arguments(self, parser):
        parser.add_argument(
            "-q",
            "--quantity",
            type=int,
            default=5,
            help="Number of posts to create (default: 5)",
        )

    def handle(self, *args, **options):
        quantity = options["quantity"]

        user, _ = User.objects.get_or_create(
            username="admin",
            defaults={"is_staff": True, "is_superuser": True},
        )

        # use first blog or create one if it doesn't exist
        blog = Blog.objects.first()
        if not blog:
            blog = Blog.objects.create(
                        name=lorem.sentence().rstrip(".")[:120],
                        description=lorem.paragraph(),
                        created_by=user,
                    )
            self.stdout.write(f"Created new blog: {blog.name}")

        # create posts with progress bar
        for _ in tqdm(range(quantity), desc="Creating posts"):
            Post.objects.create(
                blog=blog,
                title=lorem.sentence().rstrip(".")[:300],
                content=lorem.text(),
                created_by=user,
            )

        self.stdout.write(self.style.SUCCESS(f"Created {quantity} posts in '{blog.name}'")

Enter fullscreen mode Exit fullscreen mode

Running the command I will see something like this

❯ python manage.py create_posts -q 1000
Creating posts: 100%|██████████████████████████████████████████████████████████████████████| 1000/1000 [00:03<00:00, 290.33it/s]
Created 1000 posts in 'My Djangonaut Blog'
Enter fullscreen mode Exit fullscreen mode

If I refresh the delete page for the blog, I will see the new posts there:

Django admin delete confirmation page with 1220 posts to be deleted in a list

Using VS Code debugger

Ok, so eventually I'd like to send a PR in which this huge list is truncated, for example it shows the first 100 elements and adds something like "...and 998 more blog posts" at the end, so that the red delete button is not 10,000 pixels down the page.

For that I need to first find the template that renders this list, and then put a debugger somewhere in the view related to this template to see how the data is sent to it.

I am using VS Code, so I will open it on my sample_blog directory, and add the django directory to the workspace, so I can navigate seamlessly between my app code and Django's own code.

VS Code showing where the

Now, to find the probable spot where the code is, I will just search for a string that is probably somewhere in a template, Are you sure you want to delete

VS Code with search string and results

I actually found 2 occurrences, and then I figured out that one is for the template when we're deleting one item, and one for "bulk delete" of several items. We may want to change both, but for now I will focus on the first one which is in django/contrib/admin/templates/admin/delete_confirmation.html

I will not put the full code of the template here, but you can check here.

The interesting part is the where I see a ul item that points to deleted_objects, which is probably the list I want:

    <p>{% blocktranslate %}Are you sure you want to delete the {{ object_name }}{{ escaped_object }}”? All of the following related items will be deleted:{% endblocktranslate %}</p>
    {% include "admin/includes/object_delete_summary.html" %}
    <h2>{% translate "Objects" %}</h2>
    <ul id="deleted-objects">{{ deleted_objects|unordered_list }}</ul>
Enter fullscreen mode Exit fullscreen mode

Let's search for deleted_objects. Note that I selected the options for case-sensitive search and match whole words, to reduce the number of matches.

Search for deleted_objects

Looks like the thing we want is on django/contrib/admin/options.py, around line 2230!

Ok, so to wrap up this session, I wonder how does this thing looks like? is it a string with <li>s?

Let's debug!

First thing to do is to put a breakpoint, that red dot you see on the screenshot above, which can be done either by clicking to the left of the line number, or press F9 when the cursor is on the line.

Then, pressing CTRL + SHIFT + P to open the command palette, or pressing F5, will start debugging. VS Code will present you with a few options, and you should choose

More Python debugger options -> Python debugger -> Django

You will know that everything is working fine when you see this toolbar showing up, which belongs to the debugger

Debug toolbar

If I reload the delete page on Admin, and go back to VS Code, I will see this

Debugging in action with variables

We're in! Note that I can see the value of deleted_objects and all the other variables in scope.

In particular, deleted_objects looks like this

['Blog: <a href="/admin/blog/blog/6b7346c7-42c0-4947-b8a9-be5f1d451baa/change/">My Djangonaut Blog</a>', 
['Post: <a href="/admin/blog/post/48200b32-a238-4325-981f-ff5a43eaf324/change/">First post</a>', [...], 
'Post: <a href="/admin/blog/post/42928075-1743-43c6-b1b4-f41ce6ad21c6/change/">Second post</a>', 
'Post: <a href="/admin/blog/post/43e02f24-0f13-4eb8-baa2-8e3434764148/change/">Labore modi amet magnam velit adipisci 
...]
]
Enter fullscreen mode Exit fullscreen mode

So it's a list of strings with a mix of keys and links... interesting!

I guess we're ready to start playing with these values, see how we could truncate them, but that will be the next post, as this is obviously not a trivial thing, we have some sort of recursive data structure to deal with... I'll need some coffee for this :D

Good news is, the goal for this sessions is accomplished, we can now debug Django! Woot!

See you soon!

Top comments (0)