Django projects end up slotting into a bunch of software release cycles: Django itself, Python, database servers, cache servers, and more. It can be hard for us developers to keep track of all of them, with their different support policies and end-of-life dates, and running an unsupported version means missing out on important security or bug fixes.
When contributing to Django, you’ll want to run its test suite to check your changes. The official contributing docs cover setup in Running the unit tests, using a virtual environment and pip. Here’s an alternative guide using uv.
If your site has companion Apple or Android apps, you probably want links to your site to open in those apps, when installed. Both platforms support this, with Apple calling the feature Universal Links and Android calling it App Links.
When a security researcher finds a vulnerability in your site, they need a way to tell you about it. Without a clear contact, they may resort to guessing at addresses like security@<yourdomain>, messaging random folks on social media, or give up. And of course, in the worst case, they might just publish the details, leaving you to find out when attackers do.
I previously announced my Python optimization workshop in Lisbon, on 10th October (which still has a few places). I am now pleased to add a Python/Django meetup on the Thursday before.
When a password manager detects that a user’s password has been leaked or reused, it can prompt them to change it, but the password change URL varies by site. The web’s answer to such discovery problems is the reserved /.well-known/ URL namespace (RFC 8615), home to machine-readable files and endpoints like security.txt. A Well-Known URL for Changing Passwords is the web specification that uses this namespace to fix password page discovery. It reserves the URL path /.well-known/change-password to redirect to your actual change password page, wherever that lives.
django-upgrade is my tool for automatically upgrading your project code for new Django versions. It rewrites your Python files to fix deprecations and adopt some new features, taking a chunk of the monotony out of upgrading between Django versions.
It’s another day, another new package day here. Say hello to django-msgspec, a package of drop-in replacements for Django and Django REST Framework (DRF) components backed by msgspec.
Did you know that each Django release has a “code word” associated with it? It’s hidden in plain sight, in the announcement blog post describing the list of features coming in the next version. I think this is a lovely little tradition.
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.
Just as cars painted red are known to be faster, libraries implemented in Rust are also known to be faster. Today’s example is orjson, a Rusty replacement for Python’s built-in json module, boasting 10x faster serialization and 2x faster deserialization.
Back in February, I encountered an out-of-memory error while migrating a client project to Python 3.14. The issue occurred when running Django’s database migration command (migrate) on a limited-resource server, and seemed to be caused by the new incremental garbage collection algorithm in Python 3.14.
Memory usage can be hard to keep under control in Python projects. The language doesn’t make it explicit where memory is allocated, module imports can have significant costs, and it’s all too easy to create a global data structure that accidentally grows unbounded, leaking memory. Django projects can be particularly susceptible to memory bloat, as they may import many large dependencies like numpy, even if they’re only used in a few places.
Last month, I held another quiz at the December edition of Django London. The December quiz is an annual tradition at our meetup, a way of making this final event of the year more relaxed and giving away some nice prizes. This was the seventh quiz that I’ve presented, and the eighth overall.
Previously, we covered bearer authentication within HTTP’s general authentication framework. In this post, we’ll implement basic authentication, where the client provides a username and password.
Django 6.0 was released today, starting another release cycle for the loved and long-lived Python web framework (now 20 years old!). It comes with a mosaic of new features, contributed to by many, some of which I am happy to have helped with. Below is my pick of highlights from the release notes.
HTTP has a general authentication framework that defines a pattern into which various authentication schemes can fit. Clients may provide an authorization request header that contains a credential. If authorization is missing or invalid, the server may respond with a 401 (Unauthorized) status code, including a www-authenticate header advertising what authentication schemes are supported. Otherwise, the server can respond with the authenticated resource.
HTTP supports response compression, which can significantly reduce the size of responses, thereby decreasing bandwidth usage and load times for users. It’s a cheap and valuable technique for improving website performance.
Django’s runserver automatically reloads when you change Python files. Without this autoreloading feature, you’d need to manually restart the server every time you made a code change.
Here’s a little tip based on some recent work. The project has a URL pattern where the first part of the URL matches the current role the user is viewing the site as. Let’s say the roles are “chef”, “gourmand”, and “foodie”—example URLs might look like this:
Within Django’s popular admin site, you can override ModelAdmin.get_queryset() to customize the queryset used by the admin views. It’s often used for performance optimizations, such as adding a select_related() call to batch-fetch related objects: