<?xml version="1.0" encoding="utf-8"?>



<feed xmlns="http://www.w3.org/2005/Atom"
    xmlns:fh="http://purl.org/syndication/history/1.0"
    xmlns:at="http://purl.org/atompub/tombstones/1.0">

    <title>Publ</title>
    <subtitle>A personal publishing system for the modern web</subtitle>
    <link href="https://publ.beesbuzz.biz/feed" rel="self" />
    <link href="https://publ.beesbuzz.biz/feed" rel="current" />
    <link href="https://busybee.superfeedr.com" rel="hub" />
    
    <link href="https://publ.beesbuzz.biz/blog/feed?date=2025-04" rel="prev-archive" />
    
    
    <link href="https://publ.beesbuzz.biz/" />
    
    <id>tag:publ.beesbuzz.biz,2020-01-07:_all</id>
    <updated>2026-03-13T11:44:02-07:00</updated>

    
    <entry>
        <title>Tag browser</title>
        <link href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser" rel="alternate" type="text/html" />
        <published>2026-03-13T11:44:02-07:00</published>
        <updated>2026-03-13T11:44:02-07:00</updated>
        <id>urn:uuid:3ba836df-d547-42df-a2f2-b918cfc8b60f</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Allow users to narrow in on content based on multiple tags at a time.</p>

<p>Here is an implementation of a basic tag browser that allows users to filter content based on multiple criteria.</p><p>Note that you&rsquo;ll also want to implement the <a href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations">crawler mitigations</a>.</p><p>First, you&rsquo;ll need to modify your content view with <code>tag_filter=&#39;ALL&#39;</code>; for example, at the top of your template, have a line like:</p><figure class="blockcode"><figcaption>index.html</figcaption><pre class="highlight" data-language="html+jinja" data-line-numbers><span class="line" id="e577cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb1L1"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="nv">view</span> <span class="o">=</span> <span class="nv">view</span><span class="o">(</span><span class="nv">tag_filter</span><span class="o">=</span><span class="s1">&#39;ALL&#39;</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
</pre></figure><p>Next, this block will give you a tag filter that shows the top 10 tags present in the current view in addition to any tags that are currently selected, with a toggle to show all tags that have more than one entry:</p><figure class="blockcode"><pre class="highlight" data-language="html+jinja" data-line-numbers><span class="line" id="e577cb2L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L1"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">if</span> <span class="k">not</span> <span class="nv">user.is_bot</span> <span class="k">and</span> <span class="nv">category.tags</span><span class="o">(</span><span class="nv">recurse</span><span class="o">=</span><span class="nv">view.spec.recurse</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L2"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;tags&quot;</span><span class="p">&gt;&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>Topic Tags<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L3"></a><span class="line-content"></span></span>
<span class="line" id="e577cb2L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L4"></a><span class="line-content">    <span class="cp">{%</span> <span class="k">macro</span> <span class="nv">tag_link</span><span class="o">(</span><span class="nv">view</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L5"></a><span class="line-content">    <span class="cp">{{</span><span class="nv">view.link</span><span class="o">(</span><span class="nv">template</span><span class="o">=</span><span class="nv">template</span><span class="o">,</span><span class="nv">all_tags</span><span class="o">=</span><span class="m">1</span> <span class="k">if</span> <span class="nv">request.args.all_tags</span> <span class="k">else</span> <span class="kp">None</span><span class="o">)</span><span class="cp">}}</span></span></span>
<span class="line" id="e577cb2L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L6"></a><span class="line-content">    <span class="cp">{%</span>- <span class="k">endmacro</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L7"></a><span class="line-content"></span></span>
<span class="line" id="e577cb2L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L8"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L9"></a><span class="line-content">        <span class="cp">{%</span>- <span class="k">if</span> <span class="nv">view.tags</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L10"></a><span class="line-content">        <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;clear&quot;</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">tag_link</span><span class="o">(</span><span class="nv">view</span><span class="o">(</span><span class="nv">tag</span><span class="o">=</span><span class="kp">None</span><span class="o">))</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span>Clear filter<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L11"></a><span class="line-content">        <span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L12"></a><span class="line-content"></span></span>
<span class="line" id="e577cb2L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L13"></a><span class="line-content">        <span class="cp">{%</span> <span class="k">set</span> <span class="nv">tags</span> <span class="o">=</span> <span class="nv">category.tags</span><span class="o">(</span><span class="nv">tag</span><span class="o">=</span><span class="nv">view.spec.tag</span> <span class="k">if</span> <span class="nv">view.spec.tag</span> <span class="k">else</span> <span class="kp">None</span><span class="o">,</span></span></span>
<span class="line" id="e577cb2L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L14"></a><span class="line-content">            <span class="nv">tag_filter</span><span class="o">=</span><span class="s1">&#39;ALL&#39;</span><span class="o">,</span><span class="nv">recurse</span><span class="o">=</span><span class="nv">view.spec.recurse</span><span class="o">)</span></span></span>
<span class="line" id="e577cb2L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L15"></a><span class="line-content">            <span class="o">|</span> <span class="nf">sort</span><span class="o">(</span><span class="nv">attribute</span><span class="o">=</span><span class="s1">&#39;count&#39;</span><span class="o">,</span><span class="nv">reverse</span><span class="o">=</span><span class="kp">True</span><span class="o">)</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L16"></a><span class="line-content">        <span class="cp">{%</span> <span class="k">for</span> <span class="nv">name</span><span class="o">,</span><span class="nv">count</span> <span class="k">in</span> <span class="o">(</span><span class="nv">tags</span> <span class="k">if</span> <span class="nv">request.args.all_tags</span> <span class="k">else</span> <span class="nv">tags</span><span class="o">[:</span><span class="m">10</span><span class="o">+</span><span class="nv">view.tags</span><span class="o">|</span><span class="nf">length</span><span class="o">])</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L17"></a><span class="line-content">            <span class="cp">{%</span>- <span class="k">if</span> <span class="nv">count</span> <span class="o">&gt;</span> <span class="m">1</span> <span class="k">and</span></span></span>
<span class="line" id="e577cb2L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L18"></a><span class="line-content">                <span class="o">(</span><span class="nv">request.args.all_tags</span> <span class="k">or</span> <span class="nb">loop</span><span class="nv">.index</span> <span class="o">&lt;</span> <span class="m">10</span> <span class="k">or</span> <span class="nv">name</span> <span class="k">in</span> <span class="nv">view.tags</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L19"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L19"></a><span class="line-content">                <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">name</span> <span class="k">in</span> <span class="nv">view.tags</span> <span class="k">and</span> <span class="s1">&#39;selected&#39;</span> <span class="k">or</span> <span class="s1">&#39;&#39;</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;nofollow&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">tag_link</span><span class="o">(</span><span class="nv">view.tag_toggle</span><span class="o">(</span><span class="nv">name</span><span class="o">))</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span><span class="cp">{{</span><span class="nv">name</span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> (<span class="cp">{{</span><span class="nv">count</span><span class="cp">}}</span>)<span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L20"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L20"></a><span class="line-content">            <span class="cp">{%</span>- <span class="k">endif</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L21"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L21"></a><span class="line-content">        <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L22"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L22"></a><span class="line-content"></span></span>
<span class="line" id="e577cb2L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L23"></a><span class="line-content">    <span class="cp">{%</span> <span class="k">if</span> <span class="nv">request.args.all_tags</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L24"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L24"></a><span class="line-content">        <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;top-only&quot;</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;nofollow&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">view.link</span><span class="o">(</span><span class="nv">template</span><span class="o">=</span><span class="nv">template</span><span class="o">)</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span>Top tags only<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L25"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L25"></a><span class="line-content">    <span class="cp">{%</span> <span class="k">elif</span> <span class="nv">tags</span><span class="o">|</span><span class="nf">length</span> <span class="o">&gt;</span> <span class="m">10</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L26"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L26"></a><span class="line-content">        <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;all&quot;</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;nofollow&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">view.link</span><span class="o">(</span><span class="nv">template</span><span class="o">=</span><span class="nv">template</span><span class="o">,</span><span class="nv">all_tags</span><span class="o">=</span><span class="m">1</span><span class="o">)</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span>Show all tags<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L27"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L27"></a><span class="line-content">    <span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span></span></span>
<span class="line" id="e577cb2L28"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L28"></a><span class="line-content">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L29"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L29"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span>
<span class="line" id="e577cb2L30"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb2L30"></a><span class="line-content"><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span></span></span>
</pre></figure><p>This stylesheet snippet will add useful styling to the tag list, as well:</p><figure class="blockcode"><pre class="highlight" data-language="css" data-line-numbers><span class="line" id="e577cb3L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L1"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">ul</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L2"></a><span class="line-content"><span class="w">    </span><span class="k">list-style-type</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L3"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L4"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L5"></a><span class="line-content"><span class="w">    </span><span class="k">text-indent</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="w"> </span><span class="kc">hanging</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L6"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L7"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">:</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L8"></a><span class="line-content"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;☐\00a0&#39;</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L9"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L10"></a><span class="line-content"></span></span>
<span class="line" id="e577cb3L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L11"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">clear</span><span class="p">:</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L12"></a><span class="line-content"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;☒\00a0&#39;</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L13"></a><span class="line-content"><span class="w">    </span><span class="k">font-weight</span><span class="p">:</span><span class="w"> </span><span class="kc">bold</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L14"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L15"></a><span class="line-content"></span></span>
<span class="line" id="e577cb3L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L16"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">selected</span><span class="p">:</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L17"></a><span class="line-content"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;☑︎\00a0&#39;</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L18"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L19"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L19"></a><span class="line-content"></span></span>
<span class="line" id="e577cb3L20"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L20"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">all</span><span class="o">,</span><span class="w"> </span><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">top-only</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L21"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L21"></a><span class="line-content"><span class="w">    </span><span class="k">font-style</span><span class="p">:</span><span class="w"> </span><span class="kc">italic</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L22"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L22"></a><span class="line-content"><span class="p">}</span></span></span>
<span class="line" id="e577cb3L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L23"></a><span class="line-content"><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">all</span><span class="p">:</span><span class="nd">before</span><span class="o">,</span><span class="w"> </span><span class="p">#</span><span class="nn">tags</span><span class="w"> </span><span class="nt">li</span><span class="p">.</span><span class="nc">top-only</span><span class="p">:</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e577cb3L24"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L24"></a><span class="line-content"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">;</span></span></span>
<span class="line" id="e577cb3L25"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/577-Tag-browser#e577cb3L25"></a><span class="line-content"><span class="p">}</span></span></span>
</pre></figure>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Publ v0.8.8 released</title>
        <link href="https://publ.beesbuzz.biz/blog/1032-Publ-v0.8.8-released" rel="alternate" type="text/html" />
        <published>2026-02-26T13:36:09-08:00</published>
        <updated>2026-02-26T13:36:09-08:00</updated>
        <id>urn:uuid:52ccd200-d305-4c66-b52e-854d863c5751</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>I&rsquo;ve released Publ v0.8.8. It adds one new feature, namely the <code>user</code> object now has an <a href="https://publ.beesbuzz.biz/manual/api/733-User-object#is_bot"><code>is_bot</code></a> flag to indicate whether the site is being perused by a declared bot.</p><p>This functionality is particularly useful for implementing <a href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations">crawler mitigations</a>, which have been quite effective with the current onslaught of badly-behaved crawlers.</p><p>I have also finally begun to collect some <a href="https://publ.beesbuzz.biz/manual/recipes/">useful recipes</a>.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Calendar display</title>
        <link href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display" rel="alternate" type="text/html" />
        <published>2026-02-26T10:58:13-08:00</published>
        <updated>2026-02-26T10:58:13-08:00</updated>
        <id>urn:uuid:981db6de-d22f-432c-8775-7deb98ca334b</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>How to render a calendar widget</p>

<p>Here is a template snippet for rendering out a calendar for a particular view.</p><figure class="blockcode"><pre class="highlight" data-language="html+jinja" data-line-numbers><span class="line" id="e1101cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L1"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">macro</span> <span class="nv">render_calendar</span><span class="o">(</span><span class="nv">view</span><span class="o">,</span> <span class="nv">week_start</span><span class="o">=</span><span class="m">7</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L2"></a><span class="line-content"></span></span>
<span class="line" id="e1101cb1L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L3"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">if</span> <span class="k">not</span> <span class="nv">view.is_current</span> <span class="k">and</span> <span class="nv">view.newest</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L4"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="nv">month</span> <span class="o">=</span> <span class="nv">view.newest.date.floor</span><span class="o">(</span><span class="s1">&#39;month&#39;</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L5"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">else</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L6"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="nv">month</span> <span class="o">=</span> <span class="nv">arrow.now</span><span class="o">()</span><span class="nv">.floor</span><span class="o">(</span><span class="s1">&#39;month&#39;</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L7"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">endif</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L8"></a><span class="line-content"></span></span>
<span class="line" id="e1101cb1L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L9"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="nv">content</span> <span class="o">=</span> <span class="nv">view</span><span class="o">(</span><span class="nv">date</span><span class="o">=</span><span class="nv">month.format</span><span class="o">(</span><span class="s1">&#39;YYYY-MM&#39;</span><span class="o">))</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L10"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="nv">firstday</span><span class="o">,</span><span class="kp">_</span> <span class="o">=</span> <span class="nv">month.span</span><span class="o">(</span><span class="s1">&#39;week&#39;</span><span class="o">,</span><span class="nv">week_start</span><span class="o">=</span><span class="nv">week_start</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L11"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">set</span> <span class="kp">_</span><span class="o">,</span><span class="nv">lastday</span> <span class="o">=</span> <span class="nv">month.ceil</span><span class="o">(</span><span class="s1">&#39;month&#39;</span><span class="o">)</span><span class="nv">.span</span><span class="o">(</span><span class="s1">&#39;week&#39;</span><span class="o">,</span><span class="nv">week_start</span><span class="o">=</span><span class="nv">week_start</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L12"></a><span class="line-content"></span></span>
<span class="line" id="e1101cb1L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L13"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">table</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;calendar&quot;</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L14"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">thead</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L15"></a><span class="line-content">        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span><span class="cp">{%</span>- <span class="k">for</span> <span class="nv">day</span> <span class="k">in</span> <span class="nv">arrow.Arrow.range</span><span class="o">(</span><span class="s1">&#39;day&#39;</span><span class="o">,</span><span class="nv">firstday</span><span class="o">,</span><span class="nv">firstday.shift</span><span class="o">(</span><span class="nv">days</span><span class="o">=</span><span class="m">6</span><span class="o">))</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L16"></a><span class="line-content">            <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;</span><span class="cp">{{</span><span class="nv">day.format</span><span class="o">(</span><span class="s1">&#39;ddd&#39;</span><span class="o">)</span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L17"></a><span class="line-content">        <span class="cp">{%</span>- <span class="k">endfor</span> -<span class="cp">%}</span><span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L18"></a><span class="line-content">    <span class="p">&lt;/</span><span class="nt">thead</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L19"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L19"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">tbody</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L20"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L20"></a><span class="line-content"></span></span>
<span class="line" id="e1101cb1L21"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L21"></a><span class="line-content">        <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">week</span> <span class="k">in</span> <span class="nv">arrow.Arrow.range</span><span class="o">(</span><span class="s1">&#39;week&#39;</span><span class="o">,</span><span class="nv">firstday</span><span class="o">,</span><span class="nv">lastday</span><span class="o">)</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L22"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L22"></a><span class="line-content">        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L23"></a><span class="line-content">            <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">day</span> <span class="k">in</span> <span class="nv">arrow.Arrow.range</span><span class="o">(</span><span class="s1">&#39;day&#39;</span><span class="o">,</span><span class="nv">week</span><span class="o">,</span><span class="nv">week.shift</span><span class="o">(</span><span class="nv">days</span><span class="o">=</span><span class="m">6</span><span class="o">))</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L24"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L24"></a><span class="line-content">            <span class="cp">{%</span>- <span class="k">set</span> <span class="nv">items</span> <span class="o">=</span> <span class="nv">content</span><span class="o">(</span><span class="nv">date</span><span class="o">=</span><span class="nv">day.format</span><span class="o">(</span><span class="s1">&#39;YYYYMMDD&#39;</span><span class="o">))</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L25"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L25"></a><span class="line-content">            <span class="p">&lt;</span><span class="nt">td</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="o">[</span><span class="s1">&#39;entries&#39;</span> <span class="k">if</span> <span class="nv">items.entries</span> <span class="k">else</span> <span class="s1">&#39;empty&#39;</span><span class="o">,</span></span></span>
<span class="line" id="e1101cb1L26"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L26"></a><span class="line-content">            <span class="s1">&#39;thismonth&#39;</span> <span class="k">if</span> <span class="nv">day.month</span><span class="o">==</span><span class="nv">month.month</span> <span class="k">else</span> <span class="s1">&#39;othermonth&#39;</span><span class="o">,</span></span></span>
<span class="line" id="e1101cb1L27"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L27"></a><span class="line-content">            <span class="s1">&#39;today&#39;</span> <span class="k">if</span> <span class="nv">day.date</span><span class="o">()</span> <span class="o">==</span> <span class="nv">arrow.get</span><span class="o">()</span><span class="nv">.date</span><span class="o">()</span> <span class="k">else</span> <span class="s1">&#39;future&#39;</span> <span class="k">if</span> <span class="nv">day.date</span><span class="o">()</span> <span class="o">&gt;</span> <span class="nv">arrow.get</span><span class="o">()</span><span class="nv">.date</span><span class="o">()</span> <span class="k">else</span> <span class="s1">&#39;past&#39;</span><span class="o">]|</span><span class="nf">join</span><span class="o">(</span><span class="s1">&#39; &#39;</span><span class="o">)</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;&lt;</span><span class="nt">time</span> <span class="na">datetime</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">day.format</span><span class="o">(</span><span class="s1">&#39;YYYY-MM-DD&#39;</span><span class="o">)</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span><span class="cp">{{</span><span class="nv">day.format</span><span class="o">(</span><span class="s1">&#39;D&#39;</span><span class="o">)</span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">time</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L28"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L28"></a><span class="line-content">                <span class="cp">{%</span>- <span class="k">if</span> <span class="nv">items.entries</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L29"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L29"></a><span class="line-content">                <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L30"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L30"></a><span class="line-content">                    <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">entry</span> <span class="k">in</span> <span class="nv">items.entries</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L31"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L31"></a><span class="line-content">                    <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">entry.type</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">entry.link</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span><span class="cp">{{</span><span class="nv">entry.title</span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L32"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L32"></a><span class="line-content">                    <span class="cp">{%</span>- <span class="k">endfor</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L33"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L33"></a><span class="line-content">                <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L34"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L34"></a><span class="line-content">                <span class="cp">{%</span>- <span class="k">endif</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L35"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L35"></a><span class="line-content">            <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L36"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L36"></a><span class="line-content">            <span class="cp">{%</span>- <span class="k">endfor</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L37"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L37"></a><span class="line-content">        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L38"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L38"></a><span class="line-content">        <span class="cp">{%</span>- <span class="k">endfor</span> -<span class="cp">%}</span></span></span>
<span class="line" id="e1101cb1L39"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L39"></a><span class="line-content">    <span class="p">&lt;/</span><span class="nt">tbody</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L40"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L40"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span></span></span>
<span class="line" id="e1101cb1L41"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L41"></a><span class="line-content"></span></span>
<span class="line" id="e1101cb1L42"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/1101-Calendar-display#e1101cb1L42"></a><span class="line-content"><span class="cp">{%</span>- <span class="k">endmacro</span> -<span class="cp">%}</span></span></span>
</pre></figure><p>You can now use <code>render_calendar</code> to display a calendar that shows days with items on it, where the month will be based on the most recent visible item in the view.</p><p>The <code>week_start</code> parameter indicates which day of the week the week starts on (1 = Monday, 7 = Sunday).</p><p>Each cell in the table will have the following CSS classes:</p>
<ul>
<li><code>entries</code> if there are items on that day</li>
<li><code>empty</code> for days with no items</li>
<li><code>thismonth</code> for days which are on the current month</li>
<li><code>othermonth</code> for days which come from neighboring months</li>
<li><code>today</code> for the current date</li>
<li><code>future</code> for days in the future</li>
<li><code>past</code> for days in the past</li>
</ul>


]]>
        </content>
    </entry>
    
    <entry>
        <title>Crawler mitigations</title>
        <link href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations" rel="alternate" type="text/html" />
        <published>2026-02-26T10:48:15-08:00</published>
        <updated>2026-02-26T10:48:15-08:00</updated>
        <id>urn:uuid:b2ccc8e0-270c-4cde-8b36-b3c313cd0c2e</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Reducing server load caused by badly-behaved web crawlers.</p>

<p>In this day and age it&rsquo;s necessary to have mitigations in place to prevent badly-behaving web crawlers from taking down every single website. Both legitimate search bots <em>and</em> things like AI/LLM crawlers do a bunch of nasty tricks to try to extract as much detail as possible from a website, even when signals are present to indicate which pages are worth crawling.</p><p>This recipe is a starting point for implementing a simple &ldquo;sentience check&rdquo; into Publ websites, which has shown itself to be just as effective as more heavyweight options such as Anubis or Cloudflare&rsquo;s &ldquo;managed challenge&rdquo; CAPTCHA. Setting it up is pretty simple:</p>
<ol>
<li><p>Add the following functions to your <code>app.py</code>:</p><figure class="blockcode"><figcaption>app.py</figcaption><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e210cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L1"></a><span class="line-content"><span class="nd">@app</span><span class="o">.</span><span class="n">before_request</span></span></span>
<span class="line" id="e210cb1L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L2"></a><span class="line-content"><span class="k">def</span><span class="w"> </span><span class="nf">antiscraper</span><span class="p">():</span></span></span>
<span class="line" id="e210cb1L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L3"></a><span class="line-content"><span class="w">    </span><span class="sd">&quot;&quot;&quot; If something looks like a scraper, give it a sentience check &quot;&quot;&quot;</span></span></span>
<span class="line" id="e210cb1L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L4"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">arrow</span></span></span>
<span class="line" id="e210cb1L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L5"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">flask</span></span></span>
<span class="line" id="e210cb1L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L6"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">werkzeug.exceptions</span></span></span>
<span class="line" id="e210cb1L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L7"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L8"></a><span class="line-content">    <span class="c1"># Logged-in users have passed the test already</span></span></span>
<span class="line" id="e210cb1L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L9"></a><span class="line-content">    <span class="k">if</span> <span class="n">publ</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">get_active</span><span class="p">():</span></span></span>
<span class="line" id="e210cb1L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L10"></a><span class="line-content">        <span class="k">return</span></span></span>
<span class="line" id="e210cb1L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L11"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L12"></a><span class="line-content">    <span class="k">try</span><span class="p">:</span></span></span>
<span class="line" id="e210cb1L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L13"></a><span class="line-content">        <span class="c1"># Check to see if the session data matches the user&#39;s data</span></span></span>
<span class="line" id="e210cb1L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L14"></a><span class="line-content">        <span class="k">if</span> <span class="p">(</span><span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;addr&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">remote_addr</span> <span class="ow">and</span></span></span>
<span class="line" id="e210cb1L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L15"></a><span class="line-content">            <span class="n">arrow</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;sid&#39;</span><span class="p">])</span> <span class="o">&gt;</span> <span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">shift</span><span class="p">(</span><span class="n">days</span><span class="o">=-</span><span class="mi">3</span><span class="p">)</span> <span class="ow">and</span></span></span>
<span class="line" id="e210cb1L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L16"></a><span class="line-content">            <span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;ua&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;User-Agent&#39;</span><span class="p">)):</span></span></span>
<span class="line" id="e210cb1L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L17"></a><span class="line-content">            <span class="k">return</span></span></span>
<span class="line" id="e210cb1L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L18"></a><span class="line-content">    <span class="k">except</span> <span class="p">(</span><span class="ne">KeyError</span><span class="p">,</span> <span class="n">arrow</span><span class="o">.</span><span class="n">ParserError</span><span class="p">):</span></span></span>
<span class="line" id="e210cb1L19"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L19"></a><span class="line-content">        <span class="k">pass</span></span></span>
<span class="line" id="e210cb1L20"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L20"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L21"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L21"></a><span class="line-content">    <span class="c1"># Send possible crawlers to the sentience test</span></span></span>
<span class="line" id="e210cb1L22"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L22"></a><span class="line-content">    <span class="c1"># Initial score: number of items in the GET arguments</span></span></span>
<span class="line" id="e210cb1L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L23"></a><span class="line-content">    <span class="n">score</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">items</span><span class="p">(</span><span class="kc">True</span><span class="p">)))</span></span></span>
<span class="line" id="e210cb1L24"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L24"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L25"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L25"></a><span class="line-content">    <span class="c1"># add any other custom signals to the score</span></span></span>
<span class="line" id="e210cb1L26"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L26"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L27"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L27"></a><span class="line-content">    <span class="k">if</span> <span class="n">score</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">:</span></span></span>
<span class="line" id="e210cb1L28"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L28"></a><span class="line-content">        <span class="k">raise</span> <span class="n">werkzeug</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">TooManyRequests</span><span class="p">(</span><span class="s2">&quot;Sentience test&quot;</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L29"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L29"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L30"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L30"></a><span class="line-content">    <span class="k">return</span></span></span>
<span class="line" id="e210cb1L31"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L31"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L32"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L32"></a><span class="line-content"><span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">&#39;/_zuul&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;POST&#39;</span><span class="p">])</span></span></span>
<span class="line" id="e210cb1L33"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L33"></a><span class="line-content"><span class="k">def</span><span class="w"> </span><span class="nf">gatekeeper</span><span class="p">():</span></span></span>
<span class="line" id="e210cb1L34"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L34"></a><span class="line-content"><span class="w">    </span><span class="sd">&quot;&quot;&quot; Sentience check callback &quot;&quot;&quot;</span></span></span>
<span class="line" id="e210cb1L35"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L35"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">arrow</span></span></span>
<span class="line" id="e210cb1L36"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L36"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">flask</span></span></span>
<span class="line" id="e210cb1L37"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L37"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L38"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L38"></a><span class="line-content">    <span class="n">redir</span> <span class="o">=</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">&#39;redir&#39;</span><span class="p">]</span></span></span>
<span class="line" id="e210cb1L39"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L39"></a><span class="line-content">    <span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;sid&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s1">&#39;sid&#39;</span><span class="p">]</span></span></span>
<span class="line" id="e210cb1L40"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L40"></a><span class="line-content">    <span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;addr&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">remote_addr</span></span></span>
<span class="line" id="e210cb1L41"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L41"></a><span class="line-content">    <span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;ua&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">flask</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;User-Agent&#39;</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L42"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L42"></a><span class="line-content">    <span class="k">return</span> <span class="n">flask</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">redir</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="mi">303</span><span class="p">)</span></span></span>
</pre></figure></li>
<li><p>Add the following template as <code>templates/429.html</code>:</p><figure class="blockcode"><figcaption>templates/429.html</figcaption><pre class="highlight" data-language="html+jinja" data-line-numbers><span class="line" id="e210cb2L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L1"></a><span class="line-content"><span class="cp">&lt;!DOCTYPE html&gt;</span></span></span>
<span class="line" id="e210cb2L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L2"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;&lt;</span><span class="nt">head</span><span class="p">&gt;&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Sentience test<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L3"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L4"></a><span class="line-content"><span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&quot;load&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span></span></span>
<span class="line" id="e210cb2L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L5"></a><span class="line-content"><span class="w">    </span><span class="nb">document</span><span class="p">.</span><span class="nx">forms</span><span class="p">[</span><span class="s1">&#39;proxy&#39;</span><span class="p">].</span><span class="nx">submit</span><span class="p">();</span></span></span>
<span class="line" id="e210cb2L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L6"></a><span class="line-content"><span class="p">});</span></span></span>
<span class="line" id="e210cb2L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L7"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L8"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L9"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L10"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Sentience check<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L11"></a><span class="line-content"></span></span>
<span class="line" id="e210cb2L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L12"></a><span class="line-content"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">&quot;POST&quot;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#39;proxy&#39;</span> <span class="na">action</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;gatekeeper&#39;</span><span class="o">)</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L13"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;hidden&quot;</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;redir&quot;</span> <span class="na">value</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">request.full_path</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L14"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;hidden&quot;</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;sid&quot;</span> <span class="na">value</span><span class="o">=</span><span class="s">&quot;</span><span class="cp">{{</span><span class="nv">arrow.now</span><span class="o">()</span><span class="cp">}}</span><span class="s">&quot;</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L15"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;submit&quot;</span> <span class="na">value</span><span class="o">=</span><span class="s">&quot;I&#39;m actually here&quot;</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L16"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L17"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span></span></span>
<span class="line" id="e210cb2L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb2L18"></a><span class="line-content"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span></span></span>
</pre></figure></li>
</ol>
<p>Out of the box, this will present a sentience check to anyone who is exhibiting basic bad-crawler behavior, which will be skipped for anything that has a cookie indicating that the test has previously been passed. For folks running browsers with JavaScript the test should automatically pass, as well.</p><p>The test is very simple; it just indicates that the form has been submitted within the past three days and that the agent submitting the form still has the same IP address and browser user agent, as those values will be stable during a particular browsing session and tend to be randomized by the AI crawlers. Keep in mind that there may be some situations in which the IP address for a legitimate user is randomized on a per-request basis, though (such as certain VPN or caching proxy configurations, or particularly dysfunctional CGNAT deployments).</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Recipes</title>
        <link href="https://publ.beesbuzz.biz/manual/16-Recipes" rel="alternate" type="text/html" />
        <published>2026-02-26T10:23:43-08:00</published>
        <updated>2026-02-26T10:23:43-08:00</updated>
        <id>urn:uuid:81f66836-f8b6-4595-b08a-ac0a7a521fd9</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Code snippets that are useful for improving your site functionality.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>v0.8.6 released</title>
        <link href="https://publ.beesbuzz.biz/blog/947-v0.8.6-released" rel="alternate" type="text/html" />
        <published>2026-01-08T02:22:41-08:00</published>
        <updated>2026-01-08T02:22:41-08:00</updated>
        <id>urn:uuid:e97eb4be-ee69-423f-a753-17de2bb5a069</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Publ v0.8.6 is out; in this release I just added a better default token store for <a href="https://publ.beesbuzz.biz/manual/865-Python-API#auth">authentication</a>, which will now work correctly in a multiprocess configuration (as opposed to the old default which would work with multiple threads but not multiple processes).</p><p>I&rsquo;ve also finally documented how to provide your own token store for more exotic configurations, although that, much like Sam Reich, has been here the whole time.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>v0.8.5</title>
        <link href="https://publ.beesbuzz.biz/blog/605-v0.8.5" rel="alternate" type="text/html" />
        <published>2025-11-29T10:55:45-08:00</published>
        <updated>2025-11-29T10:55:45-08:00</updated>
        <id>urn:uuid:962df214-d34e-41d9-8bc4-998de4484910</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Publ v0.8.5 is out now, and it changes how UUIDs are handled on entries. First of all, now there&rsquo;s a unique constraint on them (to prevent mishaps like copying an entry as a template and forgetting to remove the UUID), and secondly it&rsquo;s now gone back to using a UUID4 instead of a UUID5, since UUID5 made it way too easy to generate collisions.</p><p>In order to upgrade you&rsquo;ll have to remove any duplicate UUIDs. To find entries with duplicate UUIDs you can run something like:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">find . -name &#x27;*.md&#x27; -print0 | xargs -0 grep &#x27;UUID&#x27; | cut -f2- -d: | sort | uniq -c | sort -n</span></span>
</pre></figure><p>which will show how many entries have each UUID. For every UUID that has a count greater than 1, you&rsquo;ll have to do something like:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">grep -rl bc3ced56-93c3-4252-aa85-48fb85b253de .</span></span>
</pre></figure><p>which will then list all of the entries with UUID <code>bc3ced56-93c3-4252-aa85-48fb85b253de</code>, and then you can decide which one(s) to remove the UUID from, and then reindex the site (with <code>poetry flask publ reindex</code>) to generate the new UUIDs.</p><p>This will help to clear up weird issues where items don&rsquo;t appear as new in feeds, and also paves the way for some future functionality I&rsquo;ve been wanting to implement which relies on the second U in UUID being true.</p><p>Note that since this change modifies the database, it will require a full site reindex. I recommend fixing any known UUID issues before upgrading Publ.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>v0.8.3 released</title>
        <link href="https://publ.beesbuzz.biz/blog/102-v0.8.3-released" rel="alternate" type="text/html" />
        <published>2025-10-19T23:51:27-07:00</published>
        <updated>2025-10-19T23:51:27-07:00</updated>
        <id>urn:uuid:914faa28-1c11-5609-85de-c8c2b8211a51</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Here&rsquo;s a new version of Publ. The only thing new about it is that I&rsquo;ve completely rewritten the <code>Accept:</code> header mechanism to be much closer to what one would expect based on the HTTP standard, and also now you can <a href="https://publ.beesbuzz.biz/manual/865-Python-API#template_mimetypes">override MIME types on a per-template or per-extension basis</a>.</p><p>&hellip; And of course with a major rewrite that I decided to push out after midnight after a very long day of coding, of <em>course</em> it had some edge-case bugs, right on the edge cases I was trying to fix to begin with. Whoops. Now we&rsquo;re on 0.8.3.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Publ v0.8.0</title>
        <link href="https://publ.beesbuzz.biz/blog/215-Publ-v0.8.0" rel="alternate" type="text/html" />
        <published>2025-06-26T21:40:44-07:00</published>
        <updated>2025-06-26T21:40:44-07:00</updated>
        <id>urn:uuid:a190bb58-4bfa-51ac-8c83-c89b568d85d7</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>I&rsquo;ve just released v0.8.0 of Publ, which is a minor version change because this has the potential of changing the URL scheme in some cases.</p><p>This is also the first release announcement since v0.7.40, so here&rsquo;s what&rsquo;s changed since then:</p>
<ul>
<li>Fixed a bug with render arguments not being passed in correctly for card images</li>
<li>Improved the mechanism for adding fallback images to a card</li>
<li>Support Unicode characters (and internal periods, as seen on this very entry!) in URLs and tags</li>
</ul>
<p>Note that any affected URLs for existing entries may not change until the site has been fully reindexed. If this matters to you, run <code>flask publ reindex -f</code>.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Use with Cloudflare or other caching CDNs</title>
        <link href="https://publ.beesbuzz.biz/manual/deploying/675-Use-with-Cloudflare-or-other-caching-CDNs" rel="alternate" type="text/html" />
        <published>2025-05-15T22:38:02-07:00</published>
        <updated>2025-05-15T22:38:02-07:00</updated>
        <id>urn:uuid:b5261c4b-0e8b-517e-b4ed-8c7f5bb4f59b</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Using Publ with <a href="https://cloudflare.com/">Cloudflare</a> is fairly straightforward, but there are a few things you should keep in mind.</p>

<p>Because of the rise in badly-behaved website scraping bots, it can be very helpful for site performance to have a fronting CDN, such as <a href="https://cloudflare.com/">Cloudflare</a>, in front of your origin server. But there are a few things you need to keep in mind.</p><h3 id="675_h3_1_Caching"><a href="https://publ.beesbuzz.biz/manual/deploying/675-Use-with-Cloudflare-or-other-caching-CDNs#675_h3_1_Caching"></a>Caching</h3><p>When configuring caching, it is very important that you exclude the following paths from any aggressive caching behavior:</p>
<ul>
<li><code>/_cb/</code></li>
<li><code>/_login</code> and <code>/_login/</code></li>
<li><code>/_logout</code> and <code>/_logout/</code></li>
</ul>
<p>If these paths get cached, it may lead to unpredictable behavior for user login and logout.</p><h3 id="675_h3_2_Image-CDN"><a href="https://publ.beesbuzz.biz/manual/deploying/675-Use-with-Cloudflare-or-other-caching-CDNs#675_h3_2_Image-CDN"></a>Image CDN</h3><p>There should be no need for any additional fronting image CDN on Publ, although having it enabled shouldn&rsquo;t hurt. However, because Publ manages its own cache lifetime and the CDN is not aware of this, some strange behaviors may occur in extreme edge cases.</p><p>Setting the CDN to aggressively prefetch images or to use status 103 &ldquo;Early Hints,&rdquo; if possible, is probably a good idea.</p>

]]>
        </content>
    </entry>
    

    
</feed>