Matthias Kestenholz: Posts about Programminghttps://406.ch/writing/category-programming/2024-03-27T12:00:00ZMatthias KestenholzThe Django admin is a CMShttps://406.ch/writing/the-django-admin-is-a-cms/2024-03-27T12:00:00Z2024-03-27T12:00:00Z<h1>The Django admin is a CMS</h1><p>The post <a href="https://www.coderedcorp.com/blog/why-is-the-django-admin-ugly/">Why is the Django Admin “Ugly”?</a> and the <a href="https://hachyderm.io/@paulox@fosstodon.org/111298440425647176">discussion on
Mastodon</a> around it finally motivated me to write down my
thoughts regarding the recurring theme in Django land that the <a href="https://docs.djangoproject.com/en/5.0/ref/contrib/admin/">Django
administration interface</a> isn’t a CMS (Content Management
System).</p>
<p>I think that this is misguided and needlessly limits the discourse around what
the admin’s current functionality is and the ideas what it could be and already
is.</p>
<p><a href="https://en.wikipedia.org/wiki/Web_content_management_system">A web content management system</a> is about website authoring for users
who do not need to be web programming experts in their own rights. <a href="https://en.wikipedia.org/wiki/Django_(web_framework)">Django was
created at the Lawrence Journal-World newspaper</a>. The admin itself was
created to allow quickly spinning up new websites, where the admin interface
was used by content managers to fill in the content while programmers finalized
the rest of the website. So obviously the admin interface was a system used to
manage content<sup id="fnref:words"><a class="footnote-ref" href="#fn:words">1</a></sup> from the beginning.</p>
<p>Sure, the <a href="https://docs.djangoproject.com/en/5.0/ref/contrib/admin/">Django admin site documentation</a> states:</p>
<blockquote>
<p>One of the most powerful parts of Django is the automatic admin interface. It reads metadata from your models to provide a quick, model-centric interface where trusted users can manage content on your site. The admin’s recommended use is limited to an organization’s internal management tool. <strong>It’s not intended for building your entire front end around.</strong> [emphasis added]</p>
</blockquote>
<p>In other words, the Django documentation also points out that the admin is
powerful and that it allows trusted users to manage content<sup id="fnref2:words"><a class="footnote-ref" href="#fn:words">1</a></sup>.</p>
<p>Yes, it will be very painful if you try to do everything on top of the Django
admin site. The warnings against using the Django admin for more than it was
designed to are necessary and I totally support them. As soon as you’re getting
into workflows, into complex permission scenarios (sad noises) or similar
things the admin definitely isn’t for you. But, the admin nicely solves 90% of
the problems with 10% of the effort. And it’s very good at that.</p>
<p>And sure, if you try building your own frontend on top of the Django admin
you’re in for a bumpy ride, but that much should be obvious.</p>
<p>Many third party apps for Django actually target the Django admin interface
itself, and not one of the (excellent!) Django-based CMS such as
<a href="https://wagtail.org/">Wagtail</a>. This means that by building on the Django
admin instead of one of the CMS you’re <a href="https://406.ch/writing/run-less-code-in-production-or-youll-end-up-paying-the-price-later/">running less code</a>, by using
more libraries instead of frameworks (on top of frameworks) you’re <a href="https://406.ch/writing/low-maintenance-software/">keeping
maintenance lower</a>, and you’re a part of a larger
community<sup id="fnref:community"><a class="footnote-ref" href="#fn:community">2</a></sup>, which brings the potential benefit of being able to
profit more from the general Django packages ecosystem.</p>
<p>Since you’re depending on smaller pieces of additional software it will
generally be possible to upgrade to new Django versions quicker. This isn’t
true for all packages of course, and I’m a reluctant maintainer of some of
them. Anecdotes aren’t data, but I see that some larger CMS systems are
definitely having a hard time keeping up with Django’s release schedule.</p>
<p>I’m not trying to say that the Django admin is a better CMS than other
Django-based CMS, or any other CMS. I’m saying it’s a trade off and you should
be mindful of the downsides of choosing a larger system. And I’m saying that
the people who tell you that you shouldn’t be using the Django admin interface
are wrong in the first approximation.</p>
<p>The fact that it’s so easy to spin up an additional site and with minimal
effort and still be able to work with clean database schemas and all the great
tools Django (and Python) offers is important for those of us who are working
on many different projects with limited financial resources, because the
website often is for example just a small part of a campaign.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:words">
<p>Sorry-not-sorry for my choice of words. <a class="footnote-backref" href="#fnref:words" title="Jump back to footnote 1 in the text">↩</a><a class="footnote-backref" href="#fnref2:words" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:community">
<p>The assumption that the communities of these Django-based CMS projects are a subset of the Django community itself shouldn’t be too controversial. <a class="footnote-backref" href="#fnref:community" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>blacknoise – ASGI app for static file servinghttps://406.ch/writing/blacknoise-asgi-app-for-static-file-serving/2024-03-20T12:00:00Z2024-03-20T12:00:00Z<h1>blacknoise – ASGI app for static file serving</h1><div class="admonition note">
<p class="admonition-title">Note</p>
<p>This blog post consists of the <a href="https://github.com/matthiask/blacknoise">blacknoise
README</a> at the time of publishing.</p>
</div>
<p>blacknoise is an <a href="https://asgi.readthedocs.io/en/latest/">ASGI</a> app for static
file serving inspired by <a href="https://github.com/evansd/whitenoise/">whitenoise</a>
and following the principles of <a href="https://406.ch/writing/low-maintenance-software/">low maintenance
software</a>.</p>
<p><strong>This is pre-alpha software and everything is subject to change. I’m not even
sure if blacknoise should exist at all or if the energy wouldn’t be better
spent improving whitenoise or other tools. Feedback and contributions are very
welcome though!</strong></p>
<h2>Using blacknoise with Django to serve static files</h2>
<p>Install blacknoise into your Python environment:</p>
<div class="chl"><pre><span></span><code><span class="go">pip install blacknoise</span>
</code></pre></div>
<p>Wrap your ASGI application with the <code>BlackNoise</code> app:</p>
<div class="chl"><pre><span></span><code><span class="kn">from</span> <span class="nn">blacknoise</span> <span class="kn">import</span> <span class="n">BlackNoise</span>
<span class="kn">from</span> <span class="nn">django.core.asgi</span> <span class="kn">import</span> <span class="n">get_asgi_application</span>
<span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
<span class="n">BASE_DIR</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span>
<span class="n">application</span> <span class="o">=</span> <span class="n">BlackNoise</span><span class="p">(</span><span class="n">get_asgi_application</span><span class="p">())</span>
<span class="n">application</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">BASE_DIR</span> <span class="o">/</span> <span class="s2">"static"</span><span class="p">,</span> <span class="s2">"/static"</span><span class="p">)</span>
</code></pre></div>
<p><code>BlackNoise</code> will automatically handle all paths below the prefixes added, and
either return the files or return 404 errors if files do not exist. The files
are added on server startup, which also means that <code>BlackNoise</code> only knows
about files which existed at that particular point in time.</p>
<h2>Improving performance</h2>
<p><code>BlackNoise</code> has worse performance than when using an optimized webserver such
as nginx and others. Sometimes it doesn’t matter much if the app is behind a
caching reverse proxy or behind a content delivery network anyway. To further
support this use case <code>BlackNoise</code> can be configured to serve media files with
far-future expiry headers and has support for serving compressed assets.</p>
<p>Compressing is possible by running:</p>
<div class="chl"><pre><span></span><code><span class="go">python -m blacknoise.compress static/</span>
</code></pre></div>
<p><code>BlackNoise</code> will try compress non-binary files using gzip or brotli (if the
<a href="ttps://pypi.org/project/Brotli/">Brotli</a> library is available), and will serve
the compressed version if the compression actually results in (significantly)
smaller files and if the client also supports it.</p>
<p>Far-future expiry headers can be enabled by passing the <code>immutable_file_test</code>
callable to the <code>BlackNoise</code> constructor:</p>
<div class="chl"><pre><span></span><code><span class="k">def</span> <span class="nf">immutable_file_test</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">True</span> <span class="c1"># Enable far-future expiry headers for all files</span>
<span class="n">application</span> <span class="o">=</span> <span class="n">BlackNoise</span><span class="p">(</span>
<span class="n">get_asgi_application</span><span class="p">(),</span>
<span class="n">immutable_file_test</span><span class="o">=</span><span class="n">immutable_file_test</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div>
<p>Maybe you want to add some other logic, for example check if the path contains
a hash based upon the contents of the static file. Such hashes can be added by
Django’s <code>ManifestStaticFilesStorage</code> or by appropriately configuring bundlers
such as <code>webpack</code> and others.</p>
<h2>License</h2>
<p><code>blacknoise</code> is distributed under the terms of the <a href="https://spdx.org/licenses/MIT.html">MIT</a> license.</p>Podcasts I like listening tohttps://406.ch/writing/podcasts-i-like-listening-to/2024-03-18T12:00:00Z2024-03-18T12:00:00Z<h1>Podcasts I like listening to</h1><p>I discovered listening to podcasts about one year ago. Previously, I never knew
why anyone would want to listen to people talk when they could listen to music
or nothing, but that has changed a bit.</p>
<p>So, here’s a list of podcasts I’m currently listening to on a regular basis.</p>
<h2><a href="https://www.techwontsave.us/">Tech Won’t Save Us</a></h2>
<p>I have recently stumbled over Tech Won’t Save Us, a Podcast which is critical
of the technological “progress” offered by Silicon Valley elites. It’s a great
antidote for the generative AI hype.</p>
<p>I still have some stupid hope that AGI will solve our problems because maybe
people will trust a computer more than scientists that something has to be done
about the combined crisis we’re facing as humans. Who knows. I do not want to
be too negative about it though, there are positive news if you’re looking in
the right places. I myself do not write about those bigger issues <a href="https://406.ch/writing/category-climate/">as
much</a> <a href="https://406.ch/writing/category-politik/">as I used
to</a>. I support those more important
issues elsewhere.</p>
<p>Generative AI is certainly not helping <em>me</em> in my work. I do not want a
computer to generate code which I have to review and maintain when I still
enjoy writing code myself. Maybe that will change at some point in the future.
When that happens I’ll probably retire and become a gardener or something.</p>
<h2><a href="ttps://www.nytimes.com/column/ezra-klein-podcast">The Ezra Klein Show</a></h2>
<p>This show seems to be a favorite of many people. I joined for the (relatively)
critical perspectives on AI and stayed for the insights into politics and into
the near east conflict. I think it’s great that people from different sides get
a voice on the show, even if Ezra doesn’t agree with them.</p>
<p>I sometimes wish the questions they ask on the show were a little bit more
critical.</p>
<h2><a href="https://www.nytimes.com/column/hard-fork">Hard Fork</a></h2>
<p>I like the perspectives and the bantering on this podcast. It’s a good way for
me to stay informed about what’s going on in AI/ML land, among other things.
It’s a lighthearted listen I often look forward to.</p>
<h2><a href="https://djangochat.com/">Django Chat</a></h2>
<p>I work with Django all the time and Django Chat is a great way to learn more
about Django, about the people involved and also about the surrounding
ecosystem. A wholehearted recommendation!</p>
<h2><a href="https://talkpython.fm/">Talk Python To Me</a></h2>
<p>Good interviews, interesting guests and topics. Just a great way to learn about
libraries, tools etc. which I cannot wait to use in my own projects.</p>
<h2><a href="https://podcast.datenschutzpartner.ch/">Datenschutzplaudereien</a></h2>
<p>Required listening for people working with customer data in Switzerland. Swiss
german knowledge required.</p>
<h2>Others</h2>
<p>A few other podcasts I’m listening to more or less regularly:</p>
<ul>
<li><a href="https://www.publiceye.ch/de/wir-muessen-reden-public-eye-spricht-klartext">Wir müssen reden. Public Eye spricht
Klartext</a>:
The podcast from public eye, an organization which uncovers human right
violations perpetrated by Swiss companies around the globe. I’m not yet sure
if I like the podcast, but I like and support the organization very much.</li>
<li><a href="https://hanselminutes.com/">Hanselminutes</a>: The more polifor the wide and
diverse array of guests.tical podcasts have displaced Hanselminutes in my
listening schedule, but I’d definitely recommend it to anyone who wants to
learn more about tech. Recommended because of the diverse array of guests,
among many other reasons.</li>
<li><a href="https://syntax.fm/">Syntax</a>: At times very interesting, at times not much
new for me. I still recommend it.</li>
<li><a href="https://www.weirdstudies.com/">Weird Studies</a>: Really weird. I liked the
discussion of Hellraiser and The Thing, and have listened to some of the
other episodes. Religiosity and spiritualism was always around me when I was
a child, and it’s interesting to revisit some of the ideas about reality from
a different angle after years and years of not engaging with these questions
at all. (Writing this down does sound a bit like a midlife crisis on the
horizon, but I don’t think that’s it. I have studied a little bit of
philosophy and ethics and have stayed interested in these topics ever since.)</li>
<li>I don’t think the german “Welt” is a good newspaper at all. I do like the
«Aha! History» and «Aha! Zehn Minuten Alltagswissen» podcasts because they
are short and often examine topics I don’t know much about.</li>
<li><a href="https://www.srf.ch/play/tv/sendung/sternstunde-philosophie?id=b7705a5d-4b68-4cb1-9404-03932cd8d569">Sternstude
Philosophie</a>:
It’s a bit slow and definitely more geared towards viewers than listeners,
but definitely interesting.</li>
</ul>Weeknotes (2024 week 11)https://406.ch/writing/weeknotes-2024-week-11/2024-03-16T12:00:00Z2024-03-16T12:00:00Z<h1>Weeknotes (2024 week 11)</h1><h2>Estimates</h2>
<p><a href="https://jacobian.org/2024/mar/11/breaking-down-tasks/">Jacob wrote an excellent post on breaking down tasks</a>. I did like the post a lot. Maybe I’ll write a longer reply later, but for now just this. <a href="https://hachyderm.io/@jacob@jacobian.org/112081126379604868">There definitely are good reasons for the pushback against estimation</a>, and it’s really not just that some people lack professionalism.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/django-cabinet/">django-cabinet 0.14.1</a>: Mini
release containing a Turkish translation. It’s always nice if software is
used.</li>
<li><a href="https://pypi.org/project/feincms3/">feincms3 4.6</a>: Fixed a bug where the
move form wouldn’t use a potentially overridden <code>ModelAdmin.get_queryset</code>
method.</li>
<li><a href="https://pypi.org/project/form-designer/">form-designer 0.24</a>: Updated the
package for django-recaptcha 4.0.</li>
<li><a href="https://pypi.org/project/html-sanitizer/">html-sanitizer 2.3.1</a>: Fixed an
edge case sanitization bug (luckily without security implications).</li>
<li><a href="https://pypi.org/project/django-content-editor/">django-content-editor
6.4.2</a>:
django-content-editor now again supports transitioning plugin fieldsets when
opening <em>and</em> closing thanks to CSS grid’s ability to animate the maximum
height of an element. Also, the initialization in 6.4 was badly broken.</li>
<li><a href="https://pypi.org/project/django-prose-editor/">django-prose-editor 0.2</a>: <a href="https://406.ch/writing/django-prose-editor-prose-editing-component-for-the-django-admin/">See the announcement blog post from Wednesday</a>.</li>
</ul>Eleven months of static site generationhttps://406.ch/writing/eleven-months-of-static-site-generation/2024-03-15T12:00:00Z2024-03-15T12:00:00Z<h1>Eleven months of static site generation</h1><p>Almost eleven months have gone by since I <a href="https://406.ch/writing/the-insides-of-my-static-site-generator/">last wrote</a> about the <a href="https://github.com/matthiask/406.ch/blob/main/generate.py">static site generator script</a> I’m using for this website.</p>
<h2>What has changed in the meantime?</h2>
<ul>
<li>The build script is 40 lines shorter.</li>
<li>Code snippets are now highlighted using pygments.</li>
<li>The script now not only reports how many files have been written but also the
categories and their post count.</li>
<li>The script now supports a folder with static assets so that images etc. do
not have to be hotlinked from elsewhere. Hotlinking images hosted in GitHub
issues doesn’t work anymore it seems.</li>
<li>The execution time isn’t reported anymore.</li>
<li>The script now supports scheduled publishing, mainly by leveraging GitHub
action’s scheduling support.</li>
</ul>
<h2>Has it been worth it?</h2>
<p>The general advice seems to not write your own static site generator because
you’ll spend time tweaking the generator instead of actually writing posts.
This definitely doesn’t have to be the case. Sometimes it’s good to hack on a
small projects just for the joy of it. I certainly enjoy the fact that my code
(arguably 🙃) does something useful each time I publish a new post.</p>django-prose-editor – Prose-editing component for the Django adminhttps://406.ch/writing/django-prose-editor-prose-editing-component-for-the-django-admin/2024-03-13T12:00:00Z2024-03-13T12:00:00Z<h1>django-prose-editor – Prose-editing component for the Django admin</h1><p>During the last few days I have been working on a prose-editing component for
the Django admin which will replace the basically dead
<a href="https://406.ch/writing/django-ckeditor/">django-ckeditor</a> in all of my
projects. It is based on <a href="https://prosemirror.net/">ProseMirror</a>, in my opinion
the greatest toolkit for building prose editors for the web.</p>
<p>Here’s a screenshot:</p>
<p><img alt="django-prose-editor screenshot" src="https://406.ch/assets/20240313-prose-editor.png" /></p>
<p>django-prose-editor is in active development; it’s <a href="https://pypi.org/project/django-prose-editor/">available on
PyPI</a> and is developed in the
open <a href="https://github.com/matthiask/django-prose-editor/">here on GitHub</a>. The
version at the time of writing is 0.2, and it’s not yet used in production
environments, only in staging/preview environments. That will soon change
though.</p>
<h2>Researching alternatives to django-ckeditor</h2>
<p>I have spent a lot of time evaluating alternatives. All of them are great
choices, and I don’t want to bash any of them. But, I didn’t feel good betting on any of the choices from the <a href="https://djangopackages.org/grids/g/wysiwyg/">WYSIWYG editors grid on Django Packages</a>.</p>
<p>A few packages have potential licensing issues. CKEditor 5’s change to the GPL
is what basically killed django-ckeditor. The upcoming TinyMCE 7 version will
also change to the GPL according to the <a href="https://github.com/tinymce/tinymce/">TinyMCE GitHub
repository</a>. Froala only has a free trial.
The <a href="https://github.com/summernote/django-summernote/">django-summernote app</a>
doesn’t have a dedicated maintainer, so I wouldn’t bet on it.
<a href="https://github.com/withlogicco/django-prose">django-prose</a> uses Trix; there
are various reasons why I didn’t want to bet on Trix, among them my personal
experience that it sometimes gets into a buggy state where only a full page
reload unfreezes the editor.</p>
<p>Other packages are basically <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a>
editors. I like Markdown and use it for my blog. I don’t think Markdown is a
good choice for a CMS which is used by people of many different skill levels. I
don’t want to teach people to use the Markdown link syntax or the heading
syntax even if the editor helps out a bit.</p>
<p>Some of the remaining choices are using JavaScript widgets which haven’t been
updated for a really long time or they are really code editors, not WYSIWYG
editors, also a no go.</p>
<h2>Deciding on going with ProseMirror</h2>
<p>I have worked with <a href="https://prosemirror.net/">ProseMirror</a> on and off since
October 2015, soon after the crowdfunding ended. It is used in a project where
people can write their own book with a standardized pipeline and process, where
the technical side of the project is implemented using Django, ProseMirror and
LaTeX. A hacked
<a href="https://github.com/ProseMirror/prosemirror-example-setup/">prosemirror-example-setup</a>
still is good enough for this project.</p>
<p>The ProseMirror deep dive came much later, only a few years back, when
implementing an editor with more custom functionality such as annotations,
different ways of marking up text and even interactive elements within the
text, for example to use it as a cloze in teaching materials.</p>
<p>The learning curve is steep. I haven’t worked with another library which was so
hard to get started with. It is my conviction that the reason for this is that
rich-text editing is actually a hard problem. The ProseMirror architecture and
implementation definitely makes sense when it finally clicks.</p>
<p>As additional bonuses the ProseMirror community is nice and ProseMirror is used
in a few large software projects which make me believe that the software has a
non-zero probability of being maintained in the long run.</p>
<h2>Current plans</h2>
<p>The only thing which is configurable right now is the server-side sanitizing of
submitted HTML content. I plan on allowing some configurability, for example to
disable links when the content will only be used inside a card teaser which
itself is already a link. Too much configurability isn’t a good thing though in
my mind, so I’ll probably be slow to add features and rather keep complexity
low.</p>
<p>I’m definitely interested to hear from people who want to use the package but
cannot do so right now or who would want some additional features. Issues and
pull requests are welcome!</p>My 2024 Development Setuphttps://406.ch/writing/my-2024-development-setup/2024-02-28T12:00:00Z2024-02-28T12:00:00Z<h1>My 2024 Development Setup</h1>
<p>I have been inspired by <a href="https://micro.webology.dev/2024/02/18/my-development-setup.html">Jeff Triplett’s post</a> to write this down. The main value – if there’s value at all in this post – lies in my ability to revisit it later and see if anything changed.</p>
<h2>Desk</h2>
<p>I’m using a standing desk since 2015 or 2016. I’m actually doing most of my work standing; when I’m tired or maybe after a sports session I sit down, but when I do that I mostly choose the sofa or a different table and get back to the standing desk as soon as possible. I have started using the standing desk when I was in therapy for severe back pain but I have come to like it a lot since then. I do have a chair but I don’t really use it all that often.</p>
<p>The greatest thing about a standing desk is that when I go to an event which involves a lot of standing around (or dancing!) I don’t get tired as fast as before :-)</p>
<h2>Hardware</h2>
<p>My main machines are two Thinkpad notebooks, a Lenovo X1 carbon and a Lenovo X1 nano. I bought the latter because my previous notebook broke during a warranty repair by a technician and I really really had to have a notebook for the coming days and I already had ordered the former, but it wasn’t to be delivered for several weeks. That was annoying. The good thing about it is that I normally do not have to carry around anything when going to the office, since I leave one of them in the office most of the time. I’m using a 27” WQHD monitor when I work. I have used a 32” 4k monitor for some time, but I have noticed that I’m starting to spend time searching for windows. I don’t like curved or widescreens monitors at all (I have tried them). I thought I’d never reach the point when I would reduce the available screen size voluntarily, but here I am.</p>
<p>I also have a desktop machine at home, but I mostly use it for gaming, and only a little development on the side. It has a 11th gen i7, 32 GB ram and a GeForce RTX 30 series graphics card.</p>
<p>This also means that I’m switching computers all the time, and basically everything has to be synced to the cloud all the time. I’m using git since the spring of 2006 and push everything all the time anyway. And when I’m not, I’m mostly using web-based software. This means that I mostly don’t care about backing up my data, I let the cloud companies and the NSA do it.</p>
<h2>Software</h2>
<p>After many years on macOS and a few years on Windows with WSL I am back to using Linux without a VM. I am using Fedora because I actually like the unmodified GNOME 3, and because I prefer flatpaks to snaps. RPM/DNF are alright, but I’m still more accustomed to DEB/APT.</p>
<p>I have left macOS because of the touch bar and the terrible keyboard, and Windows because I don’t want ads everywhere on <em>my</em> computer.</p>
<h2>Development</h2>
<p>I use a terminal emulator (gnome-terminal on Linux, <a href="https://alacritty.org/">alacritty</a> on Windows/WSL) to run <a href="https://github.com/tmux/tmux/">tmux</a>, and start everything I need directly in there.</p>
<p>I have tried escaping vim many times but have always failed. (No, not <em>exiting</em> vim, thanks for your help.) The various VIM modes in popular editors such as VisualStudio Code just do not cut it. I’m currently editing code in <a href="https://neovim.io/">neovim</a> with a few plugins. The only really important plugin is <a href="https://github.com/ctrlpvim/ctrlp.vim">ctrlp.vim</a>. Ctrl-P with fuzzy matching is a great way to open files. I like vi, and I tell people that I use it when I’m asked but I do not suggest to people that they should use it as well. The learning curve isn’t worth it except maybe if you do a lot of system administration, I don’t know.</p>
<p>I have a VisualStudio Code installation laying around for using Live Share. I think it’s a great way for helping others solve a particular problem in a project they are working on, much better than screen sharing and probably even better than sitting at a table together, just because having two keyboards and cursors is so nice.</p>
<p>I mostly only use virtualenvs created using <code>python3 -m venv venv</code> for local development, no containers or anything. I definitely see the value of containers for deployment but I haven’t yet gotten around to setting things up for local development. This means that my machine always (also) runs a PostgreSQL and a redis server and maybe some other tools. Python 3 upgrades are mostly painless. NodeJS upgrades have sometimes been painful, but I avoid most of the pain by upgrading relatively late.</p>Weeknotes (2024 week 07)https://406.ch/writing/weeknotes-2024-week-07/2024-02-16T12:00:00Z2024-02-16T12:00:00Z<h1>Weeknotes (2024 week 07)</h1><p>This is a short weeknotes entry which mainly contains a large list of releases. The reason for the large list is that I haven’t published a weeknotes entry in weeks.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/form-designer/">form-designer 0.23</a>: Only small changes, mainly updated the package for current Django and Python versions.</li>
<li><a href="https://pypi.org/project/feincms3-cookiecontrol/">feincms3-cookiecontrol 1.4.6</a>: A minor change: Swallow exceptions which happen during startup when clobbering the scripts data fails. As an aside: I find it funny that I have discovered the <code>.f3cc</code> class in some cookie banner blocklists. It feels good to be recognized even if this maybe isn’t the nicest way, but it works for me since I actually do not like cookie banners either. At least feincms3-cookiecontrol doesn’t inject anything without users’ consent, and doesn’t require a third party service to run.</li>
<li><a href="https://pypi.org/project/django_simple_redirects/">django-simple-redirects 2.2.0</a>: Minor release which adds a search field to the admin changelist. django-simple-redirects is a repackaged version of <code>django.contrib.redirects</code> without the <code>django.contrib.sites</code> dependency.</li>
<li><a href="https://pypi.org/project/speckenv/">speckenv 6.2</a>: <code>django_cache_url</code> now supports parsing redis configuration for a leader-replica redis installation with a read-write leader host and read-only replica hosts. I use the same configuration format as <a href="https://github.com/epicserve/django-cache-url">django-cache-url</a> does.</li>
<li><a href="https://pypi.org/project/django-debug-toolbar/">django-debug-toolbar 4.3</a>: I haven’t done much here, just some reviewing here and there. I enjoy the Djangonaut Space contributions a lot.</li>
<li><a href="https://pypi.org/project/django-cabinet/">django-cabinet 0.14</a>: I have removed the constraint which enforces unique names for subfolders. Enforcing the uniqueness does make sense, but it also makes bulk-updating the media library using serialized data more painful than it should be. It’s a clear case of worse is better for me. If people want to confuse themselves I’m not going to stop them (anymore, in this case) but it makes the rest of the code so much easier to write that it’s not even funny.</li>
<li><a href="https://pypi.org/project/html-sanitizer/">html-sanitizer 2.3</a>: This release contains a nice contribution which removes some whitespace which has been added by the sanitizer when merging adjacent tags of the same type, e.g. <code><strong>abc</strong><strong>def</strong></code>.</li>
<li><a href="https://pypi.org/project/django-ckeditor/">django-ckeditor 6.7.1</a>: See above.</li>
<li><a href="https://pypi.org/project/django-json-schema-editor/">django-json-schema-editor 0.0.11</a>: Fixed a crash which happened when not providing the optional (!) configuration. Shit happens. I should really have a test suite for this package.</li>
<li><a href="https://pypi.org/project/feincms3/">feincms3 4.5.2</a>: Disables the CKEditor version check.</li>
<li><a href="https://pypi.org/project/django-content-editor/">django-content-editor 6.4</a>: The first release since December 2022! Very stable software. The editor now restores the collapsed state of inlines and the scroll position when using “Save and continue editing”. This is especially useful if editing an object with many content blocks.</li>
</ul>django-ckeditorhttps://406.ch/writing/django-ckeditor/2024-02-14T12:00:00Z2024-02-14T12:00:00Z<h1>django-ckeditor</h1>
<p>It has finally happened. The open source version of CKEditor 4 does not contain fixes for known problems, see <a href="https://ckeditor.com/cke4/release/CKEditor-4.24.0-LTS">the CKEditor 4.24.0 LTS announcement</a>.</p>
<p>I totally get why the CKEditor developers did this and can only thank them for all the work that went into the editor.</p>
<p>I wish I didn’t have to do the migration work to move basically everything to a different editor. The CKEditor 4 LTS version is only expected to be supported until the end of 2026 and I have a few projects which will be around far longer than this (or at least I hope so). Therefore, buying the LTS package would only delay the inevitable. CKEditor 5 is a completely different editor and uses the GPL license, so that’s not really an option either. TinyMCE is well known and I have been using it much earlier in my career, but reimplementing plugins isn’t fun to do.</p>
<p>I would prefer moving everything to ProseMirror or some other structured editor, but we have so much legacy content contained in HTML blobs which do not use any schema at all that this isn’t workable unfortunately.</p>
<p>Stay tuned for updates – they will come since I unfortunately cannot just ignore this problem.</p>
<p>Which brings me to <a href="https://github.com/django-ckeditor/django-ckeditor">django-ckeditor</a>. CKEditor shows a very annoying popup to users when it detects a newer version with security fixes, see <a href="https://github.com/django-ckeditor/django-ckeditor/issues/761">the GitHub issue</a>. It’s not a bad idea but users cannot do much about it, so I opted to disable the version check (and warning) in django-ckeditor and replaced it with a Django system check which annoys developers instead.</p>
<p>The release containing these changes is available as <a href="https://pypi.org/project/django-ckeditor/">django-ckeditor 6.7.1</a> on PyPI.</p>Weeknotes (2024 week 03)https://406.ch/writing/weeknotes-2024-week-03/2024-01-17T12:00:00Z2024-01-17T12:00:00Z<h1>Weeknotes (2024 week 03)</h1><h2>Djangonaut Space</h2>
<p>I wish all participants a good time and much success. I do not have anything to do with it really but I enjoy the idea a lot and maybe there will be a pull request or two to review.</p>
<h2>Kubernetes</h2>
<p>After years and years of hosting all sites on VPS I have finally reached the point where the old setup is more annoying to work with than switching to a new one. I have searched long for a solution which wasn’t as limited as some PaaS and as complex as going full Kubernetes, and where I can still delegate the responsibility of actually keeping things up and running to other people. In the end I have now accepted that such a thing doesn’t exist; either you have the limitations of a ready made solution, the limitation of having to open many many support tickets or the problem of having to learn Kubernetes (or something similar) with its extremely steep learning curve.</p>
<p>After spending days with it I’m slowly getting to the point where setting up local development environments and deploying changes is fun again. I’m using the GitOps paradigm; while I’m still building and uploading Docker (podman) images from the local development environment everything else is automated and goes through a Git based process. That’s much nicer than clicking around in some interface or copy pasting obscure commands into the console.</p>
<p>The biggest problem I encountered was (perhaps unsurprisingly) managing secrets as a team. It seems to me that while <a href="https://github.com/bitnami-labs/sealed-secrets/">sealed secrets</a> work great as an individual developer they don’t really offer straightforward solutions to avoid different people overwriting and resetting each others secrets when updating them. I’m a happy user of the external secrets operator and using some cloud service to actually store those secrets.</p>
<p>I have started using <a href="https://github.com/emmett-framework/granian/">granian</a> in production. I like the idea of a Rust-based ASGI/WSGI server. Nothing mission critical yet. My idea is to build confidence in the software stack.</p>
<h2>Compulsory social measures</h2>
<p>The more I learn about how Switzerland treats its citizens the more I wonder about the ways in which humans can mistreat other humans in a so called civilized and peaceful society.</p>
<p>It’s not exactly a new topic for me, but working on platforms which help remember and which help introducing people to the history certainly causes a heightened awareness for issues such as these.</p>
<p><a href="https://www.bj.admin.ch/bj/en/home/gesellschaft/fszm.html">More on this</a>.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/feincms3-sites/">feincms3-sites 0.20.2</a>: It
previously wasn’t possible to filter the list of sites to only show those
sites which do <em>not</em> have a default language. This has been fixed.</li>
<li><a href="https://pypi.org/project/django-json-schema-editor/">django-json-schema-editor 0.0.10</a>: It’s now actually possible to use the JSON editor outside inlines! That was a fun bug… not. Apart from the mentioned bug this new release mostly contains fixes to the styles. We’re slowly getting there.</li>
<li><a href="https://pypi.org/project/django-mptt/">django-mptt 0.16</a>: I didn’t do anything except for the changelog and the release. That’s alright. The release contains a few minor fixes.</li>
</ul>Weeknotes (2024 week 01)https://406.ch/writing/weeknotes-2024-week-01/2024-01-03T12:00:00Z2024-01-03T12:00:00Z<h1>Weeknotes (2024 week 01)</h1><p>First weeknotes post for 2024! Happy new year!</p>
<h2>Looking back on 2023</h2>
<h3>Writing</h3>
<p>I have published almost 40 posts last year. That’s almost as many posts as I published in the time period from 2014 to 2023. <a href="https://jacobian.org/2021/mar/9/coworking-to-write-more/">Coworking to write more</a> does work.</p>
<p>I already had a quite active blog from 2005 to 2008 with a few posts after that; everything before 2014 was in german and mainly concerned with green politics and climate change. I’m still very interested in these topics but I don’t feel as if I have much to add to the conversation, even though it’s the more important issue.</p>
<h3>Open Source</h3>
<p>Not much changed here. I enjoy basically everything I do in open source land, and co-maintaining the Django Debug Toolbar with Tim is a joy.</p>
<p>I still wish that some of my projects had more impact in Django land, especially those who augment the Django administration interface to be a lightweight CMS which requires very little maintenance and work in the long run. I think it’s great that one of the core components, <a href="https://pypi.org/project/django-content-editor/#history">django-content-editor</a>, hasn’t required a release in more than one year. It doesn’t have to be expanded because it just works. It would be great if there was a good way to <a href="https://406.ch/writing/managing-complexity-and-technical-debt-by-releasing-open-source-software/">distinguish</a> between software which basically doesn’t require any updates and software which is abandoned.</p>
<p>The only project which doesn’t bring me much joy is django-mptt. Most people accept that there are no guarantees, but some people are just rude in the way in which they expect others to do the work. The first reaction was to return the blow and I’m glad that I didn’t give in to the temptation to do that. It’s basically never worth it to do that in writing.</p>
<h3>Family</h3>
<p>We had a good 2023 together and I’m very much looking forward to a just as good 2024.</p>
<h2>Plans for 2024</h2>
<p>I have bought a ticket for the <a href="https://2024.djangocon.eu/">DjangoCon Europe 2024</a> in Spain and I’m very much looking forward to that.</p>
<p>Maybe we’ll visit a music festival again this summer. After listening to a lot of metal music in the last ten or more years I rediscovered the dark side of D’n’B. Good times.</p>
<h2>Advent of Code</h2>
<p>I have finished the Advent of Code with some help from the Subreddit. Almost all of the puzzles were fun to think about. I didn’t have fun solving each and everyone of them, especially not the second part of a few of the later days. I feel good checking out solutions from other people in the subreddit, and maybe adding newly gained ideas to my code or even running someone else’s code 1:1 on my data after studying the algorithms used and hopefully learning something.</p>
<p>I had a good time and enjoyed shutting down the computer after that.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/FeinCMS/">FeinCMS 23.12</a>: A few minor changes to
the thumbnailing code. It’s always nice to hear from people who are still
using this project.</li>
</ul>Weeknotes (2023 week 50)https://406.ch/writing/weeknotes-2023-week-50/2023-12-15T12:00:00Z2023-12-15T12:00:00Z<h1>Weeknotes (2023 week 50)</h1><h2>django-imagefield</h2>
<p>The path building scheme used by <a href="https://pypi.org/project/django-imagefield/">django-imagefield</a> has proven problematic: It’s too likely that processed images will have the same path.</p>
<p>I have changed the strategy used for generating paths to use more data from the
source; it’s now possible (and recommended!) to set <code>IMAGEFIELD_BIN_DEPTH</code> to
a value greater than 1; 2 or 3 should be sufficient. The default value is 1
which corresponds to the old default so that the change won’t be backwards
incompatible. However, you’ll always get a deprecation warning if you don’t set
a bigger value yourself. The default will probably change in the future.</p>
<h2>Advent of Code</h2>
<p>I have always felt a bit as an imposter because I do not have any formal CS
education; not so much in the last few years but certainly earlier in my
career. I have enjoyed participating in the <a href="https://adventofcode.com/">Advent of Code
2022</a> a lot and I have definitely learned to know
when to use and how to use a few algorithms I didn’t even know before. I’m
again working through the puzzles in my own pace and have managed to solve
almost all of them up to today this year. There still are some puzzles where I
don’t even know how to start the second part 😅.</p>
<h2>Hosting</h2>
<p>We’re still hosting most sites on virtualized servers, without any containers
or any of the new stuff. I’m finally reaching the point where the downsides of
this approach start to drag new projects down and the workarounds start looking
worse than maybe switching to containers or even Kubernetes. Wish me luck, I’m
more confused than I’ve been in years.</p>
<h2>Health</h2>
<p>To absolutely nobody’s surprise the family and myself have continued to be sick
in the last two weeks. Nothing really bad happened, so we’re still lucky.</p>
<p>There’s unfortunately no way to solve a societal problem individually, so that
will probably continue to be our life for now.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/django-imagefield/">django-imagefield 0.18</a>: See
above.</li>
<li><a href="https://pypi.org/project/feincms3-sites/">feincms3-sites 0.20.1</a>: Added
additional validation (cleaning) checks. Showing error messages is
preferrable to crashing with <code>IntegrityError</code> exceptions after all.</li>
<li><a href="https://pypi.org/project/django-js-asset/">django-js-asset 2.2.0</a>: Hatchling
seems to dislike it if the project name and the Python module name do not
match. I actually like <code>django-js-asset</code>’s Python module to be <code>js_asset</code>
but I’m beginning to rethink this decision.</li>
<li><a href="https://pypi.org/project/django-json-schema-editor/">django-json-schema-editor 0.0.4</a>: See <a href="https://406.ch/writing/django-json-schema-editor/">the post from this week</a>.</li>
</ul>django-json-schema-editorhttps://406.ch/writing/django-json-schema-editor/2023-12-13T12:00:00Z2023-12-13T12:00:00Z<h1>django-json-schema-editor</h1><p>I have extracted a JSON editing component based on
<a href="https://www.npmjs.com/package/@json-editor/json-editor">@json-editor/json-editor</a>
from a client’s project and released it as open source. It isn’t the first JSON editing component by far but I like it a lot for the following reasons:</p>
<ul>
<li>It works really well.</li>
<li>It supports editing arrays of objects using a tabular presentation. Tabular
isn’t always better, but stacked definitely isn’t always better as well.</li>
<li>The data structure is defined as <a href="https://json-schema.org/">JSON schema</a>,the
data which is being entered is validated on the server using the
<a href="https://pypi.org/project/fastjsonschema/">fastjsonschema</a> library. Having a
schema and schema-based validation fixes most problems I have with less
structured data than when using only Django model fields (without JSON).</li>
</ul>
<p>Here’s a screenshot of the editing component used as a <a href="https://django-content-editor.readthedocs.io/">django-content-editor</a> plugin:</p>
<p><img alt="django-json-schema-editor screenshot" src="/assets/20231313-json-schema-editor.png" /></p>
<p>Within the first few days of having released the package it has already proven
useful in several other projects. A pleasant (but not totally unexpected)
surprise.</p>
<h2>Links:</h2>
<ul>
<li><a href="https://pypi.org/project/django-json-schema-editor/">PyPI</a></li>
<li><a href="https://github.com/matthiask/django-json-schema-editor">GitHub</a></li>
</ul>Weeknotes (2023 week 48)https://406.ch/writing/weeknotes-2023-week-48/2023-11-30T12:00:00Z2023-11-30T12:00:00Z<h1>Weeknotes (2023 week 48)</h1><p>A few weeks have passed since the last update. The whole family was repeatedly
sick with different viruses etc… I hope that the worst is over now. Who
knows.</p>
<h2>12-factor Django storage configuration</h2>
<p>I should maybe write a longer and separate post about this, but <a href="https://pypi.org/project/speckenv/">speckenv</a> has gained support for the Django <code>STORAGES</code> setting. No documentation yet, but it supports two storage backends for now, the file system storage and <a href="https://github.com/etianen/django-s3-storage/">django-s3-storage</a>, my go-to library for S3-compatible services.</p>
<p>Using it looks something like this:</p>
<div class="chl"><pre><span></span><code><span class="kn">from</span> <span class="nn">speckenv</span> <span class="kn">import</span> <span class="n">env</span>
<span class="kn">from</span> <span class="nn">speckenv_django</span> <span class="kn">import</span> <span class="n">django_storage_url</span>
<span class="n">STORAGES</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"default"</span><span class="p">:</span> <span class="n">django_storage_url</span><span class="p">(</span>
<span class="n">env</span><span class="p">(</span>
<span class="s2">"STORAGE_URL"</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="s2">"file:./media/?base_url=/media/"</span><span class="p">,</span>
<span class="n">warn</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">),</span>
<span class="n">base_dir</span><span class="o">=</span><span class="n">BASE_DIR</span><span class="p">,</span>
<span class="p">),</span>
<span class="s2">"staticfiles"</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">"BACKEND"</span><span class="p">:</span> <span class="s2">"django.contrib.staticfiles.storage.ManifestStaticFilesStorage"</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
</code></pre></div>
<p>Then, if you want to use S3 you can put something like this in your <code>.env</code> file:</p>
<div class="chl"><pre><span></span><code><span class="n">STORAGE_URL</span><span class="o">=</span><span class="nl">s3</span><span class="p">:</span><span class="o">//</span><span class="n">access</span><span class="o">-</span><span class="k">key</span><span class="err">:</span><span class="n">secret</span><span class="nv">@bucket</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="n">eu</span><span class="o">-</span><span class="n">central</span><span class="o">-</span><span class="mf">1.</span><span class="n">amazonaws</span><span class="p">.</span><span class="n">com</span><span class="o">/</span><span class="n">media</span><span class="o">/</span>
</code></pre></div>
<p>Or maybe something like this, if you want to serve media files without authentication:</p>
<div class="chl"><pre><span></span><code><span class="n">STORAGE_URL</span><span class="o">=</span><span class="nl">s3</span><span class="p">:</span><span class="o">//</span><span class="n">access</span><span class="o">-</span><span class="k">key</span><span class="err">:</span><span class="n">secret</span><span class="nv">@bucket</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="n">s3</span><span class="p">.</span><span class="n">eu</span><span class="o">-</span><span class="n">central</span><span class="o">-</span><span class="mf">1.</span><span class="n">amazonaws</span><span class="p">.</span><span class="n">com</span><span class="o">/</span><span class="n">media</span><span class="o">/</span><span class="vm">?</span><span class="n">aws_s3_public_auth</span><span class="o">=</span><span class="k">False</span><span class="o">&</span><span class="n">aws_s3_max_age_seconds</span><span class="o">=</span><span class="mi">31536000</span>
</code></pre></div>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/speckenv/">speckenv 6.1.1</a>: See above.</li>
<li><a href="https://pypi.org/project/feincms3-meta/">feincms3-meta 4.6</a>: York has contributed support for emitting structured data records. Looks nice. No documentation yet.</li>
<li><a href="https://pypi.org/project/django-tree-queries/">django-tree-queries 0.16.1</a>: <code>.values()</code> and <code>.values_list()</code> queries are now handled better and more consistently than before.</li>
</ul>Weeknotes (2023 week 44)https://406.ch/writing/weeknotes-2023-week-44/2023-11-02T12:00:00Z2023-11-02T12:00:00Z<h1>Weeknotes (2023 week 44)</h1><h2>Unmaintained but maintained packages</h2>
<p>There’s a discussion going on in the <a href="https://github.com/django-mptt/django-mptt/issues/833">django-mptt issue tracker</a> about the maintenance state of django-mptt. <a href="https://github.com/django-mptt/django-mptt/commit/6f6c1c485f3adc1d579f8d22e0279ce1d52334f6">I have marked the project as unmaintained in March 2021</a> and haven’t regretted this decision at all. I haven’t had to fix <a href="https://github.com/django-mptt/django-mptt/labels/Broken%20Tree">inconsistencies in the tree structure</a> once since switching to <a href="https://406.ch/writing/django-tree-queries/">django-tree-queries</a>. And if that wasn’t enough, I get little but only warm and thankful feedback for the latter, so that’s extra nice.</p>
<p>Despite marking django-mptt as unmaintained I seem to be doing a little bit of maintenance still. I’m still using it in old paid projects and so the things I do to make the package work for me is paid work. I’m not personally invested in the package anymore, so I’m able to tell people that there are absolutely no guarantees about the maintenance, and that feels good.</p>
<h2>Read the Docs</h2>
<p>I do understand why the <code>.readthedocs.yaml</code> file is now necessary. I wish that I wouldn’t have to do all the busywork of adding one to projects. I have just resubscribed to the Read the Docs Gold Membership which probably has expired at some point in the past. Read the Docs is excellent and everybody who can should support them.</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/feincms3/">feincms3 4.5</a>, <a href="https://pypi.org/project/feincms3-sites/">feincms3-sites 0.20</a> and <a href="https://github.com/feincms/feincms3-language-sites">feincms3-language-sites 0.3</a>: Fixed the check which only allows adding an application through the CMS to the page tree (yes, that’s right) once; feincms3 worked fine, feincms3-language-sites by accident but feincms3-sites didn’t.</li>
<li><a href="https://pypi.org/project/towel/">towel 0.31</a>: Towel is one of my oldest packages which is still being used in real-world projects. Towel is a tool for building CRUD-type applications and is designed to keep you DRY while doing that. The project has been heavily inspired by a Django-based agency software I built many years back. The package even has <a href="https://towel.readthedocs.io/en/latest/">docs</a>! I’m still quite proud of the mostly transparent support for multitenancy, but apart from that I haven’t used it in many new projects.</li>
</ul>Weeknotes (2023 week 43)https://406.ch/writing/weeknotes-2023-week-43/2023-10-27T12:00:00Z2023-10-27T12:00:00Z<h1>Weeknotes (2023 week 43)</h1><h2>Switching to ruff format</h2>
<p><a href="https://github.com/astral-sh/ruff/releases/tag/v0.1.2">ruff v0.1.2</a> includes
the beta version of the ruff formatter. It formats the code almost the same way
as black, but much much faster. I have started switching projects I’m working
on and the experience has been nothing but great.</p>
<h2>100% test coverage</h2>
<p>I have again fixed a bad bug in code which had 100% test coverage. It’s not
news that 100% test coverage isn’t a recipe for bug-free code, and it’s
probably not even worth optimizing for that metric in code. It’s nothing more
but a proxy for what you should really measure. Nevertheless it certainly does
feel good to achieve high coverage values!</p>
<h2>Releases</h2>
<p>No releases at all.</p>Customize the Django admin to differentiate environmentshttps://406.ch/writing/customize-the-django-admin-to-differentiate-environments/2023-10-19T12:00:00Z2023-10-19T12:00:00Z<h1>Customize the Django admin to differentiate environments</h1>
<p><img alt="Four different themes" src="https://user-images.githubusercontent.com/2627/276531977-6787c55e-4e8c-448c-8ed4-c71cd98c9750.png" /></p>
<p>We often have the same website running in different configurations:</p>
<ul>
<li>Once as a production site.</li>
<li>Once as a place where editors update and preview the content. The content is later automatically (and maybe <a href="https://406.ch/writing/moving-data-including-deletions-between-the-same-django-app-running-in-different-environments/">partially</a>) transferred from this environment to the production environment.</li>
<li>Once as a stage environment to stabilize the code.</li>
<li>And maybe additional environments for local development.</li>
</ul>
<p>The Django admin panel mainly uses CSS variables for styling since <a href="https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#admin-theming">theming support was introduced in Django 3.2</a> (by yours truly with a lot of help from others). This makes it simple and fun to customize the colors of all interface elements in a straightforward way without having to write loads of CSS.</p>
<p>If you have a <code>ENVIRONMENT</code> context variable available (as we do) you could add
the following template as <code>admin/base.html</code> to your project, giving you a red
color scheme for the production environment (to discourage people from updating
content) and a nice scheme for the <code>preproduction</code> environment which clearly
deviates from the standard color scheme used everywhere else:</p>
<div class="chl"><pre><span></span><code><span class="cp">{%</span> <span class="k">block</span> <span class="nv">extrahead</span> <span class="cp">%}</span>
<span class="cp">{{</span> <span class="nb">block</span><span class="nv">.super</span> <span class="cp">}}</span>
<span class="p"><</span><span class="nt">style</span><span class="p">></span>
<span class="p">#</span><span class="nn">site-name</span><span class="p">::</span><span class="nd">after</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s2">" (</span><span class="cp">{{</span> <span class="nv">ENVIRONMENT</span> <span class="cp">}}</span><span class="s2">)"</span><span class="p">;</span>
<span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mi">60</span><span class="kt">%</span><span class="p">;</span>
<span class="p">}</span>
<span class="w"> </span><span class="p"></</span><span class="nt">style</span><span class="p">></span>
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">ENVIRONMENT</span> <span class="o">==</span> <span class="s1">'production'</span> <span class="cp">%}</span>
<span class="p"><</span><span class="nt">style</span><span class="p">></span>
<span class="p">:</span><span class="nd">root</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nv">--primary</span><span class="p">:</span><span class="w"> </span><span class="mh">#aa0000</span><span class="p">;</span>
<span class="w"> </span><span class="nv">--secondary</span><span class="p">:</span><span class="w"> </span><span class="mh">#810000</span><span class="p">;</span>
<span class="w"> </span><span class="nv">--accent</span><span class="p">:</span><span class="w"> </span><span class="kc">yellow</span><span class="p">;</span>
<span class="p">}</span>
<span class="w"> </span><span class="p"></</span><span class="nt">style</span><span class="p">></span>
<span class="cp">{%</span> <span class="k">elif</span> <span class="nv">ENVIRONMENT</span> <span class="o">==</span> <span class="s1">'preproduction'</span> <span class="cp">%}</span>
<span class="p"><</span><span class="nt">style</span><span class="p">></span>
<span class="p">:</span><span class="nd">root</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nv">--primary</span><span class="p">:</span><span class="w"> </span><span class="mh">#30b181</span><span class="p">;</span>
<span class="w"> </span><span class="nv">--secondary</span><span class="p">:</span><span class="w"> </span><span class="mh">#1f7957</span><span class="p">;</span>
<span class="w"> </span><span class="nv">--accent</span><span class="p">:</span><span class="w"> </span><span class="mh">#cdffea</span><span class="p">;</span>
<span class="p">}</span>
<span class="w"> </span><span class="p"></</span><span class="nt">style</span><span class="p">></span>
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
<span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
</code></pre></div>Weeknotes (2023 week 42)https://406.ch/writing/weeknotes-2023-week-42/2023-10-18T12:00:00Z2023-10-18T12:00:00Z<h1>Weeknotes (2023 week 42)</h1><h2>Vacation in Italy</h2>
<p>We have spent a wonderful family week in Italy. The voyage by train was very
comfortable and we had a great time there. I have lived close to lakes all my
life but the sea is always something else. Now I enjoy the cold temperatures of
fall.</p>
<h2>Going back (forward) to GitJournal</h2>
<p>I have tried several note taking apps but I’m now back using
<a href="https://gitjournal.io/">GitJournal</a> with a Git repository filled with Markdown
notes. It works well enough. I just wish that there was a way to make notes
more distinguishable and I wish that the editor was more forgiving when
encountering badly formatted checklists.</p>
<h2>Analog blogging</h2>
<p>I have long wanted to write about <a href="https://406.ch/writing/why-we-switched-from-slack-to-discord-at-work/">our switch from Slack to
Discord</a>.
I have started to write this post with pen and paper. I find that I think
better when using pen and paper than when using the computer keyboard. One
factor is certainly that the computer offers more distractions, but I suspect
that another, more important factor is that as a fast typist the fingers and
the thinking are always getting out of step, and this happens less when using a
slower method of writing. This actually isn’t an idea I had myself, but I don’t
remember where I got it from.</p>
<h2>Zero-based versioning: Good or bad?</h2>
<p>I discovered <a href="https://0ver.org/">ZeroVer</a> sometime in the last few days. I have
many many Django packages with zero-based versions. Some of them have been used
in production for years now. I sometimes wonder if staying with <code>0.</code> is
unprofessional and I should just release 1.0 and be done with it or if it
doesn’t really matter at all.</p>
<p>If I evaluate software packages more often than not I don’t look at the version
number or the version numbering scheme (except when a package is still using
<code>0.0.</code>) when deciding whether to rely on it or not. The documentation and the
code itself are much more important to me.</p>
<h2>Releases</h2>
<p>I haven’t uploaded any releases in the last 14 days. That’s good: I’m one of
those people who have made their passion their job (which is great) but that
sometimes makes it hard to not work at all since I can always tell myself that
I’m not working, that it’s just a hobby.</p>Why we switched from Slack to Discord at Workhttps://406.ch/writing/why-we-switched-from-slack-to-discord-at-work/2023-10-15T12:00:00Z2023-10-15T12:00:00Z<h1>Why we switched from Slack to Discord at Work</h1><p>We have switched from Slack to Discord at <a href="https://feinheit.ch">Feinheit</a> a few years back. This post explores the history, the reasons for the switch and what we learned in the meantime.</p>
<h2>The early days</h2>
<p>In the early days we used Skype group chats. I don’t remember why we stopped.
We probably became too numerous for a single chat. After we stopped we
certainly missed the easy way to share things with everyone. An alternative had
to be found. Company based social networks were the hype for a short amount of
time. I don’t even remember the name of the product we used, that probably says
enough about that. Suffice to say that it wasn’t a happy time tool-wise.</p>
<h2>Slack</h2>
<p>Then came Slack. Finally we had something easy to use, again. Need a new space to
discuss something? Just open a new channel, everybody can do it! Need to ask a
specific person something? Direct messages are right there, and (arguably) much
better than always interrupting people by walking to their desk for everything.</p>
<p>However, my personal lived experience rapidly deteriorated. I wanted to be
accessible for urgent inquiries to unblock colleagues when they needed
something. But this also fostered an ASAP culture. Project managers knew I was
almost always available. It didn’t help that some were terribly unorganized at
times.</p>
<p>Since asking was easier that searching the documentation or the handbook people
didn’t bother to document things anymore. The information was shared in Slack
but since nobody found the relevant messages after time had passed the same
questions were asked (and answered) over and over. Slack became the place where
knowledge goes to die.</p>
<p>After a time Slack became my primary source of stress. I tried several times to
stop using it. I suffered an unreasonable amount of FOMO<sup id="fnref:fomo"><a class="footnote-ref" href="#fn:fomo">1</a></sup> during that
time but it was also true that I did not know stuff which was only shared on
Slack by others. I asked myself many times what my problem was and why I
couldn’t “simply” shut it off and ignore the downsides. And a minor but still
important point, ignore the unread messages indicator.</p>
<p>Luckily I learned of others who felt the same way, mostly outside the company
but some of them at Feinheit. Slack had to go. The question was: Which tool
could be a suitable replacement? Given the title of this post it shouldn’t come
as a surprise that the answer was Discord.</p>
<h2>Digression: Project management software</h2>
<p>It’s probably important to know that we have been using a project management
tool and also issue trackers all this time. So, Slack really wasn’t just a
possibly improved<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">2</a></sup> replacement for mail-based project management. Slack
mainly crept into the project management tooling because some people perceived
it to be more modern: A simpler way for those asking others for their time or
knowledge.</p>
<h2>Discord as an addition, not a replacement for project management software</h2>
<p>We have started using Discord in the summer of 2021 with a clear set of rules
and a small number of channels.</p>
<h3>Rules</h3>
<p>The most important rules are:</p>
<ul>
<li>Discord is the place for casual and informal exchange.</li>
<li>Nobody is expected to read anything on Discord. If it is expected that people
actually receive a message it must not be shared on Discord.</li>
<li>No discussing project work, not in channels nor in direct messages. The
former is enforced by a short list of moderators, the latter is formulated as
an expectation (since it’s impossible to enforce).</li>
</ul>
<p>The rules message also contains a direct message to a shared to do list, where
everyone can easily assign to do items to others without having to find the
project’s issue tracker. This is especially important because we have many
small projects and no one works on all of them. If assigning to dos wasn’t
relatively straightforward people would understandably fall back to direct
messages.</p>
<h3>Server configuration</h3>
<p>We are able to uphold these rules because of Discord’s strong moderation
features (compared to Slack). Discord is much more configurable. Our server is
configured in the following way:</p>
<ul>
<li>Channel creation is disallowed. Only moderators can create new channels; if
someone asks for a new channel, the request is denied if it is probable that
the rules above would be broken.</li>
<li>No one is expected to accept DMs from other members.</li>
<li>Threads are disabled. The structure they provide makes it acceptable to loop
people into a “quick” conversation which 99% of the time belongs somewhere
more permanent, for example the project management software.</li>
</ul>
<p>Those are just the most important points.</p>
<h3>Channels</h3>
<p>At the time of writing those are our text channels (The names are translated to their equivalents):</p>
<ul>
<li><code>#reception</code> – this is the only channel people see when they haven’t been
given the appropriate role. This is necessary because everybody can join a
server if they have an invite code and we don’t want random people to read
everything we post.</li>
<li><code>#staircase</code> – the place to say hello, bye, and a few things in-between.</li>
<li><code>#random</code> – all servers need this :-)</li>
<li><code>#politics</code> – a separate space for politics. Everything’s political, and we are too.</li>
<li><code>#inspiration</code> – I myself thought that <code>#staircase</code> and <code>#random</code> would be
sufficient but people wanted this and the moderators didn’t think that this
additional channel would lead to a violation of the rules above.</li>
</ul>
<p>Additionally, we have two voice channels, <code>cow-orking</code> and <code>sofa</code>. The former
was used more ofting when more people were working from home to have a place to
hear from others, the latter is more an invitation to talk. More talk happens
in-person in the office these days, but when working remotely it’s still nice
to know that those channels are just a click away.</p>
<h2>Closing words</h2>
<p>I myself have joined the work discord using my private account and I feel good
about that. I’m not friends with everyone at work but I certainly have good
relationships with basically everyone.</p>
<p>I also did not live-stream my activities to Discord before creating the work
server anyway so there hasn’t been a change there at all. And people mostly
know that Discord is about the slowest place to get something from me (and I
make a point of it!) so even if people do not heed the “No DMs” rule above this
behavior isn’t really reinforced.</p>
<p>I’m actually really happy with the way we’re using these tools these days.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:fomo">
<p>Fear Of Missing Out. <a class="footnote-backref" href="#fnref:fomo" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:1">
<p>For some people it seems to be an improvement. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>Weeknotes (2023 week 40)https://406.ch/writing/weeknotes-2023-week-40/2023-10-04T12:00:00Z2023-10-04T12:00:00Z<h1>Weeknotes (2023 week 40)</h1><h2>More work on hosting several websites from a single Django application server using feincms3-sites</h2>
<p>I have mentioned feincms3-sites last week in my last weeknotes entry; I have
again given this package a lot of attention in the last days, so another update
is in order.</p>
<p>It is now possible to override the list of languages available on each site.
That’s especially useful for an upcoming campaign site where the umbrella
group’s site is available in three languages, but (most?) individual group
sites (hosted on subdomains) will only have a subset of languages. Since I live
in a country with four national languages (english isn’t one of them, but is
spoken by many!) supporting more than one language, or even many languages is
totally commonplace. It’s great that Django has good support for
internationalization. For the sake of an example, I have the following sites:</p>
<ul>
<li><code>example.com</code>: The default. The host has to match exactly.</li>
<li><code>subdomain.example.com</code>: One individual group’s site. The host has to match the regex <code>^subdomain\.</code> (sorry, I actually do like regexes).</li>
</ul>
<h3>Overriding configured hosts for local development</h3>
<p>One thing which always annoyed me when using <code>django.contrib.sites</code> was that
“just” pulling the database from production to the local development
environment always produced links pointing back to the remote host instead of
working locally (when producing absolute URLs). This problem was shared by
feincms3-sites as well. I have now found a very ugly but perfectly workable
solution: Overwrite <code>Site.get_host()</code> locally:</p>
<div class="chl"><pre><span></span><code><span class="k">if</span> <span class="n">DEBUG</span><span class="p">:</span>
<span class="n">domain</span> <span class="o">=</span> <span class="s2">"example.com"</span> <span class="c1"># Or whatever</span>
<span class="n">_get_host</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">site</span><span class="p">:</span> <span class="n">site</span><span class="o">.</span><span class="n">host</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">domain</span><span class="p">,</span> <span class="s2">"localhost:8000"</span><span class="p">)</span>
<span class="n">FEINCMS3_SITES_SITE_GET_HOST</span> <span class="o">=</span> <span class="n">_get_host</span>
</code></pre></div>
<p>This works especially well when using <code>example.com</code> and maybe subdomains of
<code>example.com</code>: All absolute links will point to <code>localhost:8000</code> or
<code>subdomain.localhost:8000</code>. Since <code>*.localhost</code> always resolves to the local IP
the browser knows where it should connect to, and since
<code>subdomain.localhost:8000</code> also matches the <code>^subdomain\.</code> regex mentioned
above, the site selection logic works as well.</p>
<p>Of course if you have more domains, not just subdomains, you could adapt the
<code>get_host</code> override and the relevant regexes to those use cases.</p>
<h3>Closing words</h3>
<p>We’re at 100% code coverage now when running the test suite. That’s really nice.</p>
<h2>Logging into the Django admin using your Google account</h2>
<p>This functionality has long been provided by
<a href="https://pypi.org/project/django-authlib/">django-admin-sso</a>; however, as
mentioned a long time ago this package still uses a deprecated OAuth2 library.
<a href="https://github.com/matthiask/django-authlib/">django-authlib</a> supports using a
Google account to authenticate with the Django admin since 2017. I have now
fixed a small problem with it: If you are logged into a single Google account,
and this account’s email address doesn’t match the configured admin login rule,
you were out of luck: There was no way to add another account at that time
because the library didn’t request the account selection. That has changed now,
if the first login attempt doesn’t work, it now explicitly tells Google to let
the user select their Google account. A small quality of life improvement for
those using more than one Google account (voluntarily or not).</p>
<h2>Releases</h2>
<ul>
<li><a href="https://pypi.org/project/feincms3/">feincms3 4.4.3</a>: Polished the CKEditor integration a little bit. Re-enabled the source button now that we’re back to using the classic iframe-based editor again.</li>
<li><a href="https://pypi.org/project/feincms3-sites/">feincms3-sites 0.19.3</a>: See above.</li>
<li><a href="https://pypi.org/project/django-authlib/">django-authlib 0.16.4</a>: See above.</li>
</ul>