02 Sep 2026
Planet Python
Tryton News: Tryton News September 2026
September brings a balance of trytond internals and business-module refinements. The server now retries queued tasks that were dropped because a worker died, enforces request timeouts for the whole request, and exposes routes so RPC endpoints can be registered declaratively. On the user side, European VAT numbers are now validated in the background, stock periods close themselves, and the IBAN editor formats the number as it is typed-in. All of this builds on our last LTS release 8.0.
For an in depth overview of all the Tryton issues please take a look at our issue tracker or see the issues and merge requests filtered by label.
Changes for the User
Accounting, Invoicing and Payments
The automatic VIES check replaces the previous wizard for European VAT numbers. A background task validates new and modified EU VAT identifiers and refreshes them once the configured validity period has expired. The validity of the identifier is also checked when posting an invoice, so a stale or invalid VAT number is caught before the invoice is sent. The last validation-state and validation-date is displayed in the identifier view.
The party required setting can no longer be changed on an account that already has account moves. This avoids mixing moves with and without a party on the same account, which used to confuse later reports.
The redundant prefix is dropped from the statement and payment journals actions. So the menus for accounting, statements and payments no longer repeat the "statement" or "payment" word.
The IBAN of a bank account on a party is now formatted with spaces between groups of four characters on changing the field. This makes it easier to check the number was entered correctly.
The version of the Stripe API used by the payment gateway is updated to the latest one.
Stock, Production and Shipments
Stock periods can now be created and closed automatically by two scheduled tasks, automating the manual steps at the start and end of each period.
On a stock move that is linked to a shipment but opened outside the shipment form, the from and to locations are now read-only. This prevents the location domain inherited from the shipment from being bypassed. The domain is also enforced on stock moves, so each move matches at least one of the shipment's two move fields: incoming moves or inventory moves.
User Interface
In the SAO client, tabs now scroll horizontally when they overflow the tab list. The scrolling is smooth for a nicer effect when adding a new tab.
New Releases
We released bug fixes for the currently maintained long term support series 8.0, 7.8, and 7.0.
Changes for Implementers and Developers
A mixin can now be added to the Database and TableHandler from the configuration. This is used by the gis module to register PostGIS as a backend mixin.
The Pool now exposes routes, so RPC endpoints can be registered declaratively using a Router.
The trytond request timeout is now enforced for the whole request, not just for individual queries. A threading timer injects an exception into the running thread when the timeout expires.
Queued tasks that were dequeued but never finished, because the worker was killed, are now retried by a scheduled task. The retry uses the finished_at timestamp and the task lock to know which tasks are still outstanding.
Initial draft powered by Minimax-M3. Curated and finalised by human hands.
1 post - 1 participant
02 Sep 2026 6:00am GMT
Python GUIs: Understanding QPainter Coordinates in PyQt6 — How the coordinate system works for drawing on canvases in PyQt6
I really having trouble understanding the coordinate system used in
QPainter. Can you explain how this works?
If you've started drawing with QPainter in PyQt6, you might have been surprised the first time you drew a line. You pass in coordinates like (10, 10, 300, 200) and the result doesn't look quite like what you'd expect from a math class. That's because QPainter uses a coordinate system where the origin (0, 0) is in the top-left corner of the canvas, not the bottom-left.
This catches a lot of people off guard, so in this tutorial we'll walk through exactly how QPainter coordinates work, how to visualize them, and how to convert between screen coordinates and the mathematical coordinate system you might be more familiar with.
The QPainter coordinate system
In most math courses, you learn to plot points on a Cartesian plane where (0, 0) is at the bottom-left. The x-axis increases to the right, and the y-axis increases upward.
QPainter (and most screen-based graphics systems) does things differently:
(0, 0)is at the top-left corner of the drawing surface.- The x-axis increases to the right (same as math).
- The y-axis increases downward (opposite of math).
This means that as your y value gets larger, you move down the screen, not up. Here's a simple diagram to illustrate:
(0,0) &boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh&boxh► x increases
&boxv
&boxv
&boxv
&boxv
▼
y increases
So when you call painter.drawLine(10, 10, 300, 200), you're drawing a line from a point near the top-left corner down to a point further right and further down the canvas.
Seeing it in action
Let's draw a line and annotate the start and end points so you can see exactly where the coordinates land. This complete example creates a small window with a QLabel displaying a QPixmap that we draw onto.
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QPainter, QPen, QFont
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QPainter Coordinates")
canvas = QPixmap(400, 300)
canvas.fill(Qt.white)
painter = QPainter(canvas)
# Draw the line.
pen = QPen(Qt.blue, 2)
painter.setPen(pen)
painter.drawLine(10, 10, 300, 200)
# Annotate the start point.
pen = QPen(Qt.red, 6)
painter.setPen(pen)
painter.drawPoint(10, 10)
painter.setPen(QPen(Qt.black))
painter.setFont(QFont("Arial", 10))
painter.drawText(20, 15, "(10, 10)")
# Annotate the end point.
pen = QPen(Qt.red, 6)
painter.setPen(pen)
painter.drawPoint(300, 200)
painter.setPen(QPen(Qt.black))
painter.drawText(220, 220, "(300, 200)")
painter.end()
label = QLabel()
label.setPixmap(canvas)
self.setCentralWidget(label)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Run this and you'll see a blue line drawn from near the top-left corner of the canvas down to a point lower and to the right. The red dots and labels mark each endpoint, making it clear that (10, 10) is near the top-left and (300, 200) is toward the bottom-right.

This is the expected behavior - the y-axis points downward.
Why does it work this way?
Screen coordinate systems with the origin at the top-left are a convention inherited from early computer displays, where the electron beam in a CRT monitor scanned from the top-left of the screen, line by line, downward. This convention carried forward into virtually all modern windowing and graphics systems, including Qt.
Converting from mathematical coordinates
If you're working with data that uses standard mathematical coordinates (origin at the bottom-left, y increasing upward), you'll need to convert the y values before drawing. The formula is straightforward:
y_screen = height - 1 - y_math
Where:
y_screenis the y coordinate QPainter expects (origin at top-left).y_mathis the y coordinate in standard math notation (origin at bottom-left).heightis the height of your drawing surface in pixels.
The - 1 is there because pixel coordinates are zero-indexed. A QPixmap with a height of 300 has valid y coordinates from 0 to 299.
Let's say you have a canvas that's 300 pixels tall, and you want to draw a line from the mathematical point (10, 10) to (300, 200) as if the origin were at the bottom-left. You'd convert each y coordinate:
height = 300
# Mathematical coordinates.
x1, y1_math = 10, 10
x2, y2_math = 300, 200
# Convert y values for screen drawing.
y1_screen = height - 1 - y1_math # 300 - 1 - 10 = 289
y2_screen = height - 1 - y2_math # 300 - 1 - 200 = 99
painter.drawLine(x1, y1_screen, x2, y2_screen)
# Equivalent to: painter.drawLine(10, 289, 300, 99)
Now the line will go from near the bottom-left upward to the right - just like you'd expect on a math plot.
A helper function for coordinate conversion
If you're doing a lot of drawing with mathematical coordinates, a small helper function keeps things tidy:
def math_to_screen(x, y, height):
"""Convert mathematical (bottom-left origin) coordinates
to screen (top-left origin) coordinates."""
return x, height - 1 - y
You can then use it like this:
x1, y1 = math_to_screen(10, 10, canvas_height)
x2, y2 = math_to_screen(300, 200, canvas_height)
painter.drawLine(x1, y1, x2, y2)
Comparing both coordinate systems side by side
This complete example draws the same line using both coordinate systems, so you can see the difference clearly. The left canvas uses QPainter's native coordinates (origin top-left), and the right canvas converts from mathematical coordinates (origin bottom-left).
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QPainter, QPen, QFont
from PyQt6.QtWidgets import (
QApplication, QLabel, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget,
)
def math_to_screen(x, y, height):
"""Convert mathematical (bottom-left origin) coordinates
to screen (top-left origin) coordinates."""
return x, height - 1 - y
def draw_annotated_line(canvas, x1, y1, x2, y2, label_start, label_end):
"""Draw a line on a QPixmap with annotated endpoints."""
painter = QPainter(canvas)
# Draw the line.
pen = QPen(Qt.blue, 2)
painter.setPen(pen)
painter.drawLine(x1, y1, x2, y2)
# Draw and label the start point.
painter.setPen(QPen(Qt.red, 6))
painter.drawPoint(x1, y1)
painter.setPen(QPen(Qt.black))
painter.setFont(QFont("Arial", 9))
painter.drawText(x1 + 8, y1 + 5, label_start)
# Draw and label the end point.
painter.setPen(QPen(Qt.red, 6))
painter.drawPoint(x2, y2)
painter.setPen(QPen(Qt.black))
painter.drawText(x2 - 80, y2 + 20, label_end)
painter.end()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Coordinate System Comparison")
canvas_width = 350
canvas_height = 300
# --- Left canvas: native QPainter coordinates ---
canvas_native = QPixmap(canvas_width, canvas_height)
canvas_native.fill(Qt.white)
draw_annotated_line(
canvas_native,
10, 10, 300, 200,
"(10, 10)", "(300, 200)",
)
label_native = QLabel()
label_native.setPixmap(canvas_native)
title_native = QLabel("Screen coordinates\n(origin top-left)")
title_native.setAlignment(Qt.AlignCenter)
title_native.setStyleSheet("font-weight: bold;")
left_layout = QVBoxLayout()
left_layout.addWidget(title_native)
left_layout.addWidget(label_native)
# --- Right canvas: mathematical coordinates converted ---
canvas_math = QPixmap(canvas_width, canvas_height)
canvas_math.fill(Qt.white)
sx1, sy1 = math_to_screen(10, 10, canvas_height)
sx2, sy2 = math_to_screen(300, 200, canvas_height)
draw_annotated_line(
canvas_math,
sx1, sy1, sx2, sy2,
f"math(10,10) → screen({sx1},{sy1})",
f"math(300,200) → screen({sx2},{sy2})",
)
label_math = QLabel()
label_math.setPixmap(canvas_math)
title_math = QLabel("Math coordinates converted\n(origin bottom-left)")
title_math.setAlignment(Qt.AlignCenter)
title_math.setStyleSheet("font-weight: bold;")
right_layout = QVBoxLayout()
right_layout.addWidget(title_math)
right_layout.addWidget(label_math)
# --- Combine both sides ---
main_layout = QHBoxLayout()
main_layout.addLayout(left_layout)
main_layout.addLayout(right_layout)
container = QWidget()
container.setLayout(main_layout)
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
When you run this, you'll see two canvases side by side. On the left, the line slopes downward from the top-left, which is what QPainter naturally produces. On the right, the same mathematical coordinates have been converted, so the line slopes upward from the bottom-left - matching what you'd see on a standard math plot.
Drawing axes to orient yourself
When you're experimenting with coordinates, it can help to draw a simple set of axes on your canvas. Here's a quick helper that draws x and y axes with the origin marked:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QPainter, QPen, QFont
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
def draw_axes(painter, width, height):
"""Draw simple x and y axes with labels."""
painter.setPen(QPen(Qt.gray, 1, Qt.DashLine))
# X-axis along the top (y=0).
painter.drawLine(0, 0, width - 1, 0)
# Y-axis along the left (x=0).
painter.drawLine(0, 0, 0, height - 1)
# Label the origin.
painter.setPen(QPen(Qt.darkGray))
painter.setFont(QFont("Arial", 8))
painter.drawText(5, 15, "(0, 0)")
# Label the x direction.
painter.drawText(width - 60, 15, f"x → ({width - 1})")
# Label the y direction.
painter.save()
painter.translate(15, height - 10)
painter.drawText(0, 0, f"y ↓ ({height - 1})")
painter.restore()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QPainter Axes")
canvas_width = 400
canvas_height = 300
canvas = QPixmap(canvas_width, canvas_height)
canvas.fill(Qt.white)
painter = QPainter(canvas)
draw_axes(painter, canvas_width, canvas_height)
# Draw some points to see where they land.
points = [
(50, 50),
(200, 150),
(350, 250),
(350, 50),
(50, 250),
]
painter.setPen(QPen(Qt.red, 6))
for x, y in points:
painter.drawPoint(x, y)
painter.setPen(QPen(Qt.black))
painter.setFont(QFont("Arial", 9))
for x, y in points:
painter.drawText(x + 6, y - 6, f"({x}, {y})")
painter.end()
label = QLabel()
label.setPixmap(canvas)
self.setCentralWidget(label)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
This draws the axes along the top and left edges of the canvas and plots several points with their coordinates labeled. It's a great way to build intuition about where things will appear.
Valid coordinate ranges
One more thing to keep in mind: pixel coordinates on a QPixmap are zero-indexed. If you create a pixmap with:
canvas = QPixmap(400, 300)
Then the valid coordinate ranges are:
- x: 0 to 399 (that's
width - 1) - y: 0 to 299 (that's
height - 1)
Drawing outside these ranges won't cause an error, but anything beyond the edges simply won't be visible.
Summary
The QPainter coordinate system places (0, 0) at the top-left of the drawing surface, with x increasing to the right and y increasing downward. This is standard across virtually all screen-based graphics systems.
If you need to work with mathematical coordinates where (0, 0) is at the bottom-left and y increases upward, you can convert using the formula:
y_screen = height - 1 - y_math
Once you've internalized this, drawing with QPainter becomes predictable. When in doubt, drop some annotated points on your canvas - seeing the coordinates labeled right next to the dots is the fastest way to confirm everything is landing where you expect.
For more details on Qt's coordinate system, take a look at the official Qt coordinate system documentation.
For an in-depth guide to building Python GUIs with PyQt6 see my book, Create GUI Applications with Python & Qt6.
02 Sep 2026 6:00am GMT
Python GUIs: Are there any built-in QIcons? — Using built-in icons for your apps.
In the tutorials on this site and in my books I recommend using the fugue icons set. This is a free set of icons from Yusuke Kamiyamane, a freelance designer from Tokyo. The set contains 3,570 icons and is a great way to add some nice visual touches to your application without much hassle.
But this isn't the only icon set available, and there's another option you may not know about. Read on for details.
Veronica asked:
Are there any built-in icons with PyQt5? I have searched the web and it seems like there are some but I can't find any examples of them being used. Does it depend on the situation? If so, then in which cases can I use an icon without downloading it first?
First we need to clarify what is meant by built-in icons - it can mean two different things depending on context - either Qt built-in, or system built-in (Linux only). I'll start with the Qt built-ins as that's cross-platform (they're available on Windows, macOS and Linux).
Qt Standard Icons (QStyle StandardPixmap)
Qt ships with a small set of standard icons you can use in any of your applications for common actions. These built-in icons are accessed through the QStyle.StandardPixmap enum and retrieved using style().standardIcon(). They're available on all platforms - Windows, macOS, and Linux - making them a convenient choice when you need common UI icons without bundling external assets.
The following Python script displays all the built-in Qt standard icons in a grid layout:
- PyQt5
- PyQt6
- PySide2
- PySide6
import sys
from PyQt5.QtWidgets import QApplication, QGridLayout, QPushButton, QStyle, QWidget
class Window(QWidget):
def __init__(self):
super().__init__()
icons = sorted([attr for attr in dir(QStyle) if attr.startswith("SP_")])
layout = QGridLayout()
for n, name in enumerate(icons):
btn = QPushButton(name)
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
btn.setIcon(icon)
layout.addWidget(btn, n // 4, n % 4)
self.setLayout(layout)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
import sys
from PyQt6.QtWidgets import (QApplication, QGridLayout, QPushButton, QStyle,
QWidget)
class Window(QWidget):
def __init__(self):
super().__init__()
icons = sorted([attr for attr in dir(QStyle.StandardPixmap) if attr.startswith("SP_")])
layout = QGridLayout()
for n, name in enumerate(icons):
btn = QPushButton(name)
pixmapi = getattr(QStyle.StandardPixmap, name)
icon = self.style().standardIcon(pixmapi)
btn.setIcon(icon)
layout.addWidget(btn, int(n/4), int(n%4))
self.setLayout(layout)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
import sys
from PySide2.QtWidgets import QApplication, QGridLayout, QPushButton, QStyle, QWidget
class Window(QWidget):
def __init__(self):
super().__init__()
icons = sorted([attr for attr in dir(QStyle) if attr.startswith("SP_")])
layout = QGridLayout()
for n, name in enumerate(icons):
btn = QPushButton(name)
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
btn.setIcon(icon)
layout.addWidget(btn, n // 4, n % 4)
self.setLayout(layout)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
import sys
from PySide6.QtWidgets import QApplication, QGridLayout, QPushButton, QStyle, QWidget
class Window(QWidget):
def __init__(self):
super().__init__()
icons = sorted(
[attr for attr in dir(QStyle.StandardPixmap) if attr.startswith("SP_")]
)
layout = QGridLayout()
for n, name in enumerate(icons):
btn = QPushButton(name)
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
btn.setIcon(icon)
layout.addWidget(btn, n // 4, n % 4)
self.setLayout(layout)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
If you run this script you'll see the following window, listing all the available built-in Qt icons.
Qt's Built-in Icons - all QStyle.StandardPixmap icons shown with their names
Complete List of Qt Built-in Standard Icons (QStyle.StandardPixmap)
The full table of all QStyle.StandardPixmap icon names is below. You can use any of these in PyQt5, PyQt6, PySide2, or PySide6 applications.
| .. | .. | .. |
|---|---|---|
| SP_ArrowBack | SP_DirIcon | SP_MediaSkipBackward |
| SP_ArrowDown | SP_DirLinkIcon | SP_MediaSkipForward |
| SP_ArrowForward | SP_DirOpenIcon | SP_MediaStop |
| SP_ArrowLeft | SP_DockWidgetCloseButton | SP_MediaVolume |
| SP_ArrowRight | SP_DriveCDIcon | SP_MediaVolumeMuted |
| SP_ArrowUp | SP_DriveDVDIcon | SP_MessageBoxCritical |
| SP_BrowserReload | SP_DriveFDIcon | SP_MessageBoxInformation |
| SP_BrowserStop | SP_DriveHDIcon | SP_MessageBoxQuestion |
| SP_CommandLink | SP_DriveNetIcon | SP_MessageBoxWarning |
| SP_ComputerIcon | SP_FileDialogBack | SP_TitleBarCloseButton |
| SP_CustomBase | SP_FileDialogContentsView | SP_TitleBarContextHelpButton |
| SP_DesktopIcon | SP_FileDialogDetailedView | SP_TitleBarMaxButton |
| SP_DialogApplyButton | SP_FileDialogEnd | SP_TitleBarMenuButton |
| SP_DialogCancelButton | SP_FileDialogInfoView | SP_TitleBarMinButton |
| SP_DialogCloseButton | SP_FileDialogListView | SP_TitleBarNormalButton |
| SP_DialogDiscardButton | SP_FileDialogNewFolder | SP_TitleBarShadeButton |
| SP_DialogHelpButton | SP_FileDialogStart | SP_TitleBarUnshadeButton |
| SP_DialogNoButton | SP_FileDialogToParent | SP_ToolBarHorizontalExtensionButton |
| SP_DialogOkButton | SP_FileIcon | SP_ToolBarVerticalExtensionButton |
| SP_DialogResetButton | SP_FileLinkIcon | SP_TrashIcon |
| SP_DialogSaveButton | SP_MediaPause | SP_VistaShield |
| SP_DialogYesButton | SP_MediaPlay | |
| SP_DirClosedIcon | SP_MediaSeekBackward | |
| SP_DirHomeIcon | SP_MediaSeekForward |
How to Use a Specific Built-in QIcon in Your Application
In our script above to get the icons we're looking them up by name on the QStyle object, using getattr - but this is only necessary so we can iterate over the list of names and display the icon next to their name. If you want a specific icon you can access it directly. For example, to use the critical message box icon:
- Others
- PyQt6
pixmapi = QStyle.SP_MessageBoxCritical
icon = self.style().standardIcon(pixmapi)
pixmapi = QStyle.StandardPixmap.SP_MessageBoxCritical
icon = self.style().standardIcon(pixmapi)
In PyQt6 the flags must be accessed via QStyle.StandardPixmap. In other versions, they are available on QStyle itself.
Once you have the QIcon object, you can use it anywhere Qt expects an icon - on buttons, toolbars, menus, window titles, and more.
Free Desktop Theme Icons (Linux)
On Linux desktops there is something called the Free Desktop Specification which defines standard names for icons for specific actions.
If your application uses these specific icon names (and loads the icon from a "theme") then on Linux your application will use the current icon set which is enabled on the desktop. The idea is to make all applications have the same look & feel while remaining user configurable.
Setting Theme Icons in Qt Designer
To use Free Desktop theme icons within Qt Designer you would select the drop-down and choose "Set Icon From Theme..."
![]()
You then enter the name of the icon you want to use, e.g. document-new (the full list of valid names).
![]()
Setting Theme Icons in Python Code with QIcon.fromTheme()
If you're not using Qt Designer, you can set icons from a theme in your Python code using QIcon.fromTheme():
icon = QtGui.QIcon.fromTheme("document-new")
self.pushButton_n6.setIcon(icon)
If you're developing a cross-platform Python GUI application you'll still need your own icons for Windows & macOS, but by using these theme names you can ensure that your app looks native when run on Linux.
Does the
QIcon.fromTheme()method only work on Linux?
Qt themes work on all platforms, it's just that on Linux you get the theme for free. On non-Linux platforms you have to define your own icon theme from scratch. However, this is only really worth doing if you want to have a Linux-native look - for other use cases the QResource system is simpler.
Summary
There are two ways to use built-in icons in your PyQt or PySide applications without downloading external icon sets:
- Qt Standard Icons (QStyle.StandardPixmap) - A cross-platform set of common UI icons built into Qt itself, accessible via
style().standardIcon(). These work on Windows, macOS, and Linux. - Free Desktop Theme Icons - Linux-specific system icons accessed via
QIcon.fromTheme()that match the user's current desktop theme for a native look and feel.
For most cross-platform PyQt6 or PySide6 projects, bundling a dedicated icon set like Fugue gives you the most control over your app's appearance. But for quick prototypes or platform-specific tools, Qt's built-in icons are a convenient and dependency-free option.
For an in-depth guide to building Python GUIs with PyQt6 see my book, Create GUI Applications with Python & Qt6.
02 Sep 2026 6:00am GMT
Django community aggregator: Community blog posts
Django and deployments
I have been pondering the wider deployment space in Django for a while and from various angles. This includes my released package django-prodserver but also wondering if the DSF could provide hosting as a small scale commercial operation or what via alternatives I could offer in hosting for Django specifically. Then also I have considered what the wider API in Django could be for deployments.
These thoughts come at a good time, Will Vincent has done two talks on deploying python projects this year and I think his talks would serve as a great theoretical starting point to ensure we cover 90% of what is required. Then after DjangoCon US last week, Paolo made toot suggesting it's time for a deploy command. That toot triggered two things, first a memory of the chats I had in Athens this year and DjangoCon Europe and that I had been meaning to write about this topic for a while.
First let's consider the high level conceptual stages when deploying a project:
- Prepare the overall environment - signing up for an account, creating a project or just booting up a VPS
- Prepare Django and it's settings - these are changes made to the project repository
- Get the Django project from source control to the environment
- Do the first time setup - ideally this would be idempotent.
- Start the production process
- Doing a second deployment - because code always changes and then repeat step 5.
From this list, I think a single managed.py deploy might be too much magical to begin with, but I do think it's possible eventually. I'm thinking it's more likely deploy to be a command that stitches together several lower level commands and each of those commands correspond to a step in the above list. So we could have something like:
manage.py init_deploy_envmanage.py productionizemanage.py deploy_project --firstmanage.py initialize --productionmanage.py prodserver webandmanage.py workermanage.py deploy_project
A couple of very important points, first those names are simply examples for this post to communicate the idea and perhaps it would be best to have them all within a namespace of deploy, so manage.py deploy productionize etc.
Second and most importantly, I am very aware of the numerous possible combinations that exist when it comes to how a project can be deployed today and I am very much NOT suggesting Django support any of them. What I am suggesting is that we focus on the common API inside Django and we have packages and plugins like Eric has with django-simple-deploy. My approach here would be create an API that explicitly does nothing but simply prints expected inputs and outputs from each step. We can then start to automate the parts worth automating in a package, which may get us to a single deploy command.
Let me know your thoughts! As the maintainer of django-prodserver I have a vested interest in this space! :D
PS It's worth noting that there have been years of packages that have done similar things and we should use as reference, django-production is one such package or dj-lite for sqlite configuration in production.
02 Sep 2026 5:00am GMT
01 Sep 2026
Django community aggregator: Community blog posts
"Premature" optimization
"Premature optimization is the root of all evil" - our field's favorite half-sentence, quoted far more often than the sentence it was cut from. Usually it serves as permission: build it fast, profile later, fix a thing or two, done. Let's put the sentence back together and ask what it licenses: when is optimization premature, and when is "premature" the excuse?

01 Sep 2026 10:00am GMT
28 Aug 2026
Django community aggregator: Community blog posts
Issue 352: PyCharm & Django Fall Fundraiser
News
PyCharm & Django Fall Fundraiser

Buy or renew an annual PyCharm Professional license through the campaign link and you get 30% off while JetBrains donates a matching amount to the DSF, which is working to close the gap between the roughly $300,000 it raises each year and the $500,000 that would make a full-time Executive Director sustainable. The campaign runs through September 10, 2026, so buy or renew before then. A renewal adds 12 months to your existing subscription.
Django Software Foundation
DEP 0020: Annual Release Cycle
Django moves to one feature release each January under YYYY.N calendar versioning, starting with 2028.0 in place of what would have been 7.0. Every release now gets a year of mainstream support and two years of security fixes, which retires the LTS label.
The Block and Tackle of Django's Code of Conduct Working Group
The machinery behind Django's move to Contributor Covenant 3.0: a 30-day public comment period enforced by a GitHub workflow, CODEOWNERS gating changes to the CoC text, and an automated decision changelog. The working group is releasing its case-tracking templates under CC BY 3.0 for other projects to adopt.
Python Software Foundation
RISC-V is now officially supported by CPython!
CPython now supports RISC-V at tier 3, the entry level for new platforms, which means the open instruction set architecture gets ongoing testing on real hardware through buildbots donated by the RISE Project. Stan Ulbrych led the work with Ludovic Henry, Furkan Onder, and Emma Smith, backed by a Sovereign Tech Agency fellowship.
Wagtail CMS News
Wagtail 8.0
A read-and-write v3 REST API, custom base page models, a global permission policy registry, and formalized Django 6.1 support. Two smaller wins: StreamField block IDs are now available as template context variables, and AVIF and WebP images are no longer converted to PNG by default.
Wagtail security releases: 7.0.9, 7.3.4, and 7.4.3
7.0.9, 7.3.4, and 7.4.3 carry the same five fixes as 8.0: permission handling in the Pages, Documents, Images, and translation APIs, document identification by SHA1, and snippet copying. Upgrade to the patch release matching your version.
Streamlining content ops with LLMs: Wagtail user guide
Google Summer of Code contributor Raghad Dahi rebuilt the user guide site, retiring a versioning scheme that made editors duplicate the whole site per release in favor of blocks readers can filter by version. For translations, an evaluation suite scored LLM providers on cost and quality before settling on DeepSeek V4 Flash, now covering 52 languages plus right-to-left support.
CMS with AI, not AI CMS: Wagtail 8.0's new API
The thinking behind that API: rather than bolting AI buttons onto the admin, Wagtail exposes 50+ admin operations with OpenAPI docs and Markdown rich text, drivable from curl, a script, or an MCP implementation. Worked examples include fixing SEO descriptions with an LLM and a generic content importer in about 100 lines.
Django Fellow Reports
Django Fellow Report - Jacob
Jacob Tyler Walls filed an early report before heading to DjangoCon US, with his usual prolificacy triaging three tickets, reviewing nine, and authoring seven. In addition, engaged with regular security reports and provided 1-1 mentoring to his GSoC mentee, Pravin.
Editors Note
All 3 Fellows are at DjangoCon US this week, so no formal report from Sarah or Natalia. All 3 also gave excellent talks that we will link to when the videos are available later this year.
Articles
Fuzzy String Matching in Django and PostgreSQL
Four ways to match misspelled and variant names, with the trade-offs spelled out. It expands on the author's DjangoCon US 2026 talk on search-as-you-type across 54 million names.
Modern Django Deployments in 2026: My DjangoCon US 2026 Conference Talk
The slides and notes from Will Vincent's recent talk on deployments.
Nifty Django Feature: Third-Party Packages
A helpful overview of Django's third-party package ecosystem, where to look, and how to apply it in your Django projects.
When Python is Too Slow
An opinionated guide on where to turn when Python feels too slow in your application (the answer isn't always switch to Rust).
The Move to Python 3 Begins!
CCP is moving EVE Online's 2.4 million lines of Python off Stackless Python 2.7, in place since 2010, with the first changes deployed on August 25. Stage one leans on automated tools to make the code compile under both versions: 95.9% of roughly 20,000 files already do, leaving about 3,300 blocking lines (1,500 print statements, 800 long literals like 123L, 600 old-style except clauses) and another 20,000 lines that compile but behave differently and need a human to look at each one.
Core Dispatch #10
A roundup of CPython development from August 5 to 27: Python 3.12.14, 3.11.16, and 3.10.21 shipped on the 12th, 3.15.0 release candidate 2 is due September 1, and six PEPs moved, including PEP 805 on safe parallel execution and PEP 833 reaching Final for the simple repository API. Most of the discussion energy went to the competing module export proposals in PEPs 842, 843, and 844.
Events
DjangoCon Europe 2027 in Austria!
Five days of Django, Python, and community in Innsbruck, Austria, February 17-21, 2027.
DjangoCon US 2027 in Riverside, California
Join us for five days of inspiration, education, and networking at the Riverside Convention Center in beautiful Riverside, California, September 13-17, 2027.
Call for Organizers: DjangoCon US 2027
That Riverside conference needs people to run it, and more than 15 committees are recruiting, from program and sponsorship to Code of Conduct, A/V, website, and sprints. Leadership roles carry the heaviest load, with weekly check-ins and monthly board reports, but most positions do not require previous organizing experience: email hello@djangocon.us to volunteer.
Django Forum
Public thanks to our 3 Fellows
A short forum thread appreciating Django's Fellows.
Podcasts
Django Chat #205: Django Developers Survey 2026
A special summer episode on the just-released 2026 Django Developers Survey, working through what it says about Django 6.1, HTMX, async, AI, deployment, testing, and Python tooling.
Django Job Board
Two foundation roles anchor the board this week, with the DSF hiring its first Executive Director and the PSF looking for a Security Developer, alongside two full stack engineering openings.
Full Stack Software Engineer (Hybrid) at Provision
Executive Director at Django Software Foundation
AI-Assisted Software Engineer, Web Applications at Logical Media Group
Security Developer at Python Software Foundation
Projects
15r10nk/matchify
Converts eligible if/elif/else chains into Python 3.10+ match statements, preserving runtime behavior and source formatting, including isinstance checks that become class patterns with attributes.
matiasb/django-tasks-fennel
A Django Tasks backend which uses Celery as its underlying queue. Mentioned as part of the author's talk at DjangoCon US this week: Teach Django Tasks to speak Celery: Building a Celery backend for Django Tasks.
28 Aug 2026 3:00pm GMT
06 Aug 2026
Planet Twisted
Hynek Schlawack: Production-ready Python Docker Containers with uv
Starting with 0.3.0, Astral's uv brought many great features, including support for cross-platform lock files uv.lock. Together with subsequent fixes, it has become Python's finest workflow tool for my (non-scientific) use cases. Here's how I build production-ready containers, as fast as possible.
06 Aug 2026 12:00am GMT
23 Jun 2026
Planet Twisted
Glyph Lefkowitz: Adversarial Communication
As I have discussed in previous posts, "AIs" can make mistakes. In fact, they do make mistakes, and their mistake-making patterns are such that where and how they will make mistakes is both uncertain and constantly changing.
Thus, in any scenario where you want to attempt to make "productive" use of "AI", you must have a system in place for checking every result. Not checking some results; checking every result. If each result might have a consequence for you (and if it didn't have a consequence, why bother automating it?) and you cannot predict in advance which kinds of results will need verification, then verification is always required.
The verification often ends up being just as expensive as doing the work in the first place, which means that if you want your usage of "AI" to be personally profitable, you have to find someone else to externalize the cost of verification onto. This person becomes your adversary, and, if you are successful, your "AI's" victim.
The Ladder-Climber And Their Reverse-Centaur Rungs
One way that this constellation of facts can straightforwardly assemble themselves into a dystopian nightmare is the phenomenon, described by Cory Doctorow, of the reverse centaur. This is when your employer non-consensually turns you into the verification system. The "AI" does the fun part of initially performing the work, and then you do the boring part where you check if the robot is right and clean up its messes, even if everyone already knows that it would, in aggregate, be cheaper for you to do the work in the first place.
Reverse centaurs can be made from any automation, not only "AI" automation. I think that there is a reason that this term happens to have emerged in the "age of AI", though, and not with earlier automation technologies (even those which were considerably more viscerally horrific). That reason is: the wrongness of "AI" output is not merely a technical feature that must be compensated for, it is a generalized externality.
As I mentioned above, if you are responsible for the entirety of the work, both extruding the "AI" output and checking it, it's usually cheaper to have humans do the entirety of the work to begin with. When humans do the writing directly, we can check as we go, and thus verification doesn't need to be as comprehensive.
When "AI" coding advocates say "code review is the bottleneck", what they are observing is that the LLM is still rolling the dice for each PR, and a human is still necessary to verify that each of those rolls is a winner. But calling this process "code review" is a bit of a misnomer; it's not really "code review" in the traditional sense, it's human understanding.
Before the advent of "AI", the human understanding was implicit in the process of writing the code in the first place1, and the code review was a way of diffusing and extending that understanding. Now that the code can be authored with no initial understanding taking place, that cost has not gone away, it has moved.
Human understanding was always the bottleneck.
However, this is taking a collaborative view of a software project, where satisfying the needs and solving the problems of your customers are the goals. We can see that "AI" is a bad tool to satisfy those goals, because all it's doing is converting the first half of the work, that of understanding the code as you write it, to understanding the agent's output as you read it.
What if, instead, we were to take the view that every software company is a Hobbesian nightmare, red in tooth and claw? In this view, the only goal of a software project is for the individual developers to make their promo cycles and get their bonuses. Given that there is only a certain amount of money to go around, this is a zero-sum game where each programmer wants to look more productive than their colleagues.
Pretty much every organization finds it easy to reward "productivity" as expressed by lines of code emitted, but the benefits of doing thorough and thoughtful design, analysis, and code review very difficult to reward. In this world, an LLM is an invaluable tool for the sociopathic ladder-climber, particularly if your legacy organization is still structuring their workflows as if the person prompting the bot is "writing" the code, and then they get to foist off the act of "reviewing" the code onto someone else.
Here, the prompter effectively externalizes the cost of the LLM's failures but internalizes any benefits. The prompter will vibe-code a big feature, so large that the assigned reviewer can't possibly comprehend it all effectively. When this happens, the reviewer will, eventually, be pressured to approve it, even if they can try to spot a few problems along the way. The reviewer has their own work to get back to, after all, the obligation to review the prompter's (read: the bot's) code is a drain on their time that they are not going to get rewarded for.
If this feature is a big success, the prompter gets a promotion. If it causes a big issue, well, the reviewer must not have been careful enough.
This is why LLMs are "good for coding", and also why their biggest promoters keep having outages.
The Generative Gish Galloper
Coding is the biggest "success story" of this type of adversarial communication, but it is by far not the only instance of such a thing. LLMs create a new form of leverage that can turn Brandolini's law from a linear advantage into an exponential one. If you are engaged in a political debate where you want to overwhelm the other side in nonsense, an LLM can generate bullshit faster than it is physically possible for a human being to type, let alone respond thoughtfully. There is an asymmetry to the utility of this weapon as well: only one side of the political spectrum wants to flood the zone and destroy trust in institutions and the concept of truth. There's a good reason that the fascists love it.
Straightforward Spam and Fraud
This is kind of obvious, but LLMs can generate lightly-customized, plausible-looking text much more quickly than any human being. This facilitates their use in fraud, spam, and scams. In a spamming or fraudulent interaction, once again, the costs are externalized onto the victim: the recipient of a spam message has to do all the work of "checking" the LLM's output. Spammers already expect very low hit rates from boilerplate, and if the LLM can increase those percentages from 1% to 5% the technology will pay for itself; they don't need anything like reliable accuracy.
Customer "Support"
If you have any kind of commercial relationship with a company, I probably don't even need to mention this: customer "support" bots are a misery. Everybody knows it at this point. But customer support is usually conceptualized by businesses as an adversarial interaction, because it is a cost center. They maintain internal metrics on time-to-resolution and try to optimize them. Implicitly, this creates a dynamic where the goal of the customer service agent's job is not to solve your problem, but to emit noise that will cause you to think your problem is resolved, or to give up, as fast as possible. Unsurprisingly, LLMs can emit this noise faster than humans can, getting those customers off the phone. But those customers will remember those interactions, and the story outside the TTR metrics is horrible.
Similarly to the situation in software development, LLMs can look very good on paper for customer support, but mostly what they are doing is illuminating the problems with the industry's existing metrics, by turning "winning the metrics battle against the customer" into a more obvious and immediate defeat for the company's long term reputation.
"Education"
In 2026 it is sadly a fact of life that students cheat all the time using "AI", and that this cheating is very successful, in that the teachers find it very hard to detect.
LLMs are great for cheating on schoolwork because the student is externalizing the work of the checking onto the teachers, who are often starting at a disadvantage to begin with, at least in the US.
My view is that this is happening because of a divergence in the way that students vs. teachers (or, more accurately, "the broader educational system") view grading.
When a student is asked to write an essay, the teachers see the effort as both intrinsically worthwhile for the student, as well as useful as a pedagogical tool to evaluate and react to the student's progress. The student, by contrast, sees a stumbling block designed to knock them off the path to success and into a permanent underclass. It is no wonder that the student sees "AI" as useful to their own goals and has no compunction about deploying it.
There is a bitter irony that the ability to understand the inherent value of actually writing the essay on their own is the sort of thing that students can really only learn by writing a bunch of essays. There's no way that I can think of which makes the benefit legible as long as a shortcut is available.
The net effect here is a downward spiral, where the already-wobbling educational system is sustaining an attack that it doesn't have the resources to recover from. The individual students' attacks against their teachers and their schools' grading systems might appear to momentarily succeed, but they will win the battle and lose the war.
Spamming "For Good"?
Usually when we talk about someone unilaterally choosing to enter into an adversarial relationship, that's an "attack" and for good reasons we have a negative impression of the attacker. However, I would be remiss if I did not point out that there are some cases where the relationship was already adversarial; just because you're the attacker doesn't mean that you are evil.
For example we might imagine use-cases like automatically filing appeals for prior authorizations against health insurance. It's relatively well-known at this point that the main way for-profit insurers maintain their margins is by denying claims right up to the line of the policies themselves being fraud, so using a spamming tool to fight them might be entirely justifiable2 in that case.
Similarly, using an LLM could be justified in a fight against a company refusing to honor a warranty. One could imagine using an LLM to immediately generate replies and escalations.
However, even in imagined cases like these, the underlying problem is that the insurers and the vendors already have a tremendous amount of structural power, so it is more likely that they will have the advantage in deploying a communications weapon like an LLM, as well as enacting policies to simply ignore any LLM-based communication that you might submit. Worse, if these strategies were to become widespread, they might provide an excuse to reject any communications by feeding them into an unreliable "LLM detector" and issuing an automated "computer says no" even to hand-written correspondence.
It is also worth stressing that these cases are imagined, as compared to the very real coworker-abuse, spam, scam, fraud, and disinformation campaigns being waged in real life today.
Therefore, while legitimate uses might exist, it's hard to imagine that there's anywhere they would be genuinely valuable and sustainable. In the best case "AI" will provide a temporary advantage for underdogs that will provoke an arms race which the resource-advantaged adversaries will win in the long run, in the worst case the arms race itself will cement permanent structural change that will make things worse.
"Search" By Stealing
Most of the adversarial utility of "AI" is on the "write" side, since write-amplification is more obviously aggressive than reading. But the "read" side of LLMs - summarization and question-answering - can be a form of attack as well.
To begin with, the act of reading itself is currently enormously destructive, but that's arguably not a fundamental aspect of this technology. They could set reasonable rate-limits and respect things like robots.txt, as search engines have for decades now. They could also refrain from committing criminal levels of copyright infringement. But, today, using "AI" tools does suborn this sort of out-of-control crawling.
More insidiously, consider the scenario described in this YouTube video. The LTT Bros decided to try Linux again, and in the course of so doing, they had problems. When trying to solve these problems, they were faced with a choice: they could consult Reddit, or they could ask an LLM. Asking an LLM would "gaslight the heck out of" them, but they still found it preferable, because they would at least get an answer without getting yelled at.
Initially this sounds great. But it also means that you want to extract knowledge from a community, while mechanically eliding any values or norms that the community may want to impart as part of offering that knowledge. As someone who spent many years in a community tech support role, this is worrying. Many requests for support are people asking how to do things that will momentarily solve a superficial problem but create a long-term reliability problem or even an immediate security risk, that the question-asker doesn't want to hear about. Consider the question "I'm tired of entering my password so much, how do I make it so my laptop unlocks automatically". An obsequious chatbot will helpfully tell you how to do this without pushback.
But, this is also a sort of ethically murky area. The Linux community is somewhat famously, for many years now, a toxic cesspool of general hostility, misogyny, etc. It is certainly a good thing that people can get access to this knowledge without subjecting themselves to abuse. But it also means that the people with the power and the privilege to change the community for the better can just quietly withdraw, rather than fixing the problems. It also means that the positive elements of culture cannot be transmitted, and people will have no opportunity to learn about unknown unknowns.
In this case, the "adversarial" communication is with society. The thing that using an LLM for search lets you do is withdraw from society and avoid forming any personal connections. There are some personal connections which are painful and annoying, and so that can feel like a momentary balm. But the need to make connections in general is, like, the concept of society itself.
Who Am I Hurting?
LLMs are good at adversarial communication. They are so good at it, relative to their other benefits, that they will tend to make communications adversarial if you are not remaining vigilant about the possibility that it might do so. My request to you, dear reader, if you are going to use such tools, is to always ask yourself, "who might I be hurting, if I use an LLM for this?"
If you're using an "AI", who is its adversary? If you haven't given it one yet, who might the "AI" turn into an adversary? Who might you overwhelm with an asymmetric amount of output, or, if you're receiving information and not sending it, who are you taking that information from without consulting?
Figure out the answers to these questions and conduct yourself accordingly; the answer might be "yourself".
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
-
One of the reasons that software developers tend to prefer greenfield development is that when you are given a blank page, you can project your own specific understanding onto it. You can structure the codebase in a way that works for your brain, down to the variable naming conventions and the module layouts. LLM-assisted development makes everything into instant brownfield work, which makes developers instantly miserable; even those who are excited about the technology will frequently complain about how it feels like their agency has been stolen and their joy in the work has been diminished. But I digress. ↩
-
Modulo the massive amount of other externalities involved in using LLMs, of course, but I don't have the time or energy to get into those here. ↩
23 Jun 2026 8:06pm GMT
09 Jun 2026
Planet Twisted
Hynek Schlawack: How to Ditch Codecov for Python Projects
Codecov's unreliability breaking CI on my open source projects has been a constant source of frustration for me for years. I have found a way to enforce coverage over a whole GitHub Actions build matrix that doesn't rely on third-party services.
09 Jun 2026 12:00am GMT
