<?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: Manual</title>
    <subtitle>A personal publishing system for the modern web</subtitle>
    <link href="https://publ.beesbuzz.biz/manual/feed" rel="self" />
    <link href="https://publ.beesbuzz.biz/manual/feed" rel="current" />
    <link href="https://busybee.superfeedr.com" rel="hub" />
    
    <link href="https://publ.beesbuzz.biz/manual/feed?date=2019-12" rel="prev-archive" />
    
    
    <link href="https://publ.beesbuzz.biz/manual/" />
    
    <id>tag:publ.beesbuzz.biz,2020-01-07:manual</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>
    
    <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>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>
    
    <entry>
        <title>Using MySQL/MariaDB as your backing store</title>
        <link href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store" rel="alternate" type="text/html" />
        <published>2024-07-05T11:45:08-07:00</published>
        <updated>2024-07-05T11:45:08-07:00</updated>
        <id>urn:uuid:f9569d5a-44b7-549f-b536-1de795fdd8d7</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>How to use MySQL as the index database</p>

<p>For smaller Publ sites, the suggested configuration is to use <a href="https://sqlite.org/">SQLite</a> for the index database, as it is low-maintenance and performs very well due to being in-process and taking advantage of the operating system&rsquo;s disk cache.</p><p>However, sometimes it&rsquo;s helpful to use a larger-scale database system for the deployment, primarily to gain access to finer-grained locking. This is especially useful in situations where there are many thousands of entries and a desire to keep the site running at full capacity during a reindex.</p><p>Here is a configuration snippet that allows you to use MySQL/MariaDB on your Publ site:</p><figure class="blockcode"><figcaption>app.py</figcaption><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e764cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L1"></a><span class="line-content"><span class="c1"># ...</span></span></span>
<span class="line" id="e764cb1L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L2"></a><span class="line-content"></span></span>
<span class="line" id="e764cb1L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L3"></a><span class="line-content"><span class="k">if</span> <span class="s1">&#39;DATABASE_URL&#39;</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">:</span></span></span>
<span class="line" id="e764cb1L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L4"></a><span class="line-content">    <span class="kn">import</span><span class="w"> </span><span class="nn">urllib.parse</span></span></span>
<span class="line" id="e764cb1L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L5"></a><span class="line-content">    <span class="n">parsed</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">parse</span><span class="o">.</span><span class="n">urlparse</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;DATABASE_URL&#39;</span><span class="p">])</span></span></span>
<span class="line" id="e764cb1L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L6"></a><span class="line-content">    <span class="n">user</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(.*):(.*)@(.*)&#39;</span><span class="p">,</span> <span class="n">parsed</span><span class="o">.</span><span class="n">netloc</span><span class="p">)</span></span></span>
<span class="line" id="e764cb1L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L7"></a><span class="line-content">    <span class="n">db_config</span> <span class="o">=</span> <span class="p">{</span></span></span>
<span class="line" id="e764cb1L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L8"></a><span class="line-content">            <span class="s1">&#39;provider&#39;</span><span class="p">:</span> <span class="n">parsed</span><span class="o">.</span><span class="n">scheme</span><span class="p">,</span></span></span>
<span class="line" id="e764cb1L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L9"></a><span class="line-content">            <span class="s1">&#39;user&#39;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span></span></span>
<span class="line" id="e764cb1L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L10"></a><span class="line-content">            <span class="s1">&#39;password&#39;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span></span></span>
<span class="line" id="e764cb1L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L11"></a><span class="line-content">            <span class="s1">&#39;host&#39;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span></span></span>
<span class="line" id="e764cb1L12"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L12"></a><span class="line-content">            <span class="s1">&#39;database&#39;</span><span class="p">:</span> <span class="n">parsed</span><span class="o">.</span><span class="n">path</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span></span></span>
<span class="line" id="e764cb1L13"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L13"></a><span class="line-content">            <span class="c1"># charset and collation must be specified, as the MySQL defaults do</span></span></span>
<span class="line" id="e764cb1L14"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L14"></a><span class="line-content">            <span class="c1"># not properly support emoji and other 4-byte characters</span></span></span>
<span class="line" id="e764cb1L15"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L15"></a><span class="line-content">            <span class="s1">&#39;charset&#39;</span><span class="p">:</span> <span class="s1">&#39;utf8mb4&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb1L16"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L16"></a><span class="line-content">            <span class="s1">&#39;collation&#39;</span><span class="p">:</span> <span class="s1">&#39;utf8mb4_bin&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb1L17"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L17"></a><span class="line-content">        <span class="p">}</span></span></span>
<span class="line" id="e764cb1L18"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L18"></a><span class="line-content"><span class="k">else</span><span class="p">:</span></span></span>
<span class="line" id="e764cb1L19"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L19"></a><span class="line-content">    <span class="n">db_config</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;provider&#39;</span><span class="p">:</span> <span class="s1">&#39;sqlite&#39;</span><span class="p">,</span> <span class="s1">&#39;filename&#39;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">APP_PATH</span><span class="p">,</span> <span class="s1">&#39;index.db&#39;</span><span class="p">)}</span></span></span>
<span class="line" id="e764cb1L20"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L20"></a><span class="line-content"></span></span>
<span class="line" id="e764cb1L21"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L21"></a><span class="line-content"><span class="n">config</span> <span class="o">=</span> <span class="p">{</span></span></span>
<span class="line" id="e764cb1L22"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L22"></a><span class="line-content">    <span class="s1">&#39;database_config&#39;</span><span class="p">:</span> <span class="n">db_config</span><span class="p">,</span></span></span>
<span class="line" id="e764cb1L23"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb1L23"></a><span class="line-content">    <span class="c1"># ...</span></span></span>
</pre></figure><p>Then when running the site, set an environment variable such as:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">DATABASE_URL=&#x27;mysql://username:password@server/dbname&#x27;</span></span>
</pre></figure><p>If migrating from SQLite to MySQL, it is a good idea to create the index first before flipping the configuration:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">DATABASE_URL=&#x27;mysql://username:password@server/dbname&#x27; poetry run flask publ reindex</span></span>
</pre></figure><p>An alternate way to store the database configuration is to put it into a local <code>db_config.py</code> file, like so:</p><figure class="blockcode"><figcaption>db_config.py</figcaption><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e764cb4L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L1"></a><span class="line-content"><span class="n">db_config</span> <span class="o">=</span> <span class="p">{</span></span></span>
<span class="line" id="e764cb4L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L2"></a><span class="line-content">    <span class="s1">&#39;provider&#39;</span><span class="p">:</span> <span class="s1">&#39;mysql&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L3"></a><span class="line-content">    <span class="s1">&#39;user&#39;</span><span class="p">:</span> <span class="s1">&#39;db username&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L4"></a><span class="line-content">    <span class="s1">&#39;password&#39;</span><span class="p">:</span> <span class="s1">&#39;db password&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L5"></a><span class="line-content">    <span class="s1">&#39;host&#39;</span><span class="p">:</span> <span class="s1">&#39;localhost&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L6"></a><span class="line-content">    <span class="s1">&#39;database&#39;</span><span class="p">:</span> <span class="s1">&#39;my_site&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L7"></a><span class="line-content">    <span class="s1">&#39;charset&#39;</span><span class="p">:</span> <span class="s1">&#39;utf8mb4&#39;</span><span class="p">,</span></span></span>
<span class="line" id="e764cb4L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L8"></a><span class="line-content">    <span class="s1">&#39;collation&#39;</span><span class="p">:</span> <span class="s1">&#39;utf8mb4_bin&#39;</span></span></span>
<span class="line" id="e764cb4L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb4L9"></a><span class="line-content"><span class="p">}</span></span></span>
</pre></figure><figure class="blockcode"><figcaption>app.py</figcaption><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e764cb5L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L1"></a><span class="line-content"><span class="c1"># ...</span></span></span>
<span class="line" id="e764cb5L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L2"></a><span class="line-content"></span></span>
<span class="line" id="e764cb5L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L3"></a><span class="line-content"><span class="k">try</span><span class="p">:</span></span></span>
<span class="line" id="e764cb5L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L4"></a><span class="line-content">    <span class="kn">from</span><span class="w"> </span><span class="nn">.db_config</span><span class="w"> </span><span class="kn">import</span> <span class="n">db_config</span></span></span>
<span class="line" id="e764cb5L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L5"></a><span class="line-content"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span></span></span>
<span class="line" id="e764cb5L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L6"></a><span class="line-content">    <span class="n">db_config</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;provider&#39;</span><span class="p">:</span> <span class="s1">&#39;sqlite&#39;</span><span class="p">,</span> <span class="s1">&#39;filename&#39;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">APP_PATH</span><span class="p">,</span> <span class="s1">&#39;index.db&#39;</span><span class="p">)}</span></span></span>
<span class="line" id="e764cb5L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L7"></a><span class="line-content"></span></span>
<span class="line" id="e764cb5L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L8"></a><span class="line-content"><span class="n">config</span> <span class="o">=</span> <span class="p">{</span></span></span>
<span class="line" id="e764cb5L9"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L9"></a><span class="line-content">    <span class="s1">&#39;databse_config&#39;</span><span class="p">:</span> <span class="n">db_config</span><span class="p">,</span></span></span>
<span class="line" id="e764cb5L10"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L10"></a><span class="line-content">    <span class="c1"># ...</span></span></span>
<span class="line" id="e764cb5L11"><a class="line-number" href="https://publ.beesbuzz.biz/manual/deploying/764-Using-MySQL-MariaDB-as-your-backing-store#e764cb5L11"></a><span class="line-content"><span class="p">}</span></span></span>
</pre></figure><p>but as always it is important to ensure the security of this file; environment-based configuration is traditionally considered to be much easier to secure and prevent mishaps such as accidentally checking it into source control.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Admin dashboard</title>
        <link href="https://publ.beesbuzz.biz/manual/admin" rel="alternate" type="text/html" />
        <published>2020-08-02T00:35:36-07:00</published>
        <updated>2020-08-02T00:35:36-07:00</updated>
        <id>urn:uuid:9bf2d96c-e0c4-508d-91e9-991d6f96498a</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Information about the administration dashboard</p>

<p>The administration dashboard is available to anyone who is in the administration group, as configured by the <a href="https://publ.beesbuzz.biz/manual/formats/1341-User-configuration-file">user configuration</a> and <a href="https://publ.beesbuzz.biz/manual/865-Python-API#admin_group">application configuration</a>. It is accessed by going to the <code>/_admin</code> path on the website; on this site you can <a href="https://publ.beesbuzz.biz/_login/_admin?me=test:admin">log in as <code>test:admin</code></a> for a demonstration.</p><p>For this to be available, Publ needs to be configured with authentication, and there must be at least one user in the administrators group.</p><p><a href="https://publ.beesbuzz.biz/manual/admin"><img src="https://publ.beesbuzz.biz/static/_img/dd/0ea9/admin-dashboard_fe5ac06767_262x320.jpg" width="262" height="320" srcset="https://publ.beesbuzz.biz/static/_img/dd/0ea9/admin-dashboard_fe5ac06767_262x320.jpg 1x, https://publ.beesbuzz.biz/static/_img/dd/0ea9/admin-dashboard_fe5ac06767_524x640.jpg 2x" loading="lazy" alt="admin dashboard" title="Example admin dashboard view"></a></p><p>The dashboard provides information about the operation of the site; of particular interest, it shows the recent authentication activity, such as the recent users, and both authorized and unauthorized accesses to <a href="https://publ.beesbuzz.biz/manual/formats/322-Entry-files#auth">protected entries</a>.</p><p>Any time a user identity is presented, you can copy the link itself to add to your <a href="https://publ.beesbuzz.biz/manual/formats/1341-User-configuration-file">user configuration</a>, or you can visit the user&rsquo;s profile link (designated with an ℹ︎ symbol) to view their user profile.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>User object</title>
        <link href="https://publ.beesbuzz.biz/manual/api/733-User-object" rel="alternate" type="text/html" />
        <published>2020-08-01T23:51:44-07:00</published>
        <updated>2020-08-01T23:51:44-07:00</updated>
        <id>urn:uuid:a16621e8-31a5-5f50-82eb-2f52bdd5330d</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Template API for the <code>user</code> object</p>

<p>The <code>user</code> object provides the following:</p>
<ul>
<li><strong><code>identity</code></strong>: The identity URL of the user</li>
<li><p><strong><code>humanize</code></strong>: A humanized version of the identity URL</p></li>
<li><p><strong><code>name</code></strong>: The display name of the user</p></li>
<li><p><strong><code>profile</code></strong>: The user profile; see the <a href="https://authl.readthedocs.io/en/latest/authl.html#authl.disposition.Verified">Authl documentation</a> for the relevant keys.</p><p>Note that this will not necessarily be available, depending on how and when the user logged in. For example, if the database has been fully reset (due to e.g. a Publ upgrade or a server migration) since they last logged in, or if the user logged in from a different instance on a load-balanced configuration using a per-instance database, the profile will likely not be present.</p></li>
<li><p><strong><code>groups</code></strong>: A list of the groups they belong to (not including the user&rsquo;s identity group)</p></li>
<li><p><strong><code>auth_groups</code></strong>: The full list of matching authentication groups (including the user&rsquo;s own identity group)</p></li>
<li><p><strong><code>is_admin</code></strong>: <code>True</code> if the user is a member of the administrative group</p></li>
<li><p><strong><code>auth_type</code></strong>: How the user&rsquo;s authentication was obtained; possible values:</p>
<ul>
<li><code>&#39;session&#39;</code>: Normal login flow/session cookie</li>
<li><code>&#39;token&#39;</code>: Bearer token</li>
</ul></li>
<li><p><strong><code>scope</code></strong>: The user&rsquo;s permission scopes, if applicable (typically if the login was via a bearer token)</p></li>
<li><p><strong><code>last_login</code></strong>: The last time the user logged in</p><p>This may not be available, per the same rules as <code>user.profile</code></p></li>
<li><p><strong><code>last_seen</code></strong>: The last time the user was active on the site</p></li>
<li><p><span id="is_bot"><strong><code>is_bot</code></strong></span>: Whether the page is currently being viewed by a declared bot</p></li>
<li><p><span id="token"><strong><code>token(lifetime,scope=None)</code></strong></span>: A function to generate an HTTP Bearer token for preauthorization.</p><p>Arguments:</p>
<ul>
<li><code>lifetime</code>: How long the token is good for, in seconds</li>
<li><code>scope</code>: The access scopes that should be associated with the token</li>
</ul>
<p>See <a href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#token">using bearer tokens</a> for more information.</p></li>
</ul>
<p>For the most part you should only use <code>user.name</code> to address the user, and possibly use <code>user.groups</code> to check for particular group membership if that is something you want to show them. For example, if you use user groups as a means of managing memberships or rewards or the like, you could do something like:</p><figure class="blockcode"><pre class="highlight" data-language="jinja" data-line-numbers><span class="line" id="e733cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L1"></a><span class="line-content"><span class="x">Hello, </span><span class="cp">{{</span><span class="nv">user.name</span><span class="cp">}}</span><span class="x">.</span></span></span>
<span class="line" id="e733cb1L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L2"></a><span class="line-content"><span class="cp">{%</span> <span class="k">if</span> <span class="s1">&#39;current_members&#39;</span> <span class="k">in</span> <span class="nv">user.groups</span> <span class="cp">%}</span></span></span>
<span class="line" id="e733cb1L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L3"></a><span class="line-content"><span class="x">    Your membership is current!</span></span></span>
<span class="line" id="e733cb1L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L4"></a><span class="line-content"><span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span></span></span>
<span class="line" id="e733cb1L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L5"></a><span class="line-content"><span class="x">    You aren&#39;t currently a member. Please consider &lt;a href=&quot;/subscribe&quot;&gt;becoming one&lt;/a&gt;.</span></span></span>
<span class="line" id="e733cb1L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb1L6"></a><span class="line-content"><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span></span></span>
</pre></figure><p>When extending Publ using additional Python functions, the current user can be retrieved with:</p><figure class="blockcode"><pre class="highlight" data-language="python" data-line-numbers><span class="line" id="e733cb2L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb2L1"></a><span class="line-content"><span class="kn">import</span><span class="w"> </span><span class="nn">publ.user</span></span></span>
<span class="line" id="e733cb2L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb2L2"></a><span class="line-content"><span class="n">user</span> <span class="o">=</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>
</pre></figure><p>In all contexts you can check the truthiness of the <code>user</code> object to see if someone is logged in; bots do not count as logged-in users. For example, in a template:</p><figure class="blockcode"><pre class="highlight" data-language="html+jinja" data-line-numbers><span class="line" id="e733cb3L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L1"></a><span class="line-content"><span class="cp">{%</span> <span class="k">if</span> <span class="nv">user</span> <span class="cp">%}</span></span></span>
<span class="line" id="e733cb3L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L2"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Hello user <span class="cp">{{</span><span class="nv">user.name</span><span class="cp">}}</span>!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span></span></span>
<span class="line" id="e733cb3L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L3"></a><span class="line-content"><span class="cp">{%</span> <span class="k">elif</span> <span class="nv">user.is_bot</span> <span class="cp">%}</span></span></span>
<span class="line" id="e733cb3L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L4"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Hello bot <span class="cp">{{</span><span class="nv">user.name</span><span class="cp">}}</span>!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span></span></span>
<span class="line" id="e733cb3L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L5"></a><span class="line-content"><span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span></span></span>
<span class="line" id="e733cb3L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L6"></a><span class="line-content">    <span class="p">&lt;</span><span class="nt">P</span><span class="p">&gt;</span>Howdy, stranger!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span></span></span>
<span class="line" id="e733cb3L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/api/733-User-object#e733cb3L7"></a><span class="line-content"><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span></span></span>
</pre></figure>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Command-Line Interface</title>
        <link href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface" rel="alternate" type="text/html" />
        <published>2020-05-27T23:34:43-07:00</published>
        <updated>2020-05-27T23:34:43-07:00</updated>
        <id>urn:uuid:4d9a7bf8-bda2-5b5e-bd6e-a9d814471ade</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Commands that can be run from the CLI for various things</p>

<p>Publ extends the default <code>flask</code> command with some additional commands, which can be used for various useful things. In addition to this page, you can get detailed help from the command line with:</p><figure class="blockcode"><pre class="highlight" data-language="sh" data-line-numbers><span class="line" id="e238cb1L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb1L1"></a><span class="line-content">flask<span class="w"> </span>publ<span class="w"> </span><span class="o">[</span>command<span class="o">]</span><span class="w"> </span>--help</span></span>
</pre></figure><p>from within your Publ virtual environment (e.g. <code>poetry run flask publ --help</code> or <code>env/bin/flask publ --help</code>).</p><p>For usage of the <code>flask</code> command itself, see the <a href="https://flask.palletsprojects.com/en/1.1.x/cli/">Flask CLI documentation</a>.</p><h2 id="238_h2_1_reindex"><a href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#238_h2_1_reindex"></a><span id="reindex"><code>reindex</code></span></h2><p>This command lets you force a database reindex, and will wait until the index finishes. This is useful for deployment scripts and the like.</p><p>It takes two arguments:</p>
<ul>
<li><code>--fresh</code>/<code>-f</code>: Start with a fresh index (i.e. remove all the cached data); this is useful if something weird has happened and you want a fresh start (if something weird has happened, please <a href="https://publ.beesbuzz.biz/newissue">open an issue</a>!)</li>
<li><code>--quiet</code>/<code>-q</code>: Don&rsquo;t show the progress indicator</li>
</ul>
<h2 id="238_h2_2_token"><a href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#238_h2_2_token"></a><span id="token"><code>token</code></span></h2><p>This command can be used to generate an IndieAuth token for scripting purposes, such as for use with <a href="https://publ.beesbuzz.biz/tools/1295-Pushl">Pushl</a>, or for testing authentication automatically.</p><p>For example:</p><figure class="blockcode"><pre class="highlight" data-language="sh" data-line-numbers><span class="line" id="e238cb2L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb2L1"></a><span class="line-content"><span class="nv">TOKEN</span><span class="o">=</span><span class="k">$(</span>flask<span class="w"> </span>publ<span class="w"> </span>token<span class="w"> </span>https://example.com<span class="k">)</span></span></span>
<span class="line" id="e238cb2L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb2L2"></a><span class="line-content">curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">&quot;</span><span class="w"> </span>https://example.com/feed</span></span>
</pre></figure><p>will fetch the <code>https://example.com/feed</code> page with a token identifying the user as <code>https://example.com</code> (as generated by your Publ site).</p><p>Note that the session key will need to match between wherever you&rsquo;re running this and the actual site. If they do not match for some reason (for example, because you&rsquo;re running in a different configuration) this token will not be valid.</p><p>This takes the following arguments:</p>
<ul>
<li><code>--scope/-s</code>: Generate the token with the specified scope (defaults to none)</li>
<li><code>--lifetime/-l</code>: How long the token should be valid for, in seconds (default: 3600)</li>
</ul>
<p>Note that token scopes are not currently used by Publ itself; this is provided largely to make it easier to extend Publ via the <a href="https://publ.beesbuzz.biz/manual/865-Python-API">Python API</a>, such as implementing a mechanism to automatically post content from external sources.</p><h2 id="238_h2_3_normalize"><a href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#238_h2_3_normalize"></a><span id="normalize"><code>normalize</code></span></h2><p>This command allows you to bulk-rename entries within one or more categories with a templatized filename. By default it gives things a name of <code>{date} {sid} {title}</code>, so e.g. names like:</p><figure class="blockcode"><pre><span class="line"><span class="line-content">20190204 3 Welcome to my new blog.md</span></span>
<span class="line"><span class="line-content">20200101 1942 Happy new year!.md</span></span>
<span class="line"><span class="line-content">20200704 DELETED Ugh never mind.md</span></span>
</pre></figure><p>Example usage:</p><figure class="blockcode"><pre class="highlight" data-language="sh" data-line-numbers><span class="line" id="e238cb4L1"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L1"></a><span class="line-content"><span class="c1">### Give &#39;blog&#39; entries a name like &#39;20210101 Hello everyone.md&#39;</span></span></span>
<span class="line" id="e238cb4L2"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L2"></a><span class="line-content">flask<span class="w"> </span>publ<span class="w"> </span>normalize<span class="w"> </span>-af<span class="w"> </span><span class="s2">&quot;{date} {title}&quot;</span><span class="w"> </span>blog</span></span>
<span class="line" id="e238cb4L3"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L3"></a><span class="line-content"></span></span>
<span class="line" id="e238cb4L4"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L4"></a><span class="line-content"><span class="c1">### Apply the default name to published entries in &#39;articles&#39; and &#39;recipes&#39;</span></span></span>
<span class="line" id="e238cb4L5"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L5"></a><span class="line-content">flask<span class="w"> </span>publ<span class="w"> </span>normalize<span class="w"> </span>articles<span class="w"> </span>recipes</span></span>
<span class="line" id="e238cb4L6"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L6"></a><span class="line-content"></span></span>
<span class="line" id="e238cb4L7"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L7"></a><span class="line-content"><span class="c1">### Apply hyper-specific naming to every entry in the site</span></span></span>
<span class="line" id="e238cb4L8"><a class="line-number" href="https://publ.beesbuzz.biz/manual/238-Command-Line-Interface#e238cb4L8"></a><span class="line-content">flask<span class="w"> </span>publ<span class="w"> </span>normalize<span class="w"> </span>-rf<span class="w"> </span><span class="s2">&quot;{date}-{time}-{id}-{slug}&quot;</span></span></span>
</pre></figure><p>It accepts the following arguments:</p>
<ul>
<li> <code>-r</code>/<code>--recurse</code>:      Include subdirectories</li>
<li> <code>-a</code>/<code>--all</code>:  Apply to all entries, not just reachable ones (published, scheduled, hidden)</li>
<li> <code>-n</code>/<code>--dry-run</code>:      Show, but don&rsquo;t apply, changes</li>
<li> <code>-f</code>/<code>--format TEXT</code>:  Filename format to use</li>
<li> <code>-v</code>/<code>--verbose</code>:      Show detailed actions</li>
</ul>
<p>And the following format tokens can be used in the string provided to <code>-f</code>/<code>--format</code>:</p>
<ul>
<li>     <code>{date}</code>:    The entry&rsquo;s publish date, in YYYYMMDD format</li>
<li>     <code>{time}</code>:    The entry&rsquo;s publish time, in HHMMSS format</li>
<li>     <code>{id}</code>:      The entry&rsquo;s ID</li>
<li>     <code>{status}</code>:  The entry&rsquo;s publish status</li>
<li>     <code>{sid}</code>:     If the entry is reachable, the ID, otherwise the status</li>
<li>     <code>{title}</code>:   The entry&rsquo;s title, normalized to filename-safe characters</li>
<li>     <code>{slug}</code>:    The entry&rsquo;s slug text</li>
<li>     <code>{type}</code>:    The entry&rsquo;s type</li>
</ul>
<p>Note that entries in DRAFT status always get an <code>{id}</code> of <code>DRAFT</code>, even if an ID has been assigned.</p>

]]>
        </content>
    </entry>
    
    <entry>
        <title>Setting up caching</title>
        <link href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching" rel="alternate" type="text/html" />
        <published>2020-02-05T23:44:04-08:00</published>
        <updated>2020-02-05T23:44:04-08:00</updated>
        <id>urn:uuid:a76ad2de-07c7-53d3-84a0-70835dc90087</id>
        <author><name>fluffy</name></author>
        <content type="html">
<![CDATA[
<p>Some pointers for setting up the page caching mechanism</p>

<p>When templates start to get particularly complex, Publ can start to slow down; this is one of the tradeoffs when using a dynamic site generator, rather than a static one. Fortunately, it is fairly straightforward to configure Publ with caching; here are some pointers on how.</p><h2 id="20_h2_1_Basics"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h2_1_Basics"></a>Basics</h2><p>Entire textbooks can (and have been) written on caching and their optimum usage. This is not intended to be an entire textbook, but rather a set of brief-ish guidelines for how to configure Publ&rsquo;s caching mechanism.</p><h3 id="20_h3_2_What-is-a-cache"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_2_What-is-a-cache"></a>What is a cache?</h3><p>At its most basic and general, a cache is a temporary storage area where intermediate results get stored for a while.</p><p>Fundamentally, there are two algorithms involved in a cache: key generation, and result generation.</p><p>The key is basically a filename that tells the cache where to look for a result, and this is computed based on the unique aspects that are involved in generating the result. In the case of Publ&rsquo;s page cache, the key is based on what&rsquo;s being looked at (the selected category, template, entry, and URL parameters), how it&rsquo;s being looked at (the URL), and who&rsquo;s looking at it (the current user, if any).</p><p>The result is the result of performing the operation that&rsquo;s being cached. In this case, this is the rendered content of the template.</p><p>The goal in any cache is to correctly produce the right result while reusing those results as much as possible, while taking less time on average than what it would take to simply compute the result each time.</p><h3 id="20_h3_3_Performance-tradeoffs"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_3_Performance-tradeoffs"></a>Performance tradeoffs</h3><p>When setting up a cache, you need to consider what the right balance is between memory usage, performance, and immediacy. These three things are at odds with one another and there is fundamentally no perfect solution that maximizes all three.</p><p>Generally-speaking there are two statistics we are looking for in trying to optimize a cache:</p>
<ul>
<li><strong>Hit rate</strong>: How often a page render can be reused; we want this to be as high as possible</li>
<li><strong>Staleness</strong>: How out-of-date a page render can be; we want this to be as low as tolerable</li>
</ul>
<p>There is a third statistic which also matters, namely the active cache size (also called the &ldquo;hot size,&rdquo; among other things) &ndash; this is simply the total amount of storage used by page renders.</p><p>To achieve these goals, there are two &ldquo;knobs&rdquo; of particular interest: maximum cache size, and expiration time.</p><p>Increasing the expiration time will increase the hit rate, while also increasing the staleness. To understand why, if a page is only accessed once every 15 minutes, and the expiration time is only 5 minutes, then the cached result will be expired before the page is accessed again, making the cache useless. But, if the expiration time is one hour, then if a page changes at all, the update won&rsquo;t appear until an hour after it was rendered.</p><p>The other knob, maximum cache size, is simply a measure of how much can be kept in the cache. As long as the expiration time is finite, the cache will never grow to be infinitely large, but you might still want to put a limit on how large it can grow. Raising the cache size won&rsquo;t necessarily increase the hit rate, but decreasing the cache size to be less than the active cache size will decrease it.<sup id="r_e20_fn1"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#d_e20_fn1" rel="footnote">1</a></sup></p><h3 id="20_h3_4_A-note-on-staleness"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_4_A-note-on-staleness"></a><span id="expiration">A note on staleness</span></h3><p>To mitigate the staleness problem, Publ uses aspects of when content was last edited as part of its caching mechanism, so if content changes that might affect a page, all previous versions of that page will be discarded.<sup id="r_e20_fn2"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#d_e20_fn2" rel="footnote">2</a></sup> So, while many caching systems recommend only setting the expiration time to the maximum amount of time you can tolerate a change not being available, Publ does not have this issue.</p><p>However, some things that Publ can do will still become stale. For example, things that are essentially non-deterministic (such as things based on random numbers) or based on the current time (such as <code>entry.date.humanize()</code>) will still be subject to becoming stale. So if you use any of these things in your templates, you&rsquo;ll want to set your expiration time accordingly. Some guidelines:</p>
<ul>
<li>If you use <code>entry.date.humanize()</code>, an expiration time of around 1 hour is probably fine (although this will make a recent entry&rsquo;s relative time appear to be wrong for its first hour of existence or so)</li>
<li>If you use random number generation for whatever reason (such as displaying a random image on a particular page), the expiration time should be for however long you are willing to tolerate the number staying the same</li>
<li>If you want to display the current time on the page for some reason<sup id="r_e20_fn3"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#d_e20_fn3" rel="footnote">3</a></sup>, the expiration time should be for the most &ldquo;slow&rdquo; you&rsquo;re willing to have the clock be.</li>
</ul>
<p>Also, if you plan on using any sort of monitoring service (munin, Pingdom, etc.) to measure page load time over time (to get an ongoing performance indicator), it&rsquo;s a good idea to set your expiration time to a nearby prime number; for example, if you want your expiration time to be around an hour, try values of 3593 or 3659 or the like. This helps to prevent sampling bias and gives a much better overall average.</p><h2 id="20_h2_5_Configuration"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h2_5_Configuration"></a>Configuration</h2><p>Publ uses <a href="https://flask-caching.readthedocs.io/en/latest/">flask-caching</a> for its caching layer. The short version of its documentation is it provides caching backends for a number of common caching servers (Redis and a few variants of MemcacheD), as well as file-based and in-process caching.</p><p>If you have Redis or MemcacheD available, definitely use those; they have the advantage of persisting beyond your application&rsquo;s run time, and going into a common shared memory pool for all sites that use them, which can be way more memory-efficient than the in-process cache. They also both support distributed caches.</p><p>If you are self-hosting and trying to decide which cache server to go with, I suggest MemcacheD, as it&rsquo;s by far the easiest to install and configure, and most distributions pre-configure it with sensible defaults.</p><p>If you don&rsquo;t have any of those (for example, you&rsquo;re on shared hosting that doesn&rsquo;t allow you to run your own services), your best choices<sup id="r_e20_fn4"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#d_e20_fn4" rel="footnote">4</a></sup> are <code>FileSystemCache</code> (file-based) and <code>SimpleCache</code> (in-process), which each have strengths and weaknesses. Both of them suffer from not being configurable based on actual storage size; they can only track the number of items in the hot cache. <code>SimpleCache</code> is faster, but also makes your process take more memory &ndash; a big concern if you&rsquo;re on a shared system where memory usage is limited. <code>FileSystemCache</code> is generally friendlier and fast <em>enough</em> (and also will take advantage of the operating system&rsquo;s file cache which makes it kinda-sorta similar to using a caching server), but its performance disadvantage can end up slowing the site down in many cases. But if you can look at the state of the filesystem, <code>FileSystemCache</code> gives you better visibility into what&rsquo;s happening with the cache, which can give you better ideas for how to tune it.</p><p>In general, I&rsquo;d recommend this priority order:</p>
<ol>
<li>MemcacheD or Redis</li>
<li>FileSystemCache (if you have lots of storage available)</li>
<li>SimpleCache (if you have lots of RAM available)</li>
</ol>
<p>Regardless of caching backend, in your application&rsquo;s <a href="https://publ.beesbuzz.biz/manual/865-Python-API#cache">cache configuration</a> you should configure the following:</p>
<ul>
<li><code>CACHE_KEY_PREFIX</code>: Set this to some random but fixed string (this helps to prevent certain kinds of attacks that are possible if you share the cache with someone else)</li>
<li><code>CACHE_DEFAULT_TIMEOUT</code>: Set this to your expiration time, as discussed <a href="https://publ.beesbuzz.biz/manual/feed#expiration">above</a></li>
</ul>
<p>Some backend-specific notes are below.</p><h3 id="20_h3_6_MemcacheD"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_6_MemcacheD"></a>MemcacheD</h3><p>In order to use MemcacheD you&rsquo;ll also need to add a MemcacheD client to your Python environment. I generally use <code>python-memcached</code> as it&rsquo;s easier to install (especially in cross-platform scenarios), but <code>libmc</code> and <code>pylibmc</code> both have some advantages in performance-critical and large-scale scenarios, being slightly faster as well as supporting <a href="https://en.wikipedia.org/wiki/Consistent_hashing">consistent hashing</a>.</p><p>If you&rsquo;re on Google AppEngine, a suitable client is already provided to you and there is no reason to add your own.</p><h3 id="20_h3_7_SimpleCache"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_7_SimpleCache"></a>SimpleCache</h3><p>Set the value of <code>CACHE_THRESHOLD</code> to limit the number of items that can be kept in the cache. As noted above, there is no way to limit the size directly, so if you have a specific allocation limit in mind, try to figure out what the average size of a page is (in bytes), and divide your allocation limit by that. That is the value you should use.</p><h3 id="20_h3_8_FileSystemCache"><a href="https://publ.beesbuzz.biz/manual/deploying/20-Setting-up-caching#20_h3_8_FileSystemCache"></a>FileSystemCache</h3><p>Similarly to SimpleCache, this uses <code>CACHE_THRESHOLD</code> to limit the number of items in the cache, rather than the total storage size. Since filesystem space is usually much more plentiful than RAM, there&rsquo;s no reason to be particularly miserly with this. Unless you start to run out of storage space, anyway</p><p>You should also make sure that the <code>CACHE_DIR</code> is set to a directory that you have write access to, and you should set <code>CACHE_OPTIONS</code> to <code>{&#39;mode&#39;: 0o600}</code> (the default).</p>

]]>
        </content>
    </entry>
    

    
</feed>