22 Jul 2026

feedDjango community aggregator: Community blog posts

Tracking Blips

bliptracker was a side project that I happened to produce during June and last week realised I hadn't written about it here, so here goes!

One annoyance I have with Claude.ai (or other web based LLM interfaces), is that I would start multiple conversations across multiple topics such as client work, organising my Todoist, an idea to explore, gifts to research, the list goes on, but I was keeping open tabs for each conversation to not lose track of the active conversations, but this didn't work as I still had those open loops in my head to follow up to move each conversation forwards.

I didn't want a full blown task manager (I pay for Todoist which fits perfectly), but I did want to track the state of each conversation in Claude from both the web app and the mobile. The result is a two fold solution, first there is a system prompt telling Claude to end each respond with either a 🔴, along with the next action required from me, or a ✅ which tells me the conversation is resolved. The second part of the solution is a Chrome extension which then automatically updates the title of any conversation with the red dot or green check mark, so I can tell at a glance which chats need work and which are done.

I do have a couple more features planned such as supporting other LLMs and a possible snooze feature. But for now it's a small working project that keeps my chats organised. It's available at bliptracker.xyz.

One final point on this project, I hope that eventually it gets replace by Anthropic building a better native product for tracking the status of chats, it's very limited right now. More widely this highlights that while new models are powerful and can do more, it still requires us as engineers to build products that solve actual problems in novel, tasteful and well designed solutions. That is what we pay for when buy a tool and what our users expect from us and something that no model as far as I can see will ever replace.

22 Jul 2026 5:00am GMT

Django: introducing django-crawl

I recently migrated one of my client projects from the legacy django-csp package to Django 6.0's built-in Content Security Policy (CSP) support (release note). This security header is a powerful tool for preventing unwanted content from being loaded on your site, so configuration correctness is paramount. The migration was fairly straightforward, but a few pages had complicated overrides, so I wanted to be sure that no CSP headers had been changed by my swapping of CSP implementations.

I had the idea to verify no page had changed its content-security-policy header by crawling the site with Django's test client, outputting URL and header contents during the process. By diffing the output from crawls before and after the migration, I could check for changes and track down which pages had been affected.

The core loop of that script looked something like this:

from collections import deque
from django.test import Client


client = Client()
client.force_login(superuser)

queue: deque[str] = deque(["/", "/admin/"])
...

while queue:
    url = queue.popleft()
    ...
    response = client.get(url, follow=False)
    ...
    print(f"{url}\t{response.headers.get('content-security-policy')}")
    ...
    for anchor in BeautifulSoup(response.content, "html.parser").find_all(
        "a", href=True
    ):
        # Enqueue these found links
        ...

This simple crawl of the site ended up flushing out seven non-CSP bugs, despite the project having 100% test coverage and a full suite of integration tests. Those bugs were due to incorrect link generation, admin features not being disabled, and regular old broken code.

I was pretty impressed with the power of this technique for finding broken stuff! Given this experience, I wanted to expand the script into a reusable tool, which I have now done with django-crawl.

To use django-crawl, install it, add it to INSTALLED_APPS, and you can run the crawl management command:

$ ./manage.py crawl -v 2
🐛 Crawling up to 1000 URLs, logged in as Ad Min
/
/about/
/blog/
/contact/
/dev/
/blog/2026/
/blog/2025/
/blog/2026/07/22/introducing-django-crawl/
...
🦋 Crawled 1000 URLs, encountered 0 errors, stopped due to reaching max URL limit of 1000.

The command reports any errors it encounters, from broken links to exceptions. The output uses Rich for pretty formatting and a live spinner while it runs. Adding -v 2 prints the URLs as they are crawled.

The crawler discovers links in various forms in HTML responses (<a href>, <link href>, <script src>, <img src>, etc.), sitemaps, and feeds. If you have a sitemap, you can start your crawl with a simple:

$ ./manage.py crawl /sitemap.xml

The HTML parsing is done with a custom Rust extension built with html5ever, the HTML parser from the Servo project, so it's very fast. And with no overhead from real HTTP requests or inter-process communication, the crawl is limited only by how fast your application code can run.

django-crawl also provides a Python API that you can use to make a mega-test that crawls your whole site with example data, raising an ExceptionGroup if any errors are encountered:

from django.contrib.auth.models import User
from django.test import TestCase

import django_crawl


class CrawlTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.admin = User.objects.create_superuser(username="admin")

    def test_crawl(self):
        client = django_crawl.CrawlClient()
        client.force_login(self.admin)
        django_crawl.crawl("/", "/admin/", client=client)

I am not sure if this is a good fit for most projects, since it will leave you with one long test that exercises many views. But it might be good for building a "safety net" on untested projects, something I know Jeff Triplett likes to do (ref Django Chat #24).

Fin

Please try out django-crawl today and let me know how it goes.

May your site not slow to a crawl,

-Adam

22 Jul 2026 4:00am GMT

21 Jul 2026

feedDjango community aggregator: Community blog posts

EuroPython 2026 Recap

Seven days of sponsor booth, talks, sprints, and hallway chats.

21 Jul 2026 11:56am GMT