Reinout van Rees' weblog Python, grok, books, history, faith, etc. 2026-04-20T07:43:00+01:00 urn:syndication:a55644db8591c020bd38852775819a9a Djangocon EU: lightning talks day 3 http://reinout.vanrees.org/weblog/2026/04/17/9-lightning-talks-day3.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T14:19:00+01:00

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

Announcement - Carlton Gibson

They've been working on improving the technical governance of Django. They'd like to get feedback. There's a blog post about it.

Oh, and look at the "30% off PyCharm" button on the django website, that raises quite a lot of funds for Django. PyCharm's sponsoring is a very sizeable financial part of Django, thanks!

Even more table partitioning with Django, Postgres and UUIDs - Tim Bell

(See his earlier talk on partitioning).

UUID is 128-bits, usually displayed as hex strings. It starts with the unix timestamp, followed by several random fields (in version 7). In version 8, you have more flexibility. You can customize it to put a specific value (an id of a related field in their case) in the first field.

Partitioning per UUID (they used it as their ID) then effectively also partitions on the related field.

Speeding up Django startup times with lazy imports - Anze Pecar

Imports in Python can be slow. Luckily, python has something build-in to check it, the "importtime" flag:

python -X importtime manage.py check

He worked around the packages he found by importing the package inside the functions where he used them. It worked, but it was ugly.

Look at things like post_worker_init in gunicorn, you can use that to pre-load the offending modules.

You can also wait for python 3.15. PEP810: explicit lazy imports!

PyLadies Seoul: rebooting a community for women in tech scenes - Hwayoung Cha

At Pycon Korea 2023 there were only three woman in attendance. So: time to re-start Pyladies Seoul! And with success. One of the new attendees is now a CTO of a company (and also a PyLadies volunteer herself).

They'll also start a Django workshop soon.

Join your local PyLadies chapter!

What I learned during learning to solve rubic cube - Venelin Stoykov

He learned solving a Rubic cube in about two weeks.

We can learn new things more easily by association with things we already know. We need to practice a lot. Repeat, repeat: that way we tell our brain that we need to remember it.

"Thinking slow and fast" is a book he recommends.

AI is like the fast thinking. Fast is also a bit sloppy and often a bit wrong.

If we really want to understand something, it takes time and work.

Why volunteering and contributing to communities is important - Alex Gómez

Get involved! Volunteer! Do some work! Volunteers are necessary.

Volunteering is a lot of work, but it is worth it.

Djangofmt, a Django template formatter written in rust - Thibaut Decombe

Djangofmt, a fast, html aware, django template formatter, written in Rust.

https://github.com/UnknownPlatypus/djangofmt

You can run it as a pre-commit hook.

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

Unrelated photo explanation: a cat I encountered in Athens in the morning near the hotel.

]]>
Djangocon EU: supply chain attacks on Python projects - Mateusz Bełczowski http://reinout.vanrees.org/weblog/2026/04/17/8-dependencies-supply-chain-attacks.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T13:48:00+01:00

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

Full title: what's in your dependencies? Supply chain attacks on Python projects.

How supply chain attacks work: attackers don't attack your code directly, they target something you trust. A typical Django project has lots of dependencies. Direct dependencies and "transitive dependencies", dependencies of our dependencies. If you depend on requests, requests itself will grab certify and urllib3.

Possible package attacks:

  • Inject malicious code directly into the repo.
  • Create malicious package. Typosquatting (abusing typos), slopsquatting (abusing typos made by LLMs). "Brandjacking": quickly after deepseek became popular, a deepseekai package was published that stole credentials.
  • Compromise existing package. Credential stealing, CI/CD exploits.

What attackers typically do with access is to steal credentials. Environment variables, cloud keys (AWS_xyz), pypi tokens, ssh private keys, database URLs, saved passwords.

Example: num2words was hacked in July 2025. Phishing leading to maintainer credentials theft. Fake login page at pypj.org instead of pypi.org. Then they uploaded faulty releases with the captured credentials. Credentials weren't rotated, so a second attack happened a few days later. This malware targeted .pypirc files, leading to more compromises.

How can we defend from this kind of attacks? Depends on the kind of attack. When publishing via GitHub actions, use "trusted publishing", in that case there are no credentials to steal.

Another example: LiteLLM was compromised via trivy, a security scanner that itself was compromised... It in turn collected environment variables, secrets and ssh keys, bundled it all in a tarball and posted it to some legitimate-looking domain.

Some myths:

  • "Lockfiles protect us". No, they only prevent accidental upgrades, not when adding a package for the first time.
  • "Just don't install suspicious packages". Lots is installed via transitive dependencies.
  • "We run everything in Docker so we're safe". It limits the blast radius, but credentials and environment variables are still at risk.
  • "We can fully prevent attacks".

Some tips:

  • Use dependency cooldowns. uv has "exclude-newer", pip has "uploaded-prior-to". Don't be the first to install a fresh release, as most malicious packages are discovered within hours or days.
  • Pin versions and verify hashes.

Pypi is getting better:

  • Trusted publishing.
  • Project quarantine.
  • Attestations: cryptographic tools to verify the source.
  • Typosquatting protection.

AI has risks:

  • Slopsquatting. Hallucinated package names that get exploited.
  • Prompt injection via github issues.
  • Agents often "just" pip-install things directly.

Note: AI can also be used to detect malware! A small project started after the LiteLLM compromise managed to detect a dangerous different compromise almost the moment it was published. Nice!

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

Unrelated photo explanation: a cat I encountered in Athens on an evening stroll in the neighbourhood behind the hotel. It was a cat on the hunt: relevant to the topic of this talk :-)

]]>
Djangocon EU: Django templates on the frontend? - Christophe Henry http://reinout.vanrees.org/weblog/2026/04/17/7-django-templates-frontend.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T12:25:00+01:00

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

It all started with formsets: you generate a new form based on other forms. You can use it to create pretty fancy forms. But your designer can get quite creative. And you might have variable forms that have to react to user input.

A common solution is to use htmx, but that means server requests all the time. And some users have really bad connections. Regular requests aren't handy in that scenario.

He looked at django-rusty-templates: Django's template engine implemented in Rust. It had a template parser that he could re-use. With OXC (javascript oxidation compiler) he converted that to javascript.

That way, he could offload much of the django form creation handling to the frontend, including reacting to user input and showing alerts.

The work-in-progress project is called django-template-transpiler: https://github.com/christophehenry/django-template-transpiler . Don't use it for production.

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

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

]]>
Djangocon EU: How Django is helping to build the biggest X-ray observatory to date - Loes Crama http://reinout.vanrees.org/weblog/2026/04/17/6-x-ray.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T12:09:00+01:00

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

She works at Cosine, they develop measurement systems and space instrumentation. They work for the space industry (ESA, NASA, etc).

They're now working on "high-energy optics", the NewAthena x-ray observatory, the biggest one to date. NewAthena: NEW Advanced Telescope for High-ENergy Astrophysics. Planned launch is in 2037 on Ariane 6.4.

Talking about rocket science is cool, but where's the software? Within the company, software is an internal service, supporting scientists. Handling data in many ways: visualization, analysis, processing, management. Django plays a big role in all of this.

When you build something with Django in a scientific context, you really need to understand the data. Workflows must be flexible. R&D and production often don't need to be strictly separated. Multiple datastores for various purposes (like an extra MongoDB, for instance) is often handy.

Their application consists of:

  • SXRO (silicon x-ray optics) database.
  • A Mysql database.
  • Django.

The goal is to track all the components that go into the observatory. Status and quality. Configuration and geometry. Component relationships. Inspections. History of all the components.

The default Django admin is their primary method of using the application. Often, it is said that the admin is not not not intended for end users. But they're using it anyway. It is an internal tool for technical people. Most of them have a PhD: they can handle such an interface. They've been using it for years.

There are some third party packages:

  • django-simple-history: easy history.
  • djangoql: advanced queries for the search bar.
  • django-admin-rangefilter, django-admin-list-filter-dropdown, django-admin-numeric-filter: little tools to tweak the filters on the right hand side.

There are some separate forms, mostly for actions performed in the lab or cleanroom. For instance a form where you can use an ipad to indicate defects in one of the components by just drawing on the picture of the component.

There's also a REST API. Other software and data tools can use it to integrate with Django:

  • Observability tools (prometheus/grafana).
  • JupyterHub.
  • Stacking robots.

They use: djangorestframework, drf-spectacular (API docs), django-filter (filtering via GET parameters).

Django is their software backbone. A general-purpose framework that's well suited for a scientific context.

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

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

]]>
Djangocon EU: where did it all BEGIN;? Django's transaction management - Charlie Denton & Sam Searles-Bryant http://reinout.vanrees.org/weblog/2026/04/17/5-where-did-it-begin.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T10:05:00+01:00

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

Transactions are an SQL concept. You "begin" a transaction, do a bunch of statements and at the end "commit" it. Only then does it actually show up in the database for other connections:

BEGIN;
  INSERT ...;
  UPDATE ...;
  INSERT ...;
COMMIT;

If something goes wrong, you can abort it with "rollback":

BEGIN;
  INSERT ...;
  UPDATE ...;
  INSERT ...;
ROLLBACK;

The SQL default is to run in "autocommit" mode, so everything always updates the database immediately.

The history of Django's transaction management:

  • <0.95 emulated sql's autocommit by executing COMMIT; after every save.
  • 0.95 introduced with commit_on_success():, which would give you a context manager, at the end everything would be committed unless an error occurred. It did have a corner case: you couldn't really nest commit_on_success.
  • 1.6 introduced with atomic():, mostly like commit_on_success, but it introduced SAVEPOINT, allowing you to rollback a nested .atomic() to the savepoint that's created when you enter a nested 'atomic' block.
  • 3.2 introduced with atomic(durable=True):. If you had a nested atomic, a transaction you thought was committed might be rolled back by another 'atomic' wrapping the code. When you specify durable=True, calling 'atomic' fails with an error, as it isn't legal in this case.

In their own projects they sometimes wanted to have more explicit control. They sometimes had a check like if assert not connection.in_atomic_block, but that didn't look nice. They split atomic() into separate transaction(), savepoint() and durable() context managers. Nicely explicit.

The code is here: https://github.com/kraken-tech/django-subatomic

A big inspiration for them was a talk by Aymeric Augustin at the 2013 djangocon.eu. Here's my summary of that talk.

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

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

]]>
Djangocon EU: improving runserver with django-prodserver - Andrew Miller http://reinout.vanrees.org/weblog/2026/04/17/4-improving-most-used-api.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T09:33:00+01:00

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

Original title: improving one of Django's most used APIs — and it's not the one you're thinking of. (I'm using a more descriptive title for my blog entry.)

APIs are everywhere. Django rest framework, but also the models layer. And: manage.py runserver, he considers that an API. Everybody runs it. So: can we improve it?

"Runserver" doesn't sound the same as "devserver" or "rundevserver". It doesn't advertise that it is only intended for development. A name change could help there. But... Django really likes to be stable. It is probably too entrenched to change.

Production is missing a cohesive API. You normally run something like gunicorn myapp.wsgi 0.0.0.0:8000 --workers 4...

He tried to get improvements in. Since 5.2, there's a warning when you start runserver: "it is only intended for development". But you might miss it when you get lots of log messages. Other people complained about the extra two lines.

He started django-prodserver. pip install django-prodserver[gunicorn]. You can then run manage.py prodserver (and manage.py devserver, he snuck that in).

You have to add a bit of configuration to your settings file:

PRODUCTION_PROCESSES = {
    "web": {
        "BACKEND": "django_prodserver.backends.gunicorn.GunicornServer",
        "ARGS": {"bind": "0.0.0.0:8000",},  # add number of workers and so.
    },
}

Run it with manage.py prodserver web. You can add more processes, for instance for a background worker process.

It is the first version. He wants feedback, especially on the naming. manage.py prodserver web or just manage.py web? And manage.py worker? manage.py serve --prod?

Django isn't just a framework, it is a set of APIs. We can prototype new APIs in packages.

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

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

]]>
Djangocon EU: auto-prefetching with model field fetch modes in Django 6.1 - Jacob Walls http://reinout.vanrees.org/weblog/2026/04/17/3-auto-prefetching.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T08:29:00+01:00

(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.

]]>
Djangocon EU: zero-migration encryption - Vjeran Grozdanic http://reinout.vanrees.org/weblog/2026/04/17/2-zero-migration-encryption.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-17T09:05:00+01:00

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

Full title: zero-migration encryption: building drop-in encrypted field in Django.

He works at Sentry. Huge site with a Django backend and thousands requests per second.

He had to add a new table to store 3rd party API credentials. Oh: should this be encrypted? Yes. But: each team has its own way to encrypt data. And there were at least 10 encryption keys here and there (as environment variables). And tens of places where encryption/decryption happens.

So: better to build a generic solution. Or use an existing generic solution. And yes, there are multiple libraries. EncryptedCharField looked nice. But the problem was all the existing data in the various places. Sentry is not a site that you can shut down for a while, so you have to do it with zero downtime. This means you can never change an existing column type.

A solution could be to add a new encrypted field next to the existing one. Then fill it and backfill it and make sure no new data is written to the old field and then you can remove the old field. But that's quite a job with all the different locations that had to be changed.

A Field class in Django has get_prep_value() and from_db_value(). Those are called before storing data in the database and after grabbing it from the database. You could create a new CharField-like field and start to encrypt values in get_prep_value and decrypt the other way.

You'd have to be able to recognise the old un-encrypted values. A solution: prefix encrypted values with enc:. Also key rotation can be handled this way, by including that in the prefix (enc:key2:).

But there's also a bjson field. They solved that by encrypting the json and writing a json to the database with the encrypted json in a field and also the encryption key info.

The code is in the sentry repo .

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

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

]]>
Djangocon EU: body of knowledge - Daniele Procida http://reinout.vanrees.org/weblog/2026/04/17/1-body-of-knowledge.html Reinout van Rees 2026-04-17T00:00:00+01:00 2026-04-20T07:43:00+01:00

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

Athens! The thinking industry started here. Athens is often the origin if you follow ideas to the source.

Here also Socrates was found guilty (280-221) for "corrupting the youth" on trumped-up charges. Though... he made it is job to be a complete nuisance: exposing everyone's hypocrisy and asking difficult questions. After the 280-221 vote he got to give a speech in reaction. After that, the vote on the actual punishment was 360-141 in favour of the death penalty. The speech must have been particularly irritating.

On to a different subject. He watched the recent launch of the NASA rocket that went to the moon. A marvel of technology. That was measured using body parts, being 322 feet tall. And the distance to the moon in miles. Why not the scientific meter and kilometer?

Plato already mentioned it. "Now take the acquisition of knowledge; is the body a hindrance or not, if one takes it into partnership to share an investigation". And "when the soul tries to investigate anything with the help of the body, it is obviously led astray".

0.098 km = 98 m = 98000 mm, a child can understand it. Pure rationality. But ask a Metric Martyr in the UK how many feet are in a mile and most of them won't know.

The world seems to be divided in two camps:

  • Thinking, rationality, abstraction, unboundedness.
  • Bodies, materiality, tangibility, being rooted.

Wouldn't Plato have loved a computer? Pure rationality, following its logic programming without fail?

What about those body-part-units? They're not that weird actually. They're rational Roman measurements:

  • A mile is 1000 Roman paces.
  • 1 passus = 5 pedes (feet).
  • 1/12 pes (feet) = 1 uncia (thus: inch). (And ounce. Uncia is something like twelfth-part-ness.)

(Note: according to the Greek, Romans are only good for stealing Greek ideas, building roads and killing people.)

On to another aspect. Why is Django's documentation so good? Well, it has been prioritized from the start. It is complete, accurate, consistent, rational and well-structured: all Platonic values.

But Daniele also thinks the documentation is so good because it fits the human body.

The size has to be right. The limitations of our intelligence are the limits of our embodied intelligence. We can only grasp so much, mentally. A list can be too long. A page can be too long. If information is cut in too-small parts, you also can get into problems as you have to context-switch between pages too much. We tire mentally also because we tire physically.

The same applies to our body. Our hands and fingers can grasp objects. But it has to be of a certain size. Too big and we can't grasp it. Too small and our fingers can't pick it up.

We experience documentation in time and space. We move with it. How long have you been reading the Django documentation? "Where are you in the text?" We orient ourselves in text as if in a space or a building. We rely on the humanised rationality of structure. Sometimes you're in a building and it is clear where you have to go and in other buildings you feel lost.

Django's documentation is so good because of the quality of experience that it gives you. It is almost an embodied being that you can experience in space and time. Does it fit you? Do you notice it? The embodied nature of the work and intelligence that the Django community poured into the documentation?

Early Macintosh manuals had to explain new concepts and really tried to explain them in a human way. Scrolling being explained with help of an old book scroll, for instance. A floppy disk for storage as a floor plan of a building with a corridor and rooms.

Aldine Press (started 1494 in Venice) had a vision to print the old classics in a more accessible way. Books in the middle ages used to be big. And sometimes chained to the desk. Not really accessible. By printing them in smaller, lighter, more accessible formats, he wanted to make our "body of knowledge" more fitting to the human body.

You can see the bodily aspects of knowledge in our language:

  • Seizing/taking: grasp, comprehend, apprehend, perceive.
  • Measuring: ponder, weigh up, fathom.
  • Body movement: jumping to conclusions, intuitive leap, stumble/trip
  • Spatiality: understand, position

Mental space. When he asked a question of Russell Keith-Magee at a Django sprint, Russell would close his eyes and turn his eye inwards for a while. He would look at the Django codebase in his head and navigate it. Just like you yourself would navigate a city?

Being a programmer isn't so different from being a human with a body in time and space. Look at questions you might have as a beginning programmer:

  • Which file or directory or window to be in.
  • Where to expect the output.
  • When to expect it.
  • Where to enter a command.
  • When to do something.
  • In what order to do things.

And then look at an experienced programmer. They seem to know where they are. They know their way around. They can move smoothly.

Closing comment: there are some uncanny features in software nowadays. As a human, we are used to having limits. But nowadays we have infinite scrolling, doomscrolling. And edgeless, endless, virtual cloud resources. And LLM indeterminism. Those are not inherintly bad, but it is different from what we're used to. Is this still computing fit for the embodied mind?

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

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

]]>
Djangocon EU: lightning talks (day 2) http://reinout.vanrees.org/weblog/2026/04/16/9-lightning-talks-day2.html Reinout van Rees 2026-04-16T00:00:00+01:00 2026-04-16T14:32:00+01:00

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

Developing Django's community - Andy Miller

Andy likes these conferences. But getting here and attending costs at least €1000. So conferences are limited to those that can afford it.

The new online community working group that wants to improve the online possibilities of gathering, as those are available to everyone.

Perhaps new virtual social events. Better feeds of what's happening in the community. So: improve the community for everyone.

More info: https://github.com/django/online-community-working-group

To JWT or not to JWT - Benedikt

JSON web tokens are not a one-size-fits-all solution.

JWT is a bas64 encoded string with three parts: header, payload, signature. Marketed as stateless, but revocation always adds state.

Why would you want to use it? Well, third-party identity providers often give you one. And: you can save database queries by embedding info in the token. And you can use it for offline mode in mobile or desktop apps. But there are drawbacks.

Some reasons for using something else: JWTs are immutable, data remains valid until expiration even when the data changes server-side. Stateless revocation is impossible. Logout-from-all-devices requires tracking state, defeating the process.

  • JWT if your provider uses them.
  • Regular Session/cookie auth for web apps is often better.
  • Opaque tokens for mobile/desktop.

Django on the Med / Django Italia - Paolo Melchiorre

"Django on the Med" is a sprint. Not a sprint after a conference, but just a sprint. After a conference you often want to get home or you're tired, so what we get done at a conference sprint is often a bit limited.

What they got done in September at "Django on the Med" is amazing. This year it is in Pescara, Italy. 23-25 September.

Somewhat related: 27 May there'll be a free "Django off the Med" online workshop at the PyCon Italia conference.

Two ways I used GeneratedField during a rewrite - Anthony Ricaud

GeneratedField: google for Paolo, he's done lots of talks on it (for instance this one).

He used GeneratedField in a migration scenario:

archived = models.GeneratedField(
    output_field=models.BooleanField,
    db_persist=True,
    expression=(models.Q(soft_delete=True | models.Q(....)),
)

The second scenario involved generating a unique "city id" based on two other fields.

django-mediastorage - Alissa Gerhard

A media file is data that is stored as a file and accessible to users. FileFields store files attached to models.

Local filesystem is the default and sufficient for most projects. You can use X-Accell-Redirect to get the proxy (like nginx) to actually serve the file, instead of Django. But every proxy has its own solution.

django-mediastorage can handle it for you for several different proxies.

There's a new FileField subclass, ProtectedFileField, to handle authentication requirements.

There's also integration for django restframework.

There's still a lot to do, but they're using it in production themselves.

Django VPS deployments made simple - Jan Raasch

Let's talk about Django's deployment story.

He demoed deploying a simple Django app to a small virtual server. With python manage.py deploy --serveral-options.

He used django-simple-deploy and a custom plugin for django-simple-deploy that used 'kamal' to do the actual deploying.

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

Unrelated photo explanation: a trip in November to the Mosel+Eifel region in Germany. Some restored remnants of the Virneburg castle.

]]>