20 Apr 2026

feedPlanet Python

Real Python: Gemini CLI vs Claude Code: Which to Choose for Python Tasks

When comparing Gemini CLI vs Claude Code, the answer to "which one is better?" is usually it depends. Both tools boost productivity for Python developers, but they have different strengths. Choosing the right one depends on your budget, workflow, and what you value most in generated code.

Gemini CLI, for instance, is known for its generous free tier, while Claude Code is a paid tool known for its production-ready output.

In this tutorial, you'll explore features such as user experience, performance, code quality, and usage cost to help make that decision easier. The AI coding assistance these tools provide right in your terminal generally makes writing Python code much more seamless, helping you save time and be more productive.

This table highlights the key differences at a glance:

Use Case Gemini CLI Claude Code
You need free generous usage limits -
You need Google Cloud integration -
You need faster task completion -
You need code close to production quality -

You can see that Gemini CLI is a promising choice if you're looking for free usage limits and prefer Google Cloud integration. However, if you want to complete tasks faster, Claude Code has an edge. Both tools produce code of good quality, but Claude Code generates code that is closer to production quality. If you'd like a more thorough comparison, then read on.

Get Your Code: Click here to download the free sample code for the to-do app projects built with Gemini CLI and Claude Code in this tutorial.

Take the Quiz: Test your knowledge with our interactive "Gemini CLI vs Claude Code: Which to Choose for Python Tasks" quiz. You'll receive a score upon completion to help you track your learning progress:


A scientist comparing two coding terminals labeled Gemini and Claude on a balance scale with gauges for cost, speed, and quality, a Python-branded processor, and ink bottles.

Interactive Quiz

Gemini CLI vs Claude Code: Which to Choose for Python Tasks

Compare Gemini CLI and Claude Code across user experience, performance, code quality, and cost to find the right AI coding tool for you.

Metrics Comparison: Gemini CLI vs Claude Code

To ground the comparisons in hands-on data, both tools are tested using the same prompt throughout this tutorial:

Prompt

Build a CLI-based mini to-do application in Python. It should allow users to create tasks, mark tasks as completed, list tasks with filtering for completed and pending tasks, delete tasks, include error handling, persist tasks to a local JSON file, and include basic unit tests.

For a fair comparison, Gemini CLI is tested on its free tier using Gemini 3 Flash Preview, which is the default model the free tier provides access to. Claude Code is tested on the Pro plan using Claude Sonnet 4.6, which is the model Claude Code primarily uses for everyday interactions on that plan.

Each tool will run this prompt three times. Completion time, token usage, and the quality of the generated code are recorded from the runs and are referenced in the Performance, Code Quality, and Usage Cost sections of this tutorial.

Note: If you want to learn more about these tools so you can compare them yourself, Real Python has you covered. The How to Use Google's Gemini CLI for AI Code Assistance tutorial covers installation, authentication, and hands-on usage, while the Getting Started With Claude Code video course walks you through setup and core features.

You should also be comfortable using your terminal, since both Gemini CLI and Claude Code are command-line tools.

The table below provides more detailed metrics to help with each comparison:

Metric Gemini CLI Claude Code
User Experience Intuitive, browser-based auth, terminal-native Minimal setup, terminal-native, strong project awareness
Performance Good performance, however slower generation speed Good performance, code is generated generally faster
Code Quality Solid, better for exploratory tasks Strong, better for production-grade work
Usage Cost Free tier available; paid plans for heavier use Requires a paid subscription to get started

The following sections explore each metric in detail, so you can decide which tool fits your workflow best.

User Experience

When writing Python programs, it helps to be able to comfortably use your tools without dealing with unintuitive interfaces. Both Gemini CLI and Claude Code prioritize a smooth terminal experience, but user experience goes beyond the interface itself-installation, setup, available models, and features offered are also part of it.

Installation and Setup

A few differences exist between Gemini CLI and Claude Code during installation. Gemini CLI requires a Google account for authentication. Claude Code doesn't need a Google account. Instead, it requires an Anthropic subscription or API key.

Gemini CLI is first installed using npm:

Shell
$ npm install -g @google/gemini-cli

You can also install Gemini CLI with Anaconda, MacPorts, or Homebrew, which you can find in the Gemini CLI documentation.

When installing Claude Code, you run the following commands:

Read the full article at https://realpython.com/gemini-cli-vs-claude-code/ »


[ Improve Your Python With 🐍 Python Tricks 💌 - Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

20 Apr 2026 2:00pm GMT

Mike Driscoll: Textual – Logging to File and to Textual Console

When you are developing a user interface, it can be valuable to have a log of what's going on. Creating a log in Textual, a text-based user interface, is even easier than creating one for wxPython or Tkinter. Why? Well, because Textual includes a logger that is compatible with Python's own logging module, so it's almost plug-and-play to hook it all up!

You'll learn how to do this in this short tutorial!

Logging to File and the Console

Textual includes a built-in logging-type handler that you can use with Python's own logging module called TextualHandler. Python has many built-in logging handler objects that you can use to write to stdout, a file, or even to an email address!

You can hook up multiple handlers to a logger object and write to all of them at once, which gives you a lot of flexibility.

To see how this works in Textual, you will create a very simple application that contains only two buttons. Go ahead and open your favorite Python IDE or text editor and create a new file called log_to_file.py. Then enter the following code into it:

# log_to_file.py

import logging

from textual.app import App, ComposeResult
from textual.logging import TextualHandler
from textual.widgets import Button


class LogExample(App):

    def __init__(self) -> None:
        super().__init__()
        self.logger = logging.getLogger(name="log_example")
        self.logger.setLevel(logging.INFO)
        file_handler = logging.FileHandler("tui.log")
        self.logger.addHandler(file_handler)
        formatter = logging.Formatter(("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
        file_handler.setFormatter(formatter)

        textual_handler = TextualHandler()
        self.logger.addHandler(textual_handler)

    def compose(self) -> ComposeResult:
        yield Button("Toggle Dark Mode", classes="dark mode")
        yield Button("Exit", id="exit")

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "exit":
            self.logger.info("User exited")
            self.exit()
        elif event.button.has_class("dark", "mode"):
            self.theme = (
                "textual-dark" if self.theme == "textual-light" else "textual-light"
            )

            self.logger.info(f"User toggled app theme to {self.theme}")



if __name__ == "__main__":
    app = LogExample()
    app.run()

As you can see, you have just two buttons for the user to interact with:

No matter which button the user presses, the application will log out something. By default, Textual logs to stdout, but you cannot see it because your application will be on screen. If you want to see the logs, you will need to use one of the Textual Console applications, which is part of Textual's devtools. If you do not have the dev tools installed, you can do so by running this command:

pip install textual-dev

Now that you have the dev tools handy, open up a new terminal window or tab and run this command:

textual console

To get Textual to send the log messages to console, you need to run your Textual application in developer mode. You will run it in a different terminal than Textual Console!

Here's the special command:

textual run --dev log_to_file.py

You will see various events and other logged metadata appear in the Textual Console regardless of whether you specifically log to it. However, now if you do call self.log or you use Python's print() function, you will see those appear in your log.

You will also see your log messages in your log file (tui.log), though it won't include all the extra stuff that Textual Console displays. You only get what you log explicitly written into your log file.

Wrapping Up

And there you have it. You now know how to use Textual's own built-in logging handler in conjunction with Python's logging module. Remember, you can use Textual's logging handler in addition to one or more of Python's logging modules. You can format the output any way you want too!

Learn More About Logging

If you want to learn more about logging in Python, you might find my book, Python Logging, helpful.

Python Logging book cover

Purchase the book today on Gumroad, Leanpub or Amazon!

The post Textual - Logging to File and to Textual Console appeared first on Mouse Vs Python.

20 Apr 2026 12:40pm GMT

Python Bytes: #477 Lazy, Frozen, and 31% Lighter

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://django-modern-rest.readthedocs.io/en/latest/?featured_on=pythonbytes">Django Modern Rest</a></strong></li> <li><strong>Already playing with Python 3.15</strong></li> <li><strong><a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">Cutting Python Web App Memory Over 31%</a></strong></li> <li><strong><a href="https://tryke.dev?featured_on=pythonbytes">tryke - A Rust-based Ptyhon test runner with a Jest-style API</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=WmJtmS5Fn7U' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="477">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course?featured_on=pythonbytes"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a> <strong>Connect with the hosts</strong></li> <li>Michael: <a href="https://fosstodon.org/@mkennedy">@mkennedy@fosstodon.org</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">@mkennedy.codes</a> (bsky)</li> <li>Brian: <a href="https://fosstodon.org/@brianokken">@brianokken@fosstodon.org</a> / <a href="https://bsky.app/profile/brianokken.bsky.social?featured_on=pythonbytes">@brianokken.bsky.social</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">@pythonbytes@fosstodon.org</a> / <a href="https://bsky.app/profile/pythonbytes.fm">@pythonbytes.fm</a> (bsky) Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Monday</strong> at 11am PT. Older video versions available there too. Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</li> </ul> <p><strong>Michael #1: <a href="https://django-modern-rest.readthedocs.io/en/latest/?featured_on=pythonbytes">Django Modern Rest</a></strong></p> <ul> <li>Modern REST framework for Django with types and async support</li> <li>Supports Pydantic, Attrs, and msgspec</li> <li>Has ai coding support with llms.txt</li> <li>See an example at the <a href="https://django-modern-rest.readthedocs.io/en/latest/pages/getting-started.html#showcase">"showcase" section</a></li> </ul> <p><strong>Brian #2: Already playing with Python 3.15</strong></p> <ul> <li><a href="https://blog.python.org/2026/04/python-3150a8-3144-31313/?featured_on=pythonbytes">3.15.0a8, 2.14.4 and 3.13.13 are out</a> <ul> <li>Hugo von Kemenade</li> </ul></li> <li>beta comes in May, CRs in Sept, and Final planned for October</li> <li>But still, there's awesome stuff here already, here's what I'm looking forward to: <ul> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-lazy-imports"><strong>PEP 810</strong></a>: Explicit lazy imports</li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-frozendict"><strong>PEP 814</strong></a>: <code>frozendict</code> built-in type</li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-unpacking-in-comprehensions"><strong>PEP 798</strong></a>: Unpacking in comprehensions with <code>*</code> and <code>**</code></li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-utf8-default"><strong>PEP 686</strong></a>: Python now uses UTF-8 as the default encoding</li> </ul></li> </ul> <p><strong>Michael #3: <a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">Cutting Python Web App Memory Over 31%</a></strong></p> <ul> <li>I cut 3.2 GB of memory usage from our Python web apps using five techniques: <ul> <li>async workers</li> <li>import isolation</li> <li>the Raw+DC database pattern</li> <li>local imports for heavy libraries</li> <li>disk-based caching</li> </ul></li> <li><a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">See the full article</a> for details.</li> </ul> <p><strong>Brian #4: <a href="https://tryke.dev?featured_on=pythonbytes">tryke - A Rust-based Ptyhon test runner with a Jest-style API</a></strong></p> <ul> <li>Justin Chapman</li> <li>Watch mode, Native async support, Fast test discovery, In-source testing, Support for doctests, Client/server mode for fast editor integrations, Pretty, per-assertion diagnostics, Filtering and marks, Changed mode (like pytest-picked), Concurrent tests, Soft assertions,</li> <li>JSON, JUnit, Dot, and LLM reporters</li> <li>Honestly haven't tried it yet, but you know, I'm kinda a fan of thinking outside the box with testing strategies so I welcome new ideas.</li> </ul> <p><strong>Extras</strong></p> <p>Brian:</p> <ul> <li><a href="https://aleyan.com/blog/2026-why-arent-we-uv-yet/?featured_on=pythonbytes">Why are't we uv yet?</a> <ul> <li>Interesting take on the "agents prefer pip"</li> <li>Problem with analysis. <ul> <li>Many projects are libraries and don't publish uv.lock file</li> <li>Even with uv, it still often seen as a developer preference for non-libarries. You can sitll use uv with requirements.txt</li> </ul></li> </ul></li> <li><a href="https://us.pycon.org/2026/schedule/talks/?featured_on=pythonbytes">PyCon US 2026 talks schedule is up</a> <ul> <li>Interesting that there's an AI track now. I won't be attending, but I might have a bot watch the videos and summarize for me. :)</li> </ul></li> <li><a href="https://justinjackson.ca/tech-done-to-us?featured_on=pythonbytes">What has technology done to us?</a> <ul> <li>Justin Jackson</li> </ul></li> <li><a href="https://courses.pythontest.com/lean-tdd/?featured_on=pythonbytes">Lean TDD new cover</a> <ul> <li>Also, 0.6.1 is so ready for me to start f-ing reading the audio book and get on with this shipping the actual f-ing book and yes I realize I seem like I'm old because I use "f-ing" while typing. Michael:</li> </ul></li> <li><a href="https://docs.python.org/release/3.14.4/whatsnew/changelog.html?featured_on=pythonbytes">Python 3.14.4 is out</a></li> <li><a href="https://github.com/BeanieODM/beanie/releases/tag/2.1.0?featured_on=pythonbytes">Beanie 2.1 release</a></li> </ul> <p><strong>Joke: <a href="https://motherduck.com/humandb/?featured_on=pythonbytes">HumanDB</a> - Blazingly slow. Emotionally consistent.</strong></p>

20 Apr 2026 8:00am GMT

feedDjango community aggregator: Community blog posts

Django: fixing a memory “leak” from Python 3.14’s incremental garbage collection

Back in February, I encountered an out-of-memory error while migrating a client project to Python 3.14. The issue occurred when running Django's database migration command (migrate) on a limited-resource server, and seemed to be caused by the new incremental garbage collection algorithm in Python 3.14.

At the time, I wrote a workaround and started on this blog post, but other tasks took priority and I never got around to finishing it. But four days ago, Hugo van Kemenade, the Python 3.14 release manager, announced that the new garbage collection algorithm will be reverted in Python 3.14.5, and the next Python 3.15 alpha release, due to reports of increased memory usage.

Here's the story of my workaround, as extra evidence that reverting incremental garbage collection is a good call.

Python 3.14's incremental garbage collection

Python (well, CPython) has a garbage collector that runs regularly to clean up unreferenced objects. Most objects are cleaned up immediately when their reference count drops to zero, but some objects can be part of reference cycles, where some set of objects reference each other and thus never reach a reference count of zero. The garbage collector sweeps through all objects to find and clean up these cycles.

Python 3.14 changed garbage collection to operate incrementally. Previously, a garbage collection run would sweep through all objects in one go, but this could lead to "stop the world" stalls where your program's real work could pause for seconds while the garbage collector did its job. The incremental garbage collection algorithm instead does a fraction of the work at a time, spreading out the cost of garbage collection.

Here's the full release note (historical source):

Incremental garbage collection

The cycle garbage collector is now incremental. This means that maximum pause times are reduced by an order of magnitude or more for larger heaps.

There are now only two generations: young and old. When gc.collect() is not called directly, the GC is invoked a little less frequently. When invoked, it collects the young generation and an increment of the old generation, instead of collecting one or more generations.

The behavior of gc.collect() changes slightly:

  • gc.collect(1): Performs an increment of garbage collection, rather than collecting generation 1.
  • Other calls to gc.collect() are unchanged.

(Contributed by Mark Shannon in 108362.)

The problem

I'd been helping one of my clients upgrade to Python 3.14 for a few months, chipping away at compatibility work like upgrading dependencies and fixing deprecations. Tests were finally all passing and everything was working on the local development server. The next stop was to launch a temporary deployment using Python 3.14 via Heroku's review apps feature.

At the basic tier, Heroku review apps use fairly resource-constrained servers, including just 512MB of RAM, with the ability to temporarily burst up to nearly 1GB (200%). Paying for larger servers is an option, but unfortunately the next step up is pretty expensive.

When I launched a review app for my Python 3.14 branch, I found its release phase failed while running migrate. Inspecting the logs, I found the migrations started fine:

$ heroku logs --app example-python-314-wsgk3w --num 1000 | less
...
app[release.6634]: System check identified no issues (26 silenced).
app[release.6634]: Operations to perform:
app[release.6634]: Apply all migrations: admin, auth, contenttypes, ...
app[release.6634]: Running migrations:

…but partway through, these messages started appearing:

heroku[release.6634]: Process running mem=527M(101.5%)
heroku[release.6634]: Error R14 (Memory quota exceeded)

…ramping up until the 200% mark:

heroku[release.9599]: Process running mem=977M(190.3%)
heroku[release.9599]: Error R14 (Memory quota exceeded)

…and finally the termination of the release process:

heroku[release.9599]: Process running mem=1033M(201.7%)
heroku[release.9599]: Error R15 (Memory quota vastly exceeded)
heroku[release.9599]: Stopping process with SIGKILL

These messages came from Heroku's process management layer, which terminated the memory-hungry release process with SIGKILL after the hard threshold of 1GB memory usage was breached. Repeat attempts hit the same issue.

I was confused: migrations should not consume much memory. While they create a lot of temporary objects (Django model classes and fields) in order to calculate the SQL to send to the database, such objects are all short-lived and should be garbage-collected fairly swiftly. Additionally, migrations worked fine on the local and CI environments, and they'd never had memory issues on previous Python versions.

It looked like there was a memory leak, and it was time to dig in.

Initial investigation

I first profiled memory usage of migrate locally using Memray, the memory profiler that I covered in my previous post, using:

$ memray run manage.py migrate

The profiles revealed that memory usage had slightly increased on Python 3.14 compared to 3.13, but did not find a memory leak (a pattern of continual growth). Still, I made some optimizations to defer some imports, saving about 30% of startup memory usage, and tried again, to no avail.

I then had the idea to profile on a Heroku dyno directly. After hacking the release process to not run migrations, I built a review app and SSH'd into its web server:

$ heroku ps:exec -a example-python-314-rspwtc --dyno web.1 bash
Establishing credentials... done
Connecting to web.1 on ⬢ example-python-314-rspwtc...
~ $

Initially, I tried using Memray's live mode to profile the migrations as they ran:

$ memray run --live manage.py migrate

While this tool looks great for some situations, it didn't really work here, especially since it seized up after Heroku terminated the server.

I then tried running the default memray run command:

$ memray run manage.py migrate
Writing profile results into memray-manage.py.724.bin

…then, on my local computer, I repeatedly ran this command to copy down the results file:

$ trash memray-manage.py.724.bin && heroku ps:copy -a example-python-314-rspwtc --dyno web.1 memray-manage.py.724.bin

I was a bit worried here that the Memray binary file might be corrupted due to copying it while memray run was generating it. But with a final truncated copy left over after the server crashed, I asked Memray to generate a flamegraph for it:

$ memray flamegraph memray-manage.py.724.bin

…and it worked! Kudos to the Memray team for making their output format usable even when incomplete.

This more detailed flamegraph revealed more than 50% of the memory usage was allocated in ModelState.render(), which creates temporary model classes:

class ModelState:
    ...

    def render(self, apps):
        """Create a Model object from our current state into the given apps."""
        ...
        return type(self.name, bases, body)

This information hinted that these temporary model classes were hanging around beyond their expected short lifetime, leading to the memory leak. For example, every model class could also end up in a list intended for debugging, but accidentally extending the lifetime of these temporary classes.

I decided to dig a bit deeper using machete-mode debugging, with the below snippet that captures the temporary model classes and logs details about them. I wrote this within the Django settings file, where it was guaranteed to run at Django startup time, before the migrate management command.

import atexit
import gc
import tracemalloc
import weakref
from itertools import islice

from django.db.migrations.state import ModelState

tracemalloc.start(2)

orig_render = ModelState.render

rendered_classes = weakref.WeakSet()


def wrapped_render(*args, **kwargs):
    cls = orig_render(*args, **kwargs)
    rendered_classes.add(cls)
    return cls


ModelState.render = wrapped_render


@atexit.register
def show_referrers():
    print(f"🎯 {len(rendered_classes)} classes referred to.\n")

    for cls in islice(rendered_classes, 2):
        print(f"🎁🎁🎁 {cls!r} 🎁🎁🎁")
        for i, referrer in enumerate(gc.get_referrers(cls), start=1):
            print(f"🍌 Referrer #{i}: {referrer!r}")
            if tb := tracemalloc.get_object_traceback(referrer):
                print("\n".join(tb.format(most_recent_first=True)))
            print()
        print()
        print()

Note:

  1. tracemalloc.start() starts Python's built-in memory allocation tracking.
  2. The ModelState.render() method was monkeypatched with a wrapper that stores every temporary model class in a WeakSet.
  3. The @atexit.register-decorated function runs at the end of the program, and logs two things.
  4. The first piece of logging is the number of temporary model classes still alive at the end of the program, which should be close to zero. (Some may stick around from the final migration state.)
  5. The second piece of logging iterates over the first two live temporary model classes and logs their name and their referring objects, discovered via gc.get_referrers(). For each referring object, it also logs the traceback of where that object was allocated, using tracemalloc.get_object_traceback() (which is why tracemalloc.start() was needed at the beginning).
  6. The emojis are a bit of fun to make the log messages easier to skim through. I have no idea why I picked 🎁 and 🍌!!

The output from this hook was voluminous, even with the limit to the first two live classes. For example, here's the output for a temporary ContentType model class:

🎁🎁🎁 <class '__fake__.ContentType'> 🎁🎁🎁
🍌 Referrer #1: <generator object WeakSet.__iter__ at 0x1234ef300>
  File "/.../example/core/apps.py", line 45
    for cls in islice(rendered_classes, 2):

...

🍌 Referrer #11: {'name': 'model', ..., 'model': <class '__fake__.ContentType'>}
  File "/.../.venv/lib/python3.14/site-packages/django/utils/functional.py", line 47
    res = instance.__dict__[self.name] = self.func(instance)
  File "/.../.venv/lib/python3.14/site-packages/django/db/models/fields/__init__.py", line 1210
    self.validators.append(validators.MaxLengthValidator(self.max_length))

I checked the live referrers for a few classes, and they all seemed to be expected. However, it did reveal just how many cycles exist between ORM objects. For example, model classes refer to their field objects, which in turn refer back to their model classes, thanks to Django's Field.contribute_to_class() creating this reference:

def contribute_to_class(self, cls, name, private_only=False):
    ...
    self.model = cls
    ...

Anyway, from comparing the output between Python 3.13 and 3.14, I could see that no new references were being created on Python 3.14. It seemed likely that the incremental garbage collection algorithm was the culprit.

The workaround

Given the investigation, I wanted to work around the issue by forcing a full garbage collection sweep with gc.collect() after each migration file ran. I came up with the below code, saved as management/commands/migrate.py in one of the project's Django apps. It extends the default migrate command to run gc.collect() after each successful migration (where "apply" is forwards and "unapply" is backwards).

import gc

from django.core.management.commands.migrate import Command as BaseCommand


class Command(BaseCommand):
    """Extended 'migrate' command."""

    def migration_progress_callback(self, action, migration=None, fake=False):
        """
        Extend Django's migration progress reporting to force garbage
        collection after each migration. This is a workaround to keep memory
        usage low, especially because we have a low limit on Heroku. It seems
        the incremental garbage collector introduced in Python 3.14 cannot
        keep up with the migration process's tendency to create many cyclical
        objects, so our best fallback is to force collection of everything
        after each migration is applied or unapplied.

        https://adamj.eu/tech/2026/04/20/django-python-3.14-incremental-gc/
        """
        super().migration_progress_callback(action, migration=migration, fake=fake)
        if action in ("apply_success", "unapply_success"):
            gc.collect()

It felt a bit hacky, but it did the trick! The review app succeeded to launch, showing a flat memory profile as before.

We then continued to deploy to staging and production without any issues, and the team have been happily using Python 3.14 for over a month now.

Fin

Well, that's where the tale ends right now. After the incremental garbage collection algorithm is reverted in Python 3.14.5, I guess I'll be able to remove this workaround.

While it would be nice to have incremental garbage collection work well, it's clear that the current implementation has some issues. I think the core team is making the right call reverting it, but hopefully there will be energy to improve the feature for the future.

May your garbage be collected efficiently and without fuss,

-Adam

20 Apr 2026 4:00am GMT

17 Apr 2026

feedDjango community aggregator: Community blog posts

Django News - 30% Off PyCharm Pro – 100% for Django - Apr 17th 2026

Introduction

Django News Newsletter is moving!

Just a quick heads up. We're planning to move our newsletter to a new platform next week.

If things look a little different when it shows up, it's still us.

Django Newsletter

News

PyCharm & Django annual fundraiser

JetBrains and the Django Software Foundation team up again to offer 30% off PyCharm while matching donations to fund Django's core development and community programs.

djangoproject.com

New Technical Governance - request for community feedback

Django proposes a simpler, more flexible technical governance model and is inviting community feedback ahead of a planned July 2026 rollout.

djangoproject.com

Could you host DjangoCon Europe 2027? Call for organizers

DjangoCon Europe 2026 is happening right now in Athens, Greece but plans for 2027 have already begun. This post lays out all the resources for any questions, support, and more for future organizers.

djangoproject.com

Reverting the incremental GC in Python 3.14 and 3.15 - Core Development

Python is rolling back its new incremental garbage collector in 3.14 and 3.15 after real-world memory issues, reverting to the proven generational model while rethinking a future reintroduction.

python.org

PEP 772: Packaging Council governance process (Round 3) - Packaging / Coordination

PEP 772 has officially been approved, creating a new Python Packaging Council to guide the future of packaging standards, tools, and ecosystem governance.

python.org

Django Software Foundation

Django Has Adopted Contributor Covenant 3

The 3.0 edition of the new Code of Conduct is here! This milestone represents the completion of a careful, community-driven process that began earlier this year.

djangoproject.com

DSF Board monthly meeting, April 9, 2026

The Django Software Foundation approved a modernized Code of Conduct, new working group charters, and key community initiatives, signaling a fresh push toward clearer governance and sustained project growth.

django.github.io

Python Software Foundation

PyCon US 2026: Why we're asking you to think about your hotel reservation

For many years, PyCon US has relied on hotel booking commissions to help pay for conference space. If you are attending this year, please use an official hotel to be both close to the venue.

pyfound.blogspot.com

Python Software Foundation News: Reflecting on Five Years as the PSF's First CPython Developer in Residence

Łukasz Langa looks back on five years and highlights including the transition to GitHub issues from bugs.python.org, the replacement of the mostly manual CLA process with an automated system, the introduction of free threading to Python, and the replacement of the interactive shell in the interpreter. Also while addressing thousands of bugs, he's witnessed the full-time paid developer in residence roster at the Python Software Foundation grow from one person to five.

pyfound.blogspot.com

Updates to Django

Today, "Updates to Django" is presented by Johanan Oppong Amoateng from Djangonaut Space! 🚀

Last week we had 12 pull requests merged into Django by 10 different contributors - including a first-time contributor! Congratulations to Jonathan Wu for having their first commits merged into Django - welcome on board!

This week's Django highlights: 🦄

Django Newsletter

Django Fellow Reports

Fellow Report - Natalia

A good chunk of this week focused on improving contributor workflows and reducing review overhead by introducing automated quality checks for PRs :robot:. This builds on prior experimentation (thanks @frankwiles) and seeks to provide early, actionable feedback for PR authors while helping maintainers focus on substantive review. We also had a flood of overly verbose and low quality reports from the same person, which I closed eagerly making use of the recent new guidelines we published in the security policy.

djangoproject.com

Fellow Report - Jacob

The last report before DjangoCon Europe. Lots of tickets triaged, reviewed, authored, discussed, and the usual kaleidoscope of miscellaneous tasks.

djangoproject.com

Django Fellow Report - Sarah

Django Fellow Sarah Boyce returns from maternity leave with part-time updates, tackling triage, reviews, security work, and GSoC prep while navigating connectivity challenges from Turkey.

djangoproject.com

Sponsored Link 1

You know @login_required. Now meet @app.reasoner(). AgentField turns Python functions into production AI agents, structured output, async execution, agent discovery. Every decorator becomes a REST endpoint. Open source, Apache 2.0. Python, Go & TypeScript SDKs.

agentfield.ai

Articles

Enforce Business Logic in the Database with Django

A practical guide to enforcing business logic at the database layer in Django using transactions, select_for_update locks, and CheckConstraint / UniqueConstraint to prevent race conditions and invalid data rather than relying on application-level validation.

lincolnloop.com

Let's talk about LLMs

James Bennett consolidates his thoughts on AI/LLMs in this wide-ranging piece, ending with a call to invest in software fundamentals instead of racing to adopt the latest AI craze.

b-list.org

Django Table, Filter and Export With Htmx

A reusable pattern for combining django-tables2, django-filter, and HTMX into a single generic view and template. Very cool stuff.

fundor333.com

Decoupling Your Business Logic from the Django ORM

Carlton Gibson's latest The Stack Report is a detailed dive into business logic and how to handle it in Django. This is a perennial topic, but he comes at it with decades of experience and wisdom.

buttondown.com

djust 0.4.0 - The Developer Experience Release

djust 0.4.0 is about developer experience - making everyday tasks faster, safer, and more intuitive. 30+ new features, critical bug fixes, and a security hardening pass that eliminated every known vulnerability.

djust.org

Why aren't we uv yet?

A decent chunk of new Python repos already use uv. Coding agents still overwhelmingly recommend pip and requirements.txt, while many users prefer uv.

aleyan.com

Events

Are You Attending PyCon, or Orbiting It?

PSF Board Member Georgi Ker makes a personal case for booking hotels via the official PyCon US website before April 24th.

georgiker.com

Design Articles

Under the hood of MDN's new frontend

From 2-min dev server starts to 2s. They rewrote MDN's entire frontend, ditching the React SPA for Lit web components, server components, and Rspack. The result: less JS shipped, scoped CSS, and a build pipeline that just works.

mozilla.org

Videos

Debunking Django Myths - Sarah Boyce at PyTV

Django Fellow Sarah Boyce gave a talk recently at PyTV titled, "Django Has a Marketing Problem: Debunking the Myths That Won't Die." It is a fantastic overview of what Django does well and what it can improve.

youtu.be

Incremental Typing in Django - Carlton Gibson

Former Django Fellow and current Django Chat podcast host Carlton Gibson, recently gave a talk titled, "Static Islands, Dynamic Sea: Some Thoughts on Incremental Typing." In it he talks about why Python's dynamic nature is a feature, not a bug, and demonstrates Mantle - a library of utilities for typing around Django's liquid core.

youtu.be

Sponsored Link 2

Annual PyCharm Promo - 30% off, all money goes to Django

The annual PyCharm + Django promotion is live until May 1st. This is the single biggest fundraiser for Django and has raised over $350,000 since 2016.

jetbrains.com

Podcasts

Django Tasks - Jake Howard

Episode 200(!) features Jake Howard, a Senior Systems Engineer at Torchbox and the author of DEP 14, django.tasks, the highlight feature in Django 6.0. We discuss his work on the Django security team, work with Wagtail, AI dabblings, and more.

djangochat.com

Django Job Board

Python Developer at Open Data Services

Remote UK role building Python data systems for social-impact projects, offering ~£48k plus profit share in a collaborative worker co-op.

djangojobboard.com

Projects

yassi/dj-signals-panel

Display registered Django signals and receivers, showing what fires and where.

github.com

dvf/opinionated-django

An opinionated Django project with Repository pattern, Pydantic DTOs, svcs DI, and Stripe-style ULID IDs

github.com


This RSS feed is published on https://django-news.com/. You can also subscribe via email.

17 Apr 2026 3:00pm GMT

Djangocon EU: auto-prefetching with model field fetch modes in Django 6.1 - Jacob Walls

(One of my summaries of the 2026 Djangocon EU in Athens).

There's an example to experiment with here: https://dryorm.xterm.info/fetch-modes-simple

Timeline: it will be included in Django 6.1 in August.

The reason is the 1+n problem:

books = Book.objects.all()
for book in books:
    print(book.author.name)
    # This does a fresh query for author every time.

You can solve it with select_related(relation_names) or prefetch_related(relation_names). The first does an inner join. The second does two queries.

But: you might miss a relation. You might specify too many relations, getting data you don't need. Or you might not know about the relation as the code is in a totally different part of the code.

Fetch mode is intended to solve it. You can append .fetch_mode(models.FETCH_xyz) to your query:

  • models.FETCH_ONE: the current behaviour, which will be the default.
  • models.FETCH_PEERS: Fetch a deferred field for all instances that came from the same queryset. More or less prefetch_related in an automatic, lazy manner.
  • models.FETCH_RAISE: useful for development, it will raise FieldFetchBlocked. And it will thus tell you that you'll have a performance problem and that you might need FETCH_PEERS

This is what happens:

books = Book.objects.all().fetch_mode(models.FETCH_PEERS)
for book in books:
    # We're iterating over the query, so the query executes and grabs all books.
    print(book.author.name)
    # We accessed a relation, so at this point the prefetch_related-like
    # mechanism ist fired off and all authors linked to by the books are
    # grabbed in one single query.

You can write your own fetch modes, for instance if you only want a warning instead of raising an error.

https://reinout.vanrees.org/images/2026/kat3.jpeg

Unrelated photo explanation: a cat I encountered in Athens on an evening stroll in the neighbourhood behind the hotel.

17 Apr 2026 4:00am GMT

04 Apr 2026

feedPlanet Twisted

Donovan Preston: Using osascript with terminal agents on macOS

Here is a useful trick that is unreasonably effective for simple computer use goals using modern terminal agents. On macOS, there has been a terminal osascript command since the original release of Mac OS X. All you have to do is suggest your agent use it and it can perform any application control action available in any AppleScript dictionary for any Mac app. No MCP set up or tools required at all. Agents are much more adapt at using rod terminal commands, especially ones that haven't changed in 30 years. Having a computer control interface that hasn't changed in 30 years and has extensive examples in the Internet corpus makes modern models understand how to use these tools basically Effortlessly. macOS locks down these permissions pretty heavily nowadays though, so you will have to grant the application control permission to terminal. But once you have done that, the range of possibilities for commanding applications using natural language is quite extensive. Also, for both Safari and chrome on Mac, you are going to want to turn on JavaScript over AppleScript permission. This basically allows claude or another agent to debug your web applications live for you as you are using them.In chrome, go to the view menu, developer submenu, and choose "Allow JavaScript from Apple events". In Safari, it's under the safari menu, settings, developer, "Allow JavaScript from Apple events". Then you can do something like "Hey Claude, would you Please use osascript to navigate the front chrome tab to hacker news". Once you suggest using OSA script in a session it will figure out pretty quickly what it can do with it. Of course you can ask it to do casual things like open your mail app or whatever. Then you can figure out what other things will work like please click around my web app or check the JavaScript Console for errors. Another very important tips for using modern agents is to try to practice using speech to text. I think speaking might be something like five times faster than typing. It takes a lot of time to get used to, especially after a lifetime of programming by typing, but it's a very interesting and a different experience and once you have a lot of practice It starts to to feel effortless.

04 Apr 2026 1:31pm GMT

16 Mar 2026

feedPlanet Twisted

Donovan Preston: "Start Drag" and "Drop" to select text with macOS Voice Control

I have been using macOS voice control for about three years. First it was a way to reduce pain from excessive computer use. It has been a real struggle. Decades of computer use habits with typing and the mouse are hard to overcome! Text selection manipulation commands work quite well on macOS native apps like apps written in swift or safari with an accessibly tagged webpage. However, many webpages and electron apps (Visual Studio Code) have serious problems manipulating the selection, not working at all when using "select foo" where foo is a word in the text box to select, or off by one errors when manipulating the cursor position or extending the selection. I only recently expanded my repertoire with the "start drag" and "drop" commands, previously having used "Click and hold mouse", "move cursor to x", and "release mouse". Well, now I have discovered that using "start drag x" and "drop x" makes a fantastic text selection method! This is really going to improve my speed. In the long run, I believe computer voice control in general is going to end up being faster than WIMP, but for now the awkwardly rigid command phrasing and the amount of times it misses commands or misunderstands commands still really holds it back. I've been learning the macOS Voice Control specific command set for years now and I still reach for the keyboard and mouse way too often.

16 Mar 2026 11:04am GMT

04 Mar 2026

feedPlanet Twisted

Glyph Lefkowitz: What Is Code Review For?

Humans Are Bad At Perceiving

Humans are not particularly good at catching bugs. For one thing, we get tired easily. There is some science on this, indicating that humans can't even maintain enough concentration to review more than about 400 lines of code at a time..

We have existing terms of art, in various fields, for the ways in which the human perceptual system fails to register stimuli. Perception fails when humans are distracted, tired, overloaded, or merely improperly engaged.

Each of these has implications for the fundamental limitations of code review as an engineering practice:

Never Send A Human To Do A Machine's Job

When you need to catch a category of error in your code reliably, you will need a deterministic tool to evaluate - and, thanks to our old friend "alert fatigue" above - ideally, to also remedy that type of error. These tools will relieve the need for a human to make the same repetitive checks over and over. None of them are perfect, but:

Don't blame reviewers for missing these things.

Code review should not be how you catch bugs.

What Is Code Review For, Then?

Code review is for three things.

First, code review is for catching process failures. If a reviewer has noticed a few bugs of the same type in code review, that's a sign that that type of bug is probably getting through review more often than it's getting caught. Which means it's time to figure out a way to deploy a tool or a test into CI that will reliably prevent that class of error, without requiring reviewers to be vigilant to it any more.

Second - and this is actually its more important purpose - code review is a tool for acculturation. Even if you already have good tools, good processes, and good documentation, new members of the team won't necessarily know about those things. Code review is an opportunity for older members of the team to introduce newer ones to existing tools, patterns, or areas of responsibility. If you're building an observer pattern, you might not realize that the codebase you're working in already has an existing idiom for doing that, so you wouldn't even think to search for it, but someone else who has worked more with the code might know about it and help you avoid repetition.

You will notice that I carefully avoided saying "junior" or "senior" in that paragraph. Sometimes the newer team member is actually more senior. But also, the acculturation goes both ways. This is the third thing that code review is for: disrupting your team's culture and avoiding stagnation. If you have new talent, a fresh perspective can also be an extremely valuable tool for building a healthy culture. If you're new to a team and trying to build something with an observer pattern, and this codebase has no tools for that, but your last job did, and it used one from an open source library, that is a good thing to point out in a review as well. It's an opportunity to spot areas for improvement to culture, as much as it is to spot areas for improvement to process.

Thus, code review should be as hierarchically flat as possible. If the goal of code review were to spot bugs, it would make sense to reserve the ability to review code to only the most senior, detail-oriented, rigorous engineers in the organization. But most teams already know that that's a recipe for brittleness, stagnation and bottlenecks. Thus, even though we know that not everyone on the team will be equally good at spotting bugs, it is very common in most teams to allow anyone past some fairly low minimum seniority bar to do reviews, often as low as "everyone on the team who has finished onboarding".

Oops, Surprise, This Post Is Actually About LLMs Again

Sigh. I'm as disappointed as you are, but there are no two ways about it: LLM code generators are everywhere now, and we need to talk about how to deal with them. Thus, an important corollary of this understanding that code review is a social activity, is that LLMs are not social actors, thus you cannot rely on code review to inspect their output.

My own personal preference would be to eschew their use entirely, but in the spirit of harm reduction, if you're going to use LLMs to generate code, you need to remember the ways in which LLMs are not like human beings.

When you relate to a human colleague, you will expect that:

  1. you can make decisions about what to focus on based on their level of experience and areas of expertise to know what problems to focus on; from a late-career colleague you might be looking for bad habits held over from legacy programming languages; from an earlier-career colleague you might be focused more on logical test-coverage gaps,
  2. and, they will learn from repeated interactions so that you can gradually focus less on a specific type of problem once you have seen that they've learned how to address it,

With an LLM, by contrast, while errors can certainly be biased a bit by the prompt from the engineer and pre-prompts that might exist in the repository, the types of errors that the LLM will make are somewhat more uniformly distributed across the experience range.

You will still find supposedly extremely sophisticated LLMs making extremely common mistakes, specifically because they are common, and thus appear frequently in the training data.

The LLM also can't really learn. An intuitive response to this problem is to simply continue adding more and more instructions to its pre-prompt, treating that text file as its "memory", but that just doesn't work, and probably never will. The problem - "context rot" is somewhat fundamental to the nature of the technology.

Thus, code-generators must be treated more adversarially than you would a human code review partner. When you notice it making errors, you always have to add tests to a mechanical, deterministic harness that will evaluates the code, because the LLM cannot meaningfully learn from its mistakes outside a very small context window in the way that a human would, so giving it feedback is unhelpful. Asking it to just generate the code again still requires you to review it all again, and as we have previously learned, you, a human, cannot review more than 400 lines at once.

To Sum Up

Code review is a social process, and you should treat it as such. When you're reviewing code from humans, share knowledge and encouragement as much as you share bugs or unmet technical requirements.

If you must reviewing code from an LLM, strengthen your automated code-quality verification tooling and make sure that its agentic loop will fail on its own when those quality checks fail immediately next time. Do not fall into the trap of appealing to its feelings, knowledge, or experience, because it doesn't have any of those things.

But for both humans and LLMs, do not fall into the trap of thinking that your code review process is catching your bugs. That's not its job.

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!

04 Mar 2026 5:24am GMT

22 Jan 2026

feedPlanet Plone - Where Developers And Integrators Write

Maurits van Rees: Mikel Larreategi: How we deploy cookieplone based projects.

We saw that cookieplone was coming up, and Docker, and as game changer uv making the installation of Python packages much faster.

With cookieplone you get a monorepo, with folders for backend, frontend, and devops. devops contains scripts to setup the server and deploy to it. Our sysadmins already had some other scripts. So we needed to integrate that.

First idea: let's fork it. Create our own copy of cookieplone. I explained this in my World Plone Day talk earlier this year. But cookieplone was changing a lot, so it was hard to keep our copy updated.

Maik Derstappen showed me copier, yet another templating language. Our idea: create a cookieplone project, and then use copier to modify it.

What about the deployment? We are on GitLab. We host our runners. We use the docker-in-docker service. We develop on a branch and create a merge request (pull request in GitHub terms). This activates a piple to check-test-and-build. When it is merged, bump the version, use release-it.

Then we create deploy keys and tokens. We give these access to private GitLab repositories. We need some changes to SSH key management in pipelines, according to our sysadmins.

For deployment on the server: we do not yet have automatic deployments. We did not want to go too fast. We are testing the current pipelines and process, see if they work properly. In the future we can think about automating deployment. We just ssh to the server, and perform some commands there with docker.

Future improvements:

  • Start the docker containers and curl/wget the /ok endpoint.
  • lock files for the backend, with pip/uv.

22 Jan 2026 9:43am GMT

Maurits van Rees: Jakob Kahl and Erico Andrei: Flying from one Plone version to another

This is a talk about migrating from Plone 4 to 6 with the newest toolset.

There are several challenges when doing Plone migrations:

  • Highly customized source instances: custom workflow, add-ons, not all of them with versions that worked on Plone 6.
  • Complex data structures. For example a Folder with a Link as default page, with pointed to some other content which meanwhile had been moved.
  • Migrating Classic UI to Volto
  • Also, you might be migrating from a completely different CMS to Plone.

How do we do migrations in Plone in general?

  • In place migrations. Run migration steps on the source instance itself. Use the standard upgrade steps from Plone. Suitable for smaller sites with not so much complexity. Especially suitable if you do only a small Plone version update.
  • Export - import migrations. You extract data from the source, transform it, and load the structure in the new site. You transform the data outside of the source instance. Suitable for all kinds of migrations. Very safe approach: only once you are sure everything is fine, do you switch over to the newly migrated site. Can be more time consuming.

Let's look at export/import, which has three parts:

  • Extraction: you had collective.jsonify, transmogrifier, and now collective.exportimport and plone.exportimport.
  • Transformation: transmogrifier, collective.exportimport, and new: collective.transmute.
  • Load: Transmogrifier, collective.exportimport, plone.exportimport.

Transmogrifier is old, we won't talk about it now. collective.exportimport: written by Philip Bauer mostly. There is an @@export_all view, and then @@import_all to import it.

collective.transmute is a new tool. This is made to transform data from collective.exportimport to the plone.exportimport format. Potentially it can be used for other migrations as well. Highly customizable and extensible. Tested by pytest. It is standalone software with a nice CLI. No dependency on Plone packages.

Another tool: collective.html2blocks. This is a lightweight Python replacement for the JavaScript Blocks conversion tool. This is extensible and tested.

Lastly plone.exportimport. This is a stripped down version of collective.exportimport. This focuses on extract and load. No transforms. So this is best suited for importing to a Plone site with the same version.

collective.transmute is in alpha, probably a 1.0.0 release in the next weeks. Still missing quite some documentation. Test coverage needs some improvements. You can contribute with PRs, issues, docs.

22 Jan 2026 9:43am GMT

Maurits van Rees: Fred van Dijk: Behind the screens: the state and direction of Plone community IT

This is a talk I did not want to give.

I am team lead of the Plone Admin team, and work at kitconcept.

The current state: see the keynotes, lots happening on the frontend. Good.

The current state of our IT: very troubling and daunting.

This is not a 'blame game'. But focussing on resources and people this conference should be a first priority. We are a real volunteer organisation, nobody is pushing anybody around. That is a strength, but also a weakness. We also see that in the Admin team.

The Admin team is 4 senior Plonistas as allround admin, 2 release managers, 2 CI/CD experts. 3 former board members, everyone overburdened with work. We had all kinds of plans for this year, but we have mostly been putting out fires.

We are a volunteer organisation, and don't have a big company behind us that can throw money at the problems. Strength and weakness. In all society it is a problem that volunteers are decreasing.

Root causes:

  • We failed to scale down in time in our IT landscape and usage.
  • We have no clean role descriptions, team descriptions, we can't ask a minimum effort per week or month.
  • The trend is more communication channels, platforms to join and promote yourself, apps to use.

Overview of what have have to keep running as admin team:

  • Support main development process: github, CI/CD, Jenkins main and runners, dist.plone.org.
  • Main communication, documentation: pone.org, docs.plone.org, training.plone.org, conf and country sites, Matomo.
  • Community office automation: Google docds, workspacae, Quaive, Signal, Slack
  • Broader: Discourse and Discord

The first two are really needed, the second we already have some problems with.

Some services are self hosted, but also a lot of SAAS services/platforms. In all, it is quite a bit.

The Admin team does not officially support all of these, but it does provide fallback support. It is too much for the current team.

There are plans for what we can improve in the short term. Thank you to a lot of people that I have already talked to about this. 3 areas: GitHub setup and config, Google Workspace, user management.

On GitHub we have a sponsored OSS plan. So we have extra features for free, but it not enough by far. User management: hard to get people out. You can't contact your members directly. E-mail has been removed, for privacy. Features get added on GitHub, and no complete changelog.

Challenge on GitHub: we have public repositories, but we also have our deployments in there. Only really secure would be private repositories, otherwise the danger is that credentials or secret could get stolen. Every developer with access becomes an attack vector. Auditing is available for only 6 months. A simple question like: who has been active for the last 2 years? No, can't do.

Some actionable items on GitHub:

  • We will separate the contributor agreement check from the organisation membership. We create a hidden team for those who signed, and use that in the check.
  • Cleanup users, use Contributors team, Developers
  • Active members: check who has contributed the last years.
  • There have been security incidents. Someone accidentally removed a few repositories. Someone's account got hacked, luckily discovered within a few hours, and some actions had already been taken.
  • More fine grained teams to control repository access.
  • Use of GitHub Discussions for some central communication of changes.
  • Use project management better.
  • The elephant in the room that we have practice on this year, and ongoing: the Collective organisation. This was free for all, very nice, but the development world is not a nice and safe place anymore. So we already needed to lock down some things there.
  • Keep deployments and the secrets all out of GitHub, so no secrets can be stolen.

Google Workspace:

  • We are dependent on this.
  • No user management. Admins have had access because they were on the board, but they kept access after leaving the board. So remove most inactive users.
  • Spam and moderation issues
  • We could move to Google docs for all kinds of things. Use Google workspace drives for all things. But the Drive UI is a mess, so docs can be in your personal account without you realizing it.

User management:

  • We need separate standalone user management, but implementation is not clear.
  • We cannot contact our members one on one.

Oh yes, Plone websites:

  • upgrade plone.org
  • self preservation: I know what needs to be done, and can do it, but have no time, focusing on the previous points instead.

22 Jan 2026 9:43am GMT