04 Dec 2024
Django community aggregator: Community blog posts
Rebuilding django-prose-editor from the ground up
Rebuilding django-prose-editor from the ground up
The django-prose-editor package provides a HTML editor based upon the ProseMirror toolkit for the Django administration interface and for the frontend.
The package has been extracted from a customer project and open sourced so that it could be used in other projects as well. It followed a very restricted view of how rich text editors should work, which I have initially added to the FeinCMS repository when documenting the design decisions more than 15 years ago (Note that I didn't edit the paragraph, it's reproduced here as it was back then, with all the errors and heedlessness.)
All of this convinced me that offering the user a rich text editor with too much capabilites is a really bad idea. The rich text editor in FeinCMS only has bold, italic, bullets, link and headlines activated (and the HTML code button, because that's sort of inevitable - sometimes the rich text editor messes up and you cannot fix it other than going directly into the HTML code. Plus, if someone really knows what he's doing, I'd still like to give him the power to shot his own foot).
My personal views are unchanged. I have to recognize though that forcing this idea upon everyone isn't workable and that this would mean that I'd have to find a different editor for most projects just because people really want or need more rope. Going back to an editor which allows everything was out of the question, so I had to look around for a way to allow project-specific extensions for the editor.
Of course that's problematic, since Django packages and Python virtualenvs do not offer a good way of shipping CSS and JavaScript which should be available for a frontend bundler to process. The existing Django staticfiles app is great, works well, but it's not a bundler - and it shouldn't be.
So, I started shopping around for ways to make ProseMirror extensible while keeping extensions clean and well localized. Instead of inventing another plugin ecosystem I settled on Tiptap which uses ProseMirror under the hood. The abstractions are pleasantly leaky - if you know how to work with ProseMirror's API, you can use Tiptap's API without any issues. That was important for me, since I already have a somewhat large selection of plugins which I do not want to reimplement from the ground up.
I had already looked at Tiptap a few years back, but ultimately stayed with ProseMirror because I liked some behaviors better (such as not including trailing spaces in marks) and because I didn't need the extensibility which at the time only made the resulting bundle much bigger.
Now, things have improved a lot, and I'm really happy with Tiptap and the development version of django-prose-editor. Writing an editor extension in project code is great, and my editor core stays nice. Also the list of readily available extensions is large, and most of the things just work.
04 Dec 2024 6:00pm GMT
Making Django Ready for the Next 20 Years
Emma Delescolle's candidacy statement for the Django Steering Council.
Making Django ready for the next 20 years by: - lowering the barrier to contribution and involving a more diverse set of contributors - dealing with the realities of an aging code-base - building code ownership and groups specializing in specific areas of core - enacting feature requests from the steering council (django roadmap) - improving the third-party package story
Read more in the article!
04 Dec 2024 6:00am GMT
29 Nov 2024
Django community aggregator: Community blog posts
Django News - 2024 Malcolm Tredinnick Memorial Prize awarded to Rachell Calhoun - Nov 29th 2024
News
2024 Malcolm Tredinnick Memorial Prize awarded to Rachell Calhoun
This year's winner is Rachell Calhoun. Read more about her contributions to Django along with those of her follow nominees.
Django 6.x Steering Council Candidate Registration
Registration is open for candidates until December 4, 2024.
🏷️ Python Black Friday & Cyber Monday sales (2024)
The seventh annual compilation of Python learning deals compiled by Trey Hunner.
Django Software Foundation
Django Developers Survey 2024
The annual Django Developers Survey is now live! It should take you about 10 minutes to complete and provides a wealth of information to the Django team and community on how Django is actually being used.
DjangoCon Europe 2026 call for organizers completed
The DjangoCon Europe 2026 call for organizers is now over. We're elated to report we received three viable proposals, a clear improvement over recent years.
Updates to Django
Today's 'Updates to Django' is presented by Abigail Afi Gbadago from Djangonaut Space!
Last week we had 10 pull requests merged into Django by 9 different contributors - including a first-time contributor! Congratulations to Caitlin Hogan for having their first commit merged into Django - welcome on board! 🚀
New in Django 5.2
- The default mysql encoding has been updated from "utf8"/"utf8mb3" to "utf8mb4".
- The new helper
simple_block_tag
can be used to create custom "block" tags to capture contents as an argument. Special thanks for the long work on this PR 🥳
Django Newsletter
A new simple block tag helper is coming to Django 5.2
Huge congrats to Jake Howard and Natalia Bidart for pushing this over the line!
Sponsored Link 1
LearnDjango BlackFriday - 50% until December 1st!
Enjoy 50% off and lifetime access to the courses on LearnDjango.com, featuring the works of Will Vincent, author of Django for Beginners/APIs/Professionals.
Articles
Squashing Django Migrations Easily
Safely squash Django migrations in long-running projects to optimize performance and maintain migration history integrity using django-model-info
I Built a Plugin System for DJ Press
Inspired by Simon Willison's DjangoCon talk on building plugin systems, the project DJ Press now has its own system built from scratch.
The Practical Guide to Scaling Django
Most Django scaling guides focus on theoretical maximums. But real scaling isn't about handling hypothetical millions of users - it's about systematically eliminating bottlenecks as you grow. Here's how to do it right, based on patterns that work in production.
GenericForeignKey Deep Filtering
A technique for using Django's GenericForeignKey to implement a kind of deep filtering of a model.
Django: fix a view using a debugger with breakpoint()
Python's breakpoint() function opens its debugger, pdb, which pauses the program and allows you to inspect and modify things. Let's look at an example of using it within a Django view.
Steering Council 6.x Thoughts
Tim Schilling with some extended thoughts on future members of the Steering Council might accomplish.
Tutorials
Using GitHub Actions to run a Python script
Tips and code on how to use GitHub Actions to run a Python script.
Building a Chat Backend for Wikipedia Articles in Django
A tutorial that walks you through setting up a Django backend for a chat application powered by Wikipedia content to help start you on your chatbot journey.
Podcasts
Django Chat #171: Python Tooling with Hynek Schlawack
Hynek is a core Python contributor, author of the popular attrs library, and an engineer at Variomedia, a Germany-based web hosting company. We discuss Python performance, tooling (especially uv), and more.
Django News Jobs
Full-Stack Web Engineer (Python/Django Specialist) at e180, inc 🆕
Remote Senior Django Full-Stack Developer (German speaking) at ImmoMetrica 🆕
Django Newsletter
Projects
stuartmaxwell/djpress
A blog application for Django sites, inspired by classic WordPress.
joshuadavidthomas/django-github-app
A Django toolkit for GitHub Apps with batteries included.
This RSS feed is published on https://django-news.com/. You can also subscribe via email.
29 Nov 2024 5:00pm GMT
28 Nov 2024
Django community aggregator: Community blog posts
Django: launch pdb in templates with a custom {% breakpoint %} tag
In my recent Boost Your Django DX update, I added a new chapter on debuggers. Here's an extra technique I didn't finish in time for the update, but I will include it in the next one.
Django templates can be hard to debug, especially to figure out which variables are available after several levels of {% extends %}
or {% include %}
tags. The template engine doesn't provide a built-in way tag to open the debugger, but adding one is not much work.
Below is a custom template tag that starts debugging with breakpoint()
. Find this file in resources.zip
as debuggers/party-central/example/templatetags/debugging.py
.
from django import template
register = template.Library()
@register.simple_tag(name="breakpoint", takes_context=True)
def breakpoint_tag(context):
exec("breakpoint()", {}, context.flatten())
The tag uses exec()
to populate the debugger's local variables with all variables from the current context.
To set up this tag for convenient use:
-
Copy the file as a template tag library called
debugging.py
.This should be in an app's
templatetags
directory, as described in Django's custom template tag tutorial. For example, if you have an app calledcore
, copy this code tocore/templatetags/debugging.py
. -
Add the
debugging
library to thebuiltins
option inTEMPLATES
:TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "example" / "templates"], "APP_DIRS": True, "OPTIONS": { "builtins": [ "example.templatetags.debugging", ], "context_processors": ..., }, } ]
This makes the library always available, with no need for a
{% load debugging %}
tag.
Now, you can invoke the tag with:
{% breakpoint %}
Insert at the point you want to debug.
When the template is rendered, the debugger will open like:
> <string>(1)<module>()
(Pdb)
The filename is listed as <string>
due to the use of exec()
.
The debugger's local variables are populated with the template context:
(Pdb) pp locals().keys()
dict_keys(['True', 'False', 'None', 'csrf_token', 'request', 'animals'])
Note that True
, False
, and None
are included, a template engine quirk. Interact with variables as usual:
(Pdb) animals.count()
10
Running c
(continue
) resumes the template rendering:
(Pdb) c
If you add {% breakpoint %}
to a template that is rendered many times in a loop, use q
/ Ctrl-D to exit and stop rendering.
The stack trace for template rendering is normally quite deep, with many internal function calls in django/template
. You can see this as usual you can see with w
(where
):
(Pdb) w
...
/.../example/views.py(13)index()
-> return render(
/.../django/shortcuts.py(25)render()
-> content = loader.render_to_string(template_name, context, request, using=using)
/.../django/template/loader.py(62)render_to_string()
-> return template.render(context, request)
/.../django/template/backends/django.py(107)render()
...
/.../django/template/library.py(237)render()
-> output = self.func(*resolved_args, **resolved_kwargs)
/.../example/templatetags/debugging.py(8)breakpoint_tag()
-> exec("breakpoint()", {}, context.flatten())
> <string>(1)<module>()
So, if you need to jump to the code that started the template render, be prepared to run u
(up
) a bunch of times.
28 Nov 2024 6:00am GMT
27 Nov 2024
Django community aggregator: Community blog posts
Python Tooling - Hynek Schlawack
- Hynek's personal website
- Variomedia
- Hynek on YouTube, Mastodon, and GitHub
- DjangoCon 2008 Keynote by Cal Henderson: Why I Hate Django
- attrs - Python Classes Without Boilerplate
- svcs - A Flexible Service Locator for Python
- Hynek's 2011 Review, Ticket 6148 and 373
- Jacob Kaplan-Moss Thread on extending languages
- The End Of Object Inheritance & The Beginning Of A New Modularity
- The Rising Sea
- DjangoTV
- Subclassing, Composition, Python, and You - Talk
- Subclassing in Python Redux
Sponsor
27 Nov 2024 6:00pm GMT
26 Nov 2024
Django community aggregator: Community blog posts
Django: fix a view using a debugger with breakpoint()
Python's breakpoint()
function opens its debugger, pdb, which pauses the program and allows you to inspect and modify things. Let's look at an example of using it within a Django view, from a sample project included in Boost Your Django DX.
Here's what the project looks like:
This page, "Party Central", lists animals with their pizza preferences and whether they're hungry. Underneath the table are two filter buttons, "Hungry" and "Satiated", which allow you to select only animals with those hunger levels.
Unfortunately, the filter buttons are broken. Click "Hungry" to load http://localhost:8000/?hungry=1
and we see the same list of animals:
The hungry
URL parameter is there, and the button is highlighted, but the data isn't filtered. Let's use pdb to figure out why.
We can run pdb with the breakpoint()
function, a Python built-in that opens the configured debugger (which is pdb by default). Let's add it to the view function, before it renders the template.
Here's how the views.py
file looks:
from django.shortcuts import render
from example.models import Animal
def index(request):
animals = Animal.objects.order_by("name")
hungry = request.GET.get("hunger")
if hungry is not None:
animals = animals.filter(is_hungry=hungry)
return render(
request,
"index.html",
{"animals": animals},
)
Modify it to call breakpoint()
after the filtering attempt like so:
@@ -10,6 +10,8 @@ def index(request):
if hungry is not None:
animals = animals.filter(is_hungry=hungry)
+ breakpoint()
+
return render(
request,
"index.html",
After saving the file, runserver
detects the change and reload. Then, after refreshing the page in the browser (http://localhost:8000/?hungry=1
), and we see it never finishes loading. Instead, the server pauses with pdb open:
$ ./manage.py runserver
...
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
> /.../example/views.py(15)index()
-> return render(
(Pdb)
This is pdb's prompt, a bit like the Python shell. We can use pdb commands to manipulate the running program.
First, let's inspect the query parameters in request.GET
:
(Pdb) request.GET
<QueryDict: {'hungry': ['1']}>
(Pdb)
Okay, the mapping contains a hungry
parameter with the value '1'
, which matches what we see in the URL after clicking the "Hungry" filter.
Double-check the variable:
(Pdb) hungry
(Pdb)
Huh, no output, which generally means it's None
. We can double-check with the pp
command, which pretty-prints values (using Python's pprint module):
(Pdb) pp hungry
None
(Pdb)
Hmm, that's odd. There's definitely an issue with how the hungry
variable is set.
Let's try rerunning the expression from line 12 manually and compare that with request.GET
:
(Pdb) pp request.GET.get("hunger")
None
(Pdb) request.GET
<QueryDict: {'hungry': ['1']}>
(Pdb)
Did you spot the issue yet? It's a classic typo: hunger
should be hungry
.
We can check the correct key works:
(Pdb) pp request.GET.get("hungry")
'1'
(Pdb)
Yes, indeed. Time to correct the view and drop the breakpoint()
call:
@@ -6,12 +6,10 @@
def index(request):
animals = Animal.objects.order_by("name")
- hungry = request.GET.get("hunger")
+ hungry = request.GET.get("hungry")
if hungry is not None:
animals = animals.filter(is_hungry=hungry)
- breakpoint()
-
return render(
request,
"index.html",
runserver
will reload, automatically closing the pdb session:
$ ./manage.py runserver
...
(Pdb) /.../example/views.py changed, reloading.
Watching for file changes with StatReloader
...
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
And now the filter buttons work as expected:
Great, now we can run our pizza party with confidence.
Whilst this bug was a not-particularly-complicated typo, it would have been harder to spot without pdb. There was no traceback to look at, but we knew the issue was in the view. Compared to print()
debugging, pdb allowed us to inspect the expressions we wanted without modifying the code, restarting the server, and reloading the page for each check.
Fin
For more on using pdb with Django, see the Debuggers chapter in Boost Your Django DX.
Take a break, point,
-Adam
26 Nov 2024 6:00am GMT
23 Nov 2024
Django community aggregator: Community blog posts
Creating AI-based Summaries in a Django Website
Summarizing lengthy text can be tedious, especially on platforms like PyBazaar, where concise summaries improve user experience. In this post, I'll share how I used Simplemind and Gemini to automate this process in my Django-based project.
Background Info
Recently, I launched PyBazaar.com, a website for Python developers to show their skills, find job offers, and post and find development resources. Its purpose is to have a central place where Python developers can market their services, products, or projects.
PyBazaar shows lengthy descriptions of career opportunities and resources in the detail views and short summaries in the list views. Summaries help users quickly grasp the content of resources and career opportunities without opening each detailed view, enhancing the overall browsing experience on PyBazaar. To make the editing smoother, I introduced automatic summarization based on AI.
Choosing Simplemind for Communication with LLMs
Kenneth Reitz, the author of the famous package requests, recently published his newest creation-Simplemind-which improves the developer experience with the APIs of large language models (LLMs). I thought it would be a good opportunity to try integrating his package into PyBazaar.
While I chose Google Gemini for its free tier, Simplemind's support for providers like OpenAI or Claude means developers can scale up for more advanced features or more precise results if needed.
Setting Up API Keys
At first, I had to get an API Key at Google AI Studio.
Then I installed Simplemind:
(venv)$ pip install 'simplemind[full]'
However, while waiting for one of the dependencies (grpcio) to compile on my Mac, I had time for an energy drink and enough time to scroll through half my social media feed.
Simplemind expects the LLM API keys to be defined in the environment variables. In my Django projects, I store the secrets in JSON files, which Git ignores, and I read those values with a utility function I wrote, get_secret().
So, I added these lines in the Django settings:
import os
os.environ["GEMINI_API_KEY"] = get_secret("GEMINI_API_KEY")
DEFAULT_LLM_PROVIDER = "gemini"
Django Integration
I created a straightforward view that takes posted HTML content, asks LLM to summarize it, and returns the summary to the user:
import json
import simplemind
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.http import JsonResponse
from django.utils.html import strip_tags
@login_required
def summarize(request):
summary = ""
try:
if (
request.method == "POST"
and (data := json.loads(request.body))
and (content := data.get("content"))
and (text := strip_tags(content).strip())
):
summary = simplemind.generate_text(
prompt=f"Condense the following information in 2 sentences:\n\n{text}",
llm_provider=settings.DEFAULT_LLM_PROVIDER,
).strip()
except json.JSONDecodeError:
pass
data = {"summary": summary}
return JsonResponse(data)
As you can see, Simplemind is as elegant as the requests app. I could easily switch to OpenAI or Claude if I needed more advanced results or smarter queries.
I used strip_tags()
to reduce the token count and strip()
to remove leading and trailing whitespaces.
To improve the view's performance, I could also use ASGI or a background task, but that's something to consider when there are more users at PyBazaar.
The summarization button had its template, which I included in my Django Crispy Forms layout with layout.HTML("""{% include "summarizer/includes/summarize_button.html" %}""")
:
{% load i18n static %}
<button type="button" class="summarize btn btn-secondary mb-3" data-url="{% url 'summarize' %}">
{% translate "Summarize by AI" %}
</button>
<script src="{% static 'site/js/summarize.js' %}"></script>
Javascript Handling
And finally, the summarize.js
looked like this:
document.addEventListener('DOMContentLoaded', function() {
const summarizeButtons = document.querySelectorAll('.summarize');
summarizeButtons.forEach(button => {
button.addEventListener('click', async function(e) {
const btn = e.currentTarget;
const url = btn.dataset.url;
const originalText = btn.textContent;
try {
// Disable button and show loading state
btn.disabled = true;
btn.textContent = 'Summarizing...';
// Get HTML content from Quill
const contentField = document.getElementById('quill-input-id_description');
const quillData = JSON.parse(contentField.value);
const contentHtml = quillData.html;
const csrfToken = document.querySelector('[name="csrfmiddlewaretoken"]').value;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify({
content: contentHtml
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Get the summary field and update its value
const summaryField = document.getElementById('id_summary');
summaryField.value = data.summary;
// Trigger change event in case there are any listeners
summaryField.dispatchEvent(new Event('change'));
} catch (error) {
console.error('Summarization failed:', error);
alert('Failed to generate summary. Please try again.');
} finally {
// Reset button state
btn.disabled = false;
btn.textContent = originalText;
}
});
});
});
When a user clicked on the "Summarize by AI" button, the Javascript temporarily disabled the button, changed its text to "Summarizing...", read the HTML value from the QuillJS field, and posted it as {"content": "..."}
to the summarize
view. After receiving the summary as {"summary": "..."}
, the Javascript filled in the summary textarea and made the button clickable again.
Conclusion
Simplemind makes working with LLMs easier using smart defaults, so developers don't have to adjust complicated settings like temperature
or max_tokens
.
Gemini LLM can be used for free, and that seems good enough for simple features like this with a moderate number of active users.
I implemented this summarization feature at PyBazaar in just half a day, and I could easily adapt this integration to generate meta descriptions, email drafts, or personalized recommendations.
If you're a Python developer looking to showcase your skills, share resources, or find opportunities, visit PyBazaar.com today!
Cover photo by Caio
23 Nov 2024 6:00pm GMT
Creating AI-based Summaries in a Django Website
Summarizing lengthy text can be tedious, especially on platforms like PyBazaar, where concise summaries improve user experience. In this post, I'll share how I used Simplemind and Gemini to automate this process in my Django-based project.
Background Info
Recently, I launched PyBazaar.com, a website for Python developers to show their skills, find job offers, and post and find development resources. Its purpose is to have a central place where Python developers can market their services, products, or projects.
PyBazaar shows lengthy descriptions of career opportunities and resources in the detail views and short summaries in the list views. Summaries help users quickly grasp the content of resources and career opportunities without opening each detailed view, enhancing the overall browsing experience on PyBazaar. To make the editing smoother, I introduced automatic summarization based on AI.
Choosing Simplemind for Communication with LLMs
Kenneth Reitz, the author of the famous package requests, recently published his newest creation-Simplemind-which improves the developer experience with the APIs of large language models (LLMs). I thought it would be a good opportunity to try integrating his package into PyBazaar.
While I chose Google Gemini for its free tier, Simplemind's support for providers like OpenAI or Claude means developers can scale up for more advanced features or more precise results if needed.
Setting Up API Keys
At first, I had to get an API Key at Google AI Studio.
Django Integration
Then I installed Simplemind:
(venv)$ pip install 'simplemind[full]'
However, while waiting for one of the dependencies (grpcio) to compile on my Mac, I had time for an energy drink and enough time to scroll through half my social media feed.
Simplemind expects the LLM API keys to be defined in the environment variables. In my Django projects, I store the secrets in JSON files, which Git ignores, and I read those values with a utility function I wrote, get_secret().
So, I added these lines in the Django settings:
import os
os.environ["GEMINI_API_KEY"] = get_secret("GEMINI_API_KEY")
DEFAULT_LLM_PROVIDER = "gemini"
I created a straightforward view that takes posted HTML content, asks LLM to summarize it, and returns the summary to the user:
import json
import simplemind
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.http import JsonResponse
from django.utils.html import strip_tags
@login_required
def summarize(request):
summary = ""
try:
if (
request.method == "POST"
and (data := json.loads(request.body))
and (content := data.get("content"))
and (text := strip_tags(content).strip())
):
summary = simplemind.generate_text(
prompt=f"Condense the following information in 2 sentences:\n\n{text}",
llm_provider=settings.DEFAULT_LLM_PROVIDER,
).strip()
except json.JSONDecodeError:
pass
data = {"summary": summary}
return JsonResponse(data)
As you can see, Simplemind is as elegant as the requests app. I could easily switch to OpenAI or Claude if I needed more advanced results or smarter queries.
I used strip_tags()
to reduce the token count and strip()
to remove leading and trailing whitespaces.
To improve the view's performance, I could also use ASGI or a background task, but that's something to consider when there are more users at PyBazaar.
The summarization button had its template, which I included in my Django Crispy Forms layout with layout.HTML("""{% include "summarizer/includes/summarize_button.html" %}""")
:
{% load i18n static %}
<button type="button" class="summarize btn btn-secondary mb-3" data-url="{% url 'summarize' %}">
{% translate "Summarize by AI" %}
</button>
<script src="{% static 'site/js/summarize.js' %}"></script>
Javascript Handling
And finally, the summarize.js
looked like this:
document.addEventListener('DOMContentLoaded', function() {
const summarizeButtons = document.querySelectorAll('.summarize');
summarizeButtons.forEach(button => {
button.addEventListener('click', async function(e) {
const btn = e.currentTarget;
const url = btn.dataset.url;
const originalText = btn.textContent;
try {
// Disable button and show loading state
btn.disabled = true;
btn.textContent = 'Summarizing...';
// Get HTML content from Quill
const contentField = document.getElementById('quill-input-id_description');
const quillData = JSON.parse(contentField.value);
const contentHtml = quillData.html;
const csrfToken = document.querySelector('[name="csrfmiddlewaretoken"]').value;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify({
content: contentHtml
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Get the summary field and update its value
const summaryField = document.getElementById('id_summary');
summaryField.value = data.summary;
// Trigger change event in case there are any listeners
summaryField.dispatchEvent(new Event('change'));
} catch (error) {
console.error('Summarization failed:', error);
alert('Failed to generate summary. Please try again.');
} finally {
// Reset button state
btn.disabled = false;
btn.textContent = originalText;
}
});
});
});
When a user clicked on the "Summarize by AI" button, the Javascript temporarily disabled the button, changed its text to "Summarizing...", read the HTML value from the QuillJS field, and posted it as {"content": "..."}
to the summarize
view. After receiving the summary as {"summary": "..."}
, the Javascript filled in the summary textarea and made the button clickable again.
Conclusion
Simplemind makes working with LLMs easier using smart defaults, so developers don't have to adjust complicated settings like temperature
or max_tokens
.
Gemini LLM can be used for free, and that seems good enough for simple features like this with a moderate number of active users.
I implemented this summarization feature at PyBazaar in just half a day, and I could easily adapt this integration to generate meta descriptions, email drafts, or personalized recommendations.
If you're a Python developer looking to showcase your skills, share resources, or find opportunities, visit PyBazaar.com today!
Cover photo by Caio
23 Nov 2024 5:54pm GMT
22 Nov 2024
Django community aggregator: Community blog posts
Django News - 2025 DSF Board Results - Nov 22nd 2024
News
Announcing the 6.x Django Steering Council elections 🚀
The Django Software Foundation has announced early elections for the 6.x Steering Council to address technical governance challenges and guide the project's future direction.
Django Channels 4.2.0 Release Notes
Channels 4.2 adds enhanced async support, including improved handling of database connections, compatibility with Django 5.1, and various bug fixes and improvements such as better in-memory channel layer behavior and more robust WebSocket handling.
Python Insider: Python 3.14.0 alpha 2 released
Python 3.14.0 alpha 2 introduces features like deferred evaluation of annotations and a new Python configuration C API.
Django Software Foundation
2025 DSF Board Election Results
The 2025 DSF Board Election results are in, with Abigail Gbadago, Jeff Triplett, Paolo Melchiorre, and Tom Carrick joining the board for two-year terms.
2024 Django Developers Survey
The 2024 Django Developers Survey, is open until December 21st, offering insights into Django usage and a chance to win a $100 gift card for participants providing meaningful answers.
DSF Board monthly meeting, November 19, 2024
Meeting minutes for DSF Board monthly meeting, November 19, 2024
Updates to Django
Today's 'Updates to Django' is presented by Abigail Afi Gbadago from Djangonaut Space!
Last week we had 19 pull requests merged into Django by 15 different contributors - including 2 first-time contributors!🎉
Congratulations to taso and Laurence Mercer for having their first commits merged into Django - welcome on board 🚀!
New in Django 5.2:
- Access to admin docs model pages is now based on users permissions in
django.contrib.admindocs
. - HTTP status codes 307 and 308 are now possible when using the new
preserve_method
argument to:HttpResponseRedirect
HttpResponsePermanentRedirect
django.shortcuts.redirect
Django Newsletter
Wagtail CMS
Two years of the Wagtail public roadmap
Wagtail celebrates two years of its public roadmap, achieving 55 roadmap items across 8 releases, improving transparency, and inviting community input through RFCs, contributions, and sponsorship opportunities.
Wagtail 6.3.1 released
Wagtail 6.3.1 addresses several fixes, including profile picture uploads, data conversion handling, and UI improvements, along with documentation updates for clarity and accuracy.
A tale of digging into Wagtail's page tree internals
An exploration of Wagtail's page tree internals reveals how understanding its Materialized Path implementation enables efficient querying for parent pages, improving features like search result context while avoiding N+1 queries.
Sponsored Link 1
SaaS Pegasus is a starter project and community for building Django apps fast. It includes built-in teams, billing, AI apps, a modern front end, deployment, and more. This week only, you can get lifetime access to Pegasus and its 1,000-member support community for 50% off.
Articles
Django-related Deals for Black Friday 2024
Discover a variety of Django-related deals and discounts available this Black Friday and Cyber Monday, including books, courses, and software packages, with discounts up to 50%.
How to hire a Python Developer
To successfully hire a Python developer, focus on improving the interview process by streamlining stages, emphasizing cultural fit, and making quick decisions to attract top talent.
Is async django ready for prime time?
Django's async capabilities have significantly improved, making it a viable option for production use, especially in AI applications where I/O-bound tasks are prevalent.
How to efficiently Send Emails Asynchronously in Django
Efficiently send emails asynchronously in Django by implementing a custom email backend that leverages background processing tools like RQ or Celery.
Manually setting a field when saving a ModelForm
Easily configure Django ModelForm
to automatically set the current user as the article's author, while also handling multiple authors and maintaining the integrity of save operations.
SPF Record Generator
Configuring email is hard. This new tool from Stuart Maxwell is a quick-and-easy way to sort out your SPF records.
Introducing DjangoVer
DjangoVer is a new versioning system for Django-related packages that ties version numbers to the latest supported Django release, providing clear compatibility information at a glance.
DSF Board, AMA after 8 months
Sarah Abderemane reflects on the challenges and progress of the Django Software Foundation board after eight months, emphasizing the need for improved communication, fundraising, and community engagement.
Thoughts on my election as a DSF board member
Paolo Melchiorre reflects on his election to the Django Software Foundation board, expressing gratitude and a commitment to fostering an inclusive community.
Events
Django Congress Japan 2025
DjangoCongress JP 2025 is an online conference held online in Japan on Feb 22nd, 2025.
Videos
"Django: Looking Forward to the Next 20 years" with Emma Delescolle
Emma Delescolle presents her talk, "Django: Looking Forward to the Next 20 years" to the Djangonaut Space 2024 Session 3 team.
"Demystifying the Django ORM" with Simon Charette
Simon Charette presents his talk, "Demystifying the Django ORM" to the Djangonaut Space 2024 Session 3 team.
Sponsored Link 2
LearnDjango Black Friday Sale - 50% Off!
Enjoy 50% off and lifetime access to the courses on LearnDjango.com, featuring the works of Will Vincent, author of Django for Beginners/APIs/Professionals.
Podcasts
Django Chat Podcast now on YouTube
You can listen to all episodes of Django Chat on YouTube now if you prefer listening that way.
Django News Jobs
Remote Senior Django Full-Stack Developer (German speaking) at ImmoMetrica 🆕
Django Newsletter
Django Forum
Pre-PEP: Standardizing test dependency and command specification - Packaging / Standards - Discussions on Python.org
While technically a Python forum post, if you have thoughts on how test dependencies could be handled with a pyproject.toml
then this thread is for you.
Trac migration informational DEP - Feedback required - Django Internals
The group writing a DEP for a potential Trac migration is seeking input on essential Trac features, such as reports metadata fields, when comparing them to Github issues or other platforms.
Projects
pretix/pretix
Ticket shop application for conferences, festivals, concerts, tech events, shows, exhibitions, workshops, barcamps, etc.
benfred/py-spy
Sampling profiler for Python programs.
This RSS feed is published on https://django-news.com/. You can also subscribe via email.
22 Nov 2024 5:00pm GMT
Huey Background Worker - Building SaaS #207
In this episode, I continued a migration of my JourneyInbox app from Heroku to DigitalOcean. I switched how environment configuration is pulled and converted cron jobs to use Huey as a background worker. Then I integrated Kamal configuration and walked through what the config means.
22 Nov 2024 6:00am GMT
21 Nov 2024
Django community aggregator: Community blog posts
Django: find ghost tables without associated models
Heavy refactoring of models can leave a Django project with "ghost tables", which were created for a model that was removed without any trace in the migration history. Thankfully, by using some Django internals, you can find such tables.
Use the database introspection methods table_names()
to list all tables and django_table_names()
to list tables associated with models. By casting these to sets, you can subtract the latter from the former to find tables not associated with a model:
In [1]: from django.db import connection
In [2]: table_names = set(connection.introspection.table_names())
In [3]: django_table_names = set(connection.introspection.django_table_names())
In [4]: table_names - django_table_names - {"django_migrations"}
Out[4]:
{'sweetshop_humbug',
'sweetshop_jellybean',
'sweetshop_marshmallow'}
Note the django_migrations
table needs excluding. This is Django's internal table for tracking migrations, which has no associated (permanent) model.
From here, you'll want to make a judgement call on what to do with the tables. Perhaps should have models and others can be removed.
If a ghost table has no useful data or migration references, consider dropping it directly with SQL, rather than adding a migration. This can be done with dbshell
. For example, using PostgreSQL:
$ ./manage.py dbshell
psql (...)
Type "help" for help.
candy=# DROP TABLE sweetshop_humbug;
DROP TABLE
21 Nov 2024 6:00am GMT
20 Nov 2024
Django community aggregator: Community blog posts
Weeknotes (2024 week 47)
Weeknotes (2024 week 47)
I missed a single co-writing session and of course that lead to four weeks of no posts at all to the blog. Oh well.
Debugging
I want to share a few debugging stories from the last weeks.
Pillow 11 and Django's get_image_dimensions
The goal of django-imagefield was to deeply verify that Django and Pillow are able to work with uploaded files; some files can be loaded, their dimensions can be inspected, but problems happen later when Pillow actually tries resizing or filtering files. Because of this django-imagefield does more work when images are added to the system instead of working around it later. (Django doesn't do this on purpose because doing all this work up-front could be considered a DoS factor.)
In the last weeks I suddenly got recurring errors from saved files again, something which shouldn't happen, but obviously did.
Django wants to read image dimensions when accessing or saving image files (by the way, always use height_field
and width_field
, otherwise Django will open and inspect image files even when you're only loading Django models from the database…!) and it uses a smart and wonderful1 hack to do this: It reads a few hundred bytes from the image file, instructs Pillow to inspect the file and if an exception happens it reads more bytes and tries again. This process relies on the exact type of exceptions raised internally though, and the release of Pillow 11 changed the types… for some file types only. Fun times.
The issue had already been reported as #33240 and is now tracked as #8530 on the Pillow issue tracker. Let's see what happens. For now, django-imagefield declares itself to be incompatible with Pillow 11.0.0 so that this error cannot happen.
rspack and lightningcss shuffled CSS properties
rspack 1.0 started reordering CSS properties which of course lead to CSS properties overriding each other in the incorrect order. That was a fun one to debug. I tracked the issue down to the switch from the swc CSS minimizer to lightningcss and submitted a reproduction to the issue tracker. My rust knowledge wasn't up to the task of attempting to submit a fix myself. Luckily, it has been fixed in the meantime.
rspack problems
I have another problem with rspack where I haven't yet tracked down the issue. rspack produces a broken bundle starting with 1.0.0-beta.2 when compiling a particular project of mine. I have the suspicion that I have misconfigured some stuff related to import paths and yarn workspaces. I have no idea how anyone could have a complete understanding of these things…
Bundlers are complex beasts, and I'm happy that I mostly can just use them.
Closing thoughts
Debugging is definitely a rewarding activity for me. I like tracking stuff down like this. Unfortunately, problems always tend to crop up when time is scarce already, but what can you do.
Releases
Quite a few releases, many of them verifying Python 3.13 and Django 5.1 support (if it hasn't been added already in previous releases). The nicest part: If I remember correctly I didn't have to change anything anywhere, everything just continues to work.
- django-admin-ordering 0.19: I added support for automatically renumbering objects on page load. This is mostly useful if you already have existing data which isn't ordered yet.
- feincms3-data 0.7: Made sure that objects are dumped in a deterministic order when dumping. I wanted to compare JSON dumps by hand before and after a big data migration in a customer project and differently ordered dumps made the comparison impossible.
- django-prose-editor 0.9: I updated the ProseMirror packages, and put the editor into read-only mode for
<textarea disabled>
elements. - feincms3-language-sites 0.4: Finally released the update containing the necessary hook to validate page trees and their unique paths before moving produces integrity errors. Error messages are nicer than internal server errors.
- django-authlib 0.17.2: The value of the cookie which is used to save the URL where users should be redirected to after authentication wasn't checked for validity when setting it, only when reading it. This meant that attackers could produce invalid header errors in application servers. No real security problem here when using authlib's code.
- feincms3 5.3: Minor update which mostly removes support for outdated Python and Django versions.
- django-imagefield 0.20: See above.
-
wonderfully ugly ↩
20 Nov 2024 6:00pm GMT
18 Nov 2024
Django community aggregator: Community blog posts
Introducing DjangoVer
Version numbering is hard, and there are lots of popular schemes out there for how to do it. Today I want to talk about a system I've settled on for my own Django-related packages, and which I'm calling "DjangoVer", because it ties the version number of a Django-related package to the latest Django version that package supports.
But one quick note to start with: this is not really "introducing" the idea of DjangoVer, because I know I've used the name a few times already in other places. I'm also not the person who invented this, and I don't know for certain who did - I've seen several packages which appear to follow some form of DjangoVer and took inspiration from them in defining my own take on it.
Django's version scheme: an overview
The basic idea of DjangoVer is that the version number of a Django-related package should tell you which version of Django you can use it with. Which probably doesn't help much if you don't know how Django releases are numbered, so let's start there. In brief:
- Django issues a "feature release" - one which introduces new features - roughly once every eight months. The current feature release series of Django is 5.1.
- Django issues "bugfix releases" - which fix bugs in one or more feature releases - roughly once each month. As I write this, the latest bugfix release for the 5.1 feature release series is 5.1.3 (along with Django 5.0.9 for the 5.0 feature release series, and Django 4.2.16 for the 4.2 feature release series).
- The version number scheme is
MAJOR.FEATURE.BUGFIX
, whereMAJOR
,FEATURE
, andBUGFIX
are integers. - The
FEATURE
component starts at0
, then increments to1
, then to2
, thenMAJOR
is incremented andFEATURE
goes back to0
.BUGFIX
starts at0
with each new feature release, and increments for the bugfix releases for that feature release. - Every feature release whose
FEATURE
component is2
is a long-term support ("LTS") release.
This has been in effect since Django 2.0 was released, and the feature releases have been: 2.0, 2.1, 2.2 (LTS); 3.0, 3.1, 3.2 (LTS); 4.0, 4.1, 4.2 (LTS); 5.0, 5.1. Django 5.2 (LTS) is expected in April 2025, and then eight months later (if nothing is changed) will come Django 6.0.
I'll talk more about SemVer in a bit, but it's worth being crystal clear that Django does not follow Semantic Versioning, and the MAJOR
number is not a signal about API compatibility. Instead, API compatibility runs LTS-to-LTS, with a simple principle: if your code runs on a Django LTS release and raises no deprecation warnings, it will run unmodified on the next LTS release. So, for example, if you have an application that runs without deprecation warnings on Django 4.2 LTS, it will run unmodified on Django 5.2 LTS (though at that point it might begin raising new deprecation warnings, and you'd need to clear them before it would be safe to upgrade any further).
DjangoVer, defined
In DjangoVer, a Django-related package has a version number of the form DJANGO_MAJOR.DJANGO_FEATURE.PACKAGE_VERSION
, where DJANGO_MAJOR
and DJANGO_FEATURE
indicate the most recent feature release series of Django supported by the package, and PACKAGE_VERSION
begins at zero and increments by one with each release of the package supporting that feature release of Django.
Since the version number only indicates the newest Django feature release supported, a package using DjangoVer should also use Python package classifiers to indicate the full range of its Django support (such as Framework :: Django :: 5.1
to indicate support for Django 5.1 - see examples on PyPI).
But while Django takes care to maintain compatibility from one LTS to the next, I do not think DjangoVer packages need to do that; they can use the simpler approach of issuing deprecation warnings for two releases, and then making the breaking change. One of the stated reasons for Django's LTS-to-LTS compatibility policy is to help third-party packages have an easier time supporting Django releases that people are actually likely to use; otherwise, Django itself generally just follows the "deprecate for two releases, then remove it" pattern. No matter what compatibility policy is chosen, however, it should be documented clearly, since DjangoVer explicitly does not attempt to provide any information about API stability/compatibility in the version number.
That's a bit wordy, so let's try an example:
- If you started a new Django-related package today, you'd (hopefully) support the most recent Django feature release, which is 5.1. So the DjangoVer version of your package should be
5.1.0
. - As long as Django 5.1 is the newest Django feature release you support, you'd increment the third digit of the version number. As you add features or fix bugs you'd release
5.1.1
,5.1.2
, etc. - When Django 5.2 comes out next year, you'd (hopefully) add support for it. When you do, you'd set your package's version number to
5.2.0
. This would be followed by5.2.1
,5.2.2
, etc., and then eight months later by6.0.0
to support Django 6.0. - If version
5.1.0
of your package supports Django 5.1, 5.0, and 4.2 (the feature releases receiving upstream support from Django at the time of the 5.1 release), it should indicate that by including theFramework :: Django
,Framework :: Django :: 4.2
,Framework :: Django :: 5.0
, andFramework :: Django :: 5.1
classifiers in its package metadata.
Why another version system?
Some of you probably didn't even read this far before rushing to instantly post the XKCD "Standards" comic as a reply. Thank you in advance for letting the rest of us know we don't need to bother listening to or engaging with you. For everyone else: here's why I think in this case adding yet another "standard" is actually a good idea.
The elephant in the room here is Semantic Versioning ("SemVer"). Others have written about some of the problems with SemVer, but I'll add my own two cents here: "compatibility" is far too complex and nebulous a concept to be usefully encoded in a simple value like a version number. And if you want my really cynical take, the actual point of SemVer in practice is to protect developers of software from users, by providing endless loopholes and ways to say "sure, this change broke your code, but that doesn't count as a breaking change". It'll turn out that the developer had a different interpretation of the documentation than you did, or that the API contract was "underspecified" and now has been "clarified", or they'll just throw their hands up, yell "Hyrum's Law" and say they can't possibly be expected to preserve that behavior.
A lot of this is rooted in the belief that changes, and especially breaking changes, are inherently bad and shameful, and that if you introduce them you're a bad developer who should be ashamed. Which is, frankly, bullshit. Useful software almost always evolves and changes over time, and it's unrealistic to expect it not to. I wrote about this a few years back in the context of the Python 2/3 transition:
Though there is one thing I think gets overlooked a lot: usually, the anti-Python-3 argument is presented as the desire of a particular company, or project, or person, to stand still and buck the trend of the world to be ever-changing.
But really they're asking for the inverse of that. Rather than being a fixed point in a constantly-changing world, what they really seem to want is to be the only ones still moving in a world that has become static around them. If only the Python team would stop fiddling with the language! If only the maintainers of popular frameworks would stop evolving their APIs! Then we could finally stop worrying about our dependencies and get on with our real work! Of course, it's logically impossible for each one of those entities to be the sole mover in a static world, but pointing that out doesn't always go well.
But that's a rant for another day and another full post all its own. For now it's enough to just say I don't believe SemVer can ever deliver on what it promises. So where does that leave us?
Well, if the version number can't tell you whether it's safe to upgrade from one version to another, perhaps it can still tell you something useful. And for me, when I'm evaluating a piece of third-party software for possible use, one of the most important things I want to know is: is someone actually maintaining this? There are lots of potential signals to look for, but some version schemes - like CalVer - can encode this into the version number. Want to know if the software's maintained? With CalVer you can guess a package's maintenance status, with pretty good accuracy, from a glance at the version number.
Over the course of this year I've been transitioning all my personal non-Django packages to CalVer for precisely this reason. Compatibility, again, is something I think can't possibly be encoded into a version number, but "someone's keeping an eye on this" can be. Even if I'm not adding features to something, Python itself does a new version every year and I'll push a new release to explicitly mark compatibility (as I did recently for the release of Python 3.13). That'll bump the version number and let anyone who takes a quick glance at it know I'm still there and paying attention to the package.
For packages meant to be used with Django, though, the version number can usefully encode another piece of information: not just "is someone maintaining this", but "can I use this with my Django installation". And that is what DjangoVer is about: telling you at a glance the maintenance and Django compatibility status of a package.
DjangoVer in practice
All of my own personal Django-related packages are now using DjangoVer, and say so in their documentation. If I start any new Django-related projects they'll do the same thing.
A quick scroll through PyPI turns up other packages doing something that looks similar; django-cockroachdb and django-snowflake, for example, versioned their Django 5.1 packages as "5.1", and explicitly say in their READMEs to install a package version corresponding to the Django version you use (they also have a maintainer in common, who I suspect of having been an early inventor of what I'm now calling "DjangoVer").
If you maintain a Django-related package, I'd encourage you to at least think about adopting some form of DjangoVer, too. I won't say it's the best, period, because something better could always come along, but in terms of information that can be usefully encoded into the version number, I think DjangoVer is the best option I've seen for Django-related packages.
18 Nov 2024 2:04pm GMT
Django-related Deals for Black Friday 2024
Here are some Django-related deals for this year's Black Friday (29th November) and Cyber Monday (1st December), including my own.
I'll keep this post up to date with any new deals I learn about. If you are also a creator, email me with details of your offer and I'll add it here.
For more general developer-related deals, see BlackFridayDeals.dev.
My books
My three books have a 50% discount, for both individual and team licenses, until the end of Cyber Monday (1st December). This deal stacks with the purchasing power parity discount for those in lower-income countries.
Buy now:
- Boost Your Django DX - $21 instead of $42 (freshly updated!)
- Boost Your Git DX - $19.50 instead of $39
- Speed Up Your Django Tests - $24.50 instead of $49
Aidas Bendoraitis' paid packages
Aidas Bendoraitis of djangotricks.com has created two paid packages. Use the links below for a 20% discount, available until the end of the 1st December.
-
Django GDPR Cookie Consent - a customizable, self-hosted cookie consent screen. This package takes the pain out of setting up legally-mandated cookie banners and settings, without using an expensive or inflexible vendor.
-
Django Paddle Subscriptions - an integration with Paddle's billing API for subscriptions. This package allows you to collect SaaS payments internationally with a reliable payment processor.
Appliku
Appliku is a deployment tool designed for Django. It can deploy your project to AWS, DigitalOcean, Hetzner, and other cloud servers.
They're offering 30% off all annual plans, until the 3rd December. Use code BLACKFRIDAY2024
at checkout.
Async Patterns in Django
Freshly released in June, this book is a tour through the advanced topic of asynchronous programming in Django. It covers the range of tools and protocols available for asynchronous behaviour in your web application. It's written by Paul Bailey, an experienced Principle Engineer.
Paul is offering 50% off the book with the coupon link, discounting the book to $23. This is available until the 3rd December.
Django in Action
Another book published in June this year, this one covers building a project from scratch, detailing how the key pieces of Django fit together. It also extends the project a bit with htmx, a tool that I really like. It's written by Christopher Trudeau, the co-host of the Real Python podcast and a prolific writer.
The publisher, Manning, is offering 50% off the book, from 25th November to 3rd December. Use code mltrudeaublog
at checkout.
SaaS Pegasus
Cory Zue's SaaS Pegasus is a configurable Django project template with many preset niceties, including teams, Stripe subscriptions, a JavaScript pipeline, and multiple CSS themes. It can massively accelerate setting up a SaaS in Django.
The "unlimited lifetime license" is discounted 50%, from $999 to $499.50. This deal is available from the 22nd November until the 4th December.
testdriven.io course bundle
The educational site testdriven.io is running a sale on a bundle of five Django courses:
- Test-Driven Development with Django, Django REST Framework, and Docker by Michael Herman
- The Definitive Guide to Celery and Django by Michael Yin
- Full-text Search in Django with Postgres and Elasticsearch by Jason Parent
- Developing RESTful APIs with Django REST Framework by Špela Giacomelli
- Full-stack Django with HTMX and Tailwind by Špela Giacomelli
Normally these four courses would total $160, but the bundle has a 30% discount to $112. This discount is available until the 2nd December.
Bonus: Django itself
This one isn't really a "deal", but I would like to take a moment to encourage you to contribute to Django itself. If you buy any of the above products, please consider supporting the Django Software Foundation, the charity that runs Django, in its work promoting the community and the framework.
Your money will go towards:
- Paying the Django Fellows, Natalia and Sarah, who merge respond to tickets, merge code, and make releases.
- Helping organize DjangoCons in Africa, America, and Europe, and other events.
- Hosting costs of the documentation, source code, ticketing system, and CI system.
- A very long tail of activities that keep the framework alive and thriving.
Donate to Django on:
- GitHub Sponsors - adds to your GitHub bill.
- Threadless - grab some custom-printed merchandise.
- The Fundraising Page - bills from a credit card with Stripe.
At the time of writing, Django is only 82% towards its annual funding goal of $200,000:
Let's fill up that heart! 💚💚💚
Fin
May you have fun supporting Django creators and the DSF this Black Friday and Cyber Monday!
-Adam
18 Nov 2024 1:48pm GMT
How to migrate your Poetry project to uv
So, like me you've decided to switch from Poetry to uv, and now you're wondering how to actually migrate your pyproject.toml file? You've come to the right place!
18 Nov 2024 6:00am GMT
Boost Your Django DX updated again
I have just released the second update to Boost Your Django DX, my book of developer experience (DX) recommendations for Django projects. This update contains a new chapter, changes some recommended tools, and upgrades to Python 3.13 and Django 5.1. Overall, the book is 45 pages longer, now totalling 326!
The most significant new addition is a chapter on debuggers: Python's built-in one, pdb, and IPython's enhanced version, ipdb. The chapter introduces pdb with realistic examples, covers all the essential commands, and includes many tips, like using the .pdbrc
configuration file.
Another major change is swapping the recommended CSS and JavaScript tools from ESLint and Prettier to Biome. Biome is a super-fast formatter and linter for CSS, JavaScript, JSON, TypeScript, and more. The new section introduces it and provides integration advice for Django projects.
Thank you to everyone who has supported the book so far. Just the other day, it reached fifty five-star reviews. I am very grateful for all the feedback from the community and aim to keep improving the book.
This update is free for all who previously purchased the book. To help readers catch up, the introduction chapter has a changelog with links to the updated sections, reproduced below.
Boost Your Django DX and all my other books are currently discounted 50% for Black Friday. This stacks with the automatic discount to match local purchasing power in your country and the DX bundle deal.
May your workflow be ever more efficient and your code bug free,
-Adam
Changelog
-
Restructured the book:
- The first and last chapters are now called "Introduction" and "Outroduction", for consistency with my other books.
- The documentation chapter has moved from the start of the book to the middle, to make the start more exciting.
- The code quality tools chapters have been reorganized, spreading the content from two chapters to three, with a new chapter titled "Python code quality tools".
-
Upgraded to Python 3.13 and Django 5.1.
-
Added a chapter on debuggers, covering ways to use pdb and ipdb within your Django project. Previously, the IPython section had a short mention of ipdb, with the sentence:
pdb is fantastic and worth learning, but that's for another book or blog post…
Well, that's not in another book or blog post, it's here now. Enjoy and happy debugging.
-
Added a section on Djade, my new tool for formatting Django templates.
-
Added a section on Biome, a formatter and linter for CSS, JavaScript, JSON, TypeScript, and more. This tool replaces my recommendations for ESLint and Prettier, which have been removed per the below release notes.
-
Added a section on IPython's
%who
and%whos
magic commands. -
Updated the isort section to cover the
force_single_line
option. -
Updated the Flake8 section to demonstrate some rules and recommend disabling E501, the maximum line length rule. I don't think it's worth the effort to fix or individually ignore most "line too long" errors.
-
Added Django TV and django-classy-doc to Bonus Django documentation sites.
-
Edited the Black section to cover setting the target version with
project.requires-python
, rather thantool.black.target-version
. -
Removed the section on reorder-python-imports. Unfortunately, it is incompatible with Black version 24, with no fix planned. I recommend using isort instead, which is Black-compatible and can apply a similar style with the
force_single_line
option. -
Removed the (rather small) section on Mypy. It didn't really provide much value.
-
Removed the section on DjHTML. Whilst still a nice tool, its indentation modification can break some whitespace-sensitive contexts, such as
<pre>
tags. Also it removes formatting indentation from inline JavaScript, often used with frameworks like Alpine.js. -
Removed the section on ESLint. It no longer operates correctly under pre-commit since its version 9 release, and I recommend using Biome instead.
-
Removed the section on Prettier. Its pre-commit hook is no longer maintained, and I recommend using Biome instead.
Table of contents
- Introduction
- About this book
- Read in any order
- Example commands and resources
- End-of-chapter feedback links
- Acknowledgements
- Changelog
- About this book
- Virtual environments and dependencies
- On tools and choice
- Virtual environments
- Create a virtual environment with venv
- Avoid committing your virtual environment
- Activate a virtual environment
- Deactivate a virtual environment
- Maybe use virtualenv instead of venv
- Pip and extra tools
- Invoke Pip safely
- The problems with
pip freeze > requirements.txt
- pip-compile: simple dependency management
- Convert an existing
requirements.txt
file topip-compile
- Add a new dependency with
pip-compile
- Remove a dependency With
pip-compile
- Upgrade dependencies with
pip-compile
- pip-lock: keep all environments up to date
- Dependency management
- Stick to a single set of dependencies (probably)
- Pick new dependencies carefully
- Set a dependency upgrade schedule
- Python's Development Mode
- Enable development mode
- Check if development mode is enabled
- When to use development mode
- Python shell
- The
shell
command- Execute a string with
-c
- Execute a temporary file with
-c 'import t'
- Execute a string with
- IPython: a superior Python shell
- Install IPython
- Get help with
?
or??
- Advanced autocomplete with tab
- Reuse results with the output history
- Export and rerun code with the input history
- Copy in code with multi-line paste and
%cpaste
- Iterate quickly with autoreload
- List all imported names with
%who
and%whos
- Start IPython within your code with
IPython.embed()
- Benchmark code with
%timeit
- django-read-only: production data protection
- Quickly toggle read-only mode in IPython with
%read_only
- Quickly toggle read-only mode in IPython with
- The
- Debuggers
- pdb: Python's built-in debugger
- Some initial examples
- Basic commands
- Ways to start pdb
- Extend pdb with
.pdbrc
- Write snapshot-style tests quickly with
--pdb
- ipdb: IPython enhancements to pdb
- Extra features
- Install and use ipdb
- pdb: Python's built-in debugger
- Development server
- Django Debug Toolbar: a development boon
- Install and configure
- Explore the toolbar
- Find problematic database queries with the SQL panel
- Trace non-HTML requests with the history panel
- django-browser-reload: automatically reload your browser in development
- Reloads triggered by template changes
- Reloads triggered by static asset changes
- Reloads triggered by code changes
- Installation
- Rich: beautiful terminal output
- Server logs
- Management commands
- Django Debug Toolbar: a development boon
- Code quality tools
- EditorConfig: consistent text editing
- pre-commit: a code quality framework
- What pre-commit is
- Install pre-commit
- Add a configuration file
- Configuration structure
- Pin language versions with
default_language_version
- Various ways to run hooks
- Update hooks With
pre-commit autoupdate
- Run pre-commit in CI with pre-commit ci
- Introduce a hook incrementally
- Python code quality tools
- Black: the uncompromising code formatter
- How Black formats code
- Install and configure Black
- isort: sorted, grouped import statements
- isort's style
- Split
from
imports one-per-line withforce_single_line
- Install and configure isort
- Add or remove imports from every file
- Flake8: an extensible linter
- Linting examples
- Install and configure Flake8
- flake8-bugbear: extra checks for common problems
- flake8-no-pep420: avoid package problems
- Further plugins
- pyupgrade: upgrade to the latest Python syntax
- Some example rewrites
- Install and configure
- django-upgrade: upgrade to the latest Django features
- Example rewrites
- Install and configure
- Black: the uncompromising code formatter
- Further code quality tools
- Djade: a template formatter
- Formatting examples
- Install and configure
- Biome: a linter-formatter for CSS, JavaScript, JSON, and more
- CSS formatting examples
- CSS linting examples
- JavaScript formatting examples
- JavaScript linting examples
- JSON, TypeScript, and other languages
- Install and configure
- ShellCheck: find bugs in your shell scripts
- An example
- Install
- blacken-docs: apply Black to documentation
- pre-commit-hooks: general purpose checks
- Djade: a template formatter
- Build your own tools
- pre-commit's virtual languages: rapid checks
- Block files based on name with a custom "fail" hook
- Block files based on content with a regular expression hook
- Flake8 plugin: custom source code checks
- The Abstract Syntax Tree (AST)
- Further AST exploration
- Plugin structure
- Make a plugin to ban
lambda
- Test your plugin
- Further reading
- Write a custom tool
- Replace Django documentation links
- What pre-commit expects of tools
- Make a tool
- Example usage
- Run the tool with pre-commit
- Add tests
- pre-commit's virtual languages: rapid checks
- Documentation
- DevDocs: the free rapid documentation tool
- Get started and set up Django's docs
- Perform a basic search
- Search a single documentation source
- Reset the search box
- Visit the original documentation site
- Download documentation sources for offline use
- Useful documentation sources
- More on DevDocs
- DuckDuckGo: a developer-friendly search engine
- Access DuckDuckGo
- Keyboard shortcuts
- Bangs: shortcuts to other search pages
- Instant Answers: expanded results on the first page
- Bonus Django documentation sites
- Classy Class-Based Views
- Classy Django Forms
- Template Tags and Filters
- Classy Django REST Framework
- Awesome Django
- Django TV
- A bonus bonus: django-classy-doc
- Wget: download any website
- Install
- How to download a website
- Example: the Django REST Framework documentation
- Read offline documentation with Python's web server
- An explanation of all the flags
- Miscellaneous tips and tricks
- Python documentation
- Django documentation
- Read the Docs
- Sphinx
- DevDocs: the free rapid documentation tool
- Settings
- Structure your settings
- Use a single settings file with environment variables
- Load a
.env
file locally - Settings in tests
- Group and sort settings
- Order
INSTALLED_APPS
- Use
pathlib
forBASE_DIR
- A settings file template
- Some settings patterns to avoid
- Don't read settings at import time
- Avoid direct setting changes
- Don't import your project settings module
- Avoid creating custom settings where module constants would do
- Avoid creating dynamic module constants instead of settings
- Name your custom settings well
- Override complex settings correctly
- Test your settings file
- Test your settings functions
- Test your settings logic
- Structure your settings
- Models and migrations
- Seed your database with a custom management command
- Sample data approaches
- Create a command
- Test your command
- Factory Boy: easier data generation
- Install and define factories
- Invoke factories
- Use factories in tests
- Use factories in
seed_database
- Further features
- Migration safeguards
- Test for pending migrations
- django-linear-migrations: prevent merge migrations
- Seed your database with a custom management command
- System checks
- How system checks work
- The basics
- How to silence checks
- The advantages of checks
- Write your own checks
- How to write a check function
- How to register a check function
- Add a check for Python's development mode
- Add a check for model class names
- Test your checks
- Test the development mode check
- Test the model name check
- Further places that checks live
- Model class checks
- Model field checks
- django-version-checks: keep your environments in sync
- Install and configure
- Upgrading dependencies
- How system checks work
- Outroduction
- Further reading
- Thank you
18 Nov 2024 6:00am GMT