Matthias Kestenholzhttps://406.ch/2024-04-24T12:00:00ZMatthias KestenholzWorkbench, the Django-based agency softwarehttps://406.ch/writing/workbench-the-django-based-agency-software/2024-04-24T12:00:00Z2024-04-24T12:00:00Z<h1>Workbench, the Django-based agency software</h1><p>I get the impression that there&rsquo;s a lot of interesting but unknown software in Django land. I don&rsquo;t know if there&rsquo;s any interest in some of the packages I have been working on; if not this blog post is for myself only.</p> <h2>(Hi)story time</h2> <p>As people may know I work at <a href="https://feinheit.ch/">Feinheit</a>, an agency which specializes in digital communication services for SMEs, campaigns for referendums, and website and webapp development. At the time of writing we are a team of about 20-25 communication experts, graphic designers, programmers and project managers.</p> <p>We have many different clients and are working on many different projects at the same time and are billing by the hour<sup id="fnref:bythehour"><a class="footnote-ref" href="#fn:bythehour">1</a></sup>. Last year my own work has been billed to more than 50 different customers. In the early days we used a shared file server with spreadsheet files to track our working hours. Luckily we didn&rsquo;t often overwrite the edits others made but that was definitely something which happened from time to time.</p> <p>We knew of another agency who had the same problems and used a FileMaker-based software. Their solution had several problems, among them the fact that it became hard to evolve and that it got slower and slower as more and more data was entered into it over the years. They had the accounting know how and we had the software engineering know how so we wrote a webapp based on the Django framework. As always, it was much more work than the initial estimate, but if we as programmers didn&rsquo;t underestimate the effort needed we wouldn&rsquo;t have started many of the great projects we&rsquo;re now getting much value and/or enjoyment from, hopefully both. The product of that work was <a href="https://www.fineware.ch/">Metronom</a>. The first release happened a little bit later than <a href="https://www.getharvest.com/about">harvest</a> but it already came with full time tracking including an annual working time calculator, absence management, offers, invoices including PDF generation etc, so it was quite a bit more versatile while still being easier to use than &ldquo;real&rdquo; business software solutions.</p> <p>I personally was of the opinion that the product was good enough to try selling it, but for a variety of reasons (which I don&rsquo;t want to go into here) this never happened and <a href="https://feinheit.ch/">we</a> decided that we didn&rsquo;t want to be involved anymore.</p> <p>However, this meant that we were dead-end street with a software that didn&rsquo;t belong to us anymore, which wasn&rsquo;t evolving to our changing requirements. I also didn&rsquo;t enjoy working on it anymore. Over the years I have tried replacing it several times but that never came to pass until some time after the introduction of <a href="https://www.holacracy.org/">Holacracy</a> at our company. I noticed that I didn&rsquo;t have to persuade everyone but that I, as the responsible person for this particular decision, could &ldquo;just&rdquo;<sup id="fnref:just"><a class="footnote-ref" href="#fn:just">2</a></sup> move ahead with a broad interpretation of the purpose and accountabilities of one of my roles.</p> <h2><a href="https://github.com/matthiask/workbench">Workbench</a></h2> <p><img alt="Screenshot" src="https://406.ch/assets/20240424-workbench.png" /></p> <p><a href="https://github.com/matthiask/workbench">Workbench</a> is the product of a few long nights of hacking. The project was started as an experiment in 2015 and was used for sending invoices but that wasn&rsquo;t really the intended purpose. After long periods of lying dormant I have brought the project to a good enough state and switched Feinheit away from Metronom in 2019.</p> <p>I have thought long and hard about switching to one of the off-the-shelf products and it could very well be that one of them would work well for us. Also, we wouldn&rsquo;t have to pay (in form of working hours) for the maintenance and for enhancements ourselves. On the other hand, we can use a tool which is tailored to our needs. Is it worth the effort? That&rsquo;s always hard to answer. The tool certainly works well for the few companies which are using it right now, so there&rsquo;s no reason to agonize over that.</p> <p>At the time of writing, Workbench offers projects and services, offers and invoices incl. PDF generation, recurring invoices, an address book, a logbook for rendered services, annual working time reports, an acquisition funnel, a stupid project planning and resource management tool and various reports. It has not only replaced Metronom but also a selection of SaaS (for example Pipedrive and TeamGantt) we were using previously.</p> <p>The whole thing is open source because I don&rsquo;t want to try making agency software into a business anymore, I only want to solve the problems <em>we</em> have. That being said, if someone else finds it useful then that&rsquo;s certainly alright as well.</p> <p>The license has been MIT for the first few years but I have switched to the GPL because I wanted to integrate the excellent <a href="https://pypi.org/project/qrbill/">qrbill</a> module which is also licensed under the GPL. As I wrote elsewhere, I have released almost everything under the GPL in my first few open source years but have switched to BSD/MIT later when starting to work mainly with Python and Django because I thought that this license is a better fit<sup id="fnref:license"><a class="footnote-ref" href="#fn:license">3</a></sup> for the ecosystem. That being said, for a product such as this the GPL is certainly an excellent fit.</p> <h2>Final words?</h2> <p>There are a few things I could write about to make this into a series. I&rsquo;m putting a few ideas here, not as an announcement, just as a reminder for myself.</p> <ul> <li>Better time tracking</li> <li>Patterns for creating recurring invoices</li> <li>Feature switches and configurability</li> <li>Coffee time</li> </ul> <div class="footnote"> <hr /> <ol> <li id="fn:bythehour"> <p>Rather, in six minute increments. It&rsquo;s even worse.&#160;<a class="footnote-backref" href="#fnref:bythehour" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> <li id="fn:just"> <p>Of course it&rsquo;s never that simple, because the responsibility is a lot. The important point is: It would have been a lot of work anyways, the big difference is that it was sufficient to get consent from people; no consensus required.&#160;<a class="footnote-backref" href="#fnref:just" title="Jump back to footnote 2 in the text">&#8617;</a></p> </li> <li id="fn:license"> <p>That&rsquo;s not meant as a criticism in any way!&#160;<a class="footnote-backref" href="#fnref:license" title="Jump back to footnote 3 in the text">&#8617;</a></p> </li> </ol> </div>Building forms with the Django adminhttps://406.ch/writing/building-forms-with-the-django-admin/2024-04-12T12:00:00Z2024-04-12T12:00:00Z<h1>Building forms with the Django admin</h1> <p>The title of this post was shamelessly copied from <a href="https://mastodon.social/@webology/112235938469045649">Jeff Triplett&rsquo;s post on Mastodon</a>.</p> <h2>Why?</h2> <p>Many websites need a simple way of embedding forms, for example as a contact form or for simple surveys to collect some data or inputs from visitors. <a href="https://docs.djangoproject.com/en/5.0/topics/forms/">Django&rsquo;s forms library</a> makes building such forms straightforward but changing those forms requires programming skills and programmer time. Both of them may not be readily available. More importantly, sometimes it&rsquo;s just nice to give more tools to web publishers.</p> <p>The simple way to build something like this is to use a form builder such as Google Forms, Typeform, Paperform or anything of the sort. Those options work nicely. The downsides are that embedded forms using those services load slowly, look differently, cost a lot or collect a lot of data on users, or all of those options. Because of that there&rsquo;s still a place for building such functionality locally.</p> <p>If I wanted to use PHP and WordPress I could just use <a href="https://wpforms.com/">WPForms</a> and call it a day. Since I do not actually want that this blog post is a bit longer.</p> <h2>The early days: form-designer</h2> <p>One of the first Django-based third party apps I published was the <a href="https://github.com/feincms/form-designer">form-designer</a>. The first version was uploaded to PyPI in 2012 but it had already been used in production for more than two years at that point in time. I had used <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git submodules</a> for the deployment back then, before switching to <a href="https://virtualenv.pypa.io/">Python virtualenvs</a> some time later (and never looking back!)</p> <p>The form-designer is still maintained actively. Because of Django&rsquo;s stability and because of the fact that the app doesn&rsquo;t do all that much it doesn&rsquo;t require much development at all.</p> <p><img alt="A screenshot of the admin interface" src="https://406.ch/assets/20240410-form-designer.png" /></p> <p>The form designer supports a selection of standard HTML5 input fields out of the box and also has an optional <a href="https://github.com/django-recaptcha/django-recaptcha">django-recaptcha</a> integration. All fields support some basic configuration such as setting a title, a help text, marking the field as required etc. Submissions can be sent to a configurable email address and can be saved in the database and later exported as an XLSX file. It&rsquo;s also possible to define your own actions.</p> <h2>More flexibility needed: feincms3-forms</h2> <p>A few years back I mentioned <a href="https://406.ch/writing/weeknotes-2021-week-13-and-14/">feincms3-forms in a weeknotes entry</a>. The reasons why form-designer wasn&rsquo;t sufficient for a project back then are outlined in the blog post:</p> <blockquote> <h3>feincms3-forms – A new forms builder for the Django admin interface</h3> <p>For a current project <a href="https://feinheit.ch/">we</a> needed a forms builder with the following constraints:</p> <ul> <li>Simple fields (text, email, checkboxes, dropdowns etc.)</li> <li>Custom validation and processing logic</li> <li>It should be possible to add other content, e.g. headings and explanations between form fields</li> </ul> <p>The <a href="https://github.com/feincms/form_designer">form_designer</a> fulfilled a few of these requirements but not all. It still works well but I wanted a forms builder based on <a href="https://github.com/matthiask/django-content-editor">django-content-editor</a> for a long time already. Also, I really like the feincms3 pattern where the third party app only provides abstract models. Yeah, it is much more work to start with but the flexibility and configurability is worth it – especially since it&rsquo;s possible to write straightforward code to handle special cases[^2] instead of configuring even more settings.</p> <p>The humble beginnings are here in the <a href="https://github.com/matthiask/feincms3-forms/">feincms3-forms</a> repository. The <a href="https://github.com/matthiask/feincms3-forms/tree/main/tests/testapp">test suite already shows how things work together</a> but as of now no documentation exists and no release has been made yet. I hope it will be ready for a first beta release in the next few weeks 😄</p> </blockquote> <p>Since then I have used feincms3-forms more often than form-designer, for building simple forms and also to build multi-step form wizards with custom fields, custom validation, configurable steps etc. The <a href="https://github.com/feincms/feincms3-forms?tab=readme-ov-file#feincms3-forms">README</a> now actually explains why the project exists and how it could be used.</p> <p>It still doesn&rsquo;t come close to WPForms in terms of included functionality; a big feature which is missing is conditional logic because I haven&rsquo;t yet had a use for it.</p> <p><img alt="The feincms3-forms admin interface" src="https://406.ch/assets/20240410-feincms3-forms.png" /></p> <p>The feincms3-forms forms support all types of content between form fields (basically everything <a href="https://django-content-editor.readthedocs.io/">django-content-editor</a> supports). Plugins for form fields are more flexible and can add as many input fields to the form as they want, you&rsquo;re not restricted to single values or single input fields.</p> <h2>Packages</h2> <ul> <li><a href="https://github.com/feincms/form-designer">form-designer</a></li> <li><a href="https://github.com/feincms/feincms3-forms">feincms3-forms</a></li> </ul>Weeknotes (2024 week 14)https://406.ch/writing/weeknotes-2024-week-14/2024-04-06T12:00:00Z2024-04-06T12:00:00Z<h1>Weeknotes (2024 week 14)</h1><p>I&rsquo;m having a bit of a slow week with the easter weekend and a wisdom tooth extraction. I&rsquo;m recovering quite quickly it seems and I&rsquo;m glad about it.</p> <p>This weeknotes entry is short and quick. I&rsquo;m trying to get back into the habit of writing them after a mediocre start this year.</p> <h2>20th Anniversary Celebration of Young Greens Switzerland</h2> <p>I have attended the celebration of Young Greens Switzerland. I have been a founding member and have been active for close to ten years. A lot of time has passed since then. It has been great to reminisce about old times with friends and, more importantly, to see how the torch is carried on.</p> <h2>Releases</h2> <ul> <li><a href="https://pypi.org/project/blacknoise/">blacknoise 0.0.5</a>: blacknoise is an ASGI app for static file serving inspired by <a href="https://whitenoise.readthedocs.io/en/latest/">whitenoise</a>. It only supports a very limited subset of whitenoise&rsquo;s functionality, but it supports async.</li> <li><a href="https://pypi.org/project/html-sanitizer/">html-sanitizer 2.4.1</a>: The lxml library moved the HTML cleaner into its own package, <a href="https://pypi.org/project/lxml-html-clean/">lxml-html-clean</a>; this release adds support for that. I didn&rsquo;t know that the HTML cleaner is viewed as being problematic by the lxml maintainers. I&rsquo;m having another look at <a href="https://github.com/messense/nh3">nh3</a> and will maybe switch html-sanitizer&rsquo;s guts from lxml to nh3 in the future.</li> <li><a href="https://pypi.org/project/django-tree-queries/">django-tree-queries 0.18</a>: django-tree-queries now supports ordering siblings by multiple fields and even allows descending orderings.</li> <li><a href="https://pypi.org/project/django-cabinet/">django-cabinet 0.14.2</a>: This release fixes the CKEditor 4 filebrowser popup when using Django 5 or better.</li> </ul>The 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&rsquo;t a CMS (Content Management System).</p> <p>I think that this is misguided and needlessly limits the discourse around what the admin&rsquo;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&rsquo;re getting into workflows, into complex permission scenarios (sad noises) or similar things the admin definitely isn&rsquo;t for you. But, the admin nicely solves 90% of the problems with 10% of the effort. And it&rsquo;s very good at that.</p> <p>And sure, if you try building your own frontend on top of the Django admin you&rsquo;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&rsquo;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&rsquo;re <a href="https://406.ch/writing/low-maintenance-software/">keeping maintenance lower</a>, and you&rsquo;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&rsquo;re depending on smaller pieces of additional software it will generally be possible to upgrade to new Django versions quicker. This isn&rsquo;t true for all packages of course, and I&rsquo;m a reluctant maintainer of some of them. Anecdotes aren&rsquo;t data, but I see that some larger CMS systems are definitely having a hard time keeping up with Django&rsquo;s release schedule.</p> <p>I&rsquo;m not trying to say that the Django admin is a better CMS than other Django-based CMS, or any other CMS. I&rsquo;m saying it&rsquo;s a trade off and you should be mindful of the downsides of choosing a larger system. And I&rsquo;m saying that the people who tell you that you shouldn&rsquo;t be using the Django admin interface are wrong in the first approximation.</p> <p>The fact that it&rsquo;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.&#160;<a class="footnote-backref" href="#fnref:words" title="Jump back to footnote 1 in the text">&#8617;</a><a class="footnote-backref" href="#fnref2:words" title="Jump back to footnote 1 in the text">&#8617;</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&rsquo;t be too controversial.&#160;<a class="footnote-backref" href="#fnref:community" title="Jump back to footnote 2 in the text">&#8617;</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&rsquo;m not even sure if blacknoise should exist at all or if the energy wouldn&rsquo;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">&quot;static&quot;</span><span class="p">,</span> <span class="s2">&quot;/static&quot;</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&rsquo;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&rsquo;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&rsquo;s a list of podcasts I&rsquo;m currently listening to on a regular basis.</p> <h2><a href="https://www.techwontsave.us/">Tech Won&rsquo;t Save Us</a></h2> <p>I have recently stumbled over Tech Won&rsquo;t Save Us, a Podcast which is critical of the technological &ldquo;progress&rdquo; offered by Silicon Valley elites. It&rsquo;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&rsquo;re facing as humans. Who knows. I do not want to be too negative about it though, there are positive news if you&rsquo;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&rsquo;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&rsquo;s great that people from different sides get a voice on the show, even if Ezra doesn&rsquo;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&rsquo;s a good way for me to stay informed about what&rsquo;s going on in AI/ML land, among other things. It&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;t think that&rsquo;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&rsquo;t think the german &ldquo;Welt&rdquo; 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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;t work anymore it seems.</li> <li>The execution time isn&rsquo;t reported anymore.</li> <li>The script now supports scheduled publishing, mainly by leveraging GitHub action&rsquo;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&rsquo;ll spend time tweaking the generator instead of actually writing posts. This definitely doesn&rsquo;t have to be the case. Sometimes it&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;t want to bash any of them. But, I didn&rsquo;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&rsquo;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&rsquo;t have a dedicated maintainer, so I wouldn&rsquo;t bet on it. <a href="https://github.com/withlogicco/django-prose">django-prose</a> uses Trix; there are various reasons why I didn&rsquo;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&rsquo;t think Markdown is a good choice for a CMS which is used by people of many different skill levels. I don&rsquo;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&rsquo;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&rsquo;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&rsquo;t a good thing though in my mind, so I&rsquo;ll probably be slow to add features and rather keep complexity low.</p> <p>I&rsquo;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&rsquo;s post</a> to write this down. The main value &ndash; if there&rsquo;s value at all in this post &ndash; lies in my ability to revisit it later and see if anything changed.</p> <h2>Desk</h2> <p>I&rsquo;m using a standing desk since 2015 or 2016. I&rsquo;m actually doing most of my work standing; when I&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;m using a 27&rdquo; WQHD monitor when I work. I have used a 32&rdquo; 4k monitor for some time, but I have noticed that I&rsquo;m starting to spend time searching for windows. I don&rsquo;t like curved or widescreens monitors at all (I have tried them). I thought I&rsquo;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&rsquo;m switching computers all the time, and basically everything has to be synced to the cloud all the time. I&rsquo;m using git since the spring of 2006 and push everything all the time anyway. And when I&rsquo;m not, I&rsquo;m mostly using web-based software. This means that I mostly don&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;m asked but I do not suggest to people that they should use it as well. The learning curve isn&rsquo;t worth it except maybe if you do a lot of system administration, I don&rsquo;t know.</p> <p>I have a VisualStudio Code installation laying around for using Live Share. I think it&rsquo;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&rsquo;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&rsquo;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&rsquo;t the nicest way, but it works for me since I actually do not like cookie banners either. At least feincms3-cookiecontrol doesn&rsquo;t inject anything without users&rsquo; consent, and doesn&rsquo;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&rsquo;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&rsquo;s a clear case of worse is better for me. If people want to confuse themselves I&rsquo;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&rsquo;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>&lt;strong&gt;abc&lt;/strong&gt;&lt;strong&gt;def&lt;/strong&gt;</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 &ldquo;Save and continue editing&rdquo;. 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&rsquo;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&rsquo;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&rsquo;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&rsquo;t workable unfortunately.</p> <p>Stay tuned for updates &ndash; 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&rsquo;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&rsquo;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&rsquo;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&rsquo;m slowly getting to the point where setting up local development environments and deploying changes is fun again. I&rsquo;m using the GitOps paradigm; while I&rsquo;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&rsquo;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&rsquo;t really offer straightforward solutions to avoid different people overwriting and resetting each others secrets when updating them. I&rsquo;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&rsquo;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&rsquo;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&rsquo;s now actually possible to use the JSON editor outside inlines! That was a fun bug&hellip; not. Apart from the mentioned bug this new release mostly contains fixes to the styles. We&rsquo;re slowly getting there.</li> <li><a href="https://pypi.org/project/django-mptt/">django-mptt 0.16</a>: I didn&rsquo;t do anything except for the changelog and the release. That&rsquo;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&rsquo;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&rsquo;m still very interested in these topics but I don&rsquo;t feel as if I have much to add to the conversation, even though it&rsquo;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&rsquo;s great that one of the core components, <a href="https://pypi.org/project/django-content-editor/#history">django-content-editor</a>, hasn&rsquo;t required a release in more than one year. It doesn&rsquo;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&rsquo;t require any updates and software which is abandoned.</p> <p>The only project which doesn&rsquo;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&rsquo;m glad that I didn&rsquo;t give in to the temptation to do that. It&rsquo;s basically never worth it to do that in writing.</p> <h3>Family</h3> <p>We had a good 2023 together and I&rsquo;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&rsquo;m very much looking forward to that.</p> <p>Maybe we&rsquo;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&rsquo;n&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;t be backwards incompatible. However, you&rsquo;ll always get a deprecation warning if you don&rsquo;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&rsquo;t even know before. I&rsquo;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&rsquo;t even know how to start the second part 😅.</p> <h2>Hosting</h2> <p>We&rsquo;re still hosting most sites on virtualized servers, without any containers or any of the new stuff. I&rsquo;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&rsquo;m more confused than I&rsquo;ve been in years.</p> <h2>Health</h2> <p>To absolutely nobody&rsquo;s surprise the family and myself have continued to be sick in the last two weeks. Nothing really bad happened, so we&rsquo;re still lucky.</p> <p>There&rsquo;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>&rsquo;s Python module to be <code>js_asset</code> but I&rsquo;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&rsquo;s project and released it as open source. It isn&rsquo;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&rsquo;t always better, but stacked definitely isn&rsquo;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&rsquo;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&hellip; 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">&quot;default&quot;</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">&quot;STORAGE_URL&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&quot;file:./media/?base_url=/media/&quot;</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">&quot;staticfiles&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="s2">&quot;BACKEND&quot;</span><span class="p">:</span> <span class="s2">&quot;django.contrib.staticfiles.storage.ManifestStaticFilesStorage&quot;</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">&amp;</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&rsquo;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&rsquo;t regretted this decision at all. I haven&rsquo;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&rsquo;t enough, I get little but only warm and thankful feedback for the latter, so that&rsquo;s extra nice.</p> <p>Despite marking django-mptt as unmaintained I seem to be doing a little bit of maintenance still. I&rsquo;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&rsquo;m not personally invested in the package anymore, so I&rsquo;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&rsquo;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&rsquo;s right) once; feincms3 worked fine, feincms3-language-sites by accident but feincms3-sites didn&rsquo;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&rsquo;m still quite proud of the mostly transparent support for multitenancy, but apart from that I haven&rsquo;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&rsquo;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&rsquo;s not news that 100% test coverage isn&rsquo;t a recipe for bug-free code, and it&rsquo;s probably not even worth optimizing for that metric in code. It&rsquo;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">&lt;</span><span class="nt">style</span><span class="p">&gt;</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">&quot; (</span><span class="cp">{{</span> <span class="nv">ENVIRONMENT</span> <span class="cp">}}</span><span class="s2">)&quot;</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">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span> <span class="cp">{%</span> <span class="k">if</span> <span class="nv">ENVIRONMENT</span> <span class="o">==</span> <span class="s1">&#39;production&#39;</span> <span class="cp">%}</span> <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</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">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span> <span class="cp">{%</span> <span class="k">elif</span> <span class="nv">ENVIRONMENT</span> <span class="o">==</span> <span class="s1">&#39;preproduction&#39;</span> <span class="cp">%}</span> <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</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">&lt;/</span><span class="nt">style</span><span class="p">&gt;</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>