<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kodare.net/feed/by_tag/django.xml" rel="self" type="application/atom+xml" /><link href="https://kodare.net/" rel="alternate" type="text/html" /><updated>2026-03-06T05:53:44+00:00</updated><id>https://kodare.net/feed/by_tag/django.xml</id><title type="html">En kodare</title><subtitle>Personal blog for Anders Hovmöller. Mostly programming stuff.</subtitle><entry><title type="html">Smoother translations in Django</title><link href="https://kodare.net/2026/03/05/smoother-django-translations.html" rel="alternate" type="text/html" title="Smoother translations in Django" /><published>2026-03-05T00:00:00+00:00</published><updated>2026-03-05T00:00:00+00:00</updated><id>https://kodare.net/2026/03/05/smoother-django-translations</id><content type="html" xml:base="https://kodare.net/2026/03/05/smoother-django-translations.html"><![CDATA[<p>I’ve been working for roughly 5 years now in an app that is localized to Swedish, so I have built up some opinions on how to manage translation of a Django project. Here’s my list of things I do currently:</p>

<h2 id="always-use-gettext_lazy">Always use <code class="language-plaintext highlighter-rouge">gettext_lazy</code></h2>

<p>I’ve been bitten many times by accidentally using <code class="language-plaintext highlighter-rouge">gettext</code> when I should have used <code class="language-plaintext highlighter-rouge">gettext_lazy</code>, resulting in strings that were stuck in English or Swedish randomly because a user with a specific language caused that piece of code to be imported.</p>

<p>I realize that there are some performance implications here, but compared to stuff like database access this is tiny and has never shown up in profiler outputs, so I will gladly take this hit and avoid these bugs that tend to be hard to track down (if they even get reported by users at all!).</p>

<p>A simple naive hand-rolled static analysis test that forbids usages of plain <code class="language-plaintext highlighter-rouge">gettext</code> in the code base is easy to implement and stops a whole class of bugs.</p>

<h2 id="django-models">Django models</h2>

<p>The <a href="https://github.com/boxed/okrand">Okrand</a> setting <code class="language-plaintext highlighter-rouge">django_model_upgrade</code> which dynamically sets <code class="language-plaintext highlighter-rouge">verbose_name</code> for all fields correctly with the normal default, and on the model sets up <code class="language-plaintext highlighter-rouge">verbose_name</code> and <code class="language-plaintext highlighter-rouge">verbose_name_plural</code>. Then when you run the Okrand collect command you will get strings to translate without polluting your source with silly stuff like</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">ForeignKey</span><span class="p">(</span><span class="n">User</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="n">gettext_lazy</span><span class="p">(</span><span class="s">'user'</span><span class="p">))</span>
    
    <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
        <span class="n">verbose_name</span> <span class="o">=</span> <span class="n">gettext_lazy</span><span class="p">(</span><span class="s">'foo'</span><span class="p">)</span>
        <span class="n">verbose_name_plural</span> <span class="o">=</span> <span class="n">gettext_lazy</span><span class="p">(</span><span class="s">'foos'</span><span class="p">)</span>
</code></pre></div></div>

<p>and you can instead have models like:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">ForeignKey</span><span class="p">(</span><span class="n">User</span><span class="p">)</span>
</code></pre></div></div>

<p>You can still write them out explicitly if you need them to differ from the defaults.</p>

<h2 id="elm">Elm</h2>

<p>There’s a built-in regex pattern for ML-style languages in Okrand that makes it quite easy to collect strings from <a href="https://elm-lang.org/">Elm</a> code.</p>

<h2 id="menu-translations">Menu translations</h2>

<p>I use the <a href="https://iommi.rocks/">iommi</a> <code class="language-plaintext highlighter-rouge">MainMenu</code> system which looks something like this:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">menu</span> <span class="o">=</span> <span class="n">MainMenu</span><span class="p">(</span>
    <span class="n">items</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span>
        <span class="n">albums</span><span class="o">=</span><span class="n">M</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">albums_view</span><span class="p">),</span>
        <span class="n">artists</span><span class="o">=</span><span class="n">M</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">artists_view</span><span class="p">),</span>
    <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Since Okrand has a plugin system, I can build a little function that loops over this menu and collects these identifiers into translation strings. In the example above this would be “albums” and “artists”. I enjoy not having to write the English base string that is 99% the exact same as the identifier (after replacing <code class="language-plaintext highlighter-rouge">_</code> with space), which keeps the business logic clean.</p>

<h2 id="stick-to-lowercase-as-far-as-possible">Stick to lowercase as far as possible</h2>

<p>I was frustrated by the translation files ending up with translations for “album” and “Album”, “artist” and “Artist” over and over. The solution I came up with was to define two simple functions:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">Trans</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">capfirst</span><span class="p">(</span><span class="n">gettext_lazy</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>

<span class="k">def</span> <span class="nf">trans</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">gettext_lazy</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
</code></pre></div></div>

<p>I like the semantic weight of having <code class="language-plaintext highlighter-rouge">Trans("album")</code> mean that the word should start with uppercase in that place while <code class="language-plaintext highlighter-rouge">trans("album")</code> meaning that it should stay as lowercase. One could also add <code class="language-plaintext highlighter-rouge">TRANS("album")</code> if one wants all uppercase of a string for example.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><category term="okrand" /><summary type="html"><![CDATA[I’ve been working for roughly 5 years now in an app that is localized to Swedish, so I have built up some opinions on how to manage translation of a Django project. Here’s my list of things I do currently:]]></summary></entry><entry><title type="html">AI and readable APIs</title><link href="https://kodare.net/2026/02/16/AI-readable-APIs.html" rel="alternate" type="text/html" title="AI and readable APIs" /><published>2026-02-16T00:00:00+00:00</published><updated>2026-02-16T00:00:00+00:00</updated><id>https://kodare.net/2026/02/16/AI-readable-APIs</id><content type="html" xml:base="https://kodare.net/2026/02/16/AI-readable-APIs.html"><![CDATA[<p>In the AI age the importance of readable APIs goes up, as this can mean the difference between not reading the code because it’s too much, and easily reading it to verify it is correct because it’s tiny. It’s been pretty clear that one of the superpowers of AI development is that it happily deals with enormous amounts of boilerplate and workarounds in a way that would drive a human insane. But we need to be careful of this, and notice that this is what is happening.</p>

<p>High level APIs with steep learning curves (like <a href="https://iommi.rocks">iommi</a>) are now just as easy to use as simpler APIs, since the cost of initial learning is moved from the human to the AI. Since we also invested heavily in great error messages and validating as much as possible up front, the feedback to the AI models is great. We’ve been banging the drum of “no silent failures!” for a decade, and nothing kills human or AI productivity as silent failures.</p>

<p>This is the time to focus our attention as humans to making APIs that are succinct and clear. It was vital before, but it’s growing in importance for every day.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><category term="documentation" /><summary type="html"><![CDATA[In the AI age the importance of readable APIs goes up, as this can mean the difference between not reading the code because it’s too much, and easily reading it to verify it is correct because it’s tiny. It’s been pretty clear that one of the superpowers of AI development is that it happily deals with enormous amounts of boilerplate and workarounds in a way that would drive a human insane. But we need to be careful of this, and notice that this is what is happening.]]></summary></entry><entry><title type="html">Using Claude for spellchecking and grammar</title><link href="https://kodare.net/2026/02/15/using-claude-for-spell-checking.html" rel="alternate" type="text/html" title="Using Claude for spellchecking and grammar" /><published>2026-02-15T00:00:00+00:00</published><updated>2026-02-15T00:00:00+00:00</updated><id>https://kodare.net/2026/02/15/using-claude-for-spell-checking</id><content type="html" xml:base="https://kodare.net/2026/02/15/using-claude-for-spell-checking.html"><![CDATA[<p>On the pytest discord channel <a href="https://github.com/webknjaz">Sviatoslav</a> mentioned a pull request with a bunch of spelling and grammar fixes. We had a discussion about the morality of not disclosing that it was an AI driven pull request up front, but what was pretty clear was that the quality was surprisingly high.</p>

<p>Since I have <a href="https://github.com/iommirocks/iommi">a project with extensive documentation</a> that I’ve spelled checked thoroughly this interested me. I write all the documentation with PyCharm which has built in spelling and grammar checks, so I was thinking it would be hard to find many errors.</p>

<p>I sent this prompt to Claude:</p>

<blockquote>
  <p>Go through the docs directory. Strings marked with <code class="language-plaintext highlighter-rouge"># language: rst</code> will be visible as normal text in the documentation. Suggest spelling, grammar, and language clarity improvements.</p>
</blockquote>

<p>Claude fires up ~8 sub agents and found a surprising amount of things. <a href="https://github.com/iommirocks/iommi/commit/781c2521c101c78619a2a445939f06b54e7ed003">Every single change was good.</a></p>

<p>A funny detail was that Claude ignored my request to only check the docs directory and found some issues in docstrings in the main source code. I can’t be angry about that :P</p>

<p>The funniest mistake was that the docs had the word “underling” instead of “underlying” in one place (“feature set of the underling <code class="language-plaintext highlighter-rouge">Query</code> and <code class="language-plaintext highlighter-rouge">Form</code> classes”). Perfectly fine spelling <em>and</em> grammar, but Claude correctly spots that this is mistake.</p>

<p>If you have some documentation, you definitely should give this a shot.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><category term="documentation" /><summary type="html"><![CDATA[On the pytest discord channel Sviatoslav mentioned a pull request with a bunch of spelling and grammar fixes. We had a discussion about the morality of not disclosing that it was an AI driven pull request up front, but what was pretty clear was that the quality was surprisingly high. Since I have a project with extensive documentation that I’ve spelled checked thoroughly this interested me. I write all the documentation with PyCharm which has built in spelling and grammar checks, so I was thinking it would be hard to find many errors.]]></summary></entry><entry><title type="html">Documentation that is never wrong</title><link href="https://kodare.net/2025/08/08/documentation-that-is-never-wrong.html" rel="alternate" type="text/html" title="Documentation that is never wrong" /><published>2025-08-08T00:00:00+00:00</published><updated>2025-08-08T00:00:00+00:00</updated><id>https://kodare.net/2025/08/08/documentation-that-is-never-wrong</id><content type="html" xml:base="https://kodare.net/2025/08/08/documentation-that-is-never-wrong.html"><![CDATA[<p>The iommi docs are more correct than most projects because we take a different approach to documentation: part of the test suite <em>is</em> the documentation. Let’s look at an example:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">test_grouped_fields</span><span class="p">():</span>
    <span class="c1"># language=rst
</span>    <span class="s">"""
    .. _group-fields:

    How do I group fields?
    ~~~~~~~~~~~~~~~~~~~~~~

    .. uses Field.group

    Use the `group` field:
    """</span>

    <span class="n">form</span> <span class="o">=</span> <span class="n">Form</span><span class="p">(</span>
        <span class="n">auto__model</span><span class="o">=</span><span class="n">Album</span><span class="p">,</span>
        <span class="n">fields__year__group</span><span class="o">=</span><span class="s">'metadata'</span><span class="p">,</span>
        <span class="n">fields__artist__group</span><span class="o">=</span><span class="s">'metadata'</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="c1"># @test
</span>    <span class="n">show_output</span><span class="p">(</span><span class="n">form</span><span class="p">)</span>
    <span class="c1"># @end
</span></code></pre></div></div>

<p>This ends up as this documentation:</p>

<p><img src="/img/2025-08-05-documentation-that-is-never-wrong.png" alt="2025-08-05-documentation-that-is-never-wrong.png" /></p>

<p>This is a normal test that runs with the normal test suite, with some additional markup:</p>

<ul>
  <li>The triple quoted strings that are declared with <code class="language-plaintext highlighter-rouge"># language=rst</code> are included in the docs.</li>
  <li>Code is by default included in the documentation</li>
  <li>You can exclude code with <code class="language-plaintext highlighter-rouge"># @test</code>/<code class="language-plaintext highlighter-rouge"># @end</code> for checks you don’t want to include in the docs</li>
  <li><code class="language-plaintext highlighter-rouge">show_output</code> renders some HTML output into a file that is then shown inline in the finished docs</li>
  <li>The <code class="language-plaintext highlighter-rouge">.. uses</code> command is used to mark what features this test uses so the examples are automatically linked from the reference API docs</li>
</ul>

<p>With this infrastructure in place, some fixes or features can be implemented with all the required tests written as the documentation with no additional tests. This radically incentivises writing docs compared to duplicating work across tests and docs.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><category term="documentation" /><summary type="html"><![CDATA[The iommi docs are more correct than most projects because we take a different approach to documentation: part of the test suite is the documentation. Let’s look at an example:]]></summary></entry><entry><title type="html">BoundField vs iommi</title><link href="https://kodare.net/2025/04/07/boundfield-vs-iommi.html" rel="alternate" type="text/html" title="BoundField vs iommi" /><published>2025-04-07T00:00:00+00:00</published><updated>2025-04-07T00:00:00+00:00</updated><id>https://kodare.net/2025/04/07/boundfield-vs-iommi</id><content type="html" xml:base="https://kodare.net/2025/04/07/boundfield-vs-iommi.html"><![CDATA[<p>In Django 5.2 we got a way to easier customize attributes of forms. Adam Johnson <a href="https://mastodon.social/@adamchainz@fosstodon.org/114296389528826640">posted an example on mastodon</a>, which I’ve slightly abbreviated below:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">WideLabelBoundField</span><span class="p">(</span><span class="n">BoundField</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">label_tag</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">contents</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">attrs</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">label_suffix</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">attrs</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">attrs</span> <span class="o">=</span> <span class="p">{}</span>
        <span class="n">attrs</span><span class="p">[</span><span class="s">'class'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'wide'</span>
        <span class="k">return</span> <span class="nb">super</span><span class="p">().</span><span class="n">label_tag</span><span class="p">(</span><span class="n">contents</span><span class="p">,</span> <span class="n">attrs</span><span class="p">,</span> <span class="n">label_suffix</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">NebulaForm</span><span class="p">(</span><span class="n">Form</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span>
        <span class="n">bound_field_class</span><span class="o">=</span><span class="n">WideLabelBoundField</span><span class="p">,</span>
    <span class="p">)</span>
</code></pre></div></div>

<p>To set a single CSS class on a single label, you have to create an entire class. Let’s look at the same thing in <a href="https://github.com/iommirocks/iommi">iommi</a>:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NebulaForm</span><span class="p">(</span><span class="n">Form</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">Field</span><span class="p">.</span><span class="n">text</span><span class="p">(</span>
        <span class="n">label__attrs__class__wide</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
</code></pre></div></div>

<p>But, you might object, what if you need to run some code to customize it? Like if the example didn’t just set <code class="language-plaintext highlighter-rouge">"wide"</code> as the value, but set it to <code class="language-plaintext highlighter-rouge">"wide"</code> only for staff?</p>

<p>Not only is this also easy in iommi, I would argue it’s even easier and cleaner than in the <code class="language-plaintext highlighter-rouge">BoundField</code> case above:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NebulaForm</span><span class="p">(</span><span class="n">Form</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">Field</span><span class="p">.</span><span class="n">text</span><span class="p">(</span>
        <span class="n">label__attrs__class__wide</span><span class="o">=</span><span class="k">lambda</span> <span class="n">user</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">:</span> <span class="n">user</span><span class="p">.</span><span class="n">is_staff</span><span class="p">,</span>
    <span class="p">)</span>
</code></pre></div></div>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><summary type="html"><![CDATA[In Django 5.2 we got a way to easier customize attributes of forms. Adam Johnson posted an example on mastodon, which I’ve slightly abbreviated below:]]></summary></entry><entry><title type="html">Bookmarklets, defaults-from-GET, and iommi</title><link href="https://kodare.net/2025/02/15/bookmarklets-default-from-GET.html" rel="alternate" type="text/html" title="Bookmarklets, defaults-from-GET, and iommi" /><published>2025-02-15T00:00:00+00:00</published><updated>2025-02-15T00:00:00+00:00</updated><id>https://kodare.net/2025/02/15/bookmarklets-default-from-GET</id><content type="html" xml:base="https://kodare.net/2025/02/15/bookmarklets-default-from-GET.html"><![CDATA[<p>Phil Gyford <a href="https://www.gyford.com/phil/writing/2025/02/14/django-admin-bookmarklet/">wrote an article</a> about how nice it is that the Django admin pre-populates inputs from the GET parameters if there are any. This can be used for bookmarklets as in his examples, or just general bookmarks where you can quickly go to a page with parts of a form prefilled.</p>

<p>Another very useful case for this pattern is to have a link on one page of your product with a link to a create form with prefilled data based on the context of the page you linked from. Like having an artist page with a link to the create album page with the artist filled in.</p>

<p>The Django admin does this, but Django forms do not. Because Django forms have an API that takes a dict for the data and not the request object itself, it can’t be retrofitted to have this feature either. It’s a nice example of where limiting the API surface area also limits future development.</p>

<p>In <a href="https://docs.iommi.rocks">iommi</a>, defaults-from-GET is the default for all forms. So if you build with iommi you get this feature across your product for free, not just in the admin. We even handle the edge cases for you like when you have a GET form, and you supply parameters via GET, so the form needs to know if this is from some URL or from a submit of the form. This is handled in iommi for you transparently.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><summary type="html"><![CDATA[Phil Gyford wrote an article about how nice it is that the Django admin pre-populates inputs from the GET parameters if there are any. This can be used for bookmarklets as in his examples, or just general bookmarks where you can quickly go to a page with parts of a form prefilled.]]></summary></entry><entry><title type="html">Jump to dev</title><link href="https://kodare.net/2025/02/09/jump-to-dev.html" rel="alternate" type="text/html" title="Jump to dev" /><published>2025-02-09T00:00:00+00:00</published><updated>2025-02-09T00:00:00+00:00</updated><id>https://kodare.net/2025/02/09/jump-to-dev</id><content type="html" xml:base="https://kodare.net/2025/02/09/jump-to-dev.html"><![CDATA[<p>At <a href="https://dryft.se">Dryft</a> I have the luxury of a small production database, so I can mirror prod to my local dev machine in ~3 minutes. I use this a lot to get quick local reproduction of issues. I used to copy-paste the relevant URL part to my local dev and felt quite happy with it. Then I realized that I could just paste the entire URL after <code class="language-plaintext highlighter-rouge">http://localhost:8000/</code>! My browser autocompleted that part anyway, and URLs like <code class="language-plaintext highlighter-rouge">http://localhost:8000/https://[...]</code> are obviously invalid for normal uses cases, so can cleanly be made to just strip out the domain part and redirect.</p>

<p>This is the middleware I came up with to do this:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">domain_strip_middleware</span><span class="p">(</span><span class="n">get_response</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">domain_strip_middleware_inner</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">settings</span><span class="p">.</span><span class="n">DEBUG</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">get_response</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>

        <span class="n">m</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">match</span><span class="p">(</span>
            <span class="sa">r</span><span class="s">'/https?://(?P&lt;domain&gt;[^/]*)(?P&lt;path&gt;/.*)'</span><span class="p">,</span> 
            <span class="n">request</span><span class="p">.</span><span class="n">get_full_path</span><span class="p">()</span>
        <span class="p">)</span>
        <span class="k">if</span> <span class="n">m</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">HttpResponseRedirect</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">groupdict</span><span class="p">()[</span><span class="s">'path'</span><span class="p">])</span>

        <span class="k">return</span> <span class="n">get_response</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">domain_strip_middleware_inner</span>
</code></pre></div></div>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><summary type="html"><![CDATA[At Dryft I have the luxury of a small production database, so I can mirror prod to my local dev machine in ~3 minutes. I use this a lot to get quick local reproduction of issues. I used to copy-paste the relevant URL part to my local dev and felt quite happy with it. Then I realized that I could just paste the entire URL after http://localhost:8000/! My browser autocompleted that part anyway, and URLs like http://localhost:8000/https://[...] are obviously invalid for normal uses cases, so can cleanly be made to just strip out the domain part and redirect.]]></summary></entry><entry><title type="html">Strip spaces</title><link href="https://kodare.net/2025/01/23/strip-spaces.html" rel="alternate" type="text/html" title="Strip spaces" /><published>2025-01-23T00:00:00+00:00</published><updated>2025-01-23T00:00:00+00:00</updated><id>https://kodare.net/2025/01/23/strip-spaces</id><content type="html" xml:base="https://kodare.net/2025/01/23/strip-spaces.html"><![CDATA[<style>
.language-plaintext {
    white-space: pre;
}
</style>

<p>When I joined TriOptima back in 2010, a common pattern emerged where names of things were slightly off because of stray whitespaces. Sometimes we had duplicates like <code class="language-plaintext highlighter-rouge">"foo"</code>, <code class="language-plaintext highlighter-rouge">"foo "</code> and <code class="language-plaintext highlighter-rouge">" foo"</code> in the database. Sometimes we couldn’t find stuff in logs because you searched for <code class="language-plaintext highlighter-rouge">"foo was deleted"</code>, when actually you had to search for <code class="language-plaintext highlighter-rouge">"foo  was deleted"</code> (notice the double space!). Sorting was “broken” because <code class="language-plaintext highlighter-rouge">" foo"</code> and <code class="language-plaintext highlighter-rouge">"foobar"</code> are not next to each other. And more issues that I can’t remember…</p>

<p>It was <em>everywhere</em>, causing problems across the entire code base. Each individual issue was easily fixed by cleaning up the data, but it added up to an annoying support burden. My fix at the time was to make a function that took a <a href="https://djangoproject.com">Django</a> <code class="language-plaintext highlighter-rouge">Form</code> instance and returned a new instance with space stripping on all fields. Something like:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">form</span> <span class="o">=</span> <span class="n">auto_strip</span><span class="p">(</span><span class="n">Form</span><span class="p">(...))</span>
</code></pre></div></div>

<p>After I added that to every single Django form in the entire code base that slow and steady trickle of bugs and annoyances just stopped. From seeing a few a month consistently to zero for the next ~9 years. Even better: I never got a complaint about it.</p>

<p>This was fixed in Django 1.9 after some fierce debating back and forth (“will <em>never</em> happen” was uttered). In <a href="ttps://docs.iommi.rocks/">iommi</a> forms we’ve had this since the beginning, which turns out to be a few months ahead of when Django took this decision (although it was tri.form and not iommi back then).</p>

<h2 id="just-when-you-think-youre-out">Just when you think you’re out</h2>

<p>Unfortunately the story doesn’t end here. I started getting this issue again, and it took me a while to realize it’s because of SPA-like pages that uses Pydantic serializers instead of a form library. To solve this I added this base class for my schemas:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Schema</span><span class="p">(</span><span class="n">ninja</span><span class="p">.</span><span class="n">Schema</span><span class="p">):</span>
    <span class="o">@</span><span class="n">validator</span><span class="p">(</span><span class="s">'*'</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">strip_spaces</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">v</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span> <span class="ow">and</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">v</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="s">' '</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">v</span>
</code></pre></div></div>

<p>The reason I’m not stripping spaces if the text contains a newline is to avoid situations where you have multiline text fields with indented code. Maybe it’s just programmers who will care, but we tend to care a lot :P</p>

<p>Modifying data silently and by default like this sounds like a bad idea and I also get a little pit in my stomach when I think about it with that frame of mind, but this specific case seems like all win and no downside from my 14 years of experience doing it.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Don’t automate screenshots, automate iframes</title><link href="https://kodare.net/2025/01/14/iframes-not-screenshots.html" rel="alternate" type="text/html" title="Don’t automate screenshots, automate iframes" /><published>2025-01-14T00:00:00+00:00</published><updated>2025-01-14T00:00:00+00:00</updated><id>https://kodare.net/2025/01/14/iframes-not-screenshots</id><content type="html" xml:base="https://kodare.net/2025/01/14/iframes-not-screenshots.html"><![CDATA[<p>There’s a lot of tools out there to automate taking screenshots for documentation of web apps/libraries. Screenshots are certainly sometimes a good idea, but they have some serious downsides:</p>

<ul>
  <li>As you’re sending pixels instead of text, screen readers don’t work</li>
  <li>Screenshots adjust badly to zoom levels</li>
  <li>Responsive layouts don’t work</li>
  <li>Automatic dark mode selection doesn’t work</li>
  <li>They are larger to download</li>
  <li>They are hard to generate</li>
  <li>They are slow to generate</li>
</ul>

<p>When writing the iommi documentation I realized that I can bypass all that by using embedded iframes instead of screenshots. Instead of spinning up a headless Chrome, writing playwright/selenium automation and suffering through all that, I can render the page I am documenting like normal, and save the html to disk, which is then linked to with an iframe. It required some custom tooling, but <a href="https://docs.iommi.rocks/en/latest/cookbook_tables.html">check out the iommi cookbook</a> for examples and I think you’ll agree the results are pretty great.</p>

<p>Here’s a sample test from the cookbook that generates the iframe, and then the rST file for the docs:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">test_how_do_you_turn_off_pagination</span><span class="p">(</span><span class="n">small_discography</span><span class="p">):</span>
    <span class="c1"># language=rst
</span>    <span class="s">"""
    How do you turn off pagination?
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Specify `page_size=None`:
    """</span>

    <span class="n">table</span> <span class="o">=</span> <span class="n">Table</span><span class="p">(</span>
        <span class="n">auto__model</span><span class="o">=</span><span class="n">Album</span><span class="p">,</span>
        <span class="n">page_size</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="c1"># @test
</span>    <span class="n">show_output</span><span class="p">(</span><span class="n">table</span><span class="p">)</span>
    <span class="c1"># @end
</span>    
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">show_output</code> command handles all the saving to the right filename. Our custom tooling then weaves that together in the rST output to produce the iframe.</p>

<p>We write all our documentation as tests first, and sometimes the documentation is all the tests we need for new features. But the details of that is the topic for another blog post 😜</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><category term="iommi" /><category term="documentation" /><summary type="html"><![CDATA[There’s a lot of tools out there to automate taking screenshots for documentation of web apps/libraries. Screenshots are certainly sometimes a good idea, but they have some serious downsides:]]></summary></entry><entry><title type="html">Django clean urls.py</title><link href="https://kodare.net/2024/10/31/django-clean-urls.html" rel="alternate" type="text/html" title="Django clean urls.py" /><published>2024-10-31T00:00:00+00:00</published><updated>2024-10-31T00:00:00+00:00</updated><id>https://kodare.net/2024/10/31/django-clean-urls</id><content type="html" xml:base="https://kodare.net/2024/10/31/django-clean-urls.html"><![CDATA[<p>Managing URL mappings in Django can become a bit of a mess as a project grows, and you often end up with many tabs in your editor named <code class="language-plaintext highlighter-rouge">urls.py</code> which is not very helpful. In several discussions on the <a href="https://unofficial-django-discord.github.io/">Unofficial Django Discord</a>, <a href="https://github.com/cb109">cb109</a> kept mentioning that he’s got a single big <code class="language-plaintext highlighter-rouge">urls.py</code> file. At some point I started noticing every time I navigated to the wrong <code class="language-plaintext highlighter-rouge">urls.py</code> and how I got a little annoyed every time.</p>

<p>I made the switch to one big file to see how I liked it, and I am very happy I did. It’s much nicer, and my work project isn’t super large, so it’s only ~630 lines anyway. It’s much nicer to find stuff and add new path mappings. Highly recommend.</p>

<h2 id="nesting">Nesting</h2>

<p>After doing this a while I noticed that this pattern came up frequently:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/'</span><span class="p">,</span> <span class="n">ProjectPage</span><span class="p">().</span><span class="n">as_view</span><span class="p">()),</span>
<span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/duplicate/'</span><span class="p">,</span> <span class="n">duplicate_project</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/ev/'</span><span class="p">,</span> <span class="n">ev_index</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/ev/edit/'</span><span class="p">,</span> <span class="n">ev_edit</span><span class="p">),</span>
<span class="p">...</span>
</code></pre></div></div>

<p><a href="https://github.com/isik-kaplan">Işık</a> reminded me that <code class="language-plaintext highlighter-rouge">include()</code> in Django can clean that up:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/'</span><span class="p">,</span> <span class="n">include</span><span class="p">([</span>
    <span class="n">path</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="n">ProjectPage</span><span class="p">().</span><span class="n">as_view</span><span class="p">()),</span>
    <span class="n">path</span><span class="p">(</span><span class="s">'duplicate/'</span><span class="p">,</span> <span class="n">duplicate_project</span><span class="p">),</span>
    <span class="n">path</span><span class="p">(</span><span class="s">'ev/'</span><span class="p">,</span> <span class="n">ev_index</span><span class="p">),</span>
    <span class="n">path</span><span class="p">(</span><span class="s">'ev/edit/'</span><span class="p">,</span> <span class="n">ev_edit</span><span class="p">),</span>
<span class="p">])),</span>
<span class="p">...</span>
</code></pre></div></div>

<p>Not super clean, but better. I also didn’t like the verboseness of the <a href="https://docs.iommi.rocks/">iommi</a> views like <code class="language-plaintext highlighter-rouge">ProjectPage().as_view()</code> which also felt a bit messy, so after a while I wrote this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">path</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">view_or_list</span><span class="p">,</span> <span class="n">kwargs</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">view_or_list</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
        <span class="k">assert</span> <span class="n">kwargs</span> <span class="ow">is</span> <span class="bp">None</span>
        <span class="k">return</span> <span class="n">orig_path</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">view_or_list</span><span class="p">))</span>
    <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">view_or_list</span><span class="p">,</span> <span class="nb">type</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">orig_path</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">view_or_list</span><span class="p">().</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">kwargs</span><span class="o">=</span><span class="n">kwargs</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">orig_path</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">view_or_list</span><span class="p">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">kwargs</span><span class="o">=</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="k">except</span> <span class="nb">AttributeError</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">orig_path</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">view_or_list</span><span class="p">,</span> <span class="n">kwargs</span><span class="o">=</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div>

<p>Now the same path mapping can look like this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">path</span><span class="p">(</span><span class="s">'projects/&lt;project_pk&gt;/'</span><span class="p">,</span> <span class="p">[</span>
    <span class="n">path</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="n">ProjectPage</span><span class="p">),</span>
    <span class="n">path</span><span class="p">(</span><span class="s">'duplicate/'</span><span class="p">,</span> <span class="n">duplicate_project</span><span class="p">),</span>
    <span class="n">path</span><span class="p">(</span><span class="s">'ev/'</span><span class="p">,</span> <span class="p">[</span>
        <span class="n">path</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="n">ev_index</span><span class="p">),</span>
        <span class="n">path</span><span class="p">(</span><span class="s">'edit/'</span><span class="p">,</span> <span class="n">ev_edit</span><span class="p">),</span>
    <span class="p">]),</span>
<span class="p">]),</span>
<span class="p">...</span>
</code></pre></div></div>

<p>The code above is hereby released in the public domain. Try it and see how you like it.</p>]]></content><author><name>Anders Hovmöller</name></author><category term="programming" /><category term="python" /><category term="django" /><summary type="html"><![CDATA[Managing URL mappings in Django can become a bit of a mess as a project grows, and you often end up with many tabs in your editor named urls.py which is not very helpful. In several discussions on the Unofficial Django Discord, cb109 kept mentioning that he’s got a single big urls.py file. At some point I started noticing every time I navigated to the wrong urls.py and how I got a little annoyed every time.]]></summary></entry></feed>