<?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: Recipes</title>
    <subtitle>A personal publishing system for the modern web</subtitle>
    <link href="https://publ.beesbuzz.biz/manual/recipes/feed" rel="self" />
    <link href="https://publ.beesbuzz.biz/manual/recipes/feed" rel="current" />
    <link href="https://busybee.superfeedr.com" rel="hub" />
    
    
    <link href="https://publ.beesbuzz.biz/manual/recipes/" />
    
    <id>tag:publ.beesbuzz.biz,2020-01-07:manual/recipes</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>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="kn">import</span><span class="w"> </span><span class="nn">arrow</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="kn">import</span><span class="w"> </span><span class="nn">flask</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="kn">import</span><span class="w"> </span><span class="nn">werkzeug.exceptions</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></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="k">def</span><span class="w"> </span><span class="nf">keymaster</span><span class="p">(</span><span class="n">sid</span><span class="p">):</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="w">    </span><span class="sd">&quot;&quot;&quot; Generates a salted token for the browser &quot;&quot;&quot;</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 class="kn">import</span><span class="w"> </span><span class="nn">hashlib</span></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></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="n">parts</span> <span class="o">=</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="nb">str</span><span class="p">(</span><span class="n">sid</span><span class="p">),</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 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="p">,</span></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="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="e210cb1L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L13"></a><span class="line-content">    <span class="p">]</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="n">token</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">md5</span><span class="p">(</span><span class="s1">&#39;|&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">))</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="k">return</span> <span class="n">token</span><span class="o">.</span><span class="n">digest</span><span class="p">()</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></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="nd">@app</span><span class="o">.</span><span class="n">before_request</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">def</span><span class="w"> </span><span class="nf">antiscraper</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="w">    </span><span class="sd">&quot;&quot;&quot; Dissuade aggressive bots from pummeling the site &quot;&quot;&quot;</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"># Logged-in users have passed the test already</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="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="e210cb1L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L23"></a><span class="line-content">        <span class="k">return</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"># Send possible crawlers to the login page</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 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="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">1</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="c1"># Check for an existing sentience token</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 class="k">try</span><span class="p">:</span></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="n">sid</span><span class="p">,</span> <span class="n">token</span> <span class="o">=</span> <span class="n">flask</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s1">&#39;vinz&#39;</span><span class="p">]</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 class="k">if</span> <span class="p">(</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">hours</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">arrow</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">sid</span><span class="p">))</span> <span class="o">&lt;</span> <span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="ow">and</span></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="n">keymaster</span><span class="p">(</span><span class="n">sid</span><span class="p">)</span> <span class="o">==</span> <span class="n">token</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">return</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="k">except</span> <span class="p">(</span><span class="ne">KeyError</span><span class="p">,</span> <span class="ne">ValueError</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="e210cb1L35"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L35"></a><span class="line-content">            <span class="k">pass</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></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 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="e210cb1L38"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L38"></a><span class="line-content"></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="k">return</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></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="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="e210cb1L42"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L42"></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="e210cb1L43"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L43"></a><span class="line-content"><span class="w">    </span><span class="sd">&quot;&quot;&quot; Check the test response and set the salted token upon passing &quot;&quot;&quot;</span></span></span>
<span class="line" id="e210cb1L44"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L44"></a><span class="line-content">    <span class="k">try</span><span class="p">:</span></span></span>
<span class="line" id="e210cb1L45"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L45"></a><span class="line-content">        <span class="n">sid</span> <span class="o">=</span> <span class="nb">float</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">form</span><span class="p">[</span><span class="s1">&#39;sid&#39;</span><span class="p">])</span></span></span>
<span class="line" id="e210cb1L46"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L46"></a><span class="line-content">        <span class="k">if</span> <span class="n">arrow</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">sid</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></span>
<span class="line" id="e210cb1L47"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L47"></a><span class="line-content">            <span class="c1"># Someone&#39;s trying to set a token that&#39;ll last longer</span></span></span>
<span class="line" id="e210cb1L48"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L48"></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">BadRequest</span><span class="p">(</span><span class="s2">&quot;Hello time traveler&quot;</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L49"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L49"></a><span class="line-content">        <span class="k">if</span> <span class="n">arrow</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">sid</span><span class="p">)</span> <span class="o">&lt;</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">minutes</span><span class="o">=-</span><span class="mi">5</span><span class="p">):</span></span></span>
<span class="line" id="e210cb1L50"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L50"></a><span class="line-content">            <span class="c1"># Someone took a while to respond to the form</span></span></span>
<span class="line" id="e210cb1L51"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L51"></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;Try again&quot;</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L52"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L52"></a><span class="line-content">    <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span></span></span>
<span class="line" id="e210cb1L53"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L53"></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">BadRequest</span><span class="p">(</span><span class="s2">&quot;Nice try&quot;</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L54"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L54"></a><span class="line-content"></span></span>
<span class="line" id="e210cb1L55"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L55"></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="e210cb1L56"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L56"></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;vinz&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">sid</span><span class="p">,</span> <span class="n">keymaster</span><span class="p">(</span><span class="n">sid</span><span class="p">)</span></span></span>
<span class="line" id="e210cb1L57"><a class="line-number" href="https://publ.beesbuzz.biz/manual/recipes/210-Crawler-mitigations#e210cb1L57"></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="nv">.format</span><span class="o">(</span><span class="s1">&#39;X&#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="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 hour 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>
    

    
</feed>