<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel xmlns:content="http://purl.org/rss/1.0/modules/content/"><title>REVSYS Blog</title><link>https://www.revsys.com/blog/feeds/latest/</link><description>Latest News and Tips from REVSYS</description><atom:link href="http://www.revsys.com/blog/feeds/latest/" rel="self"/><language>en-us</language><lastBuildDate>Wed, 24 Dec 2025 15:39:00 +0000</lastBuildDate><item><title>Happy Holidays from REVSYS</title><link>http://www.revsys.com/tidbits/happy-holidays-from-revsys/</link><description>’Twas the deploy before Christmas, when all through the officeNot a pipeline was failing, not even a bug.The containers were built by the runner with care,In hopes that green checkmarks soon would be there.</description><pubDate>Wed, 24 Dec 2025 15:39:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/happy-holidays-from-revsys/</guid><content:encoded<![CDATA[<div class="block-content">
 <p data-block-key="0y5h9">
 </p>
 <img alt="Django Christmas Pony" class="richtext-image full-width" height="500" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/DjangoPonyChristmas.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=b8f9b3f958d3e39e40a91f275060f501dbf299f542422fd9c1fb253aa03194af" width="800"/>
 <p data-block-key="5cjj">
 </p>
</div>
<div class="block-html">
 <p>
  &rsquo;Twas the deploy before Christmas, when all through the office
  <br/>
  Not a pipeline was failing, not even a bug.
  <br/>
  The containers were built by the runner with care,
  <br/>
  In hopes that green checkmarks soon would be there.
  <br/>
 </p>
 <p>
  The devs were all nestled, eyes burning bright,
  <br/>
  While Postgres ran queries all snug through the night.
  <br/>
  And I in my hoodie, with coffee in hand,
  <br/>
  Had just kicked off migrations like &ldquo;this should go as planned&hellip;&rdquo;
  <br/>
 </p>
 <p>
  When out in the cluster there arose such a clatter,
  <br/>
  I sprang to the logs to see what was the matter.
  <br/>
  Away to kubectl I flew like a flash,
  <br/>
  kubectl get pods and I gasped at the crash.
  <br/>
 </p>
 <p>
  The moon on the YAML, so cruelly aglow,
  <br/>
  Gave luster to objects in CrashLoopBackOff below.
  <br/>
  When what to my weary eyes should appear,
  <br/>
  But a brand-new Ingress and a cert-manager deer.
  <br/>
 </p>
 <p>
  With a little old operator, so lively and quick,
  <br/>
  I knew in a moment: it&rsquo;s GitOps&rsquo; old trick.
  <br/>
  More rapid than eagles the webhooks they came,
  <br/>
  And it whistled and shouted and called them by name:
  <br/>
 </p>
 <p>
  &ldquo;Now Django! Now Gunicorn! Now Celery, hurry!
  <br/>
  On Redis! On Nginx! No time to be blurry!
  <br/>
  To the top of the node pool! To the edge of the firewall!
  <br/>
  Now roll out! roll out! roll out!
  <br/>
 </p>
 <p>
  As requests that before the load balancer fly,
  <br/>
  When they meet with an outage, they spin and retry
  <br/>
  So up to the Service the packets they flew,
  <br/>
  With headers and cookies and CSRF too!
  <br/>
 </p>
 <p>
  And then, in a twinkling, I heard on the page
  <br/>
  The clickity-clack of a React built in rage.
  <br/>
  As I drew in my breath and was turning around,
  <br/>
  HTMX swapped the div with barely a sound.
  <br/>
 </p>
 <p>
  It was dressed all in Tailwind, from head to toe,
  <br/>
  And its DOM was all tidy&mdash;no extra &ldquo;hello.&rdquo;
  <br/>
  A bundle of modules it had flung on its back,
  <br/>
  And it looked like a vendor just opening a pack.
  <br/>
 </p>
 <p>
  The design how it twinkled! JS console how merry!
  <br/>
  Its warnings were minor, not scary, not hairy.
  <br/>
  Its droll little tooltips were perfectly placed,
  <br/>
  And the lint on its beard had been neatly erased.
  <br/>
 </p>
 <p>
  It spoke not a word, but went straight to its work,
  <br/>
  Fixed CORS and SameSite (no need to be a jerk),
  <br/>
  Then laying a finger aside of its nose,
  <br/>
  It patched my .env and up prod it rose.
  <br/>
 </p>
 <p>
  It sprang to the cluster, to its team gave a Slack message,
  <br/>
  And away they all scaled like a well-tuned missile.
  <br/>
  But I heard it exclaim, ere it rolled out of sight:
  <br/>
 </p>
 <p>
  &ldquo;Happy Christmas to all and to all, and an outage free night!&rdquo;
  <br/>
 </p>
</div>
]]>/></item><item><title>Sometimes LFU &gt; LRU</title><link>http://www.revsys.com/tidbits/sometimes-lfu-lru/</link><description>Stop letting bot traffic evict your customers' sessions. A simple Redis configuration switch from LRU to LFU solved our crawler problem, with a Django configuration example.</description><pubDate>Sun, 17 Aug 2025 21:46:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/sometimes-lfu-lru/</guid><content:encoded<![CDATA[<div class="block-html">
 <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="400" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/lSNdp7qpqVo?si=dtY_d8ah_hTV6LM8" title="YouTube video player" width="750">
 </iframe>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We've seen a dramatic increase in malicious AI/web crawlers with our clients.
  </p>
  <p>
   These crawlers aren't honoring things like
   <code>
    robots.txt
   </code>
   or headers to indicate a page shouldn't be 
crawled (think links to facets like those in search results) that cause crawlers to get lost in a web of mostly 
useless or duplicated content.
  </p>
  <p>
   On top of that annoyance they're making tens if not
   <em>
    hundreds of requests per second
   </em>
   for hours at a time.
  </p>
  <p>
   These sites are reasonably fast and in many cases are behind awesome cache systems like Cloudflare, so 
why is it impacting our clients negatively?
  </p>
  <p>
   <strong>
    User sessions!
   </strong>
   These bots are creating thousands of new sessions that push the real human user's sessions out of the cache.
  </p>
  <p>
   We're using
   <a href="https://docs.djangoproject.com/en/dev/topics/http/sessions/">
    Django's Sessions
   </a>
   , but this 
technique works for any browser session token where you want to retain the sessions of real human 
users for as long as possible and expire out these robotic sessions.
  </p>
  <h2>
   Let's talk about sessions
  </h2>
  <p>
   Our default Django site template defaults to using the
   <code>
    cached_db
   </code>
   backend.  This tries to get the session from 
Django's default cache (redis in most cases), if it's not found it is retrieved from the database and stored in the 
cache for the next request.
  </p>
  <p>
   Because of these asshole crawlers we've had to rethink this default and instead only keep the session in a cache 
for some of our customers.
  </p>
  <p>
   On most sites it doesn't really matter if the session data is retained.  The worst case being the user is forced to log in again.
  </p>
  <p>
   With other sites, the user's session is absolutely crucial to the business.  A simple example is we have e-commerce clients 
who want to provide a nice experience to their users.  They want a random Internet user to be able to add items to their 
cart over time and only make them login to actually checkout.
  </p>
  <p>
   You need a session for that and you need that session to persist in cache for a reasonable amount of time.
  </p>
  <h2>
   Default Redis
  </h2>
  <p>
   By default Redis will use as much memory as possible and not expire keys that have a TTL set, but when it can no longer store keys in 
RAM it will fail to allow you to create a new session.  This often causes whatever session system you're using to raise an exception 
and your site is broken.
  </p>
  <p>
   LRU (Least Recently Used) cache expiration is probably the most commonly known way to expire data.  It's
   <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache">
    built into Python
   </a>
   and it is the default for systems like memcached. You could configure or customize your session 
handling to use an LRU mechanism to keep RAM usage under control, but then you still have a problem.
  </p>
  <p>
   When you get a half million bogus AI requests in an hour and you only have RAM to hold say half of that, your real human 
customer who painstakingly curated the perfect cart of high dollar premium widgets but had the audacity to take a break to make 
dinner returns to their browser to find an empty cart and a fresh session.
  </p>
  <p>
   And unless you have a monopoly on widgets, they're likely to take their business elsewhere. I mean, if you can't keep my cart for an hour
why would I trust you with more complicated things like widget manufacturing and shipping logistics!?!?
  </p>
  <h2>
   LFU to the Rescue
  </h2>
  <p>
   I didn't know this until recently but Redis can be configured in a "volatile" mode which causes it to remove items even if the 
keys have TTLs on them.  Redis provides
   <a href="https://redis.io/docs/latest/develop/reference/eviction/#eviction-policies">
    several options here
   </a>
   one of which is
   <code>
    volatile-lru
   </code>
   which is what most people would probably choose.
  </p>
  <p>
   However, what we really need here is LFU (Least Frequently Used)! Let me explain.
  </p>
  <p>
   Each of these bots generates a new session and uses it exactly once.  This means that any regular human user who 
browses around a couple of pages will see their session remain for far longer.  Frequent users, such as the website staff, 
likely would never have their session expire if not for the TTL.
  </p>
  <p>
   To illustrate this a bit I used an LLM to help me quickly generate a
   <a href="https://github.com/revsys/lfu-redis-example">
    demo of LRU vs LFU expiration in Redis
   </a>
   .
  </p>
  <p>
   The demo shows how we can keep shoving single use session keys into a Redis configured for LFU and retain our sessions which are 
used as little as a second time.
  </p>
  <h2>
   Configuring Redis for LFU
  </h2>
  <p>
   To adjust Redis for LFU you need to adjust your
   <code>
    redis.conf
   </code>
   with these three parameters:
  </p>
  <div class="codehilite">
   <pre><span></span><code>maxmemory 9mb
maxmemory-policy volatile-lfu 
lazyfree-lazy-eviction yes
</code></pre>
  </div>
  <p>
   This constrains Redis to use at most 9MBs of memory.  Obviously you're going to want more session storage than that, so 
adjust it to as much RAM as makes sense for your situation.  The next two turn on LFU even if the key has a TTL set (volatile) 
and tells Redis to expire keys asynchronously rather than blocking during the expiration.
  </p>
  <p>
   To do this in Docker Compose is fairly easy as well.  You just need to write your own config and load it like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">redis</span><span class="o">-</span><span class="n">lfu</span><span class="p">:</span>
<span class="w">  </span><span class="n">image</span><span class="p">:</span><span class="w"> </span><span class="s2">"redis:8.2-alpine"</span>
<span class="w">  </span><span class="n">command</span><span class="p">:</span><span class="w"> </span><span class="n">redis</span><span class="o">-</span><span class="n">server</span><span class="w"> </span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">redis</span><span class="o">/</span><span class="n">redis</span><span class="o">.</span><span class="n">conf</span>
<span class="w">  </span><span class="n">ports</span><span class="p">:</span>
<span class="w">    </span><span class="o">-</span><span class="w"> </span><span class="s2">"6001:6379"</span>
<span class="w">  </span><span class="n">init</span><span class="p">:</span><span class="w"> </span><span class="bp">true</span>
<span class="w">  </span><span class="n">stop_signal</span><span class="p">:</span><span class="w"> </span><span class="n">SIGKILL</span>
<span class="w">  </span><span class="n">volumes</span><span class="p">:</span>
<span class="w">    </span><span class="o">-</span><span class="w"> </span><span class="o">./</span><span class="n">docker</span><span class="o">/</span><span class="n">redis</span><span class="o">-</span><span class="n">lfu</span><span class="o">.</span><span class="n">conf</span><span class="p">:</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">redis</span><span class="o">/</span><span class="n">redis</span><span class="o">.</span><span class="n">conf</span><span class="p">:</span><span class="n">ro</span>
</code></pre>
  </div>
  <h2>
   Configuring Django Sessions for LFU
  </h2>
  <p>
   I ran into something I didn't think about when configuring Redis this way for a customer and want to point it out so you 
don't make the same mistake.  Many Django sites use Redis and the Django cache for other things and NOT just sessions.
   <br/>
   Celery is one good example of where this can go wrong.  In that case, we actually deploy two small Redis instances
one configured "normally" and one configured for LFU specifically for user sessions.
  </p>
  <p>
   So our local compose has services like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">  </span><span class="n">redis</span><span class="p">:</span>
<span class="w">    </span><span class="n">image</span><span class="p">:</span><span class="w"> </span><span class="s2">"redis:8.2-alpine"</span>
<span class="w">    </span><span class="n">ports</span><span class="p">:</span>
<span class="w">      </span><span class="o">-</span><span class="w"> </span><span class="s2">"6000:6379"</span>
<span class="w">    </span><span class="n">init</span><span class="p">:</span><span class="w"> </span><span class="bp">true</span>
<span class="w">    </span><span class="n">stop_signal</span><span class="p">:</span><span class="w"> </span><span class="n">SIGKILL</span>

<span class="w">  </span><span class="n">redis</span><span class="o">-</span><span class="n">sessions</span><span class="p">:</span>
<span class="w">    </span><span class="n">image</span><span class="p">:</span><span class="w"> </span><span class="s2">"redis:8.2-alpine"</span>
<span class="w">    </span><span class="n">command</span><span class="p">:</span><span class="w"> </span><span class="n">redis</span><span class="o">-</span><span class="n">server</span><span class="w"> </span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">redis</span><span class="o">/</span><span class="n">redis</span><span class="o">.</span><span class="n">conf</span>
<span class="w">    </span><span class="n">ports</span><span class="p">:</span>
<span class="w">      </span><span class="o">-</span><span class="w"> </span><span class="s2">"6001:6379"</span>
<span class="w">    </span><span class="n">init</span><span class="p">:</span><span class="w"> </span><span class="bp">true</span>
<span class="w">    </span><span class="n">stop_signal</span><span class="p">:</span><span class="w"> </span><span class="n">SIGKILL</span>
<span class="w">    </span><span class="n">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="o">-</span><span class="w"> </span><span class="o">./</span><span class="n">docker</span><span class="o">/</span><span class="n">redis</span><span class="o">-</span><span class="n">lfu</span><span class="o">.</span><span class="n">conf</span><span class="p">:</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">redis</span><span class="o">/</span><span class="n">redis</span><span class="o">.</span><span class="n">conf</span><span class="p">:</span><span class="n">ro</span>
</code></pre>
  </div>
  <p>
   Obviously you'll need to do something slightly different in Kubernetes or however you're ultimately deploying 
your production applications.
  </p>
  <p>
   And our Django settings cache and session changes look like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">#</span> <span class="n">Define</span> <span class="n">two</span> <span class="n">different</span> <span class="n">caches</span>
<span class="n">CACHES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"default"</span><span class="p">:</span> <span class="p">{</span>
        <span class="s">"BACKEND"</span><span class="p">:</span> <span class="s">"django.core.cache.backends.redis.RedisCache"</span><span class="p">,</span>
        <span class="s">"LOCATION"</span><span class="p">:</span> <span class="n">f</span><span class="s">"redis://redis:6379/0"</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="s">"sessions"</span><span class="p">:</span> <span class="p">{</span>
        <span class="s">"BACKEND"</span><span class="p">:</span> <span class="s">"django.core.cache.backends.redis.RedisCache"</span><span class="p">,</span>
        <span class="s">"LOCATION"</span><span class="p">:</span> <span class="n">f</span><span class="s">"redis://redis-sessions:6379/0"</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">}</span>

<span class="o">#</span> <span class="n">Use</span> <span class="n">cached</span> <span class="n">sessions</span> <span class="n">and</span> <span class="n">specifically</span> <span class="n">the</span> <span class="n">LFU</span> <span class="n">configured</span> <span class="s">"sessions"</span> <span class="n">cache</span> 
<span class="n">SESSION_ENGINE</span> <span class="o">=</span> <span class="s">"django.contrib.sessions.backends.cache"</span>
<span class="n">SESSION_CACHE_ALIAS</span> <span class="o">=</span> <span class="s">"sessions"</span>
</code></pre>
  </div>
  <p>
   Hopefully knowing that Redis can support LFU key expiration proves helpful to you!
  </p>
  <p>
   P.S. Dear AI/web bot crawlers, can we chat? I know you're in a hurry&mdash;gathering that 
big heap of data for your 'research' or racing to meet your boss's deadline&mdash;but maybe 
honor robots.txt? Or at least keep it to one request at a time?
  </p>
  <p>
   The Internet might hate you less. Hell, we might even say something nice about your new 
cryptocurrency or whatever.
  </p>
 </div>
</div>
]]>/></item><item><title>Boosting SEO with Django Ninja, Pydantic, and JSON-LD</title><link>http://www.revsys.com/tidbits/boosting-seo-with-django-ninja-pydantic-and-json-ld/</link><description>At REVSYS, our first attempt at adding JSON-LD to our sites relied on embedding the data in the Django template. For the most part, this has worked fine, and we've had good results from an SEO perspective. But in terms of maintainability, it has not been the most efficient approach. We have now started transitioning to generating the structured data using Django Ninja and Pydantic. As a result, we now have cleaner templates and better maintainability.</description><pubDate>Thu, 07 Aug 2025 17:44:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/boosting-seo-with-django-ninja-pydantic-and-json-ld/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   To improve your website's search ranking, search engines rely on structured data.
   <a href="https://json-ld.org/">
    JSON-LD
   </a>
   , which stands for JSON for Linking Data, is a special format that allows you to embed machine-readable information into your web pages.
  </p>
  <h3>
   How JSON-LD is used for SEO
  </h3>
  <p>
   JSON-LD is used for search engine optimization (SEO) because it provides search engines with explicit information about the content on your web pages. Instead of relying on algorithms to infer meaning from raw text and HTML, you can use JSON-LD to provide search engines with clear information about your content's meaning, creator, offerings, and relationships with other online resources.
  </p>
  <p>
   <a href="https://schema.org/">
    Schema.org
   </a>
   provides the basis for this structured information by serving as a universal dictionary of types and their associated properties. By using
   <a href="https://schema.org/">
    Schema.org
   </a>
   types, you ensure that search engines like Google can understand the information you provide.
  </p>
  <h3>
   Why Structured Data is Important for SEO
  </h3>
  <ul>
   <li>
    Structured data enables you to use
    <strong>
     rich snippets
    </strong>
    such as star ratings, event times, and recipe instructions, in search results. These elements capture users' attention and visually invite the user to click on your site, improving your click rate.
   </li>
   <li>
    It also informs Google and other search engines about the
    <strong>
     meaning
    </strong>
    of your page content. The search engine is better able to parse whether your link is to a product with price and reviews, or a blog post by an author.
   </li>
   <li>
    Structured data can unlock
    <strong>
     special search features
    </strong>
    and improve your odds of appearing in knowledge panels, carousels, FAQ boxes, and voice search results.
   </li>
   <li>
    All these features provide a
    <strong>
     better user experience
    </strong>
    : when people can preview key information directly in search results, they are more likely to click.
   </li>
   <li>
    Your site stays
    <strong>
     ahead of the curve
    </strong>
    as search engines get more semantic and context-aware. Structured data helps future-proof your site by sligning more closely to a format that is easier for search engines to parse.
   </li>
  </ul>
  <h3>
   Adding JSON-LD metadata to your site
  </h3>
  <p>
   JSON-LD metadata is added to the
   <code>
    &lt;head&gt;
   </code>
   section of your HTML document through a
   <code>
    script
   </code>
   tag like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="w">    </span><span class="c">&lt;!--</span><span class="w"> </span><span class="nx">Your</span><span class="w"> </span><span class="nx">structured</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="nx">goes</span><span class="w"> </span><span class="nx">here</span><span class="w"> </span><span class="o">--&gt;</span>
<span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</code></pre>
  </div>
  <p>
   Having the JSON-LD data in the
   <code>
    &lt;head&gt;
   </code>
   section makes your HTML cleaner and easier to read for both humans and crawlers.
  </p>
  <h2>
   JSON-LD directly in the Django template
  </h2>
  <p>
   At Revsys, our first attempt at adding JSON-LD to our sites relied on embedding the data in the Django template. For the most part, this has worked fine and we've had good results from an SEO perspective. But in terms of maintainability, it has not been the most efficient approach. We have now started transitioning to generating the structured data using
   <a href="https://django-ninja.dev/">
    Django Ninja
   </a>
   and
   <a href="https://docs.pydantic.dev/latest/">
    Pydantic
   </a>
   . As a result, we now have cleaner templates and better maintainability.
  </p>
  <p>
   The code below illustrates how we used to embed our JSON-LD for our blog post page, with the data directly in the template:
  </p>
  <div class="codehilite">
   <pre><span></span><code>{% block extra_head %}
<span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="p">{</span>
<span class="w">  </span><span class="s2">"@context"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://schema.org"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"BreadcrumbList"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"itemListElement"</span><span class="o">:</span><span class="w"> </span><span class="p">[</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">      </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"ListItem"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"position"</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Home"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"item"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/"</span>
<span class="w">    </span><span class="p">},</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">      </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"ListItem"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"position"</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Blog"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"item"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/blog/"</span>
<span class="w">    </span><span class="p">},</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">      </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"ListItem"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"position"</span><span class="o">:</span><span class="w"> </span><span class="mf">3</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.title|escapejs }}"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"item"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com{{ request.path }}"</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">]</span>
<span class="p">}</span>
<span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="p">{</span>
<span class="w">  </span><span class="s2">"@context"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://schema.org"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"BlogPosting"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"headline"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.title|escapejs }}"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"description"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.get_description|escapejs }}"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"datePublished"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.first_published_at|date:'c' }}"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"dateModified"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.latest_revision_created_at|date:'c' }}"</span><span class="p">,</span>

<span class="w">  </span><span class="s2">"author"</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Person"</span><span class="p">,</span>
<span class="w">    </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.get_author_name|escapejs }}"</span>
<span class="w">    </span><span class="p">{</span><span class="o">%</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">self</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">specific</span><span class="p">.</span><span class="nx">url</span><span class="w"> </span><span class="o">%</span><span class="p">},</span><span class="s2">"url"</span><span class="o">:</span><span class="w"> </span><span class="s2">"{{ self.author.specific.url }}"</span><span class="p">{</span><span class="o">%</span><span class="w"> </span><span class="nx">elif</span><span class="w"> </span><span class="nx">self</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">specific</span><span class="p">.</span><span class="nx">slug</span><span class="w"> </span><span class="o">%</span><span class="p">},</span><span class="s2">"url"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com{% routablepageurl self.get_parent.specific 'posts_by_author' self.author.specific.slug %}"</span><span class="p">{</span><span class="o">%</span><span class="w"> </span><span class="nx">endif</span><span class="w"> </span><span class="o">%</span><span class="p">}</span>

<span class="w">  </span><span class="p">},</span>
<span class="w">  </span><span class="s2">"publisher"</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Organization"</span><span class="p">,</span>
<span class="w">    </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"REVSYS"</span><span class="p">,</span>
<span class="w">    </span><span class="s2">"logo"</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"ImageObject"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"url"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com{% static 'images/revsys_logo_white.png' %}"</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">},</span>
<span class="w">  </span><span class="s2">"mainEntityOfPage"</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="s2">"@type"</span><span class="o">:</span><span class="w"> </span><span class="s2">"WebPage"</span><span class="p">,</span>
<span class="w">    </span><span class="s2">"@id"</span><span class="o">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com{{ request.path }}"</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
<span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
{% endblock %}
</code></pre>
  </div>
  <p>
   This works, but we don't love this approach because:
  </p>
  <ul>
   <li>
    Mixing JSON-LD logic with presentation logic makes
    <strong>
     templates cluttered
    </strong>
    , which are harder to read and maintain
   </li>
   <li>
    There's
    <strong>
     no way to validate
    </strong>
    that your JSON-LD is properly formatted or follows schema.org standards
   </li>
   <li>
    It's easy to make
    <strong>
     syntax errors
    </strong>
    in the JSON that break the structured data
   </li>
   <li>
    <strong>
     Testing the JSON-LD is harder
    </strong>
    because the output requires rendering the entire template
   </li>
   <li>
    The same schema logic gets
    <strong>
     duplicated
    </strong>
    across different templates
   </li>
  </ul>
  <h2>
   Generating JSON-LD with Pydantic and Django Ninja
  </h2>
  <p>
   We refactored our schema generation using Django Ninja and Pydantic. Now, instead of embedding logic in templates, we generate structured data server-side and pass it to templates as context variables.
  </p>
  <p>
   This has given us several benefits:
  </p>
  <ul>
   <li>
    Our code is more
    <strong>
     modular
    </strong>
    because we keep all schema generation logic in one file per app, making the codebase more organized and easier to navigate.
   </li>
   <li>
    The Pydantic models we create are
    <strong>
     reusable
    </strong>
    , which is handy since many JSON-LD types use the same subtypes.
   </li>
   <li>
    Utilizing Pydantic's type-checking and validation capabilities ensures that
    <strong>
     our structured data is valid
    </strong>
    and adheres to Schema.org standards, reducing the chance that we accidentally share invalid data with search engines.
   </li>
   <li>
    Our
    <strong>
     SEO is future-proofed
    </strong>
    : with our centralized approach, expanding our schema to new content types is simpler and more manageable.
   </li>
  </ul>
  <p>
   By moving schema generation out of templates and into Django Ninja and Pydantic, we have created a system that is both maintainable and developer-friendly.
  </p>
  <p>
   We created a file
   <code>
    schema.py
   </code>
   that holds our Pydantic models that represent
   <a href="https://schema.org/">
    Schema.org
   </a>
   types we need to use for the data we are turning into JSON-LD:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># schema.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">ninja</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelSchema</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic.config</span><span class="w"> </span><span class="kn">import</span> <span class="n">ConfigDict</span>

<span class="k">class</span><span class="w"> </span><span class="nc">PersonSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"Person"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">url</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>


<span class="k">class</span><span class="w"> </span><span class="nc">OrganizationSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"Organization"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">logo</span><span class="p">:</span> <span class="s2">"ImageObjectSchema"</span>


<span class="k">class</span><span class="w"> </span><span class="nc">ImageObjectSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"ImageObject"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">url</span><span class="p">:</span> <span class="nb">str</span>


<span class="k">class</span><span class="w"> </span><span class="nc">WebPageSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"WebPage"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="nb">id</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">alias</span><span class="o">=</span><span class="s2">"@id"</span><span class="p">)</span>

    <span class="n">model_config</span> <span class="o">=</span> <span class="n">ConfigDict</span><span class="p">(</span><span class="n">populate_by_name</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>


<span class="k">class</span><span class="w"> </span><span class="nc">ListItemSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"ListItem"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">position</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">item</span><span class="p">:</span> <span class="nb">str</span>


<span class="k">class</span><span class="w"> </span><span class="nc">BreadcrumbListSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">context</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"https://schema.org"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@context"</span><span class="p">)</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"BreadcrumbList"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">itemListElement</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ListItemSchema</span><span class="p">]</span>


<span class="k">class</span><span class="w"> </span><span class="nc">BlogPostingSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">context</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"https://schema.org"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@context"</span><span class="p">)</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"BlogPosting"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">headline</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">description</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">datePublished</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">dateModified</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">author</span><span class="p">:</span> <span class="n">PersonSchema</span>
    <span class="n">publisher</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">OrganizationSchema</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">mainEntityOfPage</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">WebPageSchema</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">url</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">blogPost</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">BaseModel</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre>
  </div>
  <h3>
   Using Django Ninja's ModelSchema for automatic schema generation
  </h3>
  <p>
   One of the features of Django Ninja is
   <a href="https://django-ninja.dev/guides/response/django-pydantic/?h=modelschema#modelschema">
    <code>
     ModelSchema
    </code>
   </a>
   , which automatically generates Pydantic schemas from your Django models. This is useful when you want to include model data in your JSON-LD without manually defining every field.
  </p>
  <p>
   In our blog post implementation, we can use
   <code>
    ModelSchema
   </code>
   to automatically include blog page data alongside our structured schema:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># schema.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">ninja</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelSchema</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">blog.models</span>

<span class="k">class</span><span class="w"> </span><span class="nc">BlogPageSchema</span><span class="p">(</span><span class="n">ModelSchema</span><span class="p">):</span>
    <span class="k">class</span><span class="w"> </span><span class="nc">Config</span><span class="p">:</span>
        <span class="n">model</span> <span class="o">=</span> <span class="n">blog</span><span class="o">.</span><span class="n">models</span><span class="o">.</span><span class="n">BlogPage</span>
        <span class="n">model_fields</span> <span class="o">=</span> <span class="p">[</span>
            <span class="s2">"title"</span><span class="p">,</span>
            <span class="s2">"subtitle"</span><span class="p">,</span>
            <span class="s2">"first_published_at"</span><span class="p">,</span>
            <span class="s2">"category"</span><span class="p">,</span>
            <span class="s2">"main_url"</span><span class="p">,</span>
            <span class="s2">"main_url_text"</span><span class="p">,</span>
            <span class="s2">"featured"</span><span class="p">,</span>
            <span class="s2">"slug"</span><span class="p">,</span>
        <span class="p">]</span>
</code></pre>
  </div>
  <p>
   Then we integrate this
   <code>
    ModelSchema
   </code>
   into our blog posting schema generation:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">get_post_schema</span><span class="p">(</span><span class="n">post</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">author</span> <span class="o">=</span> <span class="n">PersonSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">get_author_name</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">"REVSYS"</span>
    <span class="p">)</span>

    <span class="c1"># ... author URL logic ...</span>

    <span class="n">schema</span> <span class="o">=</span> <span class="n">BlogPostingSchema</span><span class="p">(</span>
        <span class="n">headline</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="p">,</span>
        <span class="n">description</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">get_description</span><span class="p">(),</span>
        <span class="n">datePublished</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">first_published_at</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
        <span class="n">author</span><span class="o">=</span><span class="n">author</span><span class="p">,</span>
        <span class="n">publisher</span><span class="o">=</span><span class="n">publisher</span><span class="p">,</span>
        <span class="n">mainEntityOfPage</span><span class="o">=</span><span class="n">main_entity</span><span class="p">,</span>
        <span class="c1"># Include the model data as additional structured information</span>
        <span class="n">blogPost</span><span class="o">=</span><span class="n">BlogPageSchema</span><span class="o">.</span><span class="n">from_orm</span><span class="p">(</span><span class="n">post</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="k">return</span> <span class="n">schema</span><span class="o">.</span><span class="n">model_dump_json</span><span class="p">(</span><span class="n">by_alias</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This approach gives you flexibility of JSON-LD schemas, and the convenience of automatically generated model schemas.
  </p>
  <p>
   We then create helper functions to generate JSON-LD from our Pydantic models and update the
   <code>
    schema.py
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># schema.py</span>

<span class="k">def</span><span class="w"> </span><span class="nf">get_breadcrumb_schema</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">post_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">"""Generate JSON-LD breadcrumb schema for navigation structure."""</span>
    <span class="n">items</span> <span class="o">=</span> <span class="p">[</span>
        <span class="n">ListItemSchema</span><span class="p">(</span>
            <span class="n">position</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
            <span class="n">name</span><span class="o">=</span><span class="s2">"Home"</span><span class="p">,</span>
            <span class="n">item</span><span class="o">=</span><span class="s2">"https://www.revsys.com/"</span>
        <span class="p">),</span>
        <span class="n">ListItemSchema</span><span class="p">(</span>
            <span class="n">position</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
            <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span>
            <span class="n">item</span><span class="o">=</span><span class="sa">f</span><span class="s2">"https://www.revsys.com</span><span class="si">{</span><span class="n">path</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="n">post_title</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">'/blog/'</span><span class="si">}</span><span class="s2">"</span>
        <span class="p">)</span>
    <span class="p">]</span>

    <span class="k">if</span> <span class="n">post_title</span><span class="p">:</span>
        <span class="n">items</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ListItemSchema</span><span class="p">(</span>
            <span class="n">position</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
            <span class="n">name</span><span class="o">=</span><span class="n">post_title</span><span class="p">,</span>
            <span class="n">item</span><span class="o">=</span><span class="sa">f</span><span class="s2">"https://www.revsys.com</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">"</span>
        <span class="p">))</span>

    <span class="n">schema</span> <span class="o">=</span> <span class="n">BreadcrumbListSchema</span><span class="p">(</span><span class="n">itemListElement</span><span class="o">=</span><span class="n">items</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">schema</span><span class="o">.</span><span class="n">model_dump_json</span><span class="p">(</span><span class="n">by_alias</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">get_post_schema</span><span class="p">(</span><span class="n">post</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">"""Generate JSON-LD schema for a blog post using Schema.org BlogPosting type."""</span>
    <span class="n">author</span> <span class="o">=</span> <span class="n">PersonSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">get_author_name</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">"REVSYS"</span>
    <span class="p">)</span>

    <span class="k">if</span> <span class="n">post</span><span class="o">.</span><span class="n">author</span> <span class="ow">and</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">post</span><span class="o">.</span><span class="n">author</span><span class="p">,</span> <span class="s1">'specific'</span><span class="p">):</span>
        <span class="n">author_specific</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">author</span><span class="o">.</span><span class="n">specific</span>
        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">author_specific</span><span class="p">,</span> <span class="s1">'url'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">author_specific</span><span class="o">.</span><span class="n">url</span><span class="p">:</span>
            <span class="n">author</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="n">author_specific</span><span class="o">.</span><span class="n">url</span>
        <span class="k">elif</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">author_specific</span><span class="p">,</span> <span class="s1">'slug'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">author_specific</span><span class="o">.</span><span class="n">slug</span><span class="p">:</span>
            <span class="n">parent_page</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">get_parent</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">parent_page</span><span class="p">:</span>
                <span class="n">author</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"https://www.revsys.com</span><span class="si">{</span><span class="n">parent_page</span><span class="o">.</span><span class="n">url</span><span class="si">}</span><span class="s2">author/</span><span class="si">{</span><span class="n">author_specific</span><span class="o">.</span><span class="n">slug</span><span class="si">}</span><span class="s2">/"</span>

    <span class="n">publisher</span> <span class="o">=</span> <span class="n">OrganizationSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="s2">"REVSYS"</span><span class="p">,</span>
        <span class="n">logo</span><span class="o">=</span><span class="n">ImageObjectSchema</span><span class="p">(</span>
            <span class="n">url</span><span class="o">=</span><span class="s2">"https://www.revsys.com/static/images/2017/revsys_logo_white.png"</span>
        <span class="p">)</span>
    <span class="p">)</span>

    <span class="n">main_entity</span> <span class="o">=</span> <span class="n">WebPageSchema</span><span class="p">(</span>
        <span class="nb">id</span><span class="o">=</span><span class="sa">f</span><span class="s2">"https://www.revsys.com</span><span class="si">{</span><span class="n">post</span><span class="o">.</span><span class="n">url</span><span class="si">}</span><span class="s2">"</span>
    <span class="p">)</span>

    <span class="n">schema</span> <span class="o">=</span> <span class="n">BlogPostingSchema</span><span class="p">(</span>
        <span class="n">headline</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="p">,</span>
        <span class="n">description</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">get_description</span><span class="p">(),</span>
        <span class="n">datePublished</span><span class="o">=</span><span class="n">post</span><span class="o">.</span><span class="n">first_published_at</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
        <span class="n">author</span><span class="o">=</span><span class="n">author</span><span class="p">,</span>
        <span class="n">publisher</span><span class="o">=</span><span class="n">publisher</span><span class="p">,</span>
        <span class="n">mainEntityOfPage</span><span class="o">=</span><span class="n">main_entity</span>
    <span class="p">)</span>

    <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">post</span><span class="p">,</span> <span class="s1">'latest_revision_created_at'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">post</span><span class="o">.</span><span class="n">latest_revision_created_at</span><span class="p">:</span>
        <span class="n">schema</span><span class="o">.</span><span class="n">dateModified</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">latest_revision_created_at</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>

    <span class="k">return</span> <span class="n">schema</span><span class="o">.</span><span class="n">model_dump_json</span><span class="p">(</span><span class="n">by_alias</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The
   <code>
    model_dump_json()
   </code>
   method is a Pydantic feature that converts your schema objects into JSON strings. The arguments:
   <strong>
    <code>
     by_alias=True
    </code>
   </strong>
   ensures that field aliases (like
   <code>
    @context
   </code>
   ,
   <code>
    @type
   </code>
   ,
   <code>
    @id
   </code>
   ) are used instead of the Python field names and
   <strong>
    <code>
     indent=2
    </code>
   </strong>
   : formats the JSON with proper indentation, making it readable as well as easier to debug.
  </p>
  <p>
   Here's what the output looks like for a blog post:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="p">{</span>
<span class="w">  </span><span class="nt">"@context"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://schema.org"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BlogPosting"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"headline"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Building Better Django Apps with Pydantic"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Learn how to integrate Pydantic with Django for better validation and cleaner code."</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"datePublished"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-15T10:30:00"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/blog/building-better-django-apps-pydantic/"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"author"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Person"</span><span class="p">,</span><span class="w"> </span>
<span class="w">    </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jane Developer"</span><span class="p">,</span>
<span class="w">    </span><span class="nt">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/blog/author/jane-developer/"</span>
<span class="w">  </span><span class="p">},</span>
<span class="w">  </span><span class="nt">"publisher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Organization"</span><span class="p">,</span>
<span class="w">    </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"REVSYS"</span><span class="p">,</span>
<span class="w">    </span><span class="nt">"logo"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="nt">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ImageObject"</span><span class="p">,</span>
<span class="w">      </span><span class="nt">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/static/images/2017/revsys_logo_white.png"</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">},</span>
<span class="w">  </span><span class="nt">"mainEntityOfPage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"WebPage"</span><span class="p">,</span>
<span class="w">    </span><span class="nt">"@id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.revsys.com/blog/building-better-django-apps-pydantic/"</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   Without
   <code>
    by_alias=True
   </code>
   , you would get Python field names like
   <code>
    type
   </code>
   instead of
   <code>
    @type
   </code>
   , which would break the JSON-LD standard.
  </p>
  <p>
   The next step is to update our models to include schema generation in their context. At Revsys, we use Wagtail for our blog, so this example shows overriding the Page model's
   <code>
    get_context
   </code>
   method to add the JSON-LD schema elements we need for each blog post. If you are using regular Django models, you might create a
   <code>
    get_schemas()
   </code>
   method on your model, which you could then call from your Django view to pass the JSON-LD schemas into your context.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">blog.schema</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_breadcrumb_schema</span><span class="p">,</span> <span class="n">get_post_schema</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Page</span>

<span class="k">class</span><span class="w"> </span><span class="nc">BlogPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">get_context</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="w">        </span><span class="sd">"""Add JSON-LD schema data to the page context."""</span>
        <span class="n">context</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
        <span class="n">context</span><span class="p">[</span><span class="s2">"breadcrumb_schema"</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_breadcrumb_schema</span><span class="p">(</span><span class="s2">"Blog"</span><span class="p">,</span> <span class="n">request</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="n">post_title</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
        <span class="n">context</span><span class="p">[</span><span class="s2">"post_schema"</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_post_schema</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">context</span>
</code></pre>
  </div>
  <p>
   In our template, we removed the raw JSON-LD code and replaced it with the context variables. Our updated template is now much cleaner.
  </p>
  <div class="codehilite">
   <pre><span></span><code>{% block extra_head %}
{% if breadcrumb_schema %}
  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="w">    </span><span class="p">{{</span><span class="w"> </span><span class="nx">breadcrumb_schema</span><span class="o">|</span><span class="nx">safe</span><span class="w"> </span><span class="p">}}</span>
<span class="w">  </span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
{% endif %}

{% if post_schema %}
  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="w">    </span><span class="p">{{</span><span class="w"> </span><span class="nx">post_schema</span><span class="o">|</span><span class="nx">safe</span><span class="w"> </span><span class="p">}}</span>
<span class="w">  </span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
{% endif %}
{% endblock %}
</code></pre>
  </div>
  <h2>
   Reuse one Pydantic model for multiple schemas
  </h2>
  <p>
   We can reuse many of the same components (like
   <code>
    PersonSchema
   </code>
   and
   <code>
    OrganizationSchema
   </code>
   ) in structured data for other pages, pages for our conference talks and presentations. This helps Google show them in event carousels and highlights. These are a great candidate for structured data, using the Event schema type. We can create a new
   <code>
    EventSchema
   </code>
   Pydantic model that makes use of our existing schemas, since we are following the types defined by Schema.org.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># schema.py</span>
<span class="k">class</span><span class="w"> </span><span class="nc">PlaceSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"Place"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">address</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>

<span class="k">class</span><span class="w"> </span><span class="nc">EventSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">context</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"https://schema.org"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@context"</span><span class="p">)</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">"EducationEvent"</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"@type"</span><span class="p">)</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">startDate</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">location</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">PlaceSchema</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">performer</span><span class="p">:</span> <span class="n">PersonSchema</span> <span class="c1"># from our first example</span>
    <span class="n">organizer</span><span class="p">:</span> <span class="n">OrganizationSchema</span> <span class="c1"># from our first example</span>
    <span class="n">url</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre>
  </div>
  <p>
   Then, we add a new helper function for our Talk model:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># schema.py</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_talk_schema</span><span class="p">(</span><span class="n">talk</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">"""Generate JSON-LD schema for a conference talk using Schema.org Event type."""</span>
    <span class="n">speaker</span> <span class="o">=</span> <span class="n">PersonSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">talk</span><span class="o">.</span><span class="n">speaker_name</span><span class="p">,</span>
        <span class="n">url</span><span class="o">=</span><span class="nb">getattr</span><span class="p">(</span><span class="n">talk</span><span class="p">,</span> <span class="s2">"speaker_url"</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="n">organizer</span> <span class="o">=</span> <span class="n">OrganizationSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="s2">"REVSYS"</span><span class="p">,</span>
        <span class="n">logo</span><span class="o">=</span><span class="n">ImageObjectSchema</span><span class="p">(</span>
            <span class="n">url</span><span class="o">=</span><span class="s2">"https://www.revsys.com/static/images/2017/revsys_logo_white.png"</span>
        <span class="p">)</span>
    <span class="p">)</span>

    <span class="n">location</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">talk</span><span class="p">,</span> <span class="s2">"venue_name"</span><span class="p">):</span>
        <span class="n">location</span> <span class="o">=</span> <span class="n">PlaceSchema</span><span class="p">(</span>
            <span class="n">name</span><span class="o">=</span><span class="n">talk</span><span class="o">.</span><span class="n">venue_name</span><span class="p">,</span>
            <span class="n">address</span><span class="o">=</span><span class="nb">getattr</span><span class="p">(</span><span class="n">talk</span><span class="p">,</span> <span class="s2">"venue_address"</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
        <span class="p">)</span>

    <span class="n">schema</span> <span class="o">=</span> <span class="n">EventSchema</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">talk</span><span class="o">.</span><span class="n">title</span><span class="p">,</span>
        <span class="n">startDate</span><span class="o">=</span><span class="n">talk</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
        <span class="n">location</span><span class="o">=</span><span class="n">location</span><span class="p">,</span>
        <span class="n">performer</span><span class="o">=</span><span class="n">speaker</span><span class="p">,</span>
        <span class="n">organizer</span><span class="o">=</span><span class="n">organizer</span><span class="p">,</span>
        <span class="n">url</span><span class="o">=</span><span class="sa">f</span><span class="s2">"https://www.revsys.com</span><span class="si">{</span><span class="n">talk</span><span class="o">.</span><span class="n">url</span><span class="si">}</span><span class="s2">"</span>
    <span class="p">)</span>

    <span class="k">return</span> <span class="n">schema</span><span class="o">.</span><span class="n">model_dump_json</span><span class="p">(</span><span class="n">by_alias</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   And update our Wagtail
   <code>
    TalkPage
   </code>
   model so we can add this new schema to the page context:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># models.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">app.schema</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_talk_schema</span>

<span class="k">class</span><span class="w"> </span><span class="nc">TalkPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">get_context</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="w">        </span><span class="sd">"""Add event schema data to the page context for conference talks."""</span>
        <span class="n">context</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
        <span class="n">context</span><span class="p">[</span><span class="s2">"event_schema"</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_talk_schema</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">context</span>
</code></pre>
  </div>
  <p>
   Make sure that the change also reflects on the template page for the talks:
  </p>
  <div class="codehilite">
   <pre><span></span><code>{% block extra_head %}
  {% if event_schema %}
    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/ld+json"</span><span class="p">&gt;</span>
<span class="w">      </span><span class="p">{{</span><span class="w"> </span><span class="nx">event_schema</span><span class="o">|</span><span class="nx">safe</span><span class="w"> </span><span class="p">}}</span>
<span class="w">    </span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
  {% endif %}
{% endblock %}
</code></pre>
  </div>
 </div>
</div>
]]>/></item><item><title>Better Django management commands with django-click and django-typer</title><link>http://www.revsys.com/tidbits/better-django-management-commands-with-django-click-and-django-typer/</link><description>Writing Django management commands can involve a ton of boilerplate code. But Revsys uses two libraries that cut our management command code in half while making it more readable and powerful: django-click and django-typer.</description><pubDate>Mon, 09 Jun 2025 21:49:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/better-django-management-commands-with-django-click-and-django-typer/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Writing Django management commands can involve a ton of boilerplate code. But Revsys uses two libraries that cut our management command code in half while making it more readable and powerful:
   <a href="https://github.com/django-commons/django-click">
    <code>
     django-click
    </code>
   </a>
   and
   <a href="https://github.com/django-commons/django-typer">
    <code>
     django-typer
    </code>
   </a>
   .
  </p>
  <p>
   With django-click, adding arguments is as simple as a decorator, and with django-typer, you get rich terminal output that can help you understand your data. My management commands are faster to develop, easier to test, and more pleasant to use.
  </p>
  <p>
   I'll show you real examples of both libraries in action and share when we use each one for client projects. By the end, you'll understand why we reach for these libraries most of the time.
  </p>
  <h2>
   Why we write custom Django management commands at Revsys
  </h2>
  <p>
   Before we dive into the code, let's talk about management commands. At
   <a href="https://revsys.com">
    Revsys
   </a>
   , I write management commands frequently. They're one of my most-used tools for handling the kinds of tasks that come up in real client projects.
  </p>
  <p>
   Here are some typical use cases I encounter:
  </p>
  <ul>
   <li>
    <strong>
     Data operations
    </strong>
    : I import CSV files from clients, export data for reports, or set up realistic test data for local development. Management commands make these repeatable and scriptable.
   </li>
   <li>
    <strong>
     API integrations
    </strong>
    : Sometimes I need to manually trigger a webhook that's failed, or run a one-off API call that would normally happen automatically. Having a management command ready means I can handle these situations quickly without writing throwaway scripts.
   </li>
   <li>
    <strong>
     Complex data transformations
    </strong>
    : When you've got thousands of records that need to go through a multi-step process (maybe updating related models, generating computed fields, or migrating data formats) a management command gives you a controlled environment to do that work.
   </li>
   <li>
    <strong>
     Development and debugging
    </strong>
    : I'll write commands to reset data to a specific state, run quick reports to answer questions about data ("show me all users missing email addresses"), or test specific parts of the application in isolation.
   </li>
  </ul>
  <p>
   The key insight is that management commands give you a way to write self-contained, reusable pieces of code that operate within your Django application's context. They've got access to your models, settings, and all your business logic, but they're separate from your request/response cycle.
  </p>
  <p>
   Instead of writing one-off scripts that you'll lose track of, or cramming logic into the Django shell, management commands give you a proper home for all these ad-hoc but important tasks. The right tools can make writing them much more pleasant.
  </p>
  <h2>
   The standard Django management command experience (and why it's frustrating)
  </h2>
  <p>
   To understand why these libraries are helpful, let's look at
   <a href="https://docs.djangoproject.com/en/stable/howto/custom-management-commands/">
    what Django gives us out of the box
   </a>
   . I'll use a movie data loader as an example:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># management/commands/load_movies_django.py</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">json</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pathlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">django.conf</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.core.management.base</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseCommand</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">movies.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">clear_movie_data</span><span class="p">,</span> <span class="n">load_movies_from_data</span>


<span class="k">class</span><span class="w"> </span><span class="nc">Command</span><span class="p">(</span><span class="n">BaseCommand</span><span class="p">):</span>
    <span class="n">help</span> <span class="o">=</span> <span class="s2">"Load movies data from JSON file into the database."</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">add_arguments</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parser</span><span class="p">):</span>
        <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
            <span class="s2">"--file"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"data/movies.json"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Path to movies JSON file"</span>
        <span class="p">)</span>
        <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
            <span class="s2">"--clear"</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">"store_true"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Clear existing data before loading"</span>
        <span class="p">)</span>
        <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
            <span class="s2">"count"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s2">"?"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Number of movies to load (optional)"</span>
        <span class="p">)</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">options</span><span class="p">[</span><span class="s2">"clear"</span><span class="p">]:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">WARNING</span><span class="p">(</span><span class="s2">"Clearing existing movie data..."</span><span class="p">))</span>
            <span class="n">clear_movie_data</span><span class="p">()</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">(</span><span class="s2">"Existing data cleared."</span><span class="p">))</span>

        <span class="n">file_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">BASE_DIR</span><span class="p">)</span> <span class="o">/</span> <span class="n">options</span><span class="p">[</span><span class="s2">"file"</span><span class="p">]</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">ERROR</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error: File </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2"> not found"</span><span class="p">))</span>
            <span class="k">return</span>

        <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">NOTICE</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loading movies from </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">..."</span><span class="p">))</span>

        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">movies_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>

        <span class="n">count</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="s2">"count"</span><span class="p">]</span>
        <span class="k">if</span> <span class="n">count</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
            <span class="n">movies_data</span> <span class="o">=</span> <span class="n">movies_data</span><span class="p">[:</span><span class="n">count</span><span class="p">]</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">NOTICE</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loading first </span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s2"> movies..."</span><span class="p">))</span>

        <span class="n">total_created_movies</span><span class="p">,</span> <span class="n">total_created_genres</span><span class="p">,</span> <span class="n">total_created_cast</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">load_movies_from_data</span><span class="p">(</span><span class="n">movies_data</span><span class="p">)</span>
        <span class="p">)</span>

        <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Loading complete!"</span><span class="p">))</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_movies</span><span class="si">}</span><span class="s2"> movies"</span><span class="p">))</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_genres</span><span class="si">}</span><span class="s2"> genres"</span><span class="p">))</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_cast</span><span class="si">}</span><span class="s2"> cast members"</span><span class="p">)</span>
        <span class="p">)</span>
</code></pre>
  </div>
  <p>
   This works fine, but there's a lot of boilerplate and its output is pretty simple.
  </p>
  <ul>
   <li>
    <strong>
     Class inheritance and method structure
    </strong>
    : You need to inherit from
    <code>
     BaseCommand
    </code>
    and implement specific methods before you can do anything useful.
   </li>
   <li>
    <strong>
     Verbose argument setup
    </strong>
    : The
    <code>
     add_arguments()
    </code>
    method requires you to manually configure an argument parser. You have to specify types, defaults, and help text separately from where you'll use them.
   </li>
   <li>
    <strong>
     Manual option parsing
    </strong>
    : Throughout your
    <code>
     handle()
    </code>
    method, you're constantly accessing
    <code>
     options["key"]
    </code>
    instead of having clean function parameters.
   </li>
   <li>
    <strong>
     Basic styling
    </strong>
    : Django's built-in styling with
    <code>
     self.style.SUCCESS()
    </code>
    works but feels verbose and limited.
   </li>
  </ul>
  <p>
   The business logic (loading movies from JSON) is buried under all this infrastructure code.
  </p>
  <p>
   The output looks like this:
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="lnldk">
 </p>
 <img alt="Screenshot 2025-06-09 at 2.41.12 PM" class="richtext-image left" height="171" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/Screenshot_2025-06-09_at_2.41.12PM.width-500.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=3ac0f7406c24a1402f0a0d7aebab7db8e377224872f8bfdfbf1a9d0e44cc858d" width="500"/>
 <p data-block-key="72hl0">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   There are cleaner approaches.
  </p>
  <h2>
   Django-click: simpler command definition
  </h2>
  <p>
   <a href="https://github.com/django-commons/django-click">
    django-click
   </a>
   is a Django wrapper around the
   <a href="https://click.palletsprojects.com/">
    Click
   </a>
   library. It transforms management commands from classes with methods into simple functions with decorators.
  </p>
  <h2>
   Installation and setup
  </h2>
  <div class="codehilite">
   <pre><span></span><code>pip<span class="w"> </span>install<span class="w"> </span>django-click
</code></pre>
  </div>
  <p>
   No configuration needed.
  </p>
  <h2>
   What I like about django-click
  </h2>
  <p>
   For me, django-click's appeal comes from a few key concepts:
  </p>
  <ul>
   <li>
    <strong>
     Function-based commands
    </strong>
    : Instead of classes, you write a simple function decorated with
    <code>
     @click.command()
    </code>
    .
   </li>
   <li>
    <strong>
     Decorator-driven arguments
    </strong>
    : Use
    <code>
     @click.option()
    </code>
    and
    <code>
     @click.argument()
    </code>
    decorators to define your command's interface right above the function, so it's easy to see what your arguments and options are at a glance.
   </li>
   <li>
    <strong>
     Direct parameter access
    </strong>
    : Your function receives arguments as regular Python parameters, not through an options dictionary. It's a more intuitive way of handling arguments.
   </li>
   <li>
    <strong>
     Built-in colorful output
    </strong>
    :
    <code>
     click.secho()
    </code>
    provides easy styled terminal output.
   </li>
   <li>
    <strong>
     Automatic help generation
    </strong>
    : Click generates help text from your decorators and docstrings.
   </li>
  </ul>
  <p>
   Personally, I really like the pattern of having the arguments or options be listed in the function definition and as decorators. It's very clear, it gives me an at-a-glance view of what my options are, and I immediately have those variables available to use like any other argument.
The whole command feels more minimal and simpler than a standard Django management command, so the commands come together really quickly.
  </p>
  <h2>
   Real-world example: Movie import with django-click
  </h2>
  <p>
   Now let me show you that same command using django-click:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># management/commands/load_movies_click.py</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">djclick</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">click</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.conf</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">movies.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">clear_movie_data</span><span class="p">,</span> <span class="n">load_movies_from_data</span>


<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">"--file"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"data/movies.json"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Path to movies JSON file"</span><span class="p">)</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">"--clear"</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Clear existing data before loading"</span><span class="p">)</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">argument</span><span class="p">(</span><span class="s2">"count"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">command</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">clear</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Load movies data from JSON file into the database."""</span>

    <span class="k">if</span> <span class="n">clear</span><span class="p">:</span>
        <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Clearing existing movie data..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"yellow"</span><span class="p">)</span>
        <span class="n">clear_movie_data</span><span class="p">()</span>
        <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Existing data cleared."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>

    <span class="n">file_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">BASE_DIR</span><span class="p">)</span> <span class="o">/</span> <span class="n">file</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
        <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error: File </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2"> not found"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"red"</span><span class="p">,</span> <span class="n">err</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
        <span class="k">return</span>

    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loading movies from </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"blue"</span><span class="p">)</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">movies_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">count</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">movies_data</span> <span class="o">=</span> <span class="n">movies_data</span><span class="p">[:</span><span class="n">count</span><span class="p">]</span>
        <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loading first </span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s2"> movies..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"cyan"</span><span class="p">)</span>

    <span class="n">total_created_movies</span><span class="p">,</span> <span class="n">total_created_genres</span><span class="p">,</span> <span class="n">total_created_cast</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">load_movies_from_data</span><span class="p">(</span><span class="n">movies_data</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Loading complete!"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">,</span> <span class="n">bold</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_movies</span><span class="si">}</span><span class="s2"> movies"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_genres</span><span class="si">}</span><span class="s2"> genres"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Created </span><span class="si">{</span><span class="n">total_created_cast</span><span class="si">}</span><span class="s2"> cast members"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The actual work being done is identical, but the command structure is much cleaner.
  </p>
  <p>
   The command definition is right there at the top with the decorators. The function signature tells you exactly what parameters you're working with, with no more
   <code>
    options["key"]
   </code>
   lookups. Here is the output (similar to regular Django):
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="lv632">
 </p>
 <img alt="Screenshot 2025-06-09 at 2.43.28 PM" class="richtext-image left" height="155" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/Screenshot_2025-06-09_at_2.43.28PM.width-500.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=65cfa8f891903b82cd6a4d16a1aee9b85337b88aa44c0bb70f31326b00bde904" width="500"/>
 <p data-block-key="5rtjn">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   A few other improvements:
  </p>
  <ul>
   <li>
    <strong>
     Arguments vs Options
    </strong>
    : Notice that
    <code>
     count
    </code>
    is a positional argument while
    <code>
     file
    </code>
    and
    <code>
     clear
    </code>
    are optional flags. Click handles the difference automatically.
   </li>
   <li>
    <strong>
     Colorful output
    </strong>
    :
    <code>
     click.secho()
    </code>
    with
    <code>
     fg="green"
    </code>
    is much cleaner than
    <code>
     self.style.SUCCESS()
    </code>
    .
   </li>
   <li>
    <strong>
     Boolean flags
    </strong>
    : The
    <code>
     is_flag=True
    </code>
    parameter makes
    <code>
     --clear
    </code>
    work as a simple boolean flag.
   </li>
  </ul>
  <h2>
   Another cool django-click feature: the lookup utility
  </h2>
  <p>
   Django-click also includes a useful
   <code>
    lookup
   </code>
   utility for working with Django models. You can use it to accept model instances as command arguments:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">djclick</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">click</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">myapp.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">User</span>

<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">argument</span><span class="p">(</span><span class="s1">'user'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">ModelInstance</span><span class="p">(</span><span class="n">User</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">command</span><span class="p">(</span><span class="n">user</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Do something with a user."""</span>
    <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing user: </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The
   <code>
    click.ModelInstance(User)
   </code>
   automatically handles lookups by primary key by default. You can also specify custom lookup fields:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># Lookup by username field</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">argument</span><span class="p">(</span><span class="s1">'user'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">ModelInstance</span><span class="p">(</span><span class="n">User</span><span class="p">,</span> <span class="n">lookup</span><span class="o">=</span><span class="s1">'username'</span><span class="p">))</span>
</code></pre>
  </div>
  <p>
   This returns the actual User instance to your function, making it easy to work with Django models in your commands.
  </p>
  <h2>
   Django-typer: When you need beautiful output that helps you think
  </h2>
  <p>
   <a href="https://github.com/django-commons/django-typer">
    django-typer
   </a>
   takes a different approach. Built on
   <a href="https://typer.tiangolo.com/">
    Typer
   </a>
   , it uses Python type annotations to define command interfaces and includes the
   <a href="https://rich.readthedocs.io/">
    Rich
   </a>
   library for beautiful terminal output.
  </p>
  <h2>
   Installation and setup
  </h2>
  <div class="codehilite">
   <pre><span></span><code>pip<span class="w"> </span>install<span class="w"> </span>django-typer
</code></pre>
  </div>
  <p>
   This brings in Typer. If you install with
   <code>
    pip install django-typer[rich]
   </code>
   , you will also get the Rich library and its capabilities, which we will go into below.
  </p>
  <h2>
   Key differences from django-click
  </h2>
  <ul>
   <li>
    <strong>
     Type annotation driven
    </strong>
    : Instead of decorators, you use Python type annotations with
    <code>
     typer.Option()
    </code>
    and
    <code>
     typer.Argument()
    </code>
    to define your interface.
   </li>
   <li>
    <strong>
     Class-based but simpler
    </strong>
    : You can still inherit from a base class (
    <code>
     TyperCommand
    </code>
    ), but the interface is much cleaner than standard Django commands. There is also a decorator available if you prefer that style.
   </li>
   <li>
    <strong>
     Rich integration
    </strong>
    : Beautiful progress bars, tables, panels, and colorful output are easy to implement if you include Rich in your installation.
   </li>
   <li>
    <strong>
     Better error handling
    </strong>
    : Typer provides more sophisticated error handling and validation.
   </li>
  </ul>
  <p>
   Recently, I needed to dig into some messy client data and answer questions like "Do all the records that are missing a FK to this other model also share these other characteristics?" My goal was to figure out if I needed to write some custom code to "fix" some records I suspected were broken, or if there was a valid reason the records were in the state they were in.
  </p>
  <p>
   With django-typer, I wrote a command that answered my questions and helped me identify patterns in my data. The structured output made it easier to spot patterns I might have missed in a plain text dump. Django-typer is great when you need output that helps you analyze data, not just dump it to the terminal.
  </p>
  <h2>
   Real-world example: Movie data import command
  </h2>
  <p>
   Converting our movie import command to django-typer shows how type annotations replace decorators:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django_typer.management</span><span class="w"> </span><span class="kn">import</span> <span class="n">Typer</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Typer</span><span class="p">(</span><span class="n">help</span><span class="o">=</span><span class="s2">"Load movies data from JSON file into the database."</span><span class="p">)</span>

<span class="nd">@app</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span>
    <span class="n">count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="n">typer</span><span class="o">.</span><span class="n">Argument</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Number of movies to load"</span><span class="p">),</span>
    <span class="n">file</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">typer</span><span class="o">.</span><span class="n">Option</span><span class="p">(</span><span class="s2">"data/movies.json"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Path to movies JSON file"</span><span class="p">),</span>
    <span class="n">clear</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="n">typer</span><span class="o">.</span><span class="n">Option</span><span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Clear existing data before loading"</span><span class="p">),</span>
<span class="p">):</span>
    <span class="k">if</span> <span class="n">clear</span><span class="p">:</span>
        <span class="n">typer</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Clearing existing movie data..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="n">typer</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">YELLOW</span><span class="p">)</span>
        <span class="n">clear_movie_data</span><span class="p">()</span>
        <span class="n">typer</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Existing data cleared."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="n">typer</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">GREEN</span><span class="p">)</span>

    <span class="n">file_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">BASE_DIR</span><span class="p">)</span> <span class="o">/</span> <span class="n">file</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
        <span class="n">typer</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error: File </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2"> not found"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="n">typer</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">RED</span><span class="p">,</span> <span class="n">err</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
        <span class="k">raise</span> <span class="n">typer</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

    <span class="n">typer</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loading movies from </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="n">typer</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">BLUE</span><span class="p">)</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">movies_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>

    <span class="n">total_created_movies</span><span class="p">,</span> <span class="n">total_created_genres</span><span class="p">,</span> <span class="n">total_created_cast</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">load_movies_from_data</span><span class="p">(</span><span class="n">movies_data</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="n">typer</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Loading complete!"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="n">typer</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">GREEN</span><span class="p">,</span> <span class="n">bold</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Pretty similar to the django-click version, just with type annotations instead of decorators. (I trimmed some logic for brevity, but you get the idea.)
  </p>
  <p>
   You could strip the django-typer function definition down even further, like so:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">file</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">"data/movies.json"</span><span class="p">,</span> <span class="n">clear</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">):</span>
</code></pre>
  </div>
  <p>
   Then, the function definition would very closely resemble any standard Python function. But then you lose the help text for your arguments and options, and you lose access to some of the extra validation that Typer can do on your behalf.
  </p>
  <h3>
   Making the movie import command output sparkle
  </h3>
  <p>
   If you need structured, visual output from a management command, django-typer can be helpful. If you install it with
   <code>
    pip install django-typer[rich]
   </code>
   and include the Rich integration, you can create very well-formatted output in your CLI.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rich.console</span><span class="w"> </span><span class="kn">import</span> <span class="n">Console</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rich.panel</span><span class="w"> </span><span class="kn">import</span> <span class="n">Panel</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rich.progress</span><span class="w"> </span><span class="kn">import</span> <span class="n">Progress</span><span class="p">,</span> <span class="n">SpinnerColumn</span><span class="p">,</span> <span class="n">TextColumn</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rich.table</span><span class="w"> </span><span class="kn">import</span> <span class="n">Table</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">Typer</span><span class="p">(</span><span class="n">help</span><span class="o">=</span><span class="s2">"Load movies data from JSON file into the database."</span><span class="p">)</span>

<span class="nd">@app</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span>
    <span class="c1"># same function definition as before</span>
<span class="p">):</span>
<span class="w">    </span><span class="sd">"""Load movies data from JSON file into the database."""</span>
    <span class="n">console</span> <span class="o">=</span> <span class="n">Console</span><span class="p">()</span>

    <span class="c1"># Display a pretty welcome banner</span>
    <span class="n">console</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="n">Panel</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="s2">"🎬 Movie Database Loader"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"bold blue"</span><span class="p">))</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">movies_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">count</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">movies_data</span> <span class="o">=</span> <span class="n">movies_data</span><span class="p">[:</span><span class="n">count</span><span class="p">]</span>
        <span class="n">console</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"🔢 Loading first [bold yellow]</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s2">[/bold yellow] movies..."</span><span class="p">)</span>

    <span class="c1"># Add a progress bar</span>
    <span class="k">with</span> <span class="n">Progress</span><span class="p">(</span><span class="n">console</span><span class="o">=</span><span class="n">console</span><span class="p">)</span> <span class="k">as</span> <span class="n">progress</span><span class="p">:</span>
        <span class="n">task</span> <span class="o">=</span> <span class="n">progress</span><span class="o">.</span><span class="n">add_task</span><span class="p">(</span><span class="s2">"🎭 Processing movies..."</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">movies_data</span><span class="p">))</span>
        <span class="n">total_created_movies</span><span class="p">,</span> <span class="n">total_created_genres</span><span class="p">,</span> <span class="n">total_created_cast</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">load_movies_from_data</span><span class="p">(</span><span class="n">movies_data</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="n">progress</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">completed</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">movies_data</span><span class="p">))</span>

    <span class="c1"># Add a table to summarize the output</span>
    <span class="n">table</span> <span class="o">=</span> <span class="n">Table</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">"📊 Loading Summary"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Category"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"cyan"</span><span class="p">,</span> <span class="n">no_wrap</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Count"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"magenta"</span><span class="p">,</span> <span class="n">justify</span><span class="o">=</span><span class="s2">"right"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Icon"</span><span class="p">,</span> <span class="n">justify</span><span class="o">=</span><span class="s2">"center"</span><span class="p">)</span>

    <span class="n">table</span><span class="o">.</span><span class="n">add_row</span><span class="p">(</span><span class="s2">"Movies"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">total_created_movies</span><span class="p">),</span> <span class="s2">"🎬"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_row</span><span class="p">(</span><span class="s2">"Genres"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">total_created_genres</span><span class="p">),</span> <span class="s2">"🎭"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_row</span><span class="p">(</span><span class="s2">"Cast Members"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">total_created_cast</span><span class="p">),</span> <span class="s2">"👥"</span><span class="p">)</span>

    <span class="n">console</span><span class="o">.</span><span class="n">print</span><span class="p">()</span>
    <span class="n">console</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="n">table</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Adding these elements gives you output like this:
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="lv632">
 </p>
 <p data-block-key="d1uhz">
 </p>
 <img alt="Screenshot 2025-06-09 at 2.48.04 PM" class="richtext-image left" height="201" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/Screenshot_2025-06-09_at_2.48.04PM.width-500.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=2ab495e5d1613dab6f26c0ad42153ec876eb880a7e126cb9e106993f026be961" width="500"/>
 <p data-block-key="cio8g">
 </p>
 <p data-block-key="8va64">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Adding these elements from the Rich library shows how many elements you can add to your CLI output. The Rich elements I used were:
  </p>
  <ul>
   <li>
    <strong>
     Rich panels
    </strong>
    : The welcome banner get displayed in a
    <a href="https://rich.readthedocs.io/en/stable/panel.html">
     Panel
    </a>
    with pretty borders
   </li>
   <li>
    <strong>
     Progress indicators
    </strong>
    : We get a progress indicator via Rich's
    <a href="https://rich.readthedocs.io/en/stable/progress.html">
     Progress
    </a>
    class.
   </li>
   <li>
    <strong>
     Beautiful tables
    </strong>
    : We used a
    <a href="https://rich.readthedocs.io/en/stable/tables.html">
     Table
    </a>
    to display our output in an organized and easy-to-read way.
   </li>
   <li>
    <strong>
     Rich markup
    </strong>
    : We use familiar-sounding arguments like
    <code>
     style
    </code>
    and
    <code>
     justify
    </code>
    to style our output.
   </li>
  </ul>
  <h2>
   When to use django-click
  </h2>
  <p>
   If you prefer decorator syntax over type annotations, you want minimal dependencies in your project, you're already familiar with
   <a href="https://click.palletsprojects.com/en/stable/">
    Click
   </a>
   from other projects, you need the
   <code>
    lookup
   </code>
   utility for Django model integration, or you're writing simple commands that don't need fancy output, then django-click might be the library for you.
  </p>
  <h2>
   When to use django-typer
  </h2>
  <p>
   If you love type annotations and want automatic validation, you need beautiful output with minimal effort, you're building complex command suites with
   <a href="https://django-typer.readthedocs.io/en/stable/howto.html#define-multiple-subcommands">
    subcommands
   </a>
   , you don't mind the extra dependencies, or you want Rich integration for tables, progress bars, and panels, then give django-typer a try.
  </p>
  <h2>
   Integration with Just
  </h2>
  <p>
   This is off the topic of django-typer and django-click, but I wanted to mention it: I often use
   <a href="https://github.com/casey/just">
    Just
   </a>
   to handle situations where I need to run multiple management commands in a specific way. When I set up commands for all three approaches:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># Load movies data into database</span>
<span class="nb">load</span><span class="o">-</span><span class="n">fresh</span><span class="o">-</span><span class="n">movie</span><span class="o">-</span><span class="n">data</span><span class="p">:</span>
<span class="w">    </span><span class="n">just</span><span class="w"> </span><span class="nb">load</span><span class="o">-</span><span class="n">genres</span><span class="w"> </span><span class="o">--</span><span class="n">clear</span><span class="w"> </span><span class="mi">1000</span>
<span class="w">    </span><span class="n">just</span><span class="w"> </span><span class="nb">load</span><span class="o">-</span><span class="n">people</span><span class="w"> </span><span class="o">--</span><span class="n">clear</span><span class="w"> </span><span class="mi">1000</span>
<span class="w">    </span><span class="n">just</span><span class="w"> </span><span class="nb">load</span><span class="o">-</span><span class="n">movies</span><span class="w"> </span><span class="o">--</span><span class="n">clear</span><span class="w"> </span><span class="mi">1000</span>

<span class="nb">load</span><span class="o">-</span><span class="n">genres</span><span class="w"> </span><span class="o">*</span><span class="n">args</span><span class="p">:</span>
<span class="w">    </span><span class="n">docker</span><span class="w"> </span><span class="n">compose</span><span class="w"> </span><span class="n">exec</span><span class="w"> </span><span class="n">web</span><span class="w"> </span><span class="n">python</span><span class="w"> </span><span class="n">manage</span><span class="o">.</span><span class="n">py</span><span class="w"> </span><span class="n">load_genres</span><span class="w"> </span><span class="p">{{</span><span class="n">args</span><span class="p">}}</span>

<span class="nb">load</span><span class="o">-</span><span class="n">people</span><span class="w"> </span><span class="o">*</span><span class="n">args</span><span class="p">:</span>
<span class="w">    </span><span class="n">docker</span><span class="w"> </span><span class="n">compose</span><span class="w"> </span><span class="n">exec</span><span class="w"> </span><span class="n">web</span><span class="w"> </span><span class="n">python</span><span class="w"> </span><span class="n">manage</span><span class="o">.</span><span class="n">py</span><span class="w"> </span><span class="n">load_people</span><span class="w"> </span><span class="p">{{</span><span class="n">args</span><span class="p">}}</span>

<span class="nb">load</span><span class="o">-</span><span class="n">movies</span><span class="w"> </span><span class="o">*</span><span class="n">args</span><span class="p">:</span>
<span class="w">    </span><span class="n">docker</span><span class="w"> </span><span class="n">compose</span><span class="w"> </span><span class="n">exec</span><span class="w"> </span><span class="n">web</span><span class="w"> </span><span class="n">python</span><span class="w"> </span><span class="n">manage</span><span class="o">.</span><span class="n">py</span><span class="w"> </span><span class="n">load_movies</span><span class="w"> </span><span class="p">{{</span><span class="n">args</span><span class="p">}}</span>
</code></pre>
  </div>
  <p>
   This pattern lets you create shortcuts for your management commands, and link them together.
  </p>
  <h2>
   My honest take
  </h2>
  <p>
   I use django-click for most of the management commands I need to write. It's clean, fast to write, and gets out of my way. But when I need to build something that helps me understand complex data or provides structured feedback during long-running operations, django-typer is the better choice.
  </p>
  <p>
   The next time you need to write a management command, try one of these libraries and let me know what you think!
  </p>
 </div>
</div>
]]>/></item><item><title>Give Your Django Admin X‑Ray Vision—Automatic Dead‑Link Detection</title><link>http://www.revsys.com/tidbits/django-deadlink-detection-linkchecker/</link><description>Stop link rot before users ever see it: this guide shows how one lightweight app scans your models, flags broken URLs, and slots neatly into any Django workflow.</description><pubDate>Thu, 29 May 2025 15:21:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-deadlink-detection-linkchecker/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Most Django projects store some sort of URLs.  Whether it's in a
   <code>
    URLField()
   </code>
   on 
some of your models or deep in some
   <code>
    TextField()
   </code>
   s full of HTML.
  </p>
  <p>
   We all know
   <a href="https://www.w3.org/Provider/Style/URI">
    cool URLs don't change
   </a>
   , but 
not all URLs are cool and some rot over time.  How do you find out which of your links 
have disappeared on you?
  </p>
  <p>
   Turns out there is a Django app for that named
   <a href="https://pypi.org/project/django-linkcheck/">
    django-linkcheck
   </a>
   . It will check your URLs on a schedule you set and give you a nice admin page to manage dealing with them.
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="ghfeu">
 </p>
 <img alt="Screenshot of django-linkcheck admin page" class="richtext-image full-width" height="550" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/CleanShot_2025-05-29_at_09.55.26.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=c9e3845a348914dd980a28c57d5f1cb69f2cd969cba120ef5de54f08546b27aa" width="800"/>
 <p data-block-key="en2u6">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It helpfully gives you the status it received, links to recheck or ignore this item and quick 'Edit' links to take you right to the model object in question!
  </p>
  <p>
   It hasn't had a release for awhile, but I can confirm it works just fine with Django 
5.1.x. The docs are a bit sparse, so I stumbled around a little bit getting it to work.  This post is here to save you from having to also stumble around in the dark.
  </p>
  <h2>
   Install and Setup
  </h2>
  <p>
   Installing the app is straight forward, you just add it to
   <code>
    INSTALLED_APPS
   </code>
   and 
include the special admin view in your main
   <code>
    urls.py
   </code>
   .
  </p>
  <p>
   It then needs to create it's own tables so run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>./manage.py<span class="w"> </span>migrate<span class="w"> </span>
</code></pre>
  </div>
  <p>
   You then need to tell it which models and fields you want it to handle.  The docs 
suggest you run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>./manage.py<span class="w"> </span>linkcheck_suggest_config<span class="w"> </span>--model<span class="w"> </span>myapp.SomeModel<span class="w"> </span>&gt;<span class="w"> </span>myapp/linklinks.py
</code></pre>
  </div>
  <p>
   Which is a great idea, but it's a bit broken actually.  This generates something like:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">myapp.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">SomeModel</span>

<span class="k">class</span><span class="w"> </span><span class="nc">SomeModelLinklist</span><span class="p">(</span><span class="n">Linklist</span><span class="p">):</span>
    <span class="n">model</span> <span class="o">=</span> <span class="n">SomeModel</span>
    <span class="n">url_fields</span> <span class="o">=</span> <span class="p">[</span><span class="n">myapp</span><span class="o">.</span><span class="n">SomeModel</span><span class="o">.</span><span class="n">url</span><span class="p">]</span>
    <span class="n">ignore_empty</span> <span class="o">=</span> <span class="p">[</span><span class="n">myapp</span><span class="o">.</span><span class="n">SomeModel</span><span class="o">.</span><span class="n">url</span><span class="p">]</span>
    <span class="n">object_filter</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"active"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>

<span class="n">linklists</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">"SomeModel"</span><span class="p">:</span> <span class="n">SomeModelLinklist</span><span class="p">,</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   It fails to import
   <code>
    Linklist
   </code>
   for one, but the larger issue is it leads you believe you
set the fields in the form
   <code>
    &lt;app&gt;.&lt;model&gt;.&lt;fieldname&gt;
   </code>
   when in fact it really needs to be 
just a list of modelfield strings.  The correct version would be:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">linkcheck</span><span class="w"> </span><span class="kn">import</span> <span class="n">Linklist</span>   <span class="c1"># actually import Linklist</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">myapp.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">SomeModel</span>

<span class="k">class</span><span class="w"> </span><span class="nc">SomeModelLinklist</span><span class="p">(</span><span class="n">Linklist</span><span class="p">):</span>
    <span class="n">model</span> <span class="o">=</span> <span class="n">SomeModel</span>
    <span class="n">url_fields</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"url"</span><span class="p">]</span>   <span class="c1"># list fields as strings </span>
    <span class="n">ignore_empty</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"url""]</span>
    <span class="n">object_filter</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"active"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>

<span class="n">linklists</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">"SomeModel"</span><span class="p">:</span> <span class="n">SomeModelLinklist</span><span class="p">,</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   This could be very confusing to a first time user or newer Django developer, but don't 
let that stop you.  It's a really nice app that solves a real world issue.
  </p>
  <h2>
   Checking your links
  </h2>
  <p>
   To actually check your URLs from your models you then need to run two commands:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>./manage.py<span class="w"> </span>findlinks<span class="w"> </span>
</code></pre>
  </div>
  <p>
   This populates linkcheck's tables with the TODO list of sorts.
  </p>
  <p>
   You can then
   <em>
    actually check
   </em>
   them with:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>./manage.py<span class="w"> </span>checklinks<span class="w"> </span>
</code></pre>
  </div>
  <p>
   <strong>
    NOTE:
   </strong>
   Both of these should be re-run periodically, perhaps via a cron job, based on 
your personal needs.
  </p>
  <p>
   If you have any broken links they should be visible and ready to handle at
   <code>
    /admin/linkcheck/
   </code>
   now.
  </p>
  <p>
   Check out the
   <a href="https://github.com/DjangoAdminHackers/django-linkcheck?tab=readme-ov-file#settings">
    README
   </a>
   for more information on how you can set this up including settings for how frequently you would like the links to be rechecked.
  </p>
  <p>
   Hopefully these small tips make it easier to start using this great Django app.  Cheers!
  </p>
 </div>
</div>
]]>/></item><item><title>Faster Python Docker Builds</title><link>http://www.revsys.com/tidbits/faster-python-docker-builds/</link><description>The 4 Docker Sins Killing Your Python Productivity. You changed a single line of code and Docker has to rebuild everything! Dramatically improve both fresh builds and rebuilds when working in Docker.</description><pubDate>Mon, 26 May 2025 20:30:43 +0000</pubDate><guid>http://www.revsys.com/tidbits/faster-python-docker-builds/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   If you're waiting more than 10 seconds for your Docker rebuilds, you're wasting hours of your life!
  </p>
  <p>
   So that you keep reading, here are the results!
  </p>
  <h2>
   Results
  </h2>
  <ul>
   <li>
    Fresh builds: 50s &rarr; 18s (2.7x faster)
   </li>
   <li>
    Rebuilds: 47s &rarr; 0.4s (117x faster)
   </li>
   <li>
    Adding dependencies: 50s &rarr; 6s (8x faster)
   </li>
  </ul>
  <h2>
   Overview
  </h2>
  <p>
   We work with Python development teams of all sizes and levels of sophistication. One area where 
many teams struggle is optimizing their Docker image build times.
  </p>
  <p>
   I was reminded of this yesterday replying to a recent
   <a href="https://old.reddit.com/r/django/comments/1klljn8/too_many_installed_django_apps/">
    r/django reddit thread
   </a>
   where the author assumed they needed to break their Django monolith up into a few services to reduce 
their build time, which isn't accurate, but is a common mistaken assumption.
  </p>
  <p>
   There are a few small things you can do to
   <em>
    dramatically
   </em>
   reduce the amount of time it takes to 
build (and more importantly REBUILD) a Python or Django docker image.
  </p>
  <p>
   The TL;DR is you need to:
  </p>
  <ul>
   <li>
    Only rebuild the dependency layer when your dependencies actually change
   </li>
   <li>
    Cache your PyPI downloads locally and in CI
   </li>
   <li>
    Switch to using uv which is stupid stupid fast
   </li>
   <li>
    Use a multi-stage build
   </li>
  </ul>
  <p>
   If you're already doing all of those things, you can safely skip the rest of this post.
  </p>
  <h2>
   Naive Approach
  </h2>
  <p>
   The most common problem and impactful issue is not ordering the lines in your Dockerfile 
to only rebuild the layers that need to be rebuilt.
  </p>
  <p>
   Not having to do anything at all is the fastest thing there is! It's a one 
million percent improvement! 🤣
  </p>
  <p>
   Here is what many people start with:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.13-slim</span>

<span class="k">RUN</span><span class="w"> </span>mkdir<span class="w"> </span>/code
<span class="k">WORKDIR</span><span class="w"> </span><span class="s">/code</span>

<span class="k">COPY</span><span class="w"> </span>.<span class="w"> </span>/code/

<span class="k">RUN</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>-r<span class="w"> </span>requirements.txt

<span class="c"># ... rest of lines ending in a `CMD` to run</span>
</code></pre>
  </div>
  <p>
   So what's wrong with this? Because every time you change ANYTHING in your git repository 
you're re-installing your pip dependencies.
  </p>
  <p>
   This is thankfully easy to resolve.  Instead of the above, you should be doing this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.13-slim</span>

<span class="k">RUN</span><span class="w"> </span>mkdir<span class="w"> </span>/code
<span class="k">WORKDIR</span><span class="w"> </span><span class="s">/code</span>

<span class="c"># Copy just the requirements first </span>
<span class="k">COPY</span><span class="w"> </span>./requirements.txt<span class="w"> </span>/code/requirements.txt<span class="w"> </span>

<span class="c"># Install dependencies </span>
<span class="k">RUN</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>-r<span class="w"> </span>requirements.txt

<span class="c"># Copy in everything else </span>
<span class="k">COPY</span><span class="w"> </span>.<span class="w"> </span>/code/

<span class="c"># ... rest of lines ending in a `CMD` to run</span>
</code></pre>
  </div>
  <p>
   Now when you rebuild this image it will only need to perform the
   <code>
    pip install
   </code>
   step when there has actually been a change to
your
   <code>
    requirements.txt
   </code>
   !
  </p>
  <p>
   Dependencies change somewhat frequently, but no where near as frequently as you change code, docs, tests, and your README this
stops wasting time rebuilding that particular Docker layer on every single change.
  </p>
  <h2>
   Caching the PyPI dependencies
  </h2>
  <p>
   Ok so now we're only doing this when there is really something new to do.  The next thing to do is not bother downloading all of 
these dependencies each and every time we build our Docker image.  By default pip caches your downloads when using it locally, 
so this little optimization is overlooked.  Python developers either assume it IS happening inside Docker or that it is hard 
or impossible to make it do so.
  </p>
  <h3>
   Where does pip cache things?
  </h3>
  <p>
   You can
   <a href="https://pip.pypa.io/en/latest/cli/pip_cache/">
    manage your pip cache
   </a>
   but the most useful thing is to simply know where 
this cache exists.  So run
   <code>
    pip cache dir
   </code>
   (or
   <code>
    uv cache dir
   </code>
   if you're already using
   <code>
    uv
   </code>
   we'll talk about it more later).  If you 
look into that directory hopefully you'll see a bunch of files.
  </p>
  <p>
   Now this is the cache on your HOST OS, not inside of Docker.  There are a couple of ways to expose this into your Docker image, but it's much easier to just have your Docker daemon cache it for you.
  </p>
  <p>
   If you're using a default Python docker image, you're running in Debian and by default everything is running as the root user.  FYI there are security implications to this and you look into running your code as another user non-root user, but that's a topic for another post.
  </p>
  <p>
   So for the
   <code>
    root
   </code>
   user on a Debian system this makes the pip and uv cache locations are going to be in
   <code>
    /root/.cache/
   </code>
   so we need to make a small change to your
   <code>
    RUN
   </code>
   that installs everything.
  </p>
  <p>
   Instead of:
  </p>
  <p>
   <code>
    RUN pip install -r requirements.txt
   </code>
  </p>
  <p>
   We need to use:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nx">RUN</span><span class="w"> </span><span class="o">--</span><span class="nx">mount</span><span class="p">=</span><span class="k">type</span><span class="p">=</span><span class="nx">cache</span><span class="p">,</span><span class="nx">target</span><span class="p">=</span><span class="o">/</span><span class="nx">root</span><span class="o">/</span><span class="p">.</span><span class="nx">cache</span><span class="p">,</span><span class="nx">id</span><span class="p">=</span><span class="nx">pip</span><span class="w"> </span>\
<span class="w">    </span><span class="nx">python</span><span class="w"> </span><span class="o">-</span><span class="nx">m</span><span class="w"> </span><span class="nx">pip</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="o">-</span><span class="nx">r</span><span class="w"> </span><span class="o">/</span><span class="nx">code</span><span class="o">/</span><span class="nx">requirements</span><span class="p">.</span><span class="nx">txt</span>
</code></pre>
  </div>
  <p>
   This is instructing the Docker daemon to cache this folder with the id
   <code>
    pip
   </code>
   and it will then be reused across builds.
  </p>
  <h3>
   What about in CI?
  </h3>
  <p>
   Things are a bit harder in CI.  Depending on what CI system you're using it's sometimes built in, sometimes you need to make configuration adjustments.  In any case, the goal you're after here is that the
   <code>
    /root/.cache/
   </code>
   folder is preserved and reused across builds so that the 
downloads are cached between CI runs.
  </p>
  <p>
   You can read up on all of the details of to
   <a href="https://docs.docker.com/build/cache/optimize/">
    optimize Docker cache usage
   </a>
   in the Docker docs.
  </p>
  <h2>
   Use uv
  </h2>
  <p>
   If you're not familiar with
   <a href="https://docs.astral.sh/uv/">
    uv
   </a>
   it's a near drop-in replacement 
for pip from the folks at Astral who also brought us the great
   <code>
    ruff
   </code>
   linting and formatting tool and the soon to be beta
   <a href="https://github.com/astral-sh/ty">
    ty
   </a>
   type checker.
  </p>
  <p>
   For most things you just prefix your normal pip command with
   <code>
    uv
   </code>
   and it works as expected, 
just a
   <strong>
    HELL OF A LOT
   </strong>
   faster.
  </p>
  <p>
   Switching to
   <code>
    uv
   </code>
   and adding in the cache mount makes our example Dockerfile now look like
this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.13-slim</span>

<span class="k">RUN</span><span class="w"> </span>mkdir<span class="w"> </span>/code
<span class="k">WORKDIR</span><span class="w"> </span><span class="s">/code</span>

<span class="c"># Install uv </span>
<span class="k">RUN</span><span class="w"> </span>--mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>cache,target<span class="o">=</span>/root/.cache,id<span class="o">=</span>pip<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>uv<span class="w"> </span>

<span class="c"># Copy just the requirements first </span>
<span class="k">COPY</span><span class="w"> </span>./requirements.txt<span class="w"> </span>/code/requirements.txt<span class="w"> </span>

<span class="c"># Run uv pip install with caching! </span>
<span class="k">RUN</span><span class="w"> </span>--mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>cache,target<span class="o">=</span>/root/.cache,id<span class="o">=</span>pip<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>uv<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--system<span class="w"> </span>-r<span class="w"> </span>/code/requirements.txt

<span class="c"># Copy in everything else </span>
<span class="k">COPY</span><span class="w"> </span>.<span class="w"> </span>/code/

<span class="c"># ... rest of lines ending in a `CMD` to run</span>
</code></pre>
  </div>
  <h2>
   So how fast is it now?
  </h2>
  <p>
   Things are quite a bit faster at the small expensive of a slightly more complicated
   <code>
    Dockerfile
   </code>
   .
  </p>
  <p>
   Naive Fresh - 50 seconds
  </p>
  <p>
   Naive Rebuild - 47 seconds.
  </p>
  <p>
   The difference here is just the speed of downloading the pip dependencies between runs.
  </p>
  <p>
   After we've fixed things to only re-run
   <code>
    pip install
   </code>
   when those requirements change gives us 
the biggest benefit.
  </p>
  <p>
   Naive Fixed Fresh - 50 seconds
  </p>
  <p>
   Naive Fixed Rebuild -
   <strong>
    10 seconds
   </strong>
  </p>
  <h3>
   With Caching
  </h3>
  <p>
   Caching our downloads improves our situation even further!
  </p>
  <p>
   Cached Fresh - 44 seconds
  </p>
  <p>
   Cached Rebuild -
   <strong>
    0.4 seconds
   </strong>
  </p>
  <h3>
   With Caching and uv
  </h3>
  <p>
   UV Fresh - 18.5 seconds
  </p>
  <p>
   UV Rebuild -
   <strong>
    0.4 seconds
   </strong>
  </p>
  <p>
   Why isn't
   <code>
    uv
   </code>
   faster? Well it IS faster downloading the files initially, I'm guessing it is doing something in parallel better or just being written in 
rust is making this aspect twice as fast as normal pip.  But for these last two we're really just testing how faster Docker is able to 
create the layer since there is really no calls to pip or uv going on.
  </p>
  <h2>
   Adding a new pip dependency into the mix
  </h2>
  <p>
   The
   <em>
    real speed up
   </em>
   is when you need to add a new dependency.  In our original requirements.txt we neglected to add the very useful
   <code>
    django-debug-toolbar
   </code>
   package.  So I added it and re-ran all of these.
  </p>
  <h3>
   Naive
  </h3>
  <p>
   Naive Fresh - 50 seconds
  </p>
  <p>
   Naive Rebuild - 47 seconds.
  </p>
  <p>
   Naive Rebuild w/ new dependency -
   <strong>
    50 seconds
   </strong>
  </p>
  <p>
   Naive Fixed Fresh - 50 seconds
  </p>
  <p>
   Naive Fixed Rebuild - 10 seconds
  </p>
  <p>
   Naive Fixed Rebuild w/ new dependency -
   <strong>
    51 seconds
   </strong>
  </p>
  <h3>
   With Caching
  </h3>
  <p>
   Cached Fresh - 44 seconds
  </p>
  <p>
   Cached Rebuild - 0.4 seconds
  </p>
  <p>
   Cached Rebuild  w/ new dependency -
   <strong>
    24 seconds
   </strong>
  </p>
  <h3>
   With Caching and uv
  </h3>
  <p>
   UV Fresh - 18.5 seconds
  </p>
  <p>
   UV Rebuild - 0.4 seconds
  </p>
  <p>
   UV Rebuild w/ new dependency -
   <strong>
    6 seconds
   </strong>
  </p>
  <p>
   So we went from a consistent 50ish seconds per build to 18 seconds for a fresh build, 6 seconds when adding a new dependency and 
nearly instant for rebuilds with no dependency changes.
  </p>
  <h2>
   Bonus info
  </h2>
  <h3>
   Multi-stage Docker Builds with Python
  </h3>
  <p>
   What are multi-stage builds? In short, they are
   <code>
    Dockerfile
   </code>
   s with multiple
   <code>
    FROM
   </code>
   lines.
  </p>
  <p>
   Why would I want to do that? Well size and security mainly.
  </p>
  <p>
   On the security front, using a multi-stage build allows you to deploy an image that does not include any 
compilers or build tools, but still use those tools to build the dependencies you use.  In terms of size,
the your final image only includes the initial runtime environment, your built dependencies, but not any of 
the tools or dev packages needed to build those dependencies.
  </p>
  <p>
   So you get a smaller and more secure image, which are good things and they add just a
   <em>
    BIT
   </em>
   more complexity to 
your Dockerfile.  Once you've been walked through it, it should be fairly clear.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.13-slim</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder-py</span>

<span class="k">RUN</span><span class="w"> </span>mkdir<span class="w"> </span>/code
<span class="k">WORKDIR</span><span class="w"> </span><span class="s">/code</span>

<span class="c"># Install uv </span>
<span class="k">RUN</span><span class="w"> </span>--mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>cache,target<span class="o">=</span>/root/.cache,id<span class="o">=</span>pip<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>uv<span class="w"> </span>

<span class="c"># Copy just the requirements first </span>
<span class="k">COPY</span><span class="w"> </span>./requirements.txt<span class="w"> </span>/code/requirements.txt<span class="w"> </span>

<span class="c"># Run uv pip install with caching! </span>
<span class="k">RUN</span><span class="w"> </span>--mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>cache,target<span class="o">=</span>/root/.cache,id<span class="o">=</span>pip<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>uv<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--system<span class="w"> </span>-r<span class="w"> </span>/code/requirements.txt

<span class="k">FROM</span><span class="w"> </span><span class="s">python:3.13-slim</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">release</span><span class="w"> </span>

<span class="c"># Copy our system wide installed pip dependencies from builder-py</span>
<span class="k">COPY</span><span class="w"> </span>--from<span class="o">=</span>builder-py<span class="w"> </span>/usr/local<span class="w"> </span>/usr/local

<span class="c"># Copy in everything else </span>
<span class="k">COPY</span><span class="w"> </span>.<span class="w"> </span>/code/

<span class="c"># ... rest of lines ending in a `CMD` to run</span>
</code></pre>
  </div>
  <h3>
   Benchmark / Testing done here
  </h3>
  <p>
   You can find the exact Docker files and bits I used to do this testing here in
   <a href="https://github.com/revsys/blog-faster-docker-builds">
    this repo
   </a>
   .
  </p>
  <p>
   I did this testing on a M4 Max MacBook Pro with 128GBs of RAM on a 1.2 Gbps fiber internet connection while catching up on 
watching some PyCon 2025 talks. I'm also using Orbstack which improves the overall performance of Docker on MacOS. Your 
results will almost certainly vary, but doing any of these steps will save you and your team time in your CI pipelines and 
when building images locally. The small differences in download speed or available CPU don't really matter, we aren't doing a
CPU heavy micro-benchmark here.
  </p>
  <p>
   Our time on this planet is short, too short to spend it waiting for Docker to needlessly rebuild images.
  </p>
  <p>
   Do yourself a favor a start using these tips now!
  </p>
 </div>
</div>
]]>/></item><item><title>18 Years of REVSYS</title><link>http://www.revsys.com/tidbits/18-years-of-revsys/</link><description>Frank left his last corporate job in April of 2007 to start working on REVSYS full time. Prior to that he was just doing the occasional consulting and freelance work under the REVSYS brand.</description><pubDate>Mon, 05 May 2025 14:27:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/18-years-of-revsys/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I turned in my two weeks notice April 23rd, 2007 to Sunflower Broadband subsidiary of the Lawrence Journal-World.  I know this only because it 
just so happened that my last day ended up being Cinco de Mayo which 
makes remembering our anniversary thankfully very easy.
  </p>
  <p>
   My last corporate job was also my best, so good I came back to the company after taking a year and a half tour through various KC tech companies that sucked for one reason or another.
  </p>
  <p>
   My various roles at the company were the absolute perfect breeding ground to grow into a consultant.  Most of my time was spent running critical ISP services and writing custom software for the company, but the company was involved in so many things it was easy to get pulled into problems like:
  </p>
  <ul>
   <li>
    antiquated nearly unsupported accounting systems
   </li>
   <li>
    ancient Solaris newspaper printing press software
   </li>
   <li>
    satellite issues receiving cable content
   </li>
   <li>
    Emergency Broadcast System and 911
   </li>
   <li>
    Fleet operations and running a good NOC
   </li>
   <li>
    Customer service and marketing needs
   </li>
   <li>
    Running of the medium/large web properties of the day
   </li>
   <li>
    Crazy side projects like trying to build an early Peloton
   </li>
  </ul>
  <p>
   All of those has proven useful with clients over the years, but it was really the experience across so many areas that made it easy to adapt oneself to the client's situation at hand.
  </p>
  <h2>
   The Process
  </h2>
  <p>
   I've written
   <a href="https://frankwiles.com/posts/day-i-did-something/">
    about the process in more depth
   </a>
   (it was my first post to be on HackerNews actually), but in short I focused on learning areas that weren't tech, reduced my debt, increased my savings and jumped when I had a contract that supposedly would have made the first 6 months easy.  Spoiler it didn't.
  </p>
  <p>
   Starting your own business is hard and scary, but 18 years later I'm
   <strong>
    VERY
   </strong>
   glad that I did.
  </p>
  <h2>
   The Result
  </h2>
  <p>
   I've had the pleasure to work with so many amazing people at great companies.  Often doing important things with technology.  The constant learning, improving and running a business can be tiring but it is never boring!
  </p>
  <p>
   It's been my pleasure to bring in great business partners like Jacob Kaplan-Moss and Jeff Triplett.  Amazing employees like Jacob Burch, Lacey Henschel, Stephen Spencer, Kojo Idrissa, and Catherine Holmes. And have former employees like Flavio Curella and Daniel Lindsley over the years.
  </p>
  <p>
   We've even had a sub-contractor around for nearly all 18 years Greg Newman!
  </p>
  <p>
   Speaking of sub-contractors, we also currently have Natalia Bidart, Velda Kiara, Mark Wirblich, and Sean Parsons helping us out on various projects.
  </p>
  <p>
   In the past we've had a string of really amazing contractors work for us.  We've had Dr. Russell Keith-McGee, Idan Gazit, and the late
   <a href="https://www.djangoproject.com/weblog/2013/sep/16/announcing-malcolm-tredinnick-memorial-prize/">
    Malcolm Tredinnick
   </a>
   .  I'm probably forgetting at least a few people over the full 18 years and for that I apologize.
  </p>
  <h2>
   The Clients
  </h2>
  <p>
   We've done massively scaled and fun projects with companies like Netflix, Sony, and The Wharton School.  We've helped with critical infrastructure, internal skunkworks projects, and companies moving to Python, Django and/or the cloud.  From cancer research to to custom furniture to event tickets and AI we've had a lot of fun helping a lot of
   <a href="/clients/">
    our clients
   </a>
   .
  </p>
  <h2>
   The Work
  </h2>
  <p>
   It is exciting to see your work go live and to see it talked about in the media, but the most meaningful work to us is helping improve the development teams we work with.  From introducing new tools and better processes we absolutely love leading them down a path to better code, in less time, with less hassle, and of course, less bugs!
  </p>
  <h2>
   Some things we've done:
  </h2>
  <ul>
   <li>
    Reduced cloud spend 90% while reducing page load time 50% for large websites like
    <a href="https://www.politifact.com">
     Politifact.com
    </a>
   </li>
   <li>
    Numerous
    <a href="/services/python/code-review/">
     code reviews
    </a>
   </li>
   <li>
    System architectural help, design, and sanity checking
   </li>
   <li>
    Taught ops automation with Python to scores of staff at companies like KPMG and Jump Trading
   </li>
   <li>
    <a href="/services/operations/">
     Moving clients to Kubernetes
    </a>
    and streamlining lining CI/CD pipelines for efficiency
   </li>
   <li>
    Helped companies move to Python, Django and the Cloud like DealerTrack and eMoney
   </li>
   <li>
    Solved performance issues in too many companies to list
   </li>
   <li>
    Building startup MVPs from the ground up in all sorts of industries
   </li>
   <li>
    Acquisition due diligence
   </li>
   <li>
    Filling in as team lead or fractional CTO when clients have an unexpected staffing issue
   </li>
   <li>
    Python and Django upgrades to keep critical systems running and patched
   </li>
   <li>
    <a href="/services/django/ai/">
     Building Python AI
    </a>
    into EdTech and ecommerce products and consulting with some of the behind the scenes players in that space.
   </li>
   <li>
    And bespoke development of course!
   </li>
  </ul>
  <p>
   <strong>
    Here's to another 18 years!
   </strong>
  </p>
  <p>
   P.S. Oh and do let us know if we can help your company. We have some availability in our calendar coming up this summer due to a company canceling their projects because of the silly tariffs!
  </p>
 </div>
</div>
]]>/></item><item><title>What IS a Django App?</title><link>http://www.revsys.com/tidbits/what-is-a-django-app/</link><description>To kick off a semi-regular series of blog posts about useful Django apps, we start with some definitions</description><pubDate>Fri, 02 May 2025 15:57:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/what-is-a-django-app/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Introduction
  </h2>
  <p>
   We'll be doing a semi-regular series of blog posts about various useful Django apps. However, it seemed like a good idea to lay some groundwork first. That will help us do a few things:
  </p>
  <ul>
   <li>
    define
    <code>
     app
    </code>
    along with a few other similar terms, and get some namespace confusion out of the way
   </li>
   <li>
    understand the value, as a
    <em>
     developer
    </em>
    , of creating separate apps in your Django projects
   </li>
   <li>
    understand the value, as a
    <em>
     developer
    </em>
    or
    <em>
     end-user
    </em>
    of a Django project, of using separate apps (third-party or internally-created) in your Django projects
   </li>
  </ul>
  <h2>
   What is an App? Context matters.
  </h2>
  <p>
   If you go to the Django home page, https://www.djangoproject.com, the blurb at the top says:
   <em>
    Django makes it easier to build better web apps more quickly and with less code.
   </em>
   Django is often described as a
   <a href="https://en.wikipedia.org/wiki/Web_framework">
    web application framework
   </a>
   or something that you use to create web applications (web apps). I've used this description myself. I have also purposefully distinguished between a
   <code>
    web app
   </code>
   and a
   <code>
    website
   </code>
   during a Django tutorial I taught. I've also written a
   <a href="https://www.revsys.com/tidbits/website-vs-web-app-hidden-complexities/">
    blog post
   </a>
   on that topic.  So, if Django is used to BUILD web apps, why are we talking about apps IN Django? The context shifts a bit if you're looking at something built with Django from the outside vs. from the inside.
  </p>
  <h2>
   A View From The Inside
  </h2>
  <p>
   From the outside, the specific nomenclature is much less important. But from the inside, as someone building something with Django, the specific terminology has more meaning. But, it can still be a little confusing.
  </p>
  <p>
   Internally, Django views an entire collection of code, what most would call a "website," as a
   <a href="https://docs.djangoproject.com/en/5.1/intro/tutorial01/#creating-a-project">
    project
   </a>
   . This is where the settings and configuration for a specific Django website are contained. Inside that project are 1 or more
   <a href="https://docs.djangoproject.com/en/5.1/intro/tutorial01/#creating-the-polls-app">
    apps
   </a>
   .  An app is usually a specific sub-system used within the project. It's a collection of related code that provides specific functionality that's available to the rest of the project.
  </p>
  <h3>
   A Github Example
  </h3>
  <p>
   We can look at GitHub and how its functionality is split up as an example.
   <em>
    Note
   </em>
   : Github is NOT built with Django. But it's a web application that's known to the people who'll probably be reading this post. So, we'll pretend Github is built in Django for this example.
  </p>
  <p>
   Looking at the
   <a href="https://github.com/python/cpython">
    repository for Python
   </a>
   , we can see that
   <a href="https://github.com/python/cpython/issues">
    the issue tracker
   </a>
   ,
   <a href="https://github.com/python/cpython/pulls">
    management of pull requests
   </a>
   , and
   <a href="https://github.com/python/cpython/actions">
    tracking CI/CD activity
   </a>
   are all handling different tasks in different parts of the GitHub web application. But those bits of functionality are available to be reused across the web app on each different repository. When I talk about "bits of functionality" from a Django perspective, I'm talking about the database tables, views, other code and endpoints related to each task. So, the tables, views, other code and endpoints needed to manage Github Issues would all live in one app, perhaps called
   <code>
    issues
   </code>
   . It would be the same for the
   <code>
    pull_requests
   </code>
   and
   <code>
    ci_actions
   </code>
   apps. As such, that functionality is available to any other part of the larger Github project. At the same time, any changes that needed to be made to one of those apps (model updates, view changes, new endpoints, etc.) can be localized in that specific app.
  </p>
  <p>
   In Django, as with most web application frameworks, visiting a certain URL causes a specific section of code to be run. In this example, that would be the code in the
   <code>
    issues
   </code>
   ,
   <code>
    pull_requests
   </code>
   or
   <code>
    ci_actions
   </code>
   apps.
  </p>
  <p>
   One of the best things about apps is that they can be used between
   <em>
    different
   </em>
   projects. It's this
   <a href="https://docs.djangoproject.com/en/5.1/intro/reusable-apps/#reusability-matters">
    reusability
   </a>
   that makes Django apps especially valuable. In many ways, a Django app can be seen as Django's analogy to Python packages. And they serve a very similar role: you don't have to re-invent something that already exists. While most 3rd party Django apps get downloaded from
   <a href="https://pypi.org">
    PyPI
   </a>
   like other Python packages, you can search for a Django app to fit your needs there or at
   <a href="https://djangopackages.org">
    Django Packages
   </a>
   .
  </p>
  <h2>
   Building Your Own Django App
  </h2>
  <h3>
   Developer-Facing Benefits
  </h3>
  <p>
   The benefits of using a third-party app are clear: you get new functionality added to your project. And you don't have to write/test/debug new code. You usually only have to make a few configuration changes.
  </p>
  <p>
   But when it comes to the code you're writing yourself, some developers may wonder why they should bother creating separate apps inside their projects. Won't that just add extra complexity? If not, then what are the benefits?
  </p>
  <h3>
   Code Modularity
  </h3>
  <p>
   By having code that performs a specific set of functions separated into an app, it's not mixed in with your larger project monolith. As a result, you end up with code that's easier to test, document, and update. Instead of having to dig through your entire code base, you can focus on JUST the parts you need to look at. You may also improve the effectiveness of your testing by realizing that some subsystems (apps) need more or less testing than others. You can tune your coverage strategy on a per-app basis. This modularity can also provide clearer internal imports due to app namespacing.
  </p>
  <h3>
   Reusability
  </h3>
  <p>
   You may find yourself writing code that is nearly identical across multiple projects. Instead of copying/pasting that code several times and hoping you've made the right changes, you can install your app into that project. This way, any changes that need to be made to that app can be made in one place instead of several. This might not be one you've considered, given that we don't always know what the full design will be as we're working on something, and it can take time to tell if a certain chunk of code will be useful in other projects.
  </p>
  <p>
   On the topic of reusability, this doesn't mean you have to share this app with the world. You can put it on Github and
   <code>
    pip install
   </code>
   it
   <a href="https://adamj.eu/tech/2019/03/11/pip-install-from-a-git-repository/">
    from a Git repository
   </a>
   . That way, it's easily available to all your other projects and other developers on your team.
  </p>
  <h3>
   A Microservices Analogy
  </h3>
  <p>
   Here's another way of looking at the relationship between a Django project and an app. We can make a comparison to the monolithic and microservice approaches to software architecture. An app in Django can be compared to a microservice but with MUCH less overhead. But an app and a microservice fill similar roles. They help deal with complexity by decomposing your solution into smaller, more manageable pieces. So, if you're working on a Django project and thinking you need a microservice, that's probably a sign that you should convert part of your monolith into an app. And, if at some point you realize you truly DO need a microservice, it's much easier to convert an app into a microservice. You already have the encapsulated structure in place. In short, breaking the codebase of a monolithic Django project into apps
   <em>
    (where appropriate, not just for the sake of doing it)
   </em>
   can provide many of the benefits of distributed systems without most of the problems.
  </p>
  <p>
   There may be a question about how much "overhead" there is in turning your working code into a reusable app? The answer is, "not much". I won't go into detail here, but the Django docs  have you covered from
   <a href="https://docs.djangoproject.com/en/5.1/intro/tutorial01/">
    start
   </a>
   to
   <a href="https://docs.djangoproject.com/en/5.1/intro/reusable-apps/">
    finish
   </a>
   .
  </p>
  <h2>
   End-User Facing Benefits
  </h2>
  <p>
   The assumption here is you are building a Django project for other people to use, not just a personal project. Perhaps it's a client you're doing freelance or agency work for. Or, you may be a developer inside a larger organization, building web apps for your co-workers to use. In situations like these, there may be concerns about "adding complexity" or "slowing down development" by creating separate apps within a larger project. As noted above, that's not really a problem, but here are a few things to keep in mind if a concern like that is raised.
  </p>
  <p>
   There are two main benefits users of your Django project will see from creating separate apps within the project. First, all users will benefit from better naming conventions and clarity about which part of the web app they're using. Most won't care about the implementation details, but knowing that they can go directly to
   <code>
    https://CompanyName/task_1/
   </code>
   or
   <code>
    https://CompanyName/task_2/
   </code>
   to do what they need to will simplifiy things for them. Contrast this with needing to go to
   <code>
    https://CompanyName/all_the_tasks/
   </code>
   and then search for what they need.
  </p>
  <p>
   Second, there may be a group of staff or superusers who will have access to the Django Admin. These may be people such as managers, department heads, and the like. For those users, having your code separated into apps will create automatic separation in the Django admin. This will make it easier for those folks to find and focus on the specific app they need, instead of sorting through the entire project.
  </p>
  <h2>
   Next: Django App Of The
   <code>
    {{ variable_time_period }}
   </code>
  </h2>
  <p>
   Now that we've set a bit of foundation, we'll be looking at various Django apps and how they can be used. Some will be apps we're currently using on projects. Some will be apps we think are interesting. We probably won't be doing an app every week, but we'll try to keep the cadence regular enough to be compelling! Stay tuned!
  </p>
 </div>
</div>
]]>/></item><item><title>REVSYS Anniversary and Office Warming Party</title><link>http://www.revsys.com/tidbits/revsys-anniversary-and-office-warming-party/</link><description>Come have some snacks and drinks at our new office.</description><pubDate>Thu, 01 May 2025 21:40:14 +0000</pubDate><guid>http://www.revsys.com/tidbits/revsys-anniversary-and-office-warming-party/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Can you believe it's been 18 years? We're celebrating our anniversary and our new offices on May 5th, 2025 between 3pm and 7pm.
  </p>
  <p>
   Stop by, have a drink, a snack, and talk tech with us.
  </p>
  <ul>
   <li>
    Date: May 5th, 2025
   </li>
   <li>
    Time: 3pm to 7pm
   </li>
   <li>
    Location: 888 New Hampshire, Suite E, Lawrence, KS 66044 (The Lofts Building)
   </li>
  </ul>
  <p>
   The only issue is our exterior door is
   <em>
    fairly hidden
   </em>
   .  It's in the alley
   <strong>
    BEHIND
   </strong>
   the Lofts building at 8th and New Hampshire.  Here is a
   <a href="https://maps.app.goo.gl/YuX8zEH9NkAz9Ho7A">
    Google Map pin for the door
   </a>
  </p>
  <p>
   We'll have the door propped open as it requires a key fob so look for the door that is slightly ajar and for a small REVSYS sign.
  </p>
 </div>
</div>
]]>/></item><item><title>How to Add Blazing Fast Search to Your Django Site with Meilisearch</title><link>http://www.revsys.com/tidbits/how-to-add-blazing-fast-search-to-your-django-site-with-meilisearch/</link><description>Step-by-step guide to integrating Meilisearch with Django, complete with automatic indexing, typo tolerance, and relevant filtering capabilities.</description><pubDate>Mon, 21 Apr 2025 15:25:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/how-to-add-blazing-fast-search-to-your-django-site-with-meilisearch/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   TL;DR: Add powerful search to your Django site in an afternoon
  </h2>
  <p>
   Last year, I needed to add robust search functionality to a client's Django-powered surplus parts website. With approximately 70,000 items spanning hundreds of categories and subcategories, each with unique part numbers and extensive metadata, the site needed a powerful search solution.
   <a href="https://surplussales.com">
    Surplus Sales
   </a>
   sells all kinds of surplus equipment, making it critical that customers quickly find specific parts using various search terms.
  </p>
  <p>
   Based on positive experiences with previous clients, we chose
   <a href="https://www.meilisearch.com/">
    Meilisearch
   </a>
   from the start. We knew how quickly we could implement it and how effectively we could refine search results with minimal tweaking. This approach paid off. Meilisearch delivered fast, typo-tolerant search that could handle all the complex metadata of our client's inventory.
  </p>
  <p>
   The client's requirements were particularly challenging. Users need to search by part numbers (and there might be multiple part numbers for a single product), product names, descriptions, and other technical specifications. They also need to find results even when making typos or using abbreviations. All the more reason to use Meilisearch!
  </p>
  <p>
   Let's walk through how to add Meilisearch to your own Django project.
  </p>
  <h2>
   Why Meilisearch for Your Django Project?
  </h2>
  <p>
   If you've ever tried to implement search in Django using just the ORM, you know it can quickly become a performance bottleneck. While Django's
   <code>
    icontains
   </code>
   lookups work for simple cases, they fall short when you need:
  </p>
  <ul>
   <li>
    Fast response times on large datasets
   </li>
   <li>
    Typo tolerance ("djagno" should still find "django")
   </li>
   <li>
    Tighter control over relevance ranking
   </li>
   <li>
    Filtering and faceting capabilities
   </li>
   <li>
    Complex multi-field searches across related models
   </li>
  </ul>
  <p>
   <a href="https://www.meilisearch.com/">
    Meilisearch
   </a>
   solves all these problems with a simple API and
   <a href="https://github.com/meilisearch/meilisearch-python">
    Python client
   </a>
   that integrates easily with Django's model structure. The
   <a href="https://www.meilisearch.com/docs">
    official documentation
   </a>
   is excellent and comprehensive, but in this post, I'll focus specifically on integrating it with Django.
  </p>
  <h3>
   Meilisearch versus other search solutions
  </h3>
  <p>
   When comparing search solutions for Django projects, Meilisearch stands out for several reasons. Unlike PostgreSQL full-text search, which requires complex query construction and index management, Meilisearch provides a dedicated search API with minimal configuration. Compared to Elasticsearch, Meilisearch is significantly easier to set up, maintain, and scale for most Django applications. The day-to-day operation of Meilisearch is quite simple, with no need for cluster management or index optimization tasks that Elasticsearch can require. Now that it's been in production for a few months, the most maintenance I need to do is manually run a management command once in a while (and even that is pretty rare).
  </p>
  <p>
   For many
   <a href="https://revsys.com/clients">
    Revsys clients
   </a>
   , Meilisearch hits the sweet spot between powerful features and developer-friendly implementation. You get advanced search capabilities without the operational complexity of heavier solutions. This makes it particularly well-suited for small to medium-sized teams that need robust search solutions they can implement quickly.
  </p>
  <h2>
   Step 0: Setting Up Meilisearch
  </h2>
  <p>
   Before we dive into our Django models and search schemas, let's set up Meilisearch.
  </p>
  <h3>
   Docker Configuration
  </h3>
  <p>
   The easiest way to run Meilisearch in your development environment is with Docker and Compose.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># compose.yml </span>
<span class="nt">services</span><span class="p">:</span>
<span class="w">  </span><span class="nt">search</span><span class="p">:</span>
<span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">getmeili/meilisearch:v1.7</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">./meili-data:/meili_data</span>
<span class="w">    </span><span class="nt">ports</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"7700:7700"</span>
</code></pre>
  </div>
  <p>
   This configuration:
  </p>
  <ul>
   <li>
    Uses the official Meilisearch Docker image (version 1.7)
   </li>
   <li>
    Creates a persistent volume for your search data
   </li>
   <li>
    Exposes the service on port 7700
   </li>
  </ul>
  <p>
   The persistent volume is handy for local development. It ensures your search index survives container restarts and updates, so you won't need to rebuild your index every time you restart your containers.
  </p>
  <p>
   To start Meilisearch, run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>docker-compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>search
</code></pre>
  </div>
  <h3>
   Django Configuration
  </h3>
  <p>
   Now let's configure our Django settings to work with Meilisearch. First, install the Python client:
  </p>
  <div class="codehilite">
   <pre><span></span><code>pip<span class="w"> </span>install<span class="w"> </span>meilisearch
</code></pre>
  </div>
  <p>
   (Or add to your dependencies using whatever method you choose.)
  </p>
  <p>
   Then add these settings to your Django project:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># settings.py</span>
<span class="n">SEARCH_API_URL</span> <span class="o">=</span> <span class="n">env</span><span class="p">(</span><span class="s2">"SEARCH_API_URL"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"http://search:7700/"</span><span class="p">)</span>
<span class="n">SEARCH_API_KEY</span> <span class="o">=</span> <span class="n">env</span><span class="p">(</span><span class="s2">"SEARCH_API_KEY"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="n">SEARCH_INDEXES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">"main"</span><span class="p">:</span> <span class="s2">"main_search"</span><span class="p">,</span>
    <span class="c1"># You can define additional indexes here for different purposes</span>
    <span class="c1"># "autocomplete": "autocomplete_search",</span>
    <span class="c1"># "admin": "admin_search",</span>
<span class="p">}</span>
<span class="c1"># Control whether we index the search or not</span>
<span class="n">INDEX_SEARCH</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">bool</span><span class="p">(</span><span class="s2">"INDEX_SEARCH"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The
   <code>
    SEARCH_INDEXES
   </code>
   dictionary is particularly powerful. It allows you to define multiple indexes for different purposes. For example:
  </p>
  <ul>
   <li>
    A "main" index for general site search
   </li>
   <li>
    An "autocomplete" index optimized for quick suggestions with different ranking rules
   </li>
   <li>
    An "admin" index that includes sensitive fields only staff should search
   </li>
  </ul>
  <p>
   Each index can have different settings, ranking rules, and even different data schemas, all while drawing from the same Django models. This flexibility lets you optimize each search experience for its specific use case.
  </p>
  <p>
   Pro-tip: That
   <code>
    INDEX_SEARCH
   </code>
   setting is particularly useful. During development and testing, you can set it to
   <code>
    False
   </code>
   to prevent your test data from being indexed. This keeps your search index clean with only real data. It's also helpful when running tests that create and destroy many model instances, as it prevents unnecessary indexing operations that would slow down your tests.
  </p>
  <p>
   Now with Meilisearch running and configured, we're ready to start building our searchable models.
  </p>
  <h2>
   Step 1: Start with Your Django Models
  </h2>
  <p>
   Let's start by looking at a typical Django model that we want to make searchable:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># products/models.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Product</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
    <span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">price</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DecimalField</span><span class="p">(</span><span class="n">max_digits</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">decimal_places</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
    <span class="n">sku</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">brand</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">created_at</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateTimeField</span><span class="p">(</span><span class="n">auto_now_add</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">updated_at</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateTimeField</span><span class="p">(</span><span class="n">auto_now</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_absolute_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="sa">f</span><span class="s2">"/products/</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">/"</span>

    <span class="k">def</span><span class="w"> </span><span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
</code></pre>
  </div>
  <p>
   This is a standard Django model for a product. Nothing special here yet, but we'll soon make it searchable with Meilisearch.
  </p>
  <p>
   The
   <code>
    get_absolute_url()
   </code>
   method is particularly important for search integration. When displaying search results, you'll need a URL to link to each result. Having this method on your model makes it easy to generate these links consistently.
  </p>
  <p>
   Before moving forward, consider what users will actually search for. For a product catalog, users might search by:
  </p>
  <ul>
   <li>
    Product names
   </li>
   <li>
    Brand names
   </li>
   <li>
    SKUs or part numbers
   </li>
   <li>
    Words in the description
   </li>
   <li>
    Technical specifications
   </li>
   <li>
    Categories or tags (which might be related models)
   </li>
  </ul>
  <p>
   Understanding these search patterns helps you design an effective search schema in the next step.
  </p>
  <h2>
   Thinking About Your Search Schema Design
  </h2>
  <p>
   Before diving into code, let's take a step back and think conceptually about how to design an effective search schema. This is one of the most important decisions you'll make when implementing search because it determines what data is searchable and how your search results will be structured.
  </p>
  <h3>
   The Unified Search Approach
  </h3>
  <p>
   When implementing search across multiple Django models, you have two main approaches:
  </p>
  <ol>
   <li>
    <strong>
     Separate indexes
    </strong>
    - Create different indexes for each model type (products, categories, etc.)
   </li>
   <li>
    <strong>
     Unified schema
    </strong>
    - Create a single schema that can represent any searchable entity
   </li>
  </ol>
  <p>
   For most Django sites, I recommend the unified approach. This allows users to search across all content types with a single query, providing a more intuitive experience. It also simplifies your frontend code since you only need to query one index.
  </p>
  <p>
   The unified approach is particularly valuable when users don't necessarily know or care about the underlying data structure. For example, a user searching for "vaccuum capacitors" might find it useful to get results that include products, categories, or blog posts about capacitors. They just want relevant information. A unified schema lets you return all these result types in a single search.
  </p>
  <p>
   Using separate indexes can be useful when you have different needs for your customer-facing frontend and your admin side. For example, you would index your customer information in its own search index, and then ensure those results could only be read by your staff members. Separate indexes are also useful when different parts of your application have different search requirements or access patterns.
  </p>
  <h3>
   Mapping Different Models to a Common Schema
  </h3>
  <p>
   The key to a unified search schema is designing fields that can accommodate different models while maintaining their unique characteristics. Let's look at how this works with a concrete example, using a Product model and a Category model.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Product</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
    <span class="n">sku</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
    <span class="n">price</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DecimalField</span><span class="p">(</span><span class="n">max_digits</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">decimal_places</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Category</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
    <span class="n">slug</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">SlugField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
    <span class="n">active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Both have a
   <code>
    name
   </code>
   field, but their secondary fields differ. In our search schema, we map them like this, using model-specific field names to maintain the unique characteristics of each model type:
  </p>
  <ul>
   <li>
    Schema
    <code>
     title
    </code>
    maps to Product
    <code>
     name
    </code>
    and Category
    <code>
     name
    </code>
   </li>
   <li>
    Schema
    <code>
     product_name
    </code>
    maps to Product
    <code>
     name
    </code>
    and isn't used for Category
   </li>
   <li>
    Schema
    <code>
     category_name
    </code>
    isn't used for Product and maps to Category
    <code>
     name
    </code>
   </li>
   <li>
    Schema
    <code>
     product_description
    </code>
    maps to Product
    <code>
     description
    </code>
    and isn't used for Category
   </li>
   <li>
    Schema
    <code>
     category_description
    </code>
    isn't used for Product and maps to Category
    <code>
     description
    </code>
   </li>
   <li>
    Schema
    <code>
     sku
    </code>
    maps to Product
    <code>
     sku
    </code>
    and isn't used for Category
   </li>
   <li>
    Schema
    <code>
     type
    </code>
    is set to "product" for Product and "category" for Category
   </li>
  </ul>
  <p>
   This approach gives us a consistent way to display search results while preserving the unique aspects of each model. The
   <code>
    title
   </code>
   field serves as a common display field, while the model-specific fields allow for targeted searching within each model type.
  </p>
  <p>
   This pattern scales well to more complex scenarios. For example, if you were building a university website, you might have models for Courses, Faculty, and Events. Your schema might include fields like:
  </p>
  <ul>
   <li>
    <code>
     title
    </code>
    - The main display title for any result
   </li>
   <li>
    <code>
     subtitle
    </code>
    - Which might be a course code for Courses, a department for Faculty, or a date for Events
   </li>
   <li>
    <code>
     type
    </code>
    - "course", "faculty", or "event"
   </li>
   <li>
    <code>
     course_description
    </code>
    ,
    <code>
     faculty_bio
    </code>
    ,
    <code>
     event_details
    </code>
    - Type-specific content fields
   </li>
  </ul>
  <p>
   The key takeaway is that
   <strong>
    your search schema doesn't have to mirror your database schema
   </strong>
   . It should be designed specifically for search, with fields that make sense for how users will search and how you'll display results.
  </p>
  <h3>
   What to Include in Your Schema
  </h3>
  <p>
   When deciding which fields to include in your search schema, consider:
  </p>
  <ol>
   <li>
    <strong>
     What users search for
    </strong>
    : Include fields that contain terms users are likely to search for
   </li>
   <li>
    <strong>
     What helps with relevance
    </strong>
    : Fields that help determine if a result is relevant
   </li>
   <li>
    <strong>
     What's needed for display
    </strong>
    : Information needed to render search results
   </li>
   <li>
    <strong>
     What's needed for filtering
    </strong>
    : Fields users might want to filter on
   </li>
  </ol>
  <p>
   For our client, we found that including part numbers, alternate part numbers, and technical specifications was crucial because many customers knew exactly what part they needed. We also included category information so users could find related parts even if they didn't know the exact part number.
  </p>
  <p>
   Equally important is what to exclude:
  </p>
  <ol>
   <li>
    <strong>
     Large text fields
    </strong>
    : Unless they're critical for search, large text fields can bloat your index
   </li>
   <li>
    <strong>
     Sensitive information
    </strong>
    : Never include passwords, private notes, etc.
   </li>
   <li>
    <strong>
     Frequently changing data
    </strong>
    : Data that changes very often might be better queried directly from your database
   </li>
   <li>
    <strong>
     Derived data
    </strong>
    : If it can be calculated from other fields, consider computing it at display time
   </li>
  </ol>
  <h3>
   Multiple Indexes for Different Purposes
  </h3>
  <p>
   While a unified schema works well for general search, sometimes you need specialized indexes for specific features. For example:
  </p>
  <ul>
   <li>
    A
    <strong>
     main
    </strong>
    index for general site search
   </li>
   <li>
    An
    <strong>
     autocomplete
    </strong>
    index optimized for quick suggestions as users type
   </li>
   <li>
    A
    <strong>
     products
    </strong>
    index with additional fields specific to product search
   </li>
   <li>
    An
    <strong>
     admin
    </strong>
    index that includes fields only relevant to staff users
   </li>
  </ul>
  <p>
   Each index can have different settings optimized for its specific use case, while still drawing from the same underlying data models.
  </p>
  <h2>
   Step 2: Define Your Search Schema with Pydantic
  </h2>
  <p>
   Now that we understand the conceptual approach, let's define what data from our model should be searchable. This is where
   <a href="https://docs.pydantic.dev/latest/">
    Pydantic
   </a>
   comes in. It helps us create a clean, type-checked schema that will be sent to Meilisearch:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/schemas.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>

<span class="k">class</span><span class="w"> </span><span class="nc">SearchSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""</span>
<span class="sd">    Pydantic model for our main search schema</span>
<span class="sd">    """</span>
    <span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="nb">type</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># 'product', 'category', etc.</span>
    <span class="n">url</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">active</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>

    <span class="c1"># Product fields</span>
    <span class="n">product_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">product_description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">brand_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">sku</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre>
  </div>
  <p>
   This schema defines exactly what will be stored in our Meilisearch index. The beauty of this approach is that we can:
  </p>
  <ol>
   <li>
    Have strict type checking for our search documents
   </li>
   <li>
    Control exactly which model fields get indexed for searching
   </li>
   <li>
    Clearly separate which fields belong to which model types
   </li>
  </ol>
  <p>
   The fields in our schema serve different purposes:
  </p>
  <ul>
   <li>
    <code>
     id
    </code>
    : A unique identifier for each document in the index. We'll prefix this with the model type to ensure uniqueness across different models.
   </li>
   <li>
    <code>
     title
    </code>
    : A common display field used for all result types.
   </li>
   <li>
    <code>
     type
    </code>
    : Identifies what kind of object this is (product, category, etc.). This is crucial for filtering and displaying results appropriately.
   </li>
   <li>
    <code>
     url
    </code>
    : The link to the full object, used when a user clicks a search result.
   </li>
   <li>
    <code>
     active
    </code>
    : Whether this item should appear in search results. This allows us to hide items without removing them from the index.
   </li>
  </ul>
  <p>
   The model-specific fields (prefixed with
   <code>
    product_
   </code>
   or later
   <code>
    category_
   </code>
   ) allow us to search within specific model types while maintaining a unified schema.
  </p>
  <p>
   Later, we'll add a second model to our search schema, demonstrating how this approach scales to multiple model types.
  </p>
  <h3>
   Why Pydantic?
  </h3>
  <p>
   Using Pydantic for this purpose offers several advantages over a simple dictionary:
  </p>
  <ol>
   <li>
    It provides type validation, ensuring that your search documents always have the expected structure. This catches errors early, before they cause problems in your search index.
   </li>
   <li>
    Pydantic's schema documentation capabilities make it easy to understand what data is being indexed.
   </li>
   <li>
    Pydantic models can be easily serialized to JSON, which is what Meilisearch expects.
   </li>
  </ol>
  <h2>
   Step 3: Connect Your Model to the Search Schema
  </h2>
  <p>
   Now let's add a method to our
   <code>
    Product
   </code>
   model that maps its fields to our search schema:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># products/models.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.schemas</span><span class="w"> </span><span class="kn">import</span> <span class="n">SearchSchema</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Product</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="c1"># fields and methods as before</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">search_data</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="w">        </span><span class="sd">"""Return data for the search index"""</span>
        <span class="k">return</span> <span class="n">SearchSchema</span><span class="p">(</span>
            <span class="c1"># prefix the ID with the "type" for the SearchSchema</span>
            <span class="nb">id</span><span class="o">=</span><span class="sa">f</span><span class="s2">"product-</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
            <span class="n">title</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
            <span class="nb">type</span><span class="o">=</span><span class="s2">"product"</span><span class="p">,</span>
            <span class="n">url</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">get_absolute_url</span><span class="p">(),</span>
            <span class="n">active</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">active</span><span class="p">,</span>
            <span class="n">product_name</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
            <span class="n">product_description</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">description</span><span class="p">,</span>
            <span class="n">brand_name</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">brand</span><span class="p">,</span>
            <span class="n">sku</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">sku</span><span class="p">,</span>
        <span class="p">)</span>
</code></pre>
  </div>
  <p>
   We've added a
   <code>
    search_data()
   </code>
   method that transforms our Django model into the Pydantic schema. The
   <code>
    id
   </code>
   field uses a prefix to ensure uniqueness across different model types (which we'll need later when we add more models to our search index).
  </p>
  <p>
   This method serves as the bridge between your Django model and your search index. It's responsible for:
  </p>
  <ol>
   <li>
    Selecting which model data should be searchable
   </li>
   <li>
    Transforming that data into the structure expected by your search schema
   </li>
   <li>
    Adding any computed or derived fields needed for search
   </li>
  </ol>
  <p>
   The prefix in the
   <code>
    id
   </code>
   field (
   <code>
    product-{self.id}
   </code>
   ) is particularly important. Without it, you might have ID collisions when indexing different model types. For example, both a Product with ID 1 and a Category with ID 1 would have the same ID in the search index. By adding the type prefix, we ensure each document has a truly unique identifier.
  </p>
  <p>
   Notice how we map fields from our Django model to our search schema:
-
   <code>
    name
   </code>
   becomes both
   <code>
    title
   </code>
   (the generic display field) and
   <code>
    product_name
   </code>
   (the product-specific field)
- We include the
   <code>
    active
   </code>
   status to control visibility in search results
- We use
   <code>
    get_absolute_url()
   </code>
   to generate the link for search results
  </p>
  <h2>
   Step 4: Indexing Our Search Data in Meilisearch
  </h2>
  <h3>
   4.1 What is Search Indexing?
  </h3>
  <p>
   So what exactly is "indexing" in the context of search engines? Think of it like creating a highly optimized lookup table for your data. When we talk about indexing in Meilisearch, we're:
  </p>
  <ul>
   <li>
    Extracting specific fields from our Django models (like names, descriptions, SKUs)
   </li>
   <li>
    Transforming this data into a format optimized for search
   </li>
   <li>
    Organizing it so Meilisearch can quickly find matching results when users search
   </li>
  </ul>
  <p>
   Without indexing, searching would require scanning through every record in your database. Indexing creates that organization system, allowing Meilisearch to deliver results in milliseconds rather than seconds or minutes.
  </p>
  <p>
   Our
   <code>
    SearchSchema
   </code>
   is how we tell Meilisearch which fields should be searchable (like product names and descriptions) and which should be filterable (like product types and whether they're active). These decisions shape how the index is structured and optimized behind the scenes.
  </p>
  <h3>
   4.2 Creating a Meilisearch Client Connection
  </h3>
  <p>
   Let's build our helper functions step by step, starting with establishing a connection to Meilisearch:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">get_meilisearch_client</span><span class="p">():</span>
<span class="w">    </span><span class="sd">"""Return a meilisearch client"""</span>
    <span class="n">url</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_API_URL</span>
    <span class="n">api_key</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_API_KEY</span>
    <span class="n">client</span> <span class="o">=</span> <span class="n">meilisearch</span><span class="o">.</span><span class="n">Client</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">api_key</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">client</span>
</code></pre>
  </div>
  <p>
   This function creates a connection to your Meilisearch server using the URL and API key from your Django settings. We'll use this in all our other functions to avoid repeating the connection code.
  </p>
  <h3>
   4.3 Managing Individual Objects in the Index
  </h3>
  <p>
   Next, we need a way to handle individual objects (like products in our inventory) when they're created, updated, or deleted:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">update_object</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Update a single object in the search index or delete if inactive"""</span>
    <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
    <span class="n">index_name</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">]</span>
    <span class="n">index</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">obj</span><span class="p">[</span><span class="s2">"active"</span><span class="p">]:</span>
        <span class="c1"># Update the object in the index</span>
        <span class="n">index</span><span class="o">.</span><span class="n">add_documents</span><span class="p">([</span><span class="n">obj</span><span class="p">])</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># Delete the object from the index</span>
        <span class="n">index</span><span class="o">.</span><span class="n">delete_document</span><span class="p">(</span><span class="n">obj</span><span class="p">[</span><span class="s2">"id"</span><span class="p">])</span>
</code></pre>
  </div>
  <p>
   This function handles adding, updating, or removing a single object in the search index. When you save a product, this function gets called to either:
  </p>
  <ul>
   <li>
    Add/update the product in the index if it's active
   </li>
   <li>
    Remove it from the index if it's inactive (like when a product is discontinued)
   </li>
  </ul>
  <p>
   The conditional logic based on the
   <code>
    active
   </code>
   field is particularly useful. It automatically handles the case where items can be deactivated but not deleted from the database. By removing inactive items from the search index, you ensure users only find items they can actually purchase or access.
  </p>
  <h3>
   4.4 Building a Complete Search Index
  </h3>
  <p>
   Now for our workhorse function that handles populating the entire search index:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">build_main_indexes</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Build the main search index with all searchable models"""</span>
    <span class="kn">from</span><span class="w"> </span><span class="nn">products.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Product</span>  <span class="c1"># Import your models</span>

    <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
    <span class="n">index_name</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">]</span>
    <span class="n">index</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>

    <span class="c1"># Configure the index settings</span>
    <span class="n">index_settings</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s2">"filterableAttributes"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"type"</span><span class="p">,</span> <span class="s2">"active"</span><span class="p">],</span>
        <span class="s2">"searchableAttributes"</span><span class="p">:</span> <span class="p">[</span>
            <span class="s2">"title"</span><span class="p">,</span>
            <span class="s2">"product_name"</span><span class="p">,</span>
            <span class="s2">"product_description"</span><span class="p">,</span>
            <span class="s2">"sku"</span><span class="p">,</span>
            <span class="s2">"brand_name"</span><span class="p">,</span>
        <span class="p">],</span>
    <span class="p">}</span>
    <span class="n">index</span><span class="o">.</span><span class="n">update_settings</span><span class="p">(</span><span class="n">index_settings</span><span class="p">)</span>

    <span class="c1"># Index all active products</span>
    <span class="n">products</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">active</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Indexing </span><span class="si">{</span><span class="n">products</span><span class="o">.</span><span class="n">count</span><span class="p">()</span><span class="si">}</span><span class="s2"> products..."</span><span class="p">)</span>

    <span class="n">product_docs</span> <span class="o">=</span> <span class="p">[</span><span class="n">product</span><span class="o">.</span><span class="n">search_data</span><span class="p">()</span><span class="o">.</span><span class="n">dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">product</span> <span class="ow">in</span> <span class="n">products</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">product_docs</span><span class="p">:</span>
        <span class="n">index</span><span class="o">.</span><span class="n">add_documents</span><span class="p">(</span><span class="n">product_docs</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This function:
  </p>
  <ul>
   <li>
    Configures which fields should be searchable (like product names and descriptions)
   </li>
   <li>
    Defines which fields can be used for filtering (type and active status)
   </li>
   <li>
    Fetches all active products from your database
   </li>
   <li>
    Converts each product to its search schema format
   </li>
   <li>
    Adds all products to Meilisearch in a single batch operation
   </li>
  </ul>
  <p>
   The
   <code>
    searchableAttributes
   </code>
   setting is particularly important. It tells Meilisearch which fields should be included in the search index. Fields not listed here won't be searchable, even if they're included in your documents. This gives you fine-grained control over what's searchable and what's not.
  </p>
  <p>
   Similarly,
   <code>
    filterableAttributes
   </code>
   defines which fields can be used for filtering results. In our case, we want to filter by
   <code>
    type
   </code>
   (to show only products or only categories) and by
   <code>
    active
   </code>
   status (to hide inactive items).
  </p>
  <p>
   The optional verbose parameter lets you see progress output, which is helpful when indexing large datasets.
  </p>
  <h3>
   4.5 Complete Index Rebuilds
  </h3>
  <p>
   Finally, we need a "nuclear option" for when we want to start fresh:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">rebuild_main_indexes</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Completely rebuild the main search index"""</span>
    <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
    <span class="n">index_name</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">]</span>

    <span class="c1"># Delete the index if it exists</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">client</span><span class="o">.</span><span class="n">delete_index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>
    <span class="k">except</span> <span class="n">meilisearch</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">MeiliSearchApiError</span><span class="p">:</span>
        <span class="k">pass</span>

    <span class="c1"># Create the index and build it</span>
    <span class="n">client</span><span class="o">.</span><span class="n">create_index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>
    <span class="n">build_main_indexes</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="n">verbose</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This function completely wipes and rebuilds your search index. You'll want to use this when:
  </p>
  <ul>
   <li>
    You've made changes to your search schema
   </li>
   <li>
    You want to reset your index after testing
   </li>
   <li>
    Your index has gotten out of sync with your database
   </li>
   <li>
    You've made significant changes to your ranking rules or index settings
   </li>
  </ul>
  <p>
   When I first implemented this for our client, I was amazed at how quickly Meilisearch could index tens of thousands of products. Even with our full catalog of 70,000+ items, the initial indexing took only a couple of minutes.
  </p>
  <h2>
   Step 5: Creating a Mixin for Automatic Indexing
  </h2>
  <p>
   Now let's create a mixin that will automatically update the search index whenever a model is saved. This way, if a product's data changes, or the website administrator marks a product inactive, the search index updates and the search results for users remain accurate.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/mixins.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.conf</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">update_object</span>

<span class="k">class</span><span class="w"> </span><span class="nc">UpdateSearchMixin</span><span class="p">:</span>
<span class="w">    </span><span class="sd">"""</span>
<span class="sd">    Mixin to update the search index when a model is saved</span>
<span class="sd">    """</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">update_search</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">settings</span><span class="o">.</span><span class="n">INDEX_SEARCH</span><span class="p">:</span>
            <span class="n">update_object</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">search_data</span><span class="p">()</span><span class="o">.</span><span class="n">dict</span><span class="p">())</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="w">        </span><span class="sd">""" Override the save method so we can call update_search after save """</span>
        <span class="c1"># Call the original save method</span>
        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="c1"># Update the search index</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">update_search</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   Now we can update our Product model to use this mixin:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># products/models.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.schemas</span><span class="w"> </span><span class="kn">import</span> <span class="n">SearchSchema</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.mixins</span><span class="w"> </span><span class="kn">import</span> <span class="n">UpdateSearchMixin</span>

<span class="c1"># Add UpdateSearchMixin</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Product</span><span class="p">(</span><span class="n">UpdateSearchMixin</span><span class="p">,</span> <span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="o">...</span>
</code></pre>
  </div>
  <p>
   With this implementation, whenever a Product is saved:
  </p>
  <ol>
   <li>
    The
    <code>
     save()
    </code>
    method from our mixin is called
   </li>
   <li>
    It calls
    <code>
     update_search()
    </code>
    , which uses the
    <code>
     search_data
    </code>
    method on the model to update the search index
   </li>
   <li>
    The
    <code>
     search_data()
    </code>
    method transforms our Django model into the Pydantic schema
   </li>
   <li>
    The data is sent to Meilisearch in the exact format we've defined
   </li>
  </ol>
  <p>
   This automatic indexing approach ensures your search index stays in sync with your database without requiring any additional code at the point of use. Once you've set up the mixin and applied it to your models, the indexing happens automatically whenever models are saved. This means your search results stay up-to-date.
  </p>
  <p>
   For more complex scenarios, you might want to extend this mixin to handle bulk operations or to update related objects. For example, if changing a Category should update all its related Products in the search index, you could add that logic to the Category model's save method.
  </p>
  <p>
   One of the great advantages of Meilisearch compared to other search engines like Elasticsearch is that it handles document indexing asynchronously. When you send a document to Meilisearch, it quickly acknowledges receipt and then processes the indexing in the background. This means your Django
   <code>
    save()
   </code>
   method won't be slowed down waiting for indexing to complete, a common issue with other search backends where indexing can add seconds to each save operation.
  </p>
  <p>
   This asynchronous approach eliminates the need for background task queues like Celery or Dramatiq just to handle search indexing, greatly simplifying your architecture.
  </p>
  <h2>
   Step 6: Creating Management Commands for Index Operations
  </h2>
  <p>
   While our automatic indexing handles day-to-day updates, we sometimes need more powerful tools for managing our search index. Django management commands are perfect for this.
  </p>
  <p>
   Let's create a management command using
   <a href="https://github.com/django-commons/django-click">
    django-click
   </a>
   for re-indexing our search:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/management/commands/index_search.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">__future__</span><span class="w"> </span><span class="kn">import</span> <span class="n">annotations</span>

<span class="kn">import</span><span class="w"> </span><span class="nn">djclick</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">click</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">search.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">rebuild_main_indexes</span>


<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">"--verbose"</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">verbose</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Index objects in Meilisearch."""</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Rebuilding main indexes..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">,</span> <span class="n">nl</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
    <span class="n">rebuild_main_indexes</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="n">verbose</span><span class="p">)</span>
    <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">"Indexes built."</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This simple command provides a convenient way to rebuild your search index from the command line.
  </p>
  <p>
   For larger projects, we can enhance this with more options and batch processing for handling large datasets:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/management/commands/index_search.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">__future__</span><span class="w"> </span><span class="kn">import</span> <span class="n">annotations</span>

<span class="kn">import</span><span class="w"> </span><span class="nn">time</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">djclick</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">click</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.conf</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">products.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Product</span><span class="p">,</span> <span class="n">Category</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_meilisearch_client</span>

<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">"--verbose"</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">"--batch-size"</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">"Batch size for indexing"</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">verbose</span><span class="p">,</span> <span class="n">batch_size</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Rebuild search indexes in Meilisearch with batch processing."""</span>
    <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="s2">"Rebuilding search indexes..."</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>

    <span class="c1"># Get client and recreate index</span>
    <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
    <span class="n">index_name</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">]</span>

    <span class="c1"># Delete and recreate index</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">client</span><span class="o">.</span><span class="n">delete_index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>
    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
        <span class="k">pass</span>

    <span class="n">client</span><span class="o">.</span><span class="n">create_index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>
    <span class="n">index</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>

    <span class="c1"># Configure index settings</span>
    <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">"Configuring index settings..."</span><span class="p">)</span>
    <span class="n">index_settings</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s2">"filterableAttributes"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"type"</span><span class="p">,</span> <span class="s2">"active"</span><span class="p">],</span>
        <span class="s2">"searchableAttributes"</span><span class="p">:</span> <span class="p">[</span>
            <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"product_name"</span><span class="p">,</span> <span class="s2">"product_description"</span><span class="p">,</span> 
            <span class="s2">"sku"</span><span class="p">,</span> <span class="s2">"brand_name"</span><span class="p">,</span> <span class="s2">"category_name"</span>
        <span class="p">],</span>
    <span class="p">}</span>
    <span class="n">index</span><span class="o">.</span><span class="n">update_settings</span><span class="p">(</span><span class="n">index_settings</span><span class="p">)</span>

    <span class="c1"># Index products in batches</span>
    <span class="n">products</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">active</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">total_products</span> <span class="o">=</span> <span class="n">products</span><span class="o">.</span><span class="n">count</span><span class="p">()</span>

    <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
        <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Indexing </span><span class="si">{</span><span class="n">total_products</span><span class="si">}</span><span class="s2"> products in batches of </span><span class="si">{</span><span class="n">batch_size</span><span class="si">}</span><span class="s2">..."</span><span class="p">)</span>

    <span class="c1"># Process in batches to avoid memory issues with large datasets</span>
    <span class="n">batch_count</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">total_products</span><span class="p">,</span> <span class="n">batch_size</span><span class="p">):</span>
        <span class="n">batch</span> <span class="o">=</span> <span class="n">products</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="n">batch_size</span><span class="p">]</span>
        <span class="n">product_docs</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">search_data</span><span class="p">()</span><span class="o">.</span><span class="n">dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">batch</span><span class="p">]</span>
        <span class="k">if</span> <span class="n">product_docs</span><span class="p">:</span>
            <span class="n">index</span><span class="o">.</span><span class="n">add_documents</span><span class="p">(</span><span class="n">product_docs</span><span class="p">)</span>

        <span class="n">batch_count</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
            <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processed batch </span><span class="si">{</span><span class="n">batch_count</span><span class="si">}</span><span class="s2">, </span><span class="si">{</span><span class="nb">min</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="n">batch_size</span><span class="p">,</span><span class="w"> </span><span class="n">total_products</span><span class="p">)</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total_products</span><span class="si">}</span><span class="s2"> products"</span><span class="p">)</span>

    <span class="c1"># Index categories (typically smaller, so we do it all at once)</span>
    <span class="n">categories</span> <span class="o">=</span> <span class="n">Category</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">active</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">category_docs</span> <span class="o">=</span> <span class="p">[</span><span class="n">cat</span><span class="o">.</span><span class="n">search_data</span><span class="p">()</span><span class="o">.</span><span class="n">dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">categories</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">category_docs</span><span class="p">:</span>
        <span class="n">index</span><span class="o">.</span><span class="n">add_documents</span><span class="p">(</span><span class="n">category_docs</span><span class="p">)</span>

    <span class="n">elapsed_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span>
    <span class="n">click</span><span class="o">.</span><span class="n">secho</span><span class="p">(</span><span class="sa">f</span><span class="s2">"&checkmark; Indexing completed in </span><span class="si">{</span><span class="n">elapsed_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> seconds"</span><span class="p">,</span> <span class="n">fg</span><span class="o">=</span><span class="s2">"green"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This enhanced command is what we used for our client with 70,000+ items. By processing records in chunks of 1,000, we avoid memory issues that can occur when trying to process all 70,000+ records at once. By processing in manageable chunks, we kept memory usage reasonable while still maintaining good performance.
  </p>
  <p>
   The entire indexing process for 70,000+ items took about 3-4 minutes, which is remarkably fast considering the volume of data. This is another area where Meilisearch shines compared to other search solutions. Its indexing performance is excellent even with large datasets.
  </p>
  <p>
   This command is invaluable when:
  </p>
  <ul>
   <li>
    You've made schema changes and need to rebuild the entire index
   </li>
   <li>
    You're deploying to a new environment and need to populate the index
   </li>
   <li>
    You suspect the index has gotten out of sync with your database
   </li>
   <li>
    You're performing data migrations that affect searchable content
   </li>
   <li>
    You want to update your index settings or ranking rules
   </li>
  </ul>
  <p>
   The ability to easily and quickly rebuild your search index is one of the many reasons Meilisearch is so developer-friendly.
  </p>
  <h2>
   Step 7: Adding More Models to the Search Index
  </h2>
  <p>
   Now that we have the basic structure in place, we can easily add more models to our search index. Let's integrate a Category model:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># products/models.py</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Category</span><span class="p">(</span><span class="n">UpdateSearchMixin</span><span class="p">,</span> <span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
    <span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_absolute_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="sa">f</span><span class="s2">"/categories/</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">/"</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">search_data</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">SearchSchema</span><span class="p">(</span>
            <span class="nb">id</span><span class="o">=</span><span class="sa">f</span><span class="s2">"category-</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
            <span class="n">title</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
            <span class="nb">type</span><span class="o">=</span><span class="s2">"category"</span><span class="p">,</span>
            <span class="n">url</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">get_absolute_url</span><span class="p">(),</span>
            <span class="n">active</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">active</span><span class="p">,</span>
            <span class="n">category_name</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
            <span class="n">category_description</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">description</span><span class="p">,</span>
            <span class="n">products_count</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">products</span><span class="o">.</span><span class="n">count</span><span class="p">(),</span>
        <span class="p">)</span>
</code></pre>
  </div>
  <p>
   And update our SearchSchema to include category fields:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/schemas.py</span>
<span class="k">class</span><span class="w"> </span><span class="nc">SearchSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""</span>
<span class="sd">    Pydantic model for our main search schema</span>
<span class="sd">    """</span>
    <span class="c1"># other fields as before... </span>

    <span class="c1"># Category fields</span>
    <span class="n">category_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">category_description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">products_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre>
  </div>
  <p>
   Then update our
   <code>
    build_main_indexes
   </code>
   function to include categories:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">build_main_indexes</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Build the main search index with all searchable models"""</span>
    <span class="kn">from</span><span class="w"> </span><span class="nn">products.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Product</span><span class="p">,</span> <span class="n">Category</span>  <span class="c1"># Import your models</span>

    <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
    <span class="n">index_name</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">]</span>
    <span class="n">index</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">index_name</span><span class="p">)</span>

    <span class="c1"># Configure the index settings</span>
    <span class="n">index_settings</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s2">"filterableAttributes"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"type"</span><span class="p">,</span> <span class="s2">"active"</span><span class="p">],</span>
        <span class="s2">"searchableAttributes"</span><span class="p">:</span> <span class="p">[</span>
            <span class="o">...</span>
            <span class="s2">"category_name"</span><span class="p">,</span>
            <span class="s2">"category_description"</span><span class="p">,</span>
        <span class="p">],</span>
    <span class="p">}</span>
    <span class="n">index</span><span class="o">.</span><span class="n">update_settings</span><span class="p">(</span><span class="n">index_settings</span><span class="p">)</span>

    <span class="c1"># Index all active products as before... </span>

    <span class="c1"># Index all active categories</span>
    <span class="n">categories</span> <span class="o">=</span> <span class="n">Category</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">active</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Indexing </span><span class="si">{</span><span class="n">categories</span><span class="o">.</span><span class="n">count</span><span class="p">()</span><span class="si">}</span><span class="s2"> categories..."</span><span class="p">)</span>

    <span class="n">category_docs</span> <span class="o">=</span> <span class="p">[</span><span class="n">cat</span><span class="o">.</span><span class="n">search_data</span><span class="p">()</span><span class="o">.</span><span class="n">dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">categories</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">category_docs</span><span class="p">:</span>
        <span class="n">index</span><span class="o">.</span><span class="n">add_documents</span><span class="p">(</span><span class="n">category_docs</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This pattern scales well to any number of models. Each model:
  </p>
  <ol>
   <li>
    Implements the
    <code>
     UpdateSearchMixin
    </code>
   </li>
   <li>
    Provides a
    <code>
     search_data()
    </code>
    method that returns a
    <code>
     SearchSchema
    </code>
    instance
   </li>
   <li>
    Sets appropriate values for common fields (
    <code>
     id
    </code>
    ,
    <code>
     title
    </code>
    ,
    <code>
     type
    </code>
    ,
    <code>
     url
    </code>
    ,
    <code>
     active
    </code>
    )
   </li>
   <li>
    Populates its model-specific fields in the schema
   </li>
  </ol>
  <p>
   The beauty of this approach is that you can add new model types to your search index without changing your existing code. You just:
  </p>
  <ol>
   <li>
    Update your
    <code>
     SearchSchema
    </code>
    to include fields for the new model type
   </li>
   <li>
    Add the mixin and
    <code>
     search_data()
    </code>
    method to the new model
   </li>
   <li>
    Update your
    <code>
     build_main_indexes
    </code>
    function to include the new model
   </li>
  </ol>
  <p>
   This extensibility is particularly valuable as your application grows. You might start with just products, then add categories, then blog posts, then user profiles, all without having to redesign your search architecture.
  </p>
  <h2>
   Step 8: Implementing the Search Frontend
  </h2>
  <p>
   Now let's create a simple view to search our index:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># search/views.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.shortcuts</span><span class="w"> </span><span class="kn">import</span> <span class="n">render</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.conf</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_meilisearch_client</span>

<span class="k">def</span><span class="w"> </span><span class="nf">search</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""Search view"""</span>
    <span class="n">query</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"q"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
    <span class="n">type_filter</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"type"</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">if</span> <span class="n">query</span><span class="p">:</span>
        <span class="n">client</span> <span class="o">=</span> <span class="n">get_meilisearch_client</span><span class="p">()</span>
        <span class="n">index</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">SEARCH_INDEXES</span><span class="p">[</span><span class="s2">"main"</span><span class="p">])</span>

        <span class="c1"># Build filter string using your filterable attributes </span>
        <span class="n">filter_str</span> <span class="o">=</span> <span class="s2">"active = true"</span>
        <span class="k">if</span> <span class="n">type_filter</span><span class="p">:</span>
            <span class="n">filter_str</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">" AND type = </span><span class="si">{</span><span class="n">type_filter</span><span class="si">}</span><span class="s2">"</span>

        <span class="c1"># Perform search</span>
        <span class="n">search_results</span> <span class="o">=</span> <span class="n">index</span><span class="o">.</span><span class="n">search</span><span class="p">(</span>
            <span class="n">query</span><span class="p">,</span> 
            <span class="p">{</span>
                <span class="s2">"filter"</span><span class="p">:</span> <span class="n">filter_str</span><span class="p">,</span>
                <span class="s2">"limit"</span><span class="p">:</span> <span class="mi">50</span>
            <span class="p">}</span>
        <span class="p">)</span>
        <span class="n">results</span> <span class="o">=</span> <span class="n">search_results</span><span class="p">[</span><span class="s2">"hits"</span><span class="p">]</span>

    <span class="k">return</span> <span class="n">render</span><span class="p">(</span>
        <span class="n">request</span><span class="p">,</span>
        <span class="s2">"search/results.html"</span><span class="p">,</span>
        <span class="p">{</span>
            <span class="s2">"results"</span><span class="p">:</span> <span class="n">results</span><span class="p">,</span>
            <span class="s2">"query"</span><span class="p">:</span> <span class="n">query</span><span class="p">,</span>
            <span class="s2">"type_filter"</span><span class="p">:</span> <span class="n">type_filter</span>
        <span class="p">},</span>
    <span class="p">)</span>
</code></pre>
  </div>
  <p>
   This view handles the search process:
  </p>
  <ol>
   <li>
    It extracts the search query and any filters from the request
   </li>
   <li>
    It connects to Meilisearch and performs the search
   </li>
   <li>
    It renders a template with the search results
   </li>
  </ol>
  <p>
   The
   <code>
    filter_str
   </code>
   construction shows how to use Meilisearch's filtering. We always filter to show only active items. You can optionally filter by type if the user has selected a specific type filter.
  </p>
  <p>
   For more advanced search needs, Meilisearch supports additional parameters like:
  </p>
  <ul>
   <li>
    <code>
     attributesToHighlight
    </code>
    : Highlight matching terms in results
   </li>
   <li>
    <code>
     facets
    </code>
    : Return facet counts for filtered attributes
   </li>
   <li>
    <code>
     sort
    </code>
    : Sort results by specific attributes
   </li>
   <li>
    <code>
     offset
    </code>
    : Paginate through results
   </li>
  </ul>
  <p>
   Now let's create a simple template to display the results:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="cm">&lt;!-- templates/search/results.html --&gt;</span>
{% extends "base.html" %}

{% block content %}
<span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"search-container"</span><span class="p">&gt;</span>
    <span class="cm">&lt;!-- Search form --&gt;</span>
    <span class="p">&lt;</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"get"</span> <span class="na">action</span><span class="o">=</span><span class="s">"{% url 'search' %}"</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"text"</span> <span class="na">name</span><span class="o">=</span><span class="s">"q"</span> <span class="na">value</span><span class="o">=</span><span class="s">"{{ query }}"</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">"Search..."</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">"submit"</span><span class="p">&gt;</span>Search<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>

    <span class="cm">&lt;!-- Results --&gt;</span>
    {% if query %}
        <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Results for "{{ query }}"<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
        {% if results %}
            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"search-results"</span><span class="p">&gt;</span>
                {% for result in results %}
                    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"search-result"</span><span class="p">&gt;</span>
                        <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ result.url }}"</span><span class="p">&gt;</span>{{ result.title }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
                    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
                {% endfor %}
            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
        {% else %}
            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>No results found.<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
        {% endif %}
    {% endif %}
<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
{% endblock %}
</code></pre>
  </div>
  <p>
   This template includes:
  </p>
  <ul>
   <li>
    A search form with the current query pre-filled
   </li>
   <li>
    A results section that displays different information based on the result type
   </li>
  </ul>
  <p>
   Don't forget to add the URL pattern:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># urls.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.urls</span><span class="w"> </span><span class="kn">import</span> <span class="n">path</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">search.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">search</span>

<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
    <span class="c1"># ... other URLs</span>
    <span class="n">path</span><span class="p">(</span><span class="s1">'search/'</span><span class="p">,</span> <span class="n">search</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'search'</span><span class="p">),</span>
<span class="p">]</span>
</code></pre>
  </div>
  <p>
   For a production application, you might want to enhance this with:
  </p>
  <ol>
   <li>
    <strong>
     Pagination
    </strong>
    : For handling large result sets
   </li>
   <li>
    <strong>
     Highlighting
    </strong>
    : To show users where their search terms matched
   </li>
   <li>
    <strong>
     Faceted search
    </strong>
    : To allow filtering by multiple attributes
   </li>
   <li>
    <strong>
     Autocomplete
    </strong>
    : To suggest search terms as users type
   </li>
   <li>
    <strong>
     Analytics
    </strong>
    : To track popular searches and improve your search experience
   </li>
  </ol>
  <p>
   Meilisearch supports all these features through its API, making it easy to build a sophisticated search experience as your application grows.
  </p>
  <h2>
   See It In Action: Surplus Sales Case Study
  </h2>
  <p>
   Want to see what a production Meilisearch implementation looks like? Visit our client
   <a href="https://surplussales.com">
    Surplus Sales
   </a>
   and try out their search functionality. Pay special attention to:
  </p>
  <ul>
   <li>
    <strong>
     Typo tolerance
    </strong>
    : Try searching for "trnasistors" (misspelled) and notice how it still finds what you need
   </li>
   <li>
    <strong>
     Speed
    </strong>
    : The millisecond response times, even when filtering through their 70,000+ item catalog
   </li>
   <li>
    <strong>
     Relevance
    </strong>
    : How the most relevant parts appear at the top of results
   </li>
   <li>
    <strong>
     Complex metadata handling
    </strong>
    : Search by part numbers, specifications, or descriptions
   </li>
  </ul>
  <p>
   Their implementation handles all the concepts we've covered in this tutorial. It's a great example of how these techniques scale to real-world applications.
  </p>
  <h2>
   Alternative Approach: Using django-meili
  </h2>
  <p>
   If you prefer a more Django-native approach with less custom code, you might want to check out
   <a href="https://github.com/ikollipara/django-meili">
    django-meili
   </a>
   . This package provides a more Haystack-like experience for integrating Meilisearch with Django. I haven't used it, but from what I can tell, these are the main differences between using django-meili and implementing something more like our custom Meilisearch implementation:
  </p>
  <ul>
   <li>
    <strong>
     Model-focused
    </strong>
    : django-meili is designed around indexing individual Django models, which works well if you're searching just one model at a time
   </li>
   <li>
    <strong>
     Less flexible
    </strong>
    : Our unified schema approach gives you more control over how different models are represented in search results
   </li>
   <li>
    <strong>
     Easier setup
    </strong>
    : django-meili requires less boilerplate code to get started
   </li>
   <li>
    <strong>
     Django-native
    </strong>
    : It follows Django conventions more closely with a familiar API
   </li>
  </ul>
  <p>
   django-meili might be a good choice if:
  </p>
  <ul>
   <li>
    You're primarily searching within a single model type
   </li>
   <li>
    You prefer a more Django-integrated approach with less custom code
   </li>
   <li>
    You don't need the flexibility of a unified schema across different model types
   </li>
  </ul>
  <h2>
   Further Reading
  </h2>
  <ul>
   <li>
    <a href="https://www.meilisearch.com/docs">
     Meilisearch Official Documentation
    </a>
   </li>
   <li>
    <a href="https://github.com/meilisearch/meilisearch-python">
     Meilisearch Python Client
    </a>
   </li>
   <li>
    <a href="https://www.meilisearch.com/docs/learn/configuration/settings">
     Meilisearch Settings Reference
    </a>
   </li>
   <li>
    <a href="https://www.meilisearch.com/docs/reference/api/search">
     Meilisearch Search Parameters
    </a>
   </li>
   <li>
    <a href="https://github.com/ikollipara/django-meili">
     django-meili GitHub Repository
    </a>
   </li>
  </ul>
  <p>
   Need help implementing advanced search for your Django project?
   <a href="https://www.revsys.com/contact/">
    Contact our team at Revsys
   </a>
   for expert
   <a href="/services/django/consulting/">
    Django consulting
   </a>
   . We've implemented search solutions for clients ranging from small startups to large enterprises, and we'd be happy to help you build a search experience that delights your users.
  </p>
 </div>
</div>
]]>/></item><item><title>Redis connections growing unbounded</title><link>http://www.revsys.com/tidbits/redis-connections-growing-unbounded/</link><description>gunicorn+gevent and Celery can be tricky. There is a setting that is often missed that keeps your Redis connections from increasing until they reach the maximum.</description><pubDate>Fri, 30 Aug 2024 21:04:31 +0000</pubDate><guid>http://www.revsys.com/tidbits/redis-connections-growing-unbounded/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We use gunicorn and gevent in most of our production deployments.  There are many options in this area, but this is one we've had great success with and at one point was the
   <em>
    fastest
   </em>
   based on some benchmarking we did many years ago.  (We should re-run that, but that's another blog post).
  </p>
  <h2>
   The Problem
  </h2>
  <p>
   The number of active Redis connections keeps growing.  At first glance you would think this would be happening in your Celery workers or even Celery Beat, but it's actually happening in your WSGI (gunicorn) process.
  </p>
  <p>
   While gevent is given you green threads, Celery isn't aware it's running in a thread safe context so when you call a task in your application code it is creating a NEW connection pool for each task.
  </p>
  <p>
   In one client's set up this manifested as the connection count growing by 4-5 connections every time a task was called.  If things are restarted frequently enough, which happens in a lot of our apps with CI/CD, it goes unnoticed. If however your app runs for weeks on end it's pretty easy to reach the Redis default of 10k connections.
  </p>
  <h2>
   The Solution
  </h2>
  <p>
   You just need to tell Celery it is working in a thread safe environment by setting
   <code>
    result_backend_thread_safe = True
   </code>
   .
  </p>
  <p>
   <strong>
    NOTE:
   </strong>
   For Django specifically this means added
   <code>
    CELERY_RESULT_BACKEND_THREAD_SAFE = True
   </code>
   to your
   <code>
    settings.py
   </code>
   .
  </p>
  <p>
   Once this is set it should properly re-use Redis connections and stop being a problem for you.
  </p>
  <p>
   Hope this helps!
  </p>
 </div>
</div>
]]>/></item><item><title>How to upgrade FluxCD</title><link>http://www.revsys.com/tidbits/how-to-upgrade-fluxcd/</link><description>Streamline your FluxCD upgrade in Kubernetes with this concise guide. Learn how a simple re-run of the flux bootstrap command can effortlessly update your FluxCD version, saving you time and hassle.</description><pubDate>Mon, 27 May 2024 15:51:57 +0000</pubDate><guid>http://www.revsys.com/tidbits/how-to-upgrade-fluxcd/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It's Memorial Day here in the USA My family celebrated it together yesterday
so my plan for the day was to "lift and shift" my personal Kubernetes cluster
from one cluster to another MOSTLY because I wanted to upgrade
   <a href="https://fluxcd.io/">
    FluxCD
   </a>
   .
  </p>
  <p>
   I was under the incorrect assumption that doing an
   <em>
    in-place
   </em>
   upgrade of Flux
was difficult, if not impossible, without some severe disruptions to the
services running on that cluster.
  </p>
  <p>
   I was mostly thinking it would be hard to upgrade the various flux manifests from
one CRD version to the next and especially hard to upgrade around the
   <a href="https://github.com/kubernetes/ingress-nginx">
    ingress-nginx
   </a>
   I have installed
WITHOUT it dropping the load balancer and forcing me to repoint all of my DNS.
  </p>
  <p>
   Considering I was prepared to rebuild all of my services from scratch in the
new cluster, I realized this was a perfect time to
   <em>
    attempt
   </em>
   an in-place upgrade
to see what sort of problems do arise. Worst case I wasted a few minutes and
proceeded with my rebuild.
  </p>
  <p>
   Luckily, I was wrong, and it took me all of 3 minutes!
  </p>
  <h1>
   Upgrading Flux
  </h1>
  <p>
   I previously searched around a couple of times trying to find any documentation or
blog posts on how to upgrade Flux. I mostly found older posts related to
upgrading various
   <code>
    v1
   </code>
   situations and this was
   <code>
    v2
   </code>
   .  In a last ditch effort,
I tried a couple of different searches today and finally ran across
   <a href="https://github.com/fluxcd/flux2/discussions/717">
    this Github Discussion
   </a>
   .
  </p>
  <blockquote>
   <p>
    If you installed it using flux bootstrap, upgrading should be as easy as running that again with the right repository flags...
   </p>
  </blockquote>
  <p>
   Wait what? I can just re-run the
   <code>
    flux bootstrap
   </code>
   command with a newer version of
the cli tool and it
   <strong>
    <em>
     just works
    </em>
   </strong>
   ?!?!?!
  </p>
  <p>
   I'm pleased to say it does!
  </p>
  <p>
   All I had to do was re-run the exact same
   <code>
    flux bootstrap ...
   </code>
   command I had used
previously which was:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>flux<span class="w"> </span>bootstrap<span class="w"> </span>github<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--components-extra<span class="o">=</span>image-reflector-controller,image-automation-controller<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--owner<span class="o">=</span>frankwiles<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--repository<span class="o">=</span>personal-ops<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--branch<span class="o">=</span>main<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--path<span class="o">=</span>cluster/<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--read-write-key<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--personal
</code></pre>
  </div>
  <p>
   After it spit out some progress output of what it was doing all of the containers
kicked over and boom, all done.
  </p>
  <h2>
   Before Flux v0.41.2
  </h2>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>flux<span class="w"> </span>version
flux:<span class="w"> </span>v0.41.2
helm-controller:<span class="w"> </span>v0.31.2
image-automation-controller:<span class="w"> </span>v0.31.0
image-reflector-controller:<span class="w"> </span>v0.26.1
kustomize-controller:<span class="w"> </span>v0.35.1
notification-controller:<span class="w"> </span>v0.33.0
source-controller:<span class="w"> </span>v0.36.1
</code></pre>
  </div>
  <p>
   <code>
    v0.41.2
   </code>
   was the last "v2"-ish release prior to them starting the 2.0 release
candidates. So while conceptually "v2", I was actually running a really odd
version to attempt this with.
  </p>
  <h2>
   After Flux Upgrade
  </h2>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>flux<span class="w"> </span>version
flux:<span class="w"> </span>v2.3.0
distribution:<span class="w"> </span>flux-v2.3.0
helm-controller:<span class="w"> </span>v1.0.1
image-automation-controller:<span class="w"> </span>v0.38.0
image-reflector-controller:<span class="w"> </span>v0.32.0
kustomize-controller:<span class="w"> </span>v1.3.0
notification-controller:<span class="w"> </span>v1.3.0
source-controller:<span class="w"> </span>v1.3.0
</code></pre>
  </div>
  <p>
   Quite the shift of version numbers!
  </p>
  <h1>
   Why use FluxCD?
  </h1>
  <p>
   I mentioned how easy this went on social media and that I was going to use the
time saved to write up this blog post.  I got a few questions about why we use
Flux vs
   <a href="https://argoproj.github.io/cd/">
    ArgoCD
   </a>
   or why we use these tools at all.
  </p>
  <p>
   When we first started using Kubernetes with our clients, it was common
to simply give your CI systems admin level k8s credentials and have them
run
   <code>
    kubectl apply
   </code>
   or
   <code>
    helm upgrade
   </code>
   commands to initiate deploys.  This never
set well with us as the security of most CI systems is usually an after thought.
In most cases we deployed our own private CI runners INSIDE our k8s clusters
to avoid the bulk of this attack vector, but it was a pain.
  </p>
  <p>
   We then found a long abandoned project, that I forget the name of, but was
very similar to Flux.  When it was obvious that tool wasn't going to continue we
made the switch.
  </p>
  <p>
   What tools like Flux and Argo do for you is they provide true
   <em>
    gitops
   </em>
   yaml
manifests that you change to affect changes in your Kubernetes clusters.
  </p>
  <p>
   They run services inside your cluster and watch a specific git repository
looking for changes to these manifests.  Let's say we have a repo named
   <code>
    gitops
   </code>
   and a cluster named
   <code>
    bob
   </code>
   , the flow is essentially:
  </p>
  <ul>
   <li>
    Make a change to a YAML manifest in the
    <code>
     gitops
    </code>
    repo, say changing a Docker
image from
    <code>
     v9.0.1
    </code>
    to
    <code>
     v9.0.2
    </code>
    .  Doesn't really matter what the change is, just
that there is some change you want to have happen in your cluster.
   </li>
   <li>
    The repo is configured to fire a webhook that alerts the CD system (Flux or Argo)
that there is a change. This may run inside of
    <code>
     bob
    </code>
    or in the case of ArgoCD can
actually be a centralized Argo service that powers several clusters.
   </li>
   <li>
    The CD containers perform a
    <code>
     git pull
    </code>
    and reconciles the change.
   </li>
   <li>
    Assuming there aren't any errors, like a YAML syntax error or something, it applies
the changes inside of your
    <code>
     bob
    </code>
    cluster.
   </li>
  </ul>
  <p>
   Benefits of CD and
   <em>
    gitops
   </em>
   in a Kubernetes landscape are:
  </p>
  <ul>
   <li>
    No cluster credentials need to exist in your CI systems.  All communication of
the intent of your change happens via a git repo.  While an attacker could still wreck havoc on your cluster by
removing services or adjusting security configurations they would not have direct access to the cluster.
   </li>
   <li>
    Developers can also make changes directly without the need for k8s access or knowledge of
    <code>
     kubectl
    </code>
    and
    <code>
     helm
    </code>
    .
   </li>
   <li>
    Changes can be vetted using Pull Requests.
   </li>
   <li>
    Changes are tracked in the git commit history for posterity.
   </li>
  </ul>
  <h1>
   Lessons Learned
  </h1>
  <p>
   You should always do
   <em>
    one more
   </em>
   search before embarking on a long task that seems
like it should be easier than it first appears.  You might get lucky like I did!
  </p>
  <p>
   Also always write down the exact
   <code>
    flux bootstrap ...
   </code>
   command you used when
bringing up a new cluster.  Personally, I like to document this in the
   <code>
    README.md
   </code>
   at
the root of the gitops repository I'm using for that cluster.
  </p>
  <p>
   Hopefully this helps other people confused by how to easily upgrade FluxCD.  If your
company struggles with Kubernetes or gitops you might reach out as we help many of
our clients with
   <a href="/services/operations/">
    Kubernetes infrastructure and operations
   </a>
   needs including being your entire ops staff if that makes sense.
  </p>
 </div>
</div>
]]>/></item><item><title>"En-Rich" your Python testing</title><link>http://www.revsys.com/tidbits/en-rich-your-python-testing/</link><description>Test output is often an afterthought. You can improve your flow and slightly gamify your bug hunting by combining the wonderful Rich Python library and pytest.</description><pubDate>Sat, 03 Feb 2024 16:49:11 +0000</pubDate><guid>http://www.revsys.com/tidbits/en-rich-your-python-testing/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Debugging complex code is not usually fun
  </h2>
  <p>
   It's hard to get motivated when faced with a big pile of numbers that just aren't lining up in the ways they are supposed to. Stupid numbers, do what you're told!
  </p>
  <h2>
   The task at hand
  </h2>
  <p>
   I've been rebuilding a 15 to 20-year-old ColdFusion currency trading learning simulation for a client.  The math involved is mostly contained in SQL Server stored procedures, some values in tables are really constants (or at least most of the time), and variable names between steps of the process aren't consistent. This has made the rebuilding effort harder than it originally appeared. Add in finding some undesired behaviors in the old app and the desire to improve these situations going forward has been a bit of a slog actually.
  </p>
  <p>
   I knew that today was the day I had to just dig in and iterate on this code until all of the numbers were correct now that I had a known good set of values to work against.
  </p>
  <p>
   Since I needed to compare several numbers together, I thought I might use a
   <a href="https://rich.readthedocs.io/en/stable/tables.html">
    Rich Table
   </a>
   to make the output a little better organized.  And I'm VERY glad I did.
  </p>
  <p>
   Here is what I started with:
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="aiokq">
 </p>
 <img alt="Initial boring pytest output" class="richtext-image full-width" height="742" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/CleanShot_2024-02-03_at_08.18.27.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=ebec1e335c299a59592927864a86b269f3fc18aee3719af35979d217d95b0822" width="800"/>
 <p data-block-key="1mjfv">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Not exactly pretty. It spit out the numbers it was calculating and I could compare them against the known good values visually. Looking and re-re-reading the numbers a half dozen times I was already bored and feeling tired despite having too much caffeine in my system.
  </p>
  <p>
   So I implemented all of the expected, calculated and differences between the two in a Rich Table. I took a couple of moments to adjust some colors and right justifying all of these numbers because I knew I was going to be staring at this
   <strong>
    all freaking day
   </strong>
   .
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="aiokq">
 </p>
 <img alt="Initial Python Rich table formatting" class="richtext-image full-width" height="475" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/CleanShot_2024-02-03_at_09.15.54.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=b50b5aeb3cf20ec61a39abe99120981f8120a10ed113b7cadedde59cf0b7689c" width="800"/>
 <p data-block-key="967ut">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   The debugging
  </h2>
  <p>
   I then went about debugging the first
   <em>
    Total Unhedged
   </em>
   number because all of the following calculations depended on this number being correct. And I quickly found the bug!
  </p>
  <p>
   I would love to say it was because of this great new visualization, but it was just closely re-reading my Python code and comparing it (
   <em>
    yet again
   </em>
   ) to the original portion of a stored procedure to find my subtle mistake.
  </p>
  <p>
   One down, 6 more to go I thought.  This is going to be a long day, but it turned out two more of the values were
   <em>
    NEARLY
   </em>
   correct now, just off due to rounding.  This
   <strong>
    WAS
   </strong>
   easier to spot because of the tabular layout.  I treated myself to a bit more table improvement, I wrote a little function named
   <code>
    calc_diff()
   </code>
   that took the expected and actual values and color coded the difference red if there was a difference and green if not.
  </p>
 </div>
</div>
<div class="block-content">
 <p data-block-key="aiokq">
 </p>
 <img alt="Improved table with red/green color coding" class="richtext-image full-width" height="341" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/CleanShot_2024-02-03_at_09.36.37.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180958Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=c71e43d2afa3a8b03e476ed6225dfa205672cd540ebd606a7026facb0bb9480a" width="800"/>
 <p data-block-key="91ha3">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Table code example
  </h2>
  <p>
   The code for this is surprisingly easy.
   <a href="https://github.com/Textualize/rich">
    Rich
   </a>
   was already a dependency of something else in my project's virtualenv so I didn't even need to install it.  Here is a simplified example of the code for you:
  </p>
 </div>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">decimal</span><span class="w"> </span><span class="kn">import</span> <span class="n">Decimal</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">rich.console</span><span class="w"> </span><span class="kn">import</span> <span class="n">Console</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rich.table</span><span class="w"> </span><span class="kn">import</span> <span class="n">Table</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rich.text</span><span class="w"> </span><span class="kn">import</span> <span class="n">Text</span> 

<span class="k">def</span><span class="w"> </span><span class="nf">calc_diff</span><span class="p">(</span><span class="n">expected</span><span class="p">,</span> <span class="n">actual</span><span class="p">):</span>
        <span class="n">diff</span> <span class="o">=</span> <span class="n">expected</span> <span class="o">-</span> <span class="n">actual</span>

        <span class="n">color</span> <span class="o">=</span> <span class="s2">"red"</span>
        <span class="k">if</span> <span class="n">diff</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">color</span> <span class="o">=</span> <span class="s2">"green"</span>

        <span class="n">text</span> <span class="o">=</span> <span class="n">Text</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">diff</span><span class="p">))</span>
        <span class="n">text</span><span class="o">.</span><span class="n">stylize</span><span class="p">(</span><span class="n">color</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">text</span>

<span class="c1"># My actual pytest test</span>
<span class="k">def</span><span class="w"> </span><span class="nf">test_ex3_math</span><span class="p">(</span><span class="n">ex3_params</span><span class="p">):</span>
    <span class="n">expected_total_unhedged</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="s2">"3.394148"</span><span class="p">)</span>

    <span class="c1"># call a method on a fixture class that does the math calculation for us, yours</span>
   <span class="c1"># would obviously be different </span>
    <span class="n">total_unhedged</span> <span class="o">=</span> <span class="n">ex3_params</span><span class="o">.</span><span class="n">total_unhedged</span><span class="p">()</span>

    <span class="c1"># Build our table</span>
    <span class="n">table</span> <span class="o">=</span> <span class="n">Table</span><span class="p">(</span>
        <span class="n">title</span><span class="o">=</span><span class="s2">"Exercise 3 Answers"</span><span class="p">,</span> <span class="n">title_style</span><span class="o">=</span><span class="s2">"gold1"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"deep_sky_blue3"</span>
    <span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span>
        <span class="s2">"Name"</span><span class="p">,</span> <span class="n">style</span><span class="o">=</span><span class="s2">"turquoise2"</span><span class="p">,</span> <span class="n">header_style</span><span class="o">=</span><span class="s2">"turquoise2"</span><span class="p">,</span> <span class="n">no_wrap</span><span class="o">=</span><span class="kc">True</span>
    <span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Expected"</span><span class="p">,</span> <span class="n">header_style</span><span class="o">=</span><span class="s2">"turquoise2"</span><span class="p">,</span> <span class="n">justify</span><span class="o">=</span><span class="s2">"right"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Actual"</span><span class="p">,</span> <span class="n">header_style</span><span class="o">=</span><span class="s2">"turquoise2"</span><span class="p">,</span> <span class="n">justify</span><span class="o">=</span><span class="s2">"right"</span><span class="p">)</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_column</span><span class="p">(</span><span class="s2">"Difference"</span><span class="p">,</span> <span class="n">header_style</span><span class="o">=</span><span class="s2">"turquoise2"</span><span class="p">,</span> <span class="n">justify</span><span class="o">=</span><span class="s2">"right"</span><span class="p">)</span>

    <span class="c1"># Total Unhedged - just showing you one row here for sake of brevity</span>
    <span class="n">table</span><span class="o">.</span><span class="n">add_row</span><span class="p">(</span>
        <span class="s2">"Total Unhedged"</span><span class="p">,</span>
        <span class="nb">str</span><span class="p">(</span><span class="n">expected_total_unhedged</span><span class="p">),</span>
        <span class="nb">str</span><span class="p">(</span><span class="n">total_unhedged</span><span class="p">),</span>
        <span class="n">calc_diff</span><span class="p">(</span><span class="n">expected_total_unhedged</span><span class="p">,</span> <span class="n">total_unhedged</span><span class="p">),</span>
    <span class="p">)</span>

   <span class="c1"># Output the table </span>
   <span class="n">console</span> <span class="o">=</span> <span class="n">Console</span><span class="p">(</span><span class="n">color_system</span><span class="o">=</span><span class="s2">"256"</span><span class="p">)</span>
   <span class="n">console</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="n">table</span><span class="p">)</span>

   <span class="c1"># Trigger our test to fail artificially </span>
   <span class="k">assert</span> <span class="kc">False</span>
</pre>
 </div>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Conclusion
  </h2>
  <p>
   Was it worth the extra time?
   <strong>
    DEFINITELY!
   </strong>
   I not only made it easier to compare the values as I worked but also motivated me a little more than usual as I turned each red line green.
  </p>
  <p>
   Every little bit helps, right? Hopefully, you enjoyed this protip and can put it use in one of your upcoming debugging sessions.
  </p>
  <p>
   If you or your company struggles with testing your Python or Django apps, check out
   <a href="/teststart/">
    TestStart
   </a>
   our testing consulting package to help jump start your team into better productivity.
  </p>
 </div>
</div>
]]>/></item><item><title>pytest fixtures are magic!</title><link>http://www.revsys.com/tidbits/pytest-fixtures-are-magic/</link><description>Fixtures are building blocks for good tests and can increase development speed. The main issue with writing tests is setting up necessary data before the test, but pytest fixtures make it easier by injecting necessary data into your tests.</description><pubDate>Tue, 13 Jun 2023 12:56:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/pytest-fixtures-are-magic/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I usually hate magic in code, but in this case, the magic is fantastic!
  </p>
  <p>
   I must admit I used
   <a href="https://pytest.org">
    pytest
   </a>
   to
   <em>
    run
   </em>
   my tests for
an embarrassingly long time before I started writing and using pytest style
tests.  I'm sure it was a mixture of laziness and not understanding the magic
behind pytest fixtures.
  </p>
  <p>
   Because I didn't understand the magic happening, it scared me away from looking
at pytest more deeply. Once I did, I wished someone had held me down and forced
the explanation down my throat.
  </p>
  <p>
   Fixtures are the building blocks of writing good tests. Writing great tests is
easy if you have great fixtures, and your development velocity will skyrocket.
  </p>
  <h2>
   The main problem with writing tests
  </h2>
  <p>
   One of the problems with writing automated tests is setting the stage for the
test.  Generating the specific data necessary to exist before we execute the thing
we want to test.
  </p>
  <p>
   It is tedious work, but it does not have to be!
  </p>
  <p>
   Why is this such a problem?  In many systems, it is cumbersome to generate all
of the branches of a tree, its roots, the ground, and the sky to verify the
leaves turn out to be green in spring.
  </p>
  <p>
   As an example, if you worked at Github and were tasked with adding a feature
that replaces curse words in issue comments on public repositories, you have to:
  </p>
  <ul>
   <li>
    create a user because Organizations need an owner
   </li>
   <li>
    create an Organization to own these repositories
   </li>
   <li>
    create a public repo to test the main functionality
   </li>
   <li>
    create a private repo to ensure it doesn't touch those comments
   </li>
   <li>
    create an issue
   </li>
  </ul>
  <p>
   And
   <strong>
    THEN
   </strong>
   , you can create comments with and without curse words to exercise
the feature you're working on. That's a lot of crap to make before you test.
Is it any surprise people get lazy and opt to not test the feature end to end?
  </p>
  <p>
   The answer is not to avoid the test but to make it far easier to generate
that data in the first place.
  </p>
  <p>
   The magic of pytest fixtures is how they are injected into your tests for
you to use and their composability. When done well, writing tests is
considerably easier and actually fun.
  </p>
  <h2>
   The Magic
  </h2>
  <p>
   pytest fixtures are injected into your test by including the name of the fixture
function into the argument list of your test.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">seuss.characters</span><span class="w"> </span><span class="kn">import</span> <span class="n">ThingOne</span><span class="p">,</span> <span class="n">ThingTwo</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">seuss.comparisons</span><span class="w"> </span><span class="kn">import</span> <span class="n">are_twins</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">thing_one</span><span class="p">():</span>
    <span class="n">t1</span> <span class="o">=</span> <span class="n">ThingOne</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">t1</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">thing_two</span><span class="p">():</span>
    <span class="n">t2</span> <span class="o">=</span> <span class="n">ThingTwo</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">t2</span>

<span class="k">def</span><span class="w"> </span><span class="nf">test_book</span><span class="p">(</span><span class="n">thing_one</span><span class="p">,</span> <span class="n">thing_two</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">are_twins</span><span class="p">(</span><span class="n">thing_one</span><span class="p">,</span> <span class="n">thing_two</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   (This is a reference to Dr. Seuss' book
   <a href="https://amzn.to/3X2qcw7">
    The Cat In The Hat
   </a>
   if you aren't familiar with it.)
  </p>
  <p>
   What happens here is pytest walks your code base looking for all of your tests.
This is called
   <a href="https://docs.pytest.org/en/7.1.x/explanation/goodpractices.html#conventions-for-python-test-discovery">
    test discovery
   </a>
   .  During that process it inspects the list of arguments
to your test functions and matches those to fixtures.
  </p>
  <p>
   It then organizes the test run to minimize the time of all fixture
creation based on the dependency tree.  The author of the fixture can control the scope/lifecycle of the
fixture data.  You can read up more on
   <a href="https://betterprogramming.pub/understand-5-scopes-of-pytest-fixtures-1b607b5c19ed">
    pytest fixture scopes
   </a>
   in this excellent explanation.
  </p>
  <p>
   So, now we know that if we create a fixture named
   <code>
    bob
   </code>
   that just adding
   <code>
    bob
   </code>
   to our list
of arguments to our test, we will get that fixture's data. That makes
sharing fixture data around hundreds of tests easy.
  </p>
  <h2>
   The composability of fixtures
  </h2>
  <p>
   It's important to realize that fixtures can build on top of each other.
  </p>
  <p>
   Let's go back to our Github naughty issue comment example.  I would structure
things so that I had the following fixtures:
  </p>
  <ul>
   <li>
    <code>
     owner
    </code>
   </li>
   <li>
    <code>
     public_repo
    </code>
   </li>
   <li>
    <code>
     private_repo
    </code>
   </li>
   <li>
    <code>
     public_issue
    </code>
   </li>
   <li>
    <code>
     private_issue
    </code>
   </li>
  </ul>
  <p>
   It is likely, as we were building our registration process perhaps, we would have
a fixture that was an already registered user, one who would be able to act
as the repository owner. Let&rsquo;s assume we have a function named
   <code>
    make_repo
   </code>
   that handles making a repository for us. But since every repo needs an owner, we create an
   <code>
    owner
   </code>
   fixture. We can now use the owner fixture as an argument to make_repo() when we create our repo fixtures. So for the next two fixtures, we would just use
that fixture as an argument, and it gets pulled in automatically:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">owner</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">make_registred_user</span><span class="p">()</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">public_repo</span><span class="p">(</span><span class="n">owner</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">make_repo</span><span class="p">(</span><span class="n">owner</span><span class="o">=</span><span class="n">owner</span><span class="p">,</span> <span class="n">visibility</span><span class="o">=</span><span class="s2">"public"</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">private_repo</span><span class="p">(</span><span class="n">owner</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">make_repo</span><span class="p">(</span><span class="n">owner</span><span class="o">=</span><span class="n">owner</span><span class="p">,</span> <span class="n">visibility</span><span class="o">=</span><span class="s2">"private"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   We can now quickly get a public and/or a private repo for all
future tests we write. Keep in mind these fixtures are available across
   <strong>
    ALL
   </strong>
   of the tests in our project. They are injected across Python namespaces and
source files.  This removes the hassle of having to import dozens of
things at the top of your test files.
  </p>
  <p>
   However, that also means you should take a moment and ensure your fixture's name
is a good one that represents it well and is easy to remember, type, and not
misspell. Ok, let's get back to work.
  </p>
  <p>
   Our task is to ensure curse words are rewritten in public issue comments.
These fixtures help but only take us halfway to where we want to be.  We can
solve this with even more fixtures!
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">issues.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_issue</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">public_issue</span><span class="p">(</span><span class="n">public_repo</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">create_issue</span><span class="p">(</span><span class="n">repo</span><span class="o">=</span><span class="n">public_repo</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">"Something is broken"</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">private_issue</span><span class="p">(</span><span class="n">private_repo</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">create_issue</span><span class="p">(</span><span class="n">repo</span><span class="o">=</span><span class="n">private_repo</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">"Something is broken in private"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Now we have the two bits of data (and the tree of data that comes with them) all
created so we can actually test our task.  It might look something like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">test_naughty_comment_public</span><span class="p">(</span><span class="n">public_issue</span><span class="p">):</span>
    <span class="n">comment</span> <span class="o">=</span> <span class="n">public_issue</span><span class="o">.</span><span class="n">new_comment</span><span class="p">(</span><span class="s2">"Frak off butthole"</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">comment</span><span class="o">.</span><span class="n">body</span> <span class="o">===</span> <span class="s2">"F*** off b**hole"</span>

<span class="k">def</span><span class="w"> </span><span class="nf">test_naughty_comment_private</span><span class="p">(</span><span class="n">private_issue</span><span class="p">):</span>
    <span class="n">comment</span> <span class="o">=</span> <span class="n">private_issue</span><span class="o">.</span><span class="n">new_comment</span><span class="p">(</span><span class="s2">"Frak off butthole"</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">comment</span><span class="o">.</span><span class="n">body</span> <span class="o">===</span> <span class="s2">"Frak off butthole"</span>
</code></pre>
  </div>
  <h2>
   Things you can return
  </h2>
  <p>
   Hopefully, it is evident by now that you can return all sorts of things from
fixtures.  Here are some ideas that might get your brain seeing how you can
use these in your projects.
  </p>
  <h3>
   Simple static data
  </h3>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">password</span><span class="p">():</span>
    <span class="c1"># We can return simple strings</span>
    <span class="k">return</span> <span class="s2">"top-secret"</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">pi</span><span class="p">():</span>
    <span class="c1"># We can return special/constant values</span>
    <span class="k">return</span> <span class="mf">3.14159</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">default_preferences</span><span class="p">():</span>
    <span class="c1"># We can return structures of things that are commonly needed</span>
    <span class="k">return</span> <span class="p">{</span>
        <span class="s2">"default_repo_visibility"</span><span class="p">:</span> <span class="s2">"public"</span><span class="p">,</span>
        <span class="s2">"require_2fa"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
        <span class="s2">"naughty_comments_allowed"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
    <span class="p">}</span>
</code></pre>
  </div>
  <h3>
   Instantiated objects
  </h3>
  <p>
   Often we need access to an object that is either complex to instantiate or
expensive in terms of processing time.  In those cases, we can turn that
instantiation itself into a fixture and reuse it across tests more easily.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">core.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Organization</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">big_org</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">Organization</span><span class="p">(</span><span class="n">members</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">small_org</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">Organization</span><span class="p">(</span><span class="n">members</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
</code></pre>
  </div>
  <h3>
   Callables to make other things
  </h3>
  <p>
   You can have fixtures that return functions or other callables to make it easier
to inject them into your tests:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">core.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">make_new_user</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">make_user</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">make_new_user</span>

<span class="k">def</span><span class="w"> </span><span class="nf">test_user_creation</span><span class="p">(</span><span class="n">make_user</span><span class="p">):</span>
    <span class="n">new_user</span> <span class="o">=</span> <span class="n">make_user</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s1">'frank'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">new_user</span><span class="o">.</span><span class="n">is_active</span>
    <span class="k">assert</span> <span class="n">new_user</span><span class="o">.</span><span class="n">username</span> <span class="o">==</span> <span class="s1">'frank'</span>
</code></pre>
  </div>
  <p>
   Or you can use it to curry these functions to make them easier to use:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">core.utils</span><span class="w"> </span><span class="kn">import</span> <span class="n">make_complicated_user</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">make_admin</span><span class="p">():</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">inner_function</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">email</span><span class="p">,</span> <span class="n">password</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">make_complicated_user</span><span class="p">(</span>
            <span class="n">username</span><span class="o">=</span><span class="n">username</span><span class="p">,</span>
            <span class="n">email</span><span class="o">=</span><span class="n">email</span><span class="p">,</span>
            <span class="n">password</span><span class="o">=</span><span class="n">password</span><span class="p">,</span>
            <span class="n">is_admin</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
            <span class="n">can_create_public_repos</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
            <span class="n">can_create_private_repos</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
        <span class="p">)</span>
    <span class="k">return</span> <span class="n">inner_function</span>

<span class="k">def</span><span class="w"> </span><span class="nf">test_admin_creation</span><span class="p">(</span><span class="n">make_admin</span><span class="p">):</span>
    <span class="n">admin</span> <span class="o">=</span> <span class="n">make_admin</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s1">'frank'</span><span class="p">,</span> <span class="n">email</span><span class="o">=</span><span class="s1">'frank@revsys.com'</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'django'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">admin</span><span class="o">.</span><span class="n">username</span> <span class="o">==</span> <span class="s1">'frank'</span>
    <span class="k">assert</span> <span class="n">admin</span><span class="o">.</span><span class="n">is_admin</span> <span class="o">==</span> <span class="kc">True</span>
</code></pre>
  </div>
  <h2>
   Organizing Your Fixtures
  </h2>
  <p>
   Here at REVSYS, we primarily work with Django projects, so our fixtures
tend to live with the Django app with the models used for that fixture.
It doesn't have to be this way, of course, it just makes sense in the context
of a Django project.
  </p>
  <p>
   There is nothing keeping you from defining fixtures all over the place in
your tests. It just makes it more challenging to know where to look for them.
  </p>
  <p>
   If a fixture isn't going to be reused outside of a single test file, then it's
absolutely appropriate to keep it in that file. Any tests used across a project
should be in some central or semi-central location.
  </p>
  <p>
   pytest helps you with this as well, of course.  pytest looks for a file named
   <code>
    conftest.py
   </code>
   in the directory where you're running
   <code>
    pytest
   </code>
   (and sub-directories FYI)
and that allows you to define where to look for fixtures that live outside of the test files
themselves.
  </p>
  <p>
   Here is a typical project structure here at REVSYS:
  </p>
  <div class="codehilite">
   <pre><span></span><code>project-repo/
    config/
        settings.py
        urls.py
        wsgi.py
    users/
        models.py
        views.py
        tests/
            fixtures.py
            test_models.py
            test_views.py
    orgs/
        models.py
        views.py
        tests/
            fixtures.py
            test_models.py
            test_views.py
</code></pre>
  </div>
  <p>
   We put all of the fixtures we intend to share in the project into the
   <code>
    tests/fixtures.py
   </code>
   files.  We then define
   <code>
    project-repo/conftest.py
   </code>
   like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">users.tests.fixtures</span><span class="w"> </span><span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">orgs.tests.fixtures</span><span class="w"> </span><span class="kn">import</span> <span class="o">*</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">globalthing</span><span class="p">():</span>
    <span class="k">return</span> <span class="s2">"some-global-project-data-here"</span>
</code></pre>
  </div>
  <p>
   As you can see, we also are able to define any project wide or global fixtures
directly in the
   <code>
    conftest.py
   </code>
   file if it's more appropriate for something to live
there and not in a particular Django app directory.
  </p>
  <h2>
   Plugin Fixtures
  </h2>
  <p>
   pytest plugins can also create and provide fixtures to you.  For Django users,
   <a href="https://pytest-django.readthedocs.io/en/latest/">
    pytest-django
   </a>
   provides
several valuable fixtures:
  </p>
  <ul>
   <li>
    <code>
     db
    </code>
    which automatically marks a test as needing the database to be configured
   </li>
   <li>
    <code>
     client
    </code>
    provides an instance of
    <code>
     django.test.Client
    </code>
   </li>
   <li>
    <code>
     admin_user
    </code>
    returns an automatically created superuser
   </li>
   <li>
    <code>
     admin_client
    </code>
    returns an instance of the test client, already logged in with the
    <code>
     admin_user
    </code>
   </li>
   <li>
    <code>
     settings
    </code>
    gives you
    <code>
     django.conf.settings
    </code>
   </li>
  </ul>
  <p>
   To show you another example, our library
   <a href="https://github.com/revsys/django-test-plus">
    django-test-plus
   </a>
   provides itself as a pytest fixture using the name
   <code>
    tp
   </code>
   .  This allows you to take
advantage of the test helping functions it provides more easily in pytest style
tests.  Here is a quick example:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">test_named_view</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="n">admin_user</span><span class="p">):</span>
    <span class="c1"># GET the view named 'my-named-view' handling the reverse for you</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">tp</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'my-named-view'</span><span class="p">)</span>
    <span class="c1"># Ensure the response is a 401 because we need to be logged in</span>
    <span class="n">tp</span><span class="o">.</span><span class="n">response_401</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>

    <span class="c1"># Login as the admin_user and ensure a 200</span>
    <span class="k">with</span> <span class="n">tp</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">admin_user</span><span class="p">):</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">tp</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'my-named-view'</span><span class="p">)</span>
        <span class="n">tp</span><span class="o">.</span><span class="n">response_200</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This is accomplished with
   <a href="https://github.com/revsys/django-test-plus/blob/main/setup.cfg#L46-L48">
    just a couple lines of config
   </a>
   in our library and then
   <a href="https://github.com/revsys/django-test-plus/blob/main/test_plus/plugin.py">
    the fixtures themselves
   </a>
   . That means you can also provide
fixtures in internal libraries that are pip installed and not confine yourself
to only having fixtures in single repository situations.
  </p>
  <h2>
   Automatically using pytest fixtures everywhere
  </h2>
  <p>
   Everything we've shown so far is nice, but you may face a code base
that needs
   <strong>
    TONS
   </strong>
   of fixtures most everywhere.  Or even just a handful of
fixtures that are needed across most of the code base.  No worries,
   <code>
    pytest
   </code>
   has
you covered.
  </p>
  <p>
   You can define a fixture to automatically be run for all of your tests
without specifying it as an argument to each test individually.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">global_thing</span><span class="p">():</span>
    <span class="c1"># Create something in our database we need everywhere or always</span>
    <span class="n">Config</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">something</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>

<span class="k">def</span><span class="w"> </span><span class="nf">test_something_lame</span><span class="p">():</span>
    <span class="k">assert</span> <span class="n">Config</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">something</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span>
</code></pre>
  </div>
  <h2>
   Built-in Fixtures
  </h2>
  <p>
   pytest comes with a bunch of built-in fixtures as well.  I admit I didn't even
know about some of these for a long time and they can come in very handy.
  </p>
  <p>
   Some examples include:
  </p>
  <ul>
   <li>
    <code>
     tmpdir
    </code>
    returns a temporary directory path object that is unique to each test
   </li>
   <li>
    <code>
     tmp_path
    </code>
    gives you a temporary directory as a
    <a href="https://docs.python.org/3/library/pathlib.html#basic-use">
     Path
    </a>
    object
   </li>
   <li>
    <code>
     capsys
    </code>
    fixture for capturing stdout and stderr
   </li>
   <li>
    <code>
     caplog
    </code>
    fixture that captures logging output
   </li>
   <li>
    <code>
     monkeypatch
    </code>
    quickly and easily monkeypatch objects, dicts, etc.
   </li>
   <li>
    <code>
     cache
    </code>
    a cache object that can persist state between testing sessions
   </li>
  </ul>
  <h2>
   Further Learning
  </h2>
  <p>
   Learn more details in the
   <a href="https://docs.pytest.org/en/7.3.x/how-to/fixtures.html">
    pytest fixture docs
   </a>
   or look over the
   <a href="https://docs.pytest.org/en/7.3.x/builtin.html">
    full list of built-in fixtures
   </a>
   .
  </p>
  <h2>
   Conclusion
  </h2>
  <p>
   The more of our codebase that is covered by meaningful tests the faster we can
develop new features and refactor cruft.  It is not just about bug-free code.
It's about the speed at which you can develop good code.
  </p>
  <p>
   The easier we make it to write tests, the faster we can deliver great software.
  </p>
  <p>
   P.S.  If you or your team aren't writing tests or just struggling with them,
we can help get you there with our
   <a href="https://www.revsys.com/teststart/">
    TestStart
   </a>
   product.
One of our experts will swoop in, get you set up, and write a bunch of your first tests
for you to show you how it's done in your own code base!
  </p>
 </div>
</div>
]]>/></item><item><title>REVSYS is 16 Years Old Today!</title><link>http://www.revsys.com/tidbits/revsys-is-16-years-old-today/</link><description>Find out the details of our origin story along with examples of some of the more interesting projects we&amp;#x27;ve tackled in our history.</description><pubDate>Fri, 05 May 2023 14:17:19 +0000</pubDate><guid>http://www.revsys.com/tidbits/revsys-is-16-years-old-today/</guid><content:encoded<![CDATA[<div class="block-content">
 <p data-block-key="fxz11">
 </p>
 <img alt="Picture of Kid&amp;#x27;s Birthday" class="richtext-image full-width" height="450" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/jorge-ibanez-cK6ixOQx1fI-unsplash.width-800.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180959Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=b79507e3f2594ea1e33a65868eed61920304425e2092e029929a87bb2b928a2d" width="800"/>
 <p data-block-key="1pcqj">
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   REVSYS is 16 today
  </h2>
  <p>
   Well, technically, it's 21 years old, but it got held back 5 years.  I was still
employed at The World Company / Sunflower Broadband, the company that was the
2nd cable model ISP in the US and ultimately released the Django web framework.
I did some consulting on the side and a few clients needed me to be a "company"
and not just "some guy".
  </p>
  <p>
   It wasn't super expensive to register or cumbersome at tax time, so I started working
to find a name.  I grabbed some ice cream with the then marketing director
of Linux Professional Institute to help me come up with a decent name.  I had
recently watched the documentary
   <a href="https://en.wikipedia.org/wiki/Revolution_OS">
    RevolutionOS
   </a>
   and floated using
   <em>
    revolution
   </em>
   in the name somehow.  We settled on "Revolution Systems", which
we eventually shortened to REVSYS for branding purposes.
  </p>
  <p>
   So it was registered in 2002, but I left my corporate job to focus on it full time on May 5th, 2007.
I put in my notice and it just so happened to line up with Cinco De Mayo so the customary after
work drinks leaned toward tequila.
  </p>
  <h2>
   The early days
  </h2>
  <p>
   I wouldn't recommend starting a business just before a major recession.  The
Great Recession hit in December 2007 just a few months later and I barely
survived it.  However, once the economy started to pick back up after the
initial shock wore off it was a feast of work as everyone had some budget,
work to do, but were still in a hiring freeze so contracting was their only
option.
  </p>
  <p>
   After maybe a year,
   <a href="https://jacobian.org/">
    Jacob Kaplan-Moss
   </a>
   also had left the
World Company and wondered if I wanted to share an office.  I had been working
from home and coffee shops and didn't
   <em>
    really
   </em>
   feel like I needed or wanted an
office but it was a reasonable price so we did it.
  </p>
  <p>
   Soon after that Jacob joined me in REVSYS as a partner and I switched our focus
from Perl/PostgreSQL/Ops consulting to Python/Django/PostgreSQL/Ops consulting.
  </p>
  <p>
   Funny story about how that happened.
   <a href="https://floss.social/@rikkiends">
    Rikki Endsley
   </a>
   was my editor at SysAdmin and LinuxPro magazines at the time and she asked me to
write up an interview with Jacob about the creation of the
   <a href="https://www.djangoproject.com/foundation/">
    Django Software Foundation
   </a>
   and a brief tutorial on Django.
  </p>
  <p>
   So I had to learn Python and Django for the article and I quickly realized it
was
   <em>
    soooooo
   </em>
   much better than the tools I was using in the Perl world (some of
which I had personally written hahaha) that I essentially immediately switched.
  </p>
  <p>
   Thanks Rikki!!!
  </p>
  <p>
   We operated just the two of us for a while and eventually hired
   <a href="https://jefftriplett.com/about/">
    Jeff Triplett
   </a>
   as Employee #1.  We made Jeff a partner in REVSYS in 2019.
  </p>
  <p>
   We followed up that smart hiring decision with LOTS of smart hiring decisions
to the team that
   <a href="https://www.revsys.com/about/">
    you can find here
   </a>
   .
  </p>
  <h2>
   Our work
  </h2>
  <p>
   While at a PyCon dinner, a long time friend asked if we "still primarily do rescue projects".
I hadn't thought about it much, but we do actually take on a LOT of rescue projects.
  </p>
  <p>
   Rescuing a project with bad performance, stability, or both.
  </p>
  <p>
   Rescuing a project from the clutches of a team of ineffective programmers.
  </p>
  <p>
   Rescuing a project's deadline by bringing a lot of senior developer velocity.
  </p>
  <p>
   Rescuing projects isn't all we do, there are plenty of projects that are greenfield
or even legacy projects that are in need of some help but don't really qualify as a
"rescue" in my mind.
  </p>
  <p>
   Some examples of our more interesting previous projects:
  </p>
  <ul>
   <li>
    Sony corporate homepage for a few years
   </li>
   <li>
    Keep CashStar from crumbling on Cyber Mondays
   </li>
   <li>
    Optimizing tasks into the queues of expensive proprietary software systems to allow the client to maximize their use of the expensive product (Cirrus Logic, Boston University, etc)
   </li>
   <li>
    Helping set the architecture of eMoney's foray into cloud based Django microservices and doing quite a bit of the ops and development
   </li>
   <li>
    Game for Netflix's German speaking market that was played by over 750k people over a weekend
   </li>
   <li>
    Help RueLaLa with a high-performance inventory caching expiration issue
   </li>
   <li>
    Integrating several POS systems into a cohesive whole for beer gardens with multiple vendors for a company named Porter
   </li>
   <li>
    Building and refreshing
    <a href="https://www.python.org">
     Python.org
    </a>
    itself for the PSF
   </li>
   <li>
    Hosted
    <a href="https://djangobirthday.com/">
     DjangoBirthday
    </a>
    when Django turned 10 in Lawrence
   </li>
   <li>
    Lots of Django upgrades for clients.  Some relatively easy.  Some insane.  (Don't hack up the ORM internals ok)
   </li>
   <li>
    Tons of Python/Django/Kubernetes performance-related projects
   </li>
  </ul>
  <p>
   Some of our current projects include:
  </p>
  <ul>
   <li>
    Politifact.com (the Liar Liar Pants on Fire people)
   </li>
   <li>
    Custom system for an event promotion company to track concert events they promote. Which is far more complicated than you may realize.
   </li>
   <li>
    Oil pumpjack inspection and repair tracking software
   </li>
   <li>
    Rewriting a popular Open Source library project website
   </li>
   <li>
    An interesting EdTech product that smartly uses ChatGPT in a way that truly helps students learn
   </li>
   <li>
    Business simulations at The Wharton School
   </li>
   <li>
    Helping to optimize 2TBs of dairy cow data in a heavily used mission critical app (if you're into milk and cheese in North American anyway).  It's an older legacy code base that we've been helping modernize and optimize to improve it's resiliency and reduce infrastructure costs.
   </li>
  </ul>
  <p>
   We also help a lot of companies improve their DX (Developer Experience) to be able to achieve vastly more efficient development
by identifying and removing all of the small barriers that get in the way of your developer's day.  Sometimes it's code or CI
related, often it's people and process related.
  </p>
  <p>
   Our experience as consultants allows us to have a razor sharp wide angle lens of what will and won't work for the current
team and the project at hand.  Each situation is a bit different, but the results we achieve on those sorts of projects are
often surprising to everyone involved!
  </p>
  <p>
   We have some availability coming up over the summer so if your company needs help
   <a href="https://www.revsys.com/contact/">
    reach out to us on the website
   </a>
   or
   <a href="mailto:sales@revsys.com">
    email us
   </a>
  </p>
  <h2>
   New Consulting Products
  </h2>
  <p>
   Are you still reading this? Wow you must really like us! While I have you then...
  </p>
  <p>
   If you're interested we are working on a couple new consulting product packages.
Specifically:
  </p>
  <h3>
   Testing Sprint
  </h3>
  <p>
   1 or 2 week Python/Django testing consulting and development.  If you currently
have some tests, we'll super charge them and lend our experience in making them
easier to write and faster to run.
  </p>
  <p>
   If you aren't currently testing your code programatically, we'll get you set up
and going.
  </p>
  <h3>
   Architecture Sprint
  </h3>
  <p>
   One of the best value-for-money aspects of working with us is to have us design
your high-level architecture (tech choices, database schema design, etc.) in a
way that does not force you to build for the scale you won't have in the first few
years but MAY need down the road and make those changes easier when the time
comes.
  </p>
  <p>
   An architecture sprint can easily save your team several developer MONTHS of
going down bad roads with good intentions.
  </p>
  <h3>
   Documentation Sprint
  </h3>
  <p>
   Have one of our amazing writers (who are also senior devs) help put in place a
good documentation set up for your project to accelerate your internal development
or ease customers using your products.
  </p>
  <p>
   If you have questions or an idea for something you
   <em>
    think
   </em>
   we might be able to
help you with reach out, it's quite possible we can help just haven't thought
to advertise it as a service we provide.
  </p>
  <h1>
   Conclusion
  </h1>
  <p>
   No big finish here.  16 years has been a wild ride and thankfully, I don't see
this ride ending any time soon.
  </p>
  <p>
   Heartfelt thanks to all of our employees, customers, and tech community friends
for making all of this even possible.
  </p>
 </div>
</div>
]]>/></item><item><title>Formatting Gone Wrong</title><link>http://www.revsys.com/tidbits/formatting-gone-wrong/</link><description>Your code formatter may have reformatted your API key. This could cause many confusing errors.</description><pubDate>Wed, 22 Feb 2023 17:30:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/formatting-gone-wrong/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I posted this
   <a href="https://twitter.com/KojoIdrissa/status/1624177135500898305">
    tweet after
   </a>
   feeling defeated by trying to set up SendGrid with Django. I kept getting a
   <code>
    smtplib.SMTPServerDisconnected: Connection unexpectedly closed
   </code>
   error and had NO IDEA why.
  </p>
  <p>
   Turns out, it was my editor. I had two settings that I usually depend on, but today, they were working against me.
  </p>
  <h2>
   Background
  </h2>
  <p>
   I've been going through Will Vincent's "
   <a href="https://djangoforbeginners.com/">
    Django For Beginners
   </a>
   " book to help catch gaps in my knowledge. It's a great book and Will's a great person. In addition, I often get asked where/how to start learning Django, so I wanted to go through the book in more detail, so I could make a stronger, more informed recommendation. QA isn't just for software! Spoiler alert: It's a great book and Will's a great person.
  </p>
  <p>
   In Chapter 1, Will has you set up VS Code as your editor (
   <em>
    but it could have happened with most editors
   </em>
   ). He tells you to configure it to do two things:
  </p>
  <ul>
   <li>
    Setup
    <a href="https://black.readthedocs.io/en/stable/">
     black
    </a>
    to be your python formatting provider
    <em>
     (an excellent suggestion)
    </em>
   </li>
   <li>
    Configure your editor to format on save
    <em>
     (an excellent suggestion)
    </em>
   </li>
  </ul>
  <p>
   I already had VS Code configured to do one more thing, and this is the thing that bit me:
   <code>
    Autosave on focus change
   </code>
   .
  </p>
  <p>
   Lots of people use this combination of settings. Automatically linting and formatting your code when you save a file can help you avoid CI fails and having to make commits with messages like
   <code>
    "fixed linting"
   </code>
   . And if you were to ask me, I'd suggest you use
   <code>
    black
   </code>
   as your Python formatter. It's what
   <a href="https://github.com/django/deps/blob/main/final/0008-black.rst">
    the Django codebase
   </a>
   uses.  Here's where things went wrong for me:
  </p>
  <ul>
   <li>
    an API key
   </li>
   <li>
    not following along closely enough!
   </li>
  </ul>
  <h2>
   API Keys: They're Sensitive
  </h2>
  <p>
   Jumping ahead to Chapter 12, I was setting up my Django project to be able to send emails using
   <a href="https://sendgrid.com/">
    SendGrid
   </a>
   . While setting up SendGrid, I had to generate an API key, then add it to my
   <code>
    settings.py
   </code>
   file, so my Django project could connect to the service. When you generate an API key, there's an instant of panic where you think, "Ok, let me copy/paste this somewhere so I don't lose it!" A common approach is to just paste it into the file where it'll be used, then assign/use it later. Since this was a practice project, I was more concerned with, "Does this work?" than application security. So, I hard-coded the API key into my settings file:
  </p>
  <p>
   <code>
    EMAIL_HOST_PASSWORD = MY.secret-API.key-dont-look
   </code>
  </p>
  <p>
   But, since it was a Python file,
   <code>
    black
   </code>
   took one look, said, "
   <em>
    That's not right!
   </em>
   " and 'fixed' it. So, as soon as I switched to another window (remember: I've turned on
   <code>
    Autosave on focus change
   </code>
   , which was not one of Will's suggestions),
   <code>
    black
   </code>
   turned it into this:
  </p>
  <p>
   <code>
    EMAIL_HOST_PASSWORD = MY.secret - API.key - dont - look
   </code>
  </p>
  <p>
   I didn't notice the change, but if you know anything about API keys, those spaces make a difference. Now I was using an invalid API key and wondering why I couldn't connect. After some time pairing with
   <a href="https://www.revsys.com/about/bio/jefftriplett">
    Jeff
   </a>
   , he realized there were way too many spaces in that API key. 🤦🏾&zwj;&male;️
  </p>
  <h2>
   How To Avoid This?
  </h2>
  <p>
   There are a few approaches I could have taken.
  </p>
  <h3>
   Hard-code the API key as a string
  </h3>
  <p>
   <strong>
    This is exactly what Will suggested!
   </strong>
   When I go back and look at the screenshot of
   <code>
    settings.py
   </code>
   in his book, I see:
  </p>
  <p>
   <code>
    EMAIL_HOST_PASSWORD = "MY.secret-API.key-dont-look"
   </code>
   .
  </p>
  <p>
   <code>
    black
   </code>
   won't change the contents of a string literal. That would have worked in the short term
   <em>
    (Remember kids: don't put your API keys into files you commit to Git!)
   </em>
   .
  </p>
  <p>
   Will
   <strong>
    literally
   </strong>
   reminds you of this in the paragraph after the
   <code>
    settings.py
   </code>
   screenshot. I have that entire paragraph highlighted. He tells you,
   <em>
    (in Chapter 12)
   </em>
   ,
   <code>
    In Chapter 16 we will learn how to add environment variables to our project so that we can keep secret information truly secret.
   </code>
   So, if I'd followed the screenshot more closely, I'd have been finished with this mid-afternoon Friday.🤦🏾&zwj;&male;️
  </p>
  <h3>
   Disable
   <code>
    autosave on focus change
   </code>
  </h3>
  <p>
   I go back and forth on this. The convenience of not forgetting to save a change is nice. However, there's also something to be said for being mindful and intentional about saving your work. This is especially true if you have something else set to run when you save. For now, I've opted for convenience. I could also disable
   <code>
    format on save
   </code>
   , but I don't like that option. If I'm going to save a change, I want it formatted.
  </p>
  <h3>
   Current best practice
  </h3>
  <p>
   The approach I took was to do what Will foreshadowed from Chapter 16. I made the API key an environment variable and set the project up to use it. Don't ever think you're too fancy to remember your
   <a href="https://12factor.net/config">
    12 Factor App fundamentals
   </a>
   , even on practice projects. In this case that means:
  </p>
  <ul>
   <li>
    adding a
    <code>
     .env
    </code>
    file of some sort, to store my secret
   </li>
   <li>
    making sure that file does NOT go into version control
   </li>
   <li>
    importing something into
    <code>
     settings.py
    </code>
    to allow it to read environment variables
   </li>
  </ul>
  <p>
   Again, this is part of the challenge of being self and community-taught. Going through a book for beginners, there are times I already know to do things that will come later, and there are things that are new to me that might be old hat for others.
  </p>
  <h2>
   The Final Product
  </h2>
  <p>
   I'm using
   <a href="https://direnv.net/#use-cases">
    direnv
   </a>
   , so I made a
   <code>
    .envrc
   </code>
   file that contains:
  </p>
  <p>
   <code>
    export EMAIL_HOST_PASSWORD = "MY.secret-API.key-dont-look"
   </code>
   .
  </p>
  <p>
   I've also added a
   <code>
    .envrc
   </code>
   line to my
   <code>
    .gitignore
   </code>
   file.
  </p>
  <p>
   As a team, we like
   <a href="https://django-environ.readthedocs.io/en/latest/quickstart.html">
    django-environ
   </a>
   over
   <code>
    os.environ
   </code>
   <em>
    (but os.environ is built-in to Python and it will also get the job done)
   </em>
   . So
   <strong>
    my
   </strong>
   <code>
    settings.py
   </code>
   looks like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># settings.py</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">environs</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">environs</span><span class="o">.</span><span class="n">Env</span><span class="p">()</span>
<span class="o">...</span>
<span class="o">***</span> <span class="n">various</span> <span class="n">Django</span> <span class="n">settings</span> <span class="o">***</span>
<span class="o">...</span>
<span class="n">EMAIL_HOST_PASSWORD</span> <span class="o">=</span> <span class="n">env</span><span class="p">(</span><span class="s2">"EMAIL_HOST_PASSWORD"</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   And there you go: it all works now! Teamwork Makes The Dream Work (thanks to
   <a href="https://www.revsys.com/about/bio/lacey-williams-henschel">
    Lacey
   </a>
   for all the helpful input)! And
   <a href="https://www.rif.org/">
    Reading Is Fundamental
   </a>
   !
  </p>
 </div>
</div>
]]>/></item><item><title>devdata - Improving developer velocity and experience</title><link>http://www.revsys.com/tidbits/devdata-improving-developer-velocity-and-experience/</link><description>Developers are the largest expense with software. Improving their productivity doesn&amp;#x27;t just make sense to the company&amp;#x27;s bottom line, but it improves morale as they no longer have to wade through a bunch of crap to get started on the job at hand. This data generation technique helps improve your developer experience and testing.</description><pubDate>Fri, 17 Feb 2023 00:24:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/devdata-improving-developer-velocity-and-experience/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Developers spend the majority of their time either debugging existing code or
writing
   <em>
    new
   </em>
   bugs into
   <em>
    new
   </em>
   code. Except for all the time we spend in
meetings talking about code of course.
  </p>
  <p>
   I'm half joking, but there is some truth to this.  After 25+ years writing code
professionally, I've given up looking for magic bullets that solve all the
problems.  Today's AI ain't gonna help you here folks.
  </p>
  <p>
   Instead, I've focused on small tactical changes that seem to improve developer
productivity.
  </p>
  <p>
   One technique we've been using recently I call "devdata".
  </p>
  <p>
   I'm going to cover the problem and then a high-level explanation of why devdata
works better and finish off with some deeper details to be successful if you
choose to try this technique.
  </p>
  <h2>
   The Challenge
  </h2>
  <p>
   One of the biggest challenges to debugging is setting up your data exactly like
the scenario where the bug happened.  It's the same with writing tests. We have
to set the stage before asserting our code does the right thing.
  </p>
  <p>
   One way to do this is to snapshot your production database(s), pull it local
to your laptop, and start debugging.  This
   <em>
    can
   </em>
   work and is often the first
thing developers reach for.  It sure is more manageable than hand-crafting the exact
situation.
  </p>
  <p>
   This technique isn't optimal if your data is sensitive.  Now you have to
spend some time writing code to sanitize your data.  Did you do it right and
obfuscate it enough? Who knows.  I don't and you probably don't either
because maybe Susan added a new Social Security Number field since the last time
anyone updated the sanitization code.
  </p>
  <p>
   Can we trust this sub-contractor to have the sanitized version? To be safe maybe
he can try and work without it?
  </p>
  <p>
   And it falls apart when your production database is 2TB.  Even if will
fit on your laptop, transferring it there means you get to
   <em>
    start
   </em>
   debugging in
a couple hours.
  </p>
  <p>
   Another way to tackle the problem is to not use a copy of production and instead
craft a specific test and fixture data to closely mimic the problem. Safer, but
this specific fixture of data isn't likely applicable to any other bug or
feature.
  </p>
  <h2>
   A Better Way
  </h2>
  <p>
   Developers are the largest expense with software. Improving their productivity
doesn't just make sense to the company's bottom line, but it improves morale as
they no longer have to wade through a bunch of crap to get started on the job
at hand.
  </p>
  <p>
   The suggestion I'm proposing is to invest some time in a realistic but fake
data generation tool.  We call it
   <code>
    devdata
   </code>
   but the name isn't at all important.
  </p>
  <p>
   This tool should:
  </p>
  <ul>
   <li>
    Dynamically generate common scenarios in your application in your local development environment
   </li>
   <li>
    It has to be able to reset to known states quickly and easily
   </li>
   <li>
    It should keep some names and credentials consistent
   </li>
   <li>
    It should be easy to extend as your application changes over time
   </li>
   <li>
    And it has to be used by the vast majority of your team
   </li>
  </ul>
  <p>
   At this point I think it's easier to start talking in terms of how this works
using a real application.
  </p>
  <h2>
   The Example App: Pizza
  </h2>
  <p>
   Everybody is familiar with pizza, so we're going to talk about a SaaS product
that does pizzaria management.
  </p>
  <p>
   In software development we often talk about User Stories and Personas.  What
I'm essentially suggesting here is that you automate bringing some of these
story scenarios and personas to life.
  </p>
  <p>
   Pizzarias come in all shapes and sizes, but what I would start with is a few
specific scenarios.
  </p>
  <h3>
   Scenario 1: Lombardi's
  </h3>
  <p>
   Lombardi's is your typical small, single-location, local pizza place.  There is one
owner, Gennaro.  Who is also the only manager, but has 7 employees. They only offer
two crusts in three sizes, have regular toppings, and don't do delivery.
  </p>
  <h3>
   Scenario 2: Kansas Pizza Kitchen
  </h3>
  <p>
   The lesser known cousin of the California variety.  KPK has 6 locations in 4
cities.  There are three owners, 8 managers, 12 supervisors, 41 employees, and
they do delivery in 4 of their 6 locations. Along with pizza they also have a few
pasta dishes and need a fairly complicated intake form for their occasional
catering gig.
  </p>
  <h3>
   Scenario 3: ACME Pizza
  </h3>
  <p>
   ACME is a huge publicly traded pizza empire.  Lots of locations across many
states, several layers of management, and more employees than you think.
Complicated in every way possible. Uses all the features of your app.
  </p>
  <p>
   They're so large you're certain they're going to stop using your app and build
their own in-house any day now.
  </p>
  <h2>
   So what does this do for us?
  </h2>
  <p>
   First, this gives us some named scenarios we can talk about.
  </p>
  <p>
   "I see how this is useful for Lombardi's, but how is the UI going to work for
KPK and ACME users?"
  </p>
  <p>
   "So I think I found a weird sales tax bug that doesn't happen for Lombardi's,
but does when you order a delivery with KPK."
  </p>
  <p>
   Here at REVSYS we mostly work with Django, so our
   <code>
    devdata
   </code>
   tool is built as a
Django management command.  Because ACME is a big beast with lots of data it
takes awhile to generate all the fake pizza orders so we set up two initial options.
  </p>
  <div class="codehilite">
   <pre><span></span><code>./manage.py<span class="w"> </span>devdata<span class="w"> </span>common
</code></pre>
  </div>
  <p>
   It will wipe away your existing local development database, leaving just your
Django superusers in place so you can get to the Django admin without generating a
new user/password each time.  You're going to run this command at least a few
times a day, if not dozens, so that would be annoying.
  </p>
  <p>
   This
   <code>
    common
   </code>
   scenario then sets up Lombardi's and KPK.  Here is where consistency
of naming comes in handy.  We should hard code
   <code>
    Gennaro Lombardi
   </code>
   and
   <code>
    gennaro@lombardispizza.com
   </code>
   to be the owner.  The rest of the employees, orders,
and customers should be random-ish and generated with something like
   <a href="https://faker.readthedocs.io/en/master/">
    Faker
   </a>
   .
  </p>
  <p>
   The hard coded bits are our anchors we can use to quickly
   <a href="https://github.com/django-hijack/django-hijack#django-hijack">
    hijack
   </a>
   a user who is of a certain persona to poke around in the UI.
  </p>
  <p>
   We can also then setup
   <code>
    ./manage.py devdata complex
   </code>
   which will run the same
common scenario as above, but add in the larger, more time consuming ACME
scenario when needed.
  </p>
  <p>
   Hopefully you're seeing how this can apply to your own application, but some other
things I would likely generate are:
  </p>
  <ul>
   <li>
    Customers for each pizzaria with varying levels of previous orders, reward points, etc
   </li>
   <li>
    A few orders for each at various stages (new, cooking, out for delivery, etc) with random-ish data. A few simple "large cheese" orders and a couple more complicated orders.
   </li>
   <li>
    I'd randomly set each to run out of something.  No mushrooms for you!
   </li>
   <li>
    Maybe we'd also set one of KPK's locations to have weird hours so they are closed during the day, but open midnight to 6am. Timezones are hard and this helps us test them.
   </li>
   <li>
    A common sale or promotion or two.
   </li>
  </ul>
  <p>
   The main benefit of all of this is that we can quickly jump into a variety
of situations in these scenarios as different user personas.
  </p>
  <p>
   Did that little logic change I just made to coupons break something in the UI? Customers
reported if you add pineapple to thin crust pizza it shows up on the checkout screen
as extra cheese, but shows the kitchen staff the right information.
  </p>
  <p>
   Easy, just hijack a user with a persona nearest your problem and adjust things
a bit to your situation.
  </p>
  <p>
   And then, when you've found and fixed your bug, run
   <code>
    ./manage.py devdata common
   </code>
   again
and you're back to a known state.
  </p>
  <h2>
   Additional Benefits
  </h2>
  <ul>
   <li>
    Done right this generation code can often be re-used or built from the same code you're
using to generate test data for your automated tests. If you are consistent with this both your ability to generate awesome test fixtures
    <strong>
     AND
    </strong>
    your ability to manually test are greatly improved
   </li>
   <li>
    Your frontend developers can get started more easily as they have realistic data to throw on the screen
   </li>
   <li>
    I've found several small UI bugs simply because much of the data was random and faked.
    <em>
     Oh look, that wraps weirdly when the user's last name is longer than 12 characters
    </em>
   </li>
  </ul>
  <h2>
   Technical Details
  </h2>
  <p>
   Since we are typically using Django, our
   <code>
    devdata
   </code>
   commands are implemented
using
   <a href="https://pypi.org/project/django-click/">
    django-click
   </a>
   which makes it extremely
easy to make a great
   <a href="https://docs.djangoproject.com/en/4.1/howto/custom-management-commands/">
    Django Management Command
   </a>
   with all of the great argument parsing and power
of the
   <a href="https://click.palletsprojects.com/en/8.1.x/">
    Click
   </a>
   .
  </p>
  <p>
   We also use
   <code>
    pytest
   </code>
   for our tests, but you can just call a pytest
fixture directly.  You can however wrap a function that generates something
in another function so that you can share it between the two.  For example:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># File: toppings/tests/fixtures.py</span>

<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">devdata.generation</span><span class="w"> </span><span class="kn">import</span> <span class="n">generate_available_toppings</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span><span class="w"> </span><span class="nf">available_toppings</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">generate_available_toppings</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   I would also encourage you to write an option into your command to clear out
all of your generated test data completely.  This is useful if you ever want to
generate (and then wipe it all away) safely in production.
  </p>
  <h2>
   Conclusion and Challenge
  </h2>
  <p>
   I encourage you to give this technique a try.  Now that I've been using in a
few projects I immediately miss it in the ones that do not have it.  Automated testing,
manual testing and even just exploring around a new UI feature is more of a pain
and hence don't happen as frequently as they should.
  </p>
  <p>
   I promise your team's velocity will improve far beyond the time investment.
  </p>
  <p>
   <em>
    P.S If you need help convincing your boss, I'd be
    <a href="/contact/">
     happy to help convince them
    </a>
    of the benefits.
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Breaking up django-ninja views</title><link>http://www.revsys.com/tidbits/breaking-up-django-ninja-views/</link><description>A simple import trick to make it easy to keep your django-ninja view functions in different Django apps and files.</description><pubDate>Sat, 04 Feb 2023 22:44:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/breaking-up-django-ninja-views/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I've been evaluating
   <a href="https://django-ninja.rest-framework.com/">
    django-ninja
   </a>
   recently, using it in a personal project to see when it might be appropriate for
our clients.
  </p>
  <h2>
   The Problem - How to have multiple view files
  </h2>
  <p>
   One thing that was not clear was how to break up your API views and sprinkle
them around in multiple files.  The default tutorial and documentation is not
bad, but just does not cover this small thing.
  </p>
  <p>
   The way django-ninja works is that you decorate functional views with
   <code>
    @api
   </code>
   where you indicate what HTTP method it is for and some other parameters.
Everything is perfect until you realize your views.py has grown huge or you
want to include api views from several Django applications.
  </p>
  <p>
   For those of us who are very familiar with DRF, it is not immediately clear how
you can break your views into multiple files.
  </p>
  <p>
   Let's use an example of having a Django apps named
   <code>
    core
   </code>
   ,
   <code>
    users
   </code>
   , and
   <code>
    posts
   </code>
   to mimic some sort of blog like software and our project directory, which contains
our settings and urls files will be named
   <code>
    config/
   </code>
   which is the standard way
we roll at REVSYS.
  </p>
  <p>
   Your first guess might be that you should create another instance of
   <code>
    NinjaAPI
   </code>
   and use that to decorate your views in each file.  Unfortunately, because of how
the internal registry works for these, that would create multiple django-ninja
"sites", each with their own docs, etc. which is obviously not what we want.
  </p>
  <h2>
   The Solution
  </h2>
  <p>
   The easiest way to do this is to pass around your API decorator and ensure
your view files are imported before you call on Ninja to generate your URLs.
  </p>
  <p>
   While I hate having an app named
   <code>
    core
   </code>
   , I used it in this case.  So we
create our API instance with the following:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># File: core/api.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">ninja</span><span class="w"> </span><span class="kn">import</span> <span class="n">NinjaAPI</span>

<span class="n">api</span> <span class="o">=</span> <span class="n">NinjaAPI</span><span class="p">(</span><span class="n">csrf</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   And then in our views we import this and use it:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># File: users/views.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">core.api</span><span class="w"> </span><span class="kn">import</span> <span class="n">api</span>

<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"users/something/"</span><span class="p">):</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
   <span class="c1"># ... do something interesting here ...</span>

<span class="c1"># File: posts/views.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">core.api</span><span class="w"> </span><span class="kn">import</span> <span class="n">api</span>

<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"posts/</span><span class="si">{username}</span><span class="s2">/"</span><span class="p">):</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_posts</span><span class="p">(</span><span class="n">requests</span><span class="p">,</span> <span class="n">username</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
   <span class="c1"># ... do something interesting here ...</span>
</code></pre>
  </div>
  <p>
   Then the final bit is to make sure we import these files in our
   <code>
    urls.py
   </code>
   before
we call
   <code>
    api.urls
   </code>
   like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># File: config/urls.py</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">users.views</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">posts.views</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">core.api</span><span class="w"> </span><span class="kn">import</span> <span class="n">api</span>

<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
    <span class="n">path</span><span class="p">(</span><span class="s2">"api/"</span><span class="p">,</span> <span class="n">api</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span>
<span class="p">]</span>
</code></pre>
  </div>
  <h2>
   Alternative Solution
  </h2>
  <p>
   Turns out there is also a DRF like concept of
   <a href="https://django-ninja.rest-framework.com/guides/routers/">
    routers
   </a>
   which is honestly probably a better more
maintainable solution than the above.
  </p>
  <p>
   Thanks to
   <a href="https://twitter.com/uninen/status/1622007355067056132?s=46&amp;t=6Jl-2SgFBle7KLjsma1uQw">
    Ville S&auml;&auml;vuori
   </a>
   for point this out after I posted this.
  </p>
  <p>
   Hopefully, this saves you a bit of time when you first start using django-ninja!
  </p>
  <p>
   If you need help creating Django REST APIs don't hesitate to
   <a href="/contact/">
    reach out to us
   </a>
   !
  </p>
 </div>
</div>
]]>/></item><item><title>Website vs Web App: Hidden Complexities</title><link>http://www.revsys.com/tidbits/website-vs-web-app-hidden-complexities/</link><description>Most of what are called `websites` today are `web apps`. These are computer applications that use the web as their delivery method and GUI. However, since they look like websites, their complexity and design challenges are often underestimated.</description><pubDate>Mon, 19 Dec 2022 20:42:48 +0000</pubDate><guid>http://www.revsys.com/tidbits/website-vs-web-app-hidden-complexities/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   The Old Days: Static Files &amp; the Request/Response Cycle
  </h2>
  <p>
   Back in The Old Days, we built websites that were mostly for reading text or looking at pictures. Someone's web client sent a
   <code>
    request
   </code>
   to your web server and it sent static HTML files back as a
   <code>
    response
   </code>
   . Then we added CSS for style and JavaScript for dynamism. And while websites became increasingly more complex, they remained, primarily, static files sent as a
   <code>
    response
   </code>
   to an HTTP
   <code>
    request
   </code>
   . The
   <code>
    request
   </code>
   identified which set of pre-existing static files to send as a
   <code>
    response
   </code>
   . This model (
   <em>
    each page on a site roughly equates to one .html file that can be 'jazzed up' with CSS and JS
   </em>
   ) is fairly straightforward and easily held in the mind. It can also be easily explained to people who don't create websites, but want one created. Think of a department manager or business owner. Sadly, that led many people to think, "Building a website is easy!"
  </p>
  <h2>
   Modern Web Apps: Database-Backed Web Sites
  </h2>
  <p>
   The majority of web addresses people type into their browsers are not websites. They're web applications or web apps. But what is a web app and how is it different from a website? In simple terms, most modern web apps are a database of related content with an HTML GUI. The HTML GUI makes the database easily accessible to the intended audience. Amazon is a database of books and various other items for sale. Instagram is a database of pictures. Most blogs are primarily databases of text documents.
  </p>
  <p>
   The benefit of a database is its ability to store large amounts of information. The problem with a database is getting the specific information you want from it, as well as getting that information in the format you want.
  </p>
  <p>
   As I said before, these days most
   <code>
    websites
   </code>
   are
   <code>
    web applications
   </code>
   . And while the two often look the same, the design and implementation challenges are vastly different.
  </p>
  <h2>
   The Difference: Processing the Request
  </h2>
  <p>
   One of the main ways that a web app differs from a website is that a web app adds an extra step to the request-response cycle that's often overlooked: processing.
  </p>
  <p>
   Most websites don't need to do much processing. They send a
   <code>
    response
   </code>
   of HTML/CSS/JavaScript to the client that made the
   <code>
    request
   </code>
   . That
   <code>
    request
   </code>
   specifies which documents are desired via a URI with a path to those pre-existing documents. So,
   <code>
    https://send.me/this/thing.html
   </code>
   where
   <code>
    thing.html
   </code>
   already exists.
  </p>
  <p>
   Contrast this with a web app. They frequently get requests that don&rsquo;t end in a file extension like
   <code>
    .html
   </code>
   . This request ends in a query string, which begins with the
   <code>
    ?
   </code>
   character:
   <code>
    https://send.me/search?q=thing&amp;SomeOtherStuff
   </code>
   . In this case, the app is being asked (queried), "Tell me what you know about
   <code>
    thing
   </code>
   and
   <code>
    SomeOtherStuff
   </code>
   and how the two are related." This is where the processing happens. It includes:
- converting the search terms into something useable by the backend software
- forming a valid query to send to the database with those terms
- getting the database response back
- converting that database response into a properly formed HTTP
   <code>
    response
   </code>
   that can be sent back to the client
  </p>
  <p>
   While websites have a
   <code>
    request/response
   </code>
   cycle, web apps almost always have a
   <code>
    request/processing/response
   </code>
   cycle. That processing step can vary quite a bit in terms of time and resource cost. A lot of it can relate to having to talk to your database and wait for it to respond. Then, multiply that
   <code>
    processing
   </code>
   cost (in time and resources) by the number of visitors to your web app. It can get out of hand fast.
  </p>
  <p>
   As an aside, this is one reason
   <a href="https://en.wikipedia.org/wiki/Static_site_generator">
    static-site generators
   </a>
   have made a return to popularity for blogs and many other uses. Depending on your use case, you don't always
   <strong>
    need
   </strong>
   a database. As much as we at REVSYS love Django, the
   <a href="https://upgradedjango.com/">
    Upgrade Django
   </a>
   and
   <a href="https://2022.djangocon.us/">
    DjangoCon US
   </a>
   sites are both built with SSGs.
  </p>
  <h2>
   Speed and Data Structure
  </h2>
  <p>
   Shortly after starting at REVSYS, I was talking to my co-worker
   <a href="https://www.revsys.com/about/bio/jefftriplett">
    Jeff
   </a>
   about best practices when starting a new Django project. He said when working on a new web app (I'm going to paraphrase), he starts by thinking about the data models, or:
- What information do you want in the database (what are you trying to keep track of with this app)?
- How do those bits of data relate to each other?
  </p>
  <p>
   When someone goes to a web app, they could be doing
   <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete#RESTful_APIs">
    multiple things
   </a>
   . Most frequently, they're looking for something. But search terms that seem simple to most people ("Chinese restaurant near me" or "1-inch green washi tape") are ONLY simple in a web app if proper consideration has been given to the data models and how they can be queried. When every click is a database query, you want those queries to be as fast as possible. And as your data becomes more complex or increases in volume (imagine the options on a growing fleet of cars or laptops or different models of pens or notebooks), the structure of your data becomes even more important. Beyond the structure of the data (Do you want categories? How many?), the structure of the queries themselves becomes more critical. Writing a nested query may seem to make sense as you step through the logic, but it's almost always a bad idea.
  </p>
  <p>
   The related issues of query speed and data schemas are things most websites don't have to deal with. But almost every web app has to be designed with this constraint in mind.
  </p>
  <h2>
   Efficiency vs Effectiveness
  </h2>
  <p>
   Slow queries are problematic. They turn off potential users and can use more computing resources, becoming expensive from a time and financial standpoint. But an ineffective query, one that returns a useless result, is just as bad. It doesn't matter how fast it runs if the result has no value. Structuring your data properly helps ensure you can run queries that will return the results people want. I won't name names, but there's at least ONE internet destination whose terrible search function almost ALWAYS disappoints me.😭
  </p>
  <h2>
   Other Things and Remote Control
  </h2>
  <p>
   There are two other issues I want to discuss.
  </p>
  <p>
   First, more on the
   <code>
    processing
   </code>
   step. I've focused on database queries, but depending on your web app, there can be a LOT of other things going on. The app could be making API calls to other web apps or software (so each request triggers a new, external
   <code>
    request/processing/response
   </code>
   cycle). It could be doing all manner of complex calculations on your request or the results of the database query. Anything a desktop app could be doing, a web app could also be doing in that
   <code>
    processing
   </code>
   step. This is very different from a website, which is usually able to skip the
   <code>
    processing
   </code>
   step.
  </p>
  <p>
   Second, let's recall that a web app is a computer application, similar in many ways to a desktop application. But instead of running on your local machine, you're driving it by remote control, over the web. Modern web apps like Google Sheets and Gmail make this look simple (how that happens is an entirely different conversation and beyond the scope of this post), but I assure you, it isn't. Also, most of us don't have Google's resources.
  </p>
  <h2>
   Conclusion
  </h2>
  <p>
   While they look the same, websites and web apps are VERY different things. And with web apps becoming more common, the confusion has only grown.
  </p>
  <p>
   This is not to say that one is better or worse than the other. But because they look identical from the outside, it's easy to confuse what's needed to build one that works well. There's usually a lot more that goes into designing and building a well-functioning web app than a well-functioning website that primarily delivers static or mildly dynamic content.
  </p>
  <p>
   Something most web (and desktop application) developers have heard is, "It's just a button!" when someone wants to add something new. The visual representation, the colored shape, is easy. It's making the button function that contains the difficulty. If you click a button, you want a result. That means kicking off a brand new
   <code>
    request/processing/response
   </code>
   cycle and writing and maintaining the code that does the underlying processing.
  </p>
 </div>
</div>
]]>/></item><item><title>Nullable DRF FloatFields and Javascript</title><link>http://www.revsys.com/tidbits/nullable-drf-floatfields-and-js/</link><description>Sometimes you want your FloatFields to be nullable, but your JS code isn&amp;#x27;t quite smart enough to handle it.</description><pubDate>Tue, 15 Nov 2022 21:43:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/nullable-drf-floatfields-and-js/</guid><content:encoded<![CDATA[<div class="block-content">
 <p data-block-key="j4bal">
  I run into this situation every couple of years, and I always forget the solution. Even asked around the office and everyone had the same "Oh yeah, I remember doing that once... but now how".
 </p>
 <p data-block-key="cjmgn">
  When these situations happen to us, it is often time to write a blog post!
 </p>
 <p data-block-key="5lbtv">
  The situation I was faced with was I had:
 </p>
 <ul>
  <li data-block-key="7lj4t">
   A model with a few nullable Django Model FloatFields
  </li>
  <li data-block-key="b2ksr">
   Django Rest Framework, using a ModelSerializer
  </li>
  <li data-block-key="rfo1">
   react-hook-form, react-query and standard fetch calls to POST the data to the endpoint
  </li>
 </ul>
 <p data-block-key="5o1up">
  The issue is that we have a simple HTML text input to collect a possible float value. It's easy enough to mark this as not required on the form and any client-side form validation. However, it is NOT always that easy to handle what happens next. Because it's a text field, the JS tooling wants to send it as an empty string. So we end up with a payload that looks like:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="p">{</span>
<span class="w">  </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Frank Wiles"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"weight"</span><span class="o">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span>
<span class="p">}</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="j4bal">
  When what we'd really like to have is:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="p">{</span>
<span class="w">  </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"Frank Wiles"</span><span class="p">,</span>
<span class="w">  </span><span class="s2">"weight"</span><span class="o">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
<span class="p">}</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="j4bal">
  DRF rejects this saying that an empty string is not a number. It is correct of course, just not being super helpful in this exact situation. So what is the solution? Enter BlankableFloatField
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.serializers</span><span class="w"> </span><span class="kn">import</span> <span class="n">FloatField</span>


<span class="k">class</span><span class="w"> </span><span class="nc">BlankableFloatField</span><span class="p">(</span><span class="n">FloatField</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""</span>
<span class="sd">    This accepts an empty string as None</span>
<span class="sd">    """</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">to_internal_value</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">data</span> <span class="o">==</span> <span class="s2">""</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">None</span>
        <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">to_internal_value</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="n60gb">
  You can override this on your ModelSerializer by just specifying the fields manually like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">PersonSerializer</span><span class="p">(</span><span class="n">serializers</span><span class="o">.</span><span class="n">ModelSerializer</span><span class="p">):</span>
    <span class="c1"># ... your other fields here ... </span>
    <span class="n">weight</span> <span class="o">=</span> <span class="n">BlankableFloatField</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="n60gb">
  The solution isn't hard to execute, but it's hard to reason about and figure out the
  <i>
   best
  </i>
  place to adjust this in your Django project.
 </p>
 <p data-block-key="a371e">
  Hopefully, the next time you find yourself with Javascript error in your console saying that an empty string is not a number this helps you out!
 </p>
</div>
]]>/></item><item><title>Helpful Shell alias to git repo root</title><link>http://www.revsys.com/tidbits/shell-alias-to-git-repo-root/</link><description>I created a simple `r` shell alias that will take me to the root of the current git repository. As this is a common thing I need to do on my Mac BookPro when I&amp;#x27;ve been exploring deep into a code base and now need to run some command at the root.</description><pubDate>Tue, 08 Nov 2022 22:43:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/shell-alias-to-git-repo-root/</guid><content:encoded<![CDATA[<div class="block-content">
 <p data-block-key="t0zw4">
  I've recently been on a mission to take
  <i>
   my own
  </i>
  advice and spend a little bit of time each week optimizing my personal coding setup. Recently I constantly need to got to the root of the current git repository I'm in.
 </p>
 <p data-block-key="d61b8">
  It doesn't sound like a big deal, does it?
 </p>
 <p data-block-key="908pl">
  It isn't that big of a deal. However, it's keeping me from dozens to hundreds of `cd ../../..` type keystrokes every day! This won't single-handedly keep RSI away, but every little bit helps.
 </p>
 <p data-block-key="8set8">
  So what did I set up? I set up two zsh aliases.
 </p>
 <ul>
  <li data-block-key="9guvg">
   `r` which will immediately cd me to the root of the current git repository I'm in
  </li>
  <li data-block-key="fp4gc">
   `rd` which will run the
   <a href="https://github.com/sharkdp/fd#fd">
    fd
   </a>
   command from the perspective of the root of the git repo
  </li>
 </ul>
 <p data-block-key="dpa9g">
  I'm on OSX, so the only new dependency was I needed to install `git-extras` with `brew install git-extras`. git-extras is useful all on it's own, but I haven't used that many of its features yet. My muscle memory for such things takes awhile to set in as I'm sure it does for you.
 </p>
 <p data-block-key="cqn7a">
  I added the following to the end of my `~/.zshrc`:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="c1"># Git customization</span>
<span class="nb">alias</span><span class="w"> </span><span class="nv">r</span><span class="o">=</span><span class="s1">'cd $(git root)'</span>
<span class="k">function</span><span class="w"> </span>rd<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="w">    </span>fd<span class="w"> </span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="w"> </span><span class="k">$(</span>git<span class="w"> </span>root<span class="k">)</span>
<span class="o">}</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="q01rj">
  Now, if I type `r` I'm immediately taken to the git root. And if I run `rd settings` from a couple of directories deep in a code base I get.
 </p>
 <img alt="git zsh rd function shell output" class="richtext-image full-width" height="237" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/git-bash-rd.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180959Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=7012971b16d34636346e8577fa1ff0f439d8ccefa952b935d17724faddecaf7b" width="800"/>
 <p data-block-key="28rpu">
  I hope you'll join me in taking a little time to improve our workflows for better developer experience (DX) and a slightly easier day!
 </p>
 <h3 data-block-key="7bv75">
  Fish Shell Version
 </h3>
 <p data-block-key="ek5o5">
  <a href="/team/daniel-lindsley/">
   Daniel
  </a>
  worked up the
  <a href="https://fishshell.com/">
   fish shell
  </a>
  version for us all
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="c1"># ~/fish-configs/functions/r.fish</span>
<span class="k">function</span><span class="w"> </span>r
<span class="w">    </span><span class="nb">cd</span><span class="w"> </span><span class="o">(</span>git<span class="w"> </span>root<span class="o">)</span>
end

<span class="k">function</span><span class="w"> </span>rd<span class="w"> </span>-a<span class="w"> </span>pattern
<span class="w">    </span>fd<span class="w"> </span><span class="s2">"</span><span class="nv">$pattern</span><span class="s2">"</span><span class="w"> </span><span class="o">(</span>git<span class="w"> </span>root<span class="o">)</span>
end
</pre>
 </div>
</div>
<div class="block-content">
 <h3 data-block-key="ck721">
  More Info on git-extras
 </h3>
 <ul>
  <li data-block-key="9kv60">
   <a href="https://vimeo.com/45506445">
    Introduction screencast
   </a>
  </li>
  <li data-block-key="1kf5g">
   <a href="https://github.com/tj/git-extras/blob/master/Commands.md">
    List of Commands
   </a>
  </li>
 </ul>
</div>
]]>/></item><item><title>A Tip About DRF Permissions</title><link>http://www.revsys.com/tidbits/tip-about-drf-permissions/</link><description>I needed to structure permissions in an API view that had multiple user roles, and I learned about using the &amp;amp; (and), | (or) and ~ (not) operators with Django REST Framework permissions.</description><pubDate>Tue, 11 Feb 2020 23:44:26 +0000</pubDate><guid>http://www.revsys.com/tidbits/tip-about-drf-permissions/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Recently, I needed to structure permissions in an API view so that someone could access an API if they were a superuser or they had some other status, like being a manager. The
   <a href="https://www.django-rest-framework.org/api-guide/permissions/#how-permissions-are-determined">
    Django Rest Framework Permissions docs
   </a>
   say that &ldquo;If any permission check fails an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised,&rdquo; which made me think I&rsquo;d have to override the
   <a href="http://www.cdrf.co/3.9/rest_framework.views/APIView.html">
    <code>
     get_permissions()
    </code>
   </a>
   method of the view or write a series of complicated
   <code>
    IsUserTypeA
   </code>
   ,
   <code>
    IsUserTypeAOrUserTypeB
   </code>
   permissions classes, in order to do what I wanted. That didn&rsquo;t seem ideal.
  </p>
  <p>
   Then a
   <a href="https://twitter.com/webology">
    colleague
   </a>
   pointed out one line in the DRF docs further down on the
   <a href="https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy">
    same page
   </a>
   : &ldquo;Note: it supports &amp; (and), | (or) and ~ (not).&rdquo; This was exactly what I needed.
  </p>
  <p>
   Here&rsquo;s how it works.
  </p>
  <h2>
   All permissions must return True
  </h2>
  <p>
   The expected way that DRF permissions work is as stated above; when you list multiple permission classes in
   <code>
    permission_classes
   </code>
   , each of them must return True in order for the user to have access to that view. This is most likely what you&rsquo;re used to when implementing DRF permissions.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsAuthenticated</span><span class="p">,</span> <span class="n">IsAdminUser</span><span class="p">,</span> <span class="n">ReadOnly</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.viewsets</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelViewSet</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">my_app.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsStudent</span><span class="p">,</span> <span class="n">IsFaculty</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyModelViewSet</span><span class="p">(</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">permission_classes</span> <span class="o">=</span> <span class="p">(</span><span class="n">IsAuthenticated</span><span class="p">,</span> <span class="n">IsAdminUser</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   In this case, the user must be authenticated
   <em>
    and
   </em>
   be an admin user, or else they cannot access any part of the view.
  </p>
  <h2>
   The | (or) operator
  </h2>
  <p>
   Using the
   <code>
    |
   </code>
   (or) operator in DRF permissions allows you to set multiple conditions the user might meet and be able to access this view. For instance, for an educational app, a view might be available to students
   <em>
    or
   </em>
   faculty.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsAuthenticated</span><span class="p">,</span> <span class="n">IsAdminUser</span><span class="p">,</span> <span class="n">ReadOnly</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.viewsets</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelViewSet</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">my_app.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsStudent</span><span class="p">,</span> <span class="n">IsFaculty</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyModelViewSet</span><span class="p">(</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">permission_classes</span> <span class="o">=</span> <span class="p">(</span><span class="n">IsAdminUser</span> <span class="o">|</span> <span class="n">IsStudent</span> <span class="o">|</span> <span class="n">IsFaculty</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This expression
   <code>
    (IsAdminUser | IsStudent | IsFaculty)
   </code>
   allows a user who is an admin, a student, or a faculty member to access the view. Instead of needing to pass all three of these conditions, a user only needs to pass one.
  </p>
  <p>
   The way I accomplished this before was a custom permissions class named something like
   <code>
    IsAdminStudentOrFaculty
   </code>
   , but with multiple roles and complex permissions, this meant creating custom permission classes for nearly every combination of user role. (In this example, I refer to custom permissions
   <code>
    IsStudent
   </code>
   and
   <code>
    IsFaculty
   </code>
   ; there are
   <a href="https://www.django-rest-framework.org/api-guide/permissions/#examples">
    some examples in the docs
   </a>
   of how to implement custom permissions like these.)
  </p>
  <h2>
   The &amp; (and) operator
  </h2>
  <p>
   Especially combined with the
   <code>
    |
   </code>
   (or) operator, the
   <code>
    &amp;
   </code>
   (and) operator is really powerful. It enables you to set clear permissions boundaries without writing increasingly complex custom permissions classes.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsAuthenticated</span><span class="p">,</span> <span class="n">IsAdminUser</span><span class="p">,</span> <span class="n">ReadOnly</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.viewsets</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelViewSet</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">my_app.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsStudent</span><span class="p">,</span> <span class="n">IsFaculty</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyModelViewSet</span><span class="p">(</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">permission_classes</span> <span class="o">=</span> <span class="p">(</span><span class="n">IsAuthenticated</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">IsAdminUser</span> <span class="o">|</span> <span class="n">IsFaculty</span> <span class="o">|</span> <span class="n">ReadOnly</span><span class="p">))</span>
</code></pre>
  </div>
  <p>
   This example,
   <code>
    (IsAuthenticated &amp; (IsAdminUser | IsFaculty | ReadOnly))
   </code>
   , requires that all users accessing this view be authenticated. In addition, it sets other permissions: a user must be authenticated
   <em>
    and
   </em>
   be an admin or a faculty member, or they only have &ldquo;read&rdquo; access to this view.
  </p>
  <h2>
   The
   <code>
    ~
   </code>
   (not) operator
  </h2>
  <p>
   The
   <code>
    ~
   </code>
   (not) operator allows you to simplify your permissions to permit everyone
   <em>
    except
   </em>
   users who meet specific criteria. Personally, I prefer to set permissions with positive language, like &ldquo;allow admins and faculty&rdquo; rather than &ldquo;don&rsquo;t allow students,&rdquo; but there are probably cases where the
   <code>
    ~
   </code>
   operator is a great option.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsAuthenticated</span><span class="p">,</span> <span class="n">IsAdminUser</span><span class="p">,</span> <span class="n">ReadOnly</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.viewsets</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelViewSet</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">my_app.permissions</span><span class="w"> </span><span class="kn">import</span> <span class="n">IsStudent</span><span class="p">,</span> <span class="n">IsFaculty</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyModelViewSet</span><span class="p">(</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">permission_classes</span> <span class="o">=</span> <span class="p">(</span><span class="o">~</span><span class="n">IsStudent</span> <span class="o">&amp;</span> <span class="n">IsAuthenticated</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   <code>
    (~IsStudent &amp; IsAuthenticated)
   </code>
   says that anyone who is authenticated and who is
   <em>
    not
   </em>
   a student can access this view.
  </p>
  <p>
   I hope you, like me, now can free yourself from the cumbersome task of writing too many custom permission classes in your apps with several different types of user roles and instead make use of simpler permission classes and the
   <code>
    &amp;
   </code>
   ,
   <code>
    |
   </code>
   and
   <code>
    ~
   </code>
   operators!
  </p>
  <h2>
   Further Reading
  </h2>
  <ul>
   <li>
    <a href="https://www.django-rest-framework.org/api-guide/permissions">
     Django REST Framework Permissions
    </a>
    docs
   </li>
   <li>
    The
    <a href="https://github.com/encode/django-rest-framework/pull/5753">
     pull request
    </a>
    that added the
    <code>
     &amp;
    </code>
    (and) and
    <code>
     |
    </code>
    (or) operators. This, and the next pull request, are also the inspiration for some of my examples.
   </li>
   <li>
    The
    <a href="https://github.com/encode/django-rest-framework/pull/6361">
     pull request
    </a>
    that added the
    <code>
     ~
    </code>
    (not) operator
   </li>
  </ul>
  <p>
   <em>
    Thanks to
    <a href="https://twitter.com/webology">
     Jeff Triplett
    </a>
    for reviewing a draft of this article and finding this tip in the docs in the first place!
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Custom Exceptions in Django REST Framework</title><link>http://www.revsys.com/tidbits/custom-exceptions-django-rest-framework/</link><description>I was working on a project with a coworker recently and I noticed in one of their pull requests that they used a custom exception in one of our Django REST Framework viewsets. I prefer this way to what I was doing before, so I wanted to share it with you!</description><pubDate>Tue, 04 Feb 2020 23:06:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/custom-exceptions-django-rest-framework/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was working on a project with a
   <a href="https://twitter.com/fwiles">
    coworker
   </a>
   recently and I noticed in one of their pull requests that they used a custom exception in one of our Django REST Framework viewsets. I prefer this way to what I was doing before, so I wanted to share it with you!
  </p>
  <h2>
   My Less Clean, Less Consistent Exception Handling
  </h2>
  <p>
   I made good use of the
   <a href="https://www.django-rest-framework.org/api-guide/status-codes/">
    status codes
   </a>
   page of the DRF docs. For example, if a user uploaded a CSV file that my view couldn&rsquo;t read, I might have had something like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework</span><span class="w"> </span><span class="kn">import</span> <span class="n">status</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.response</span><span class="w"> </span><span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">APIView</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyCSVUploadView</span><span class="p">(</span><span class="n">APIView</span><span class="p">):</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="err">&hellip;</span>
        <span class="k">if</span> <span class="n">file_error</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">Response</span><span class="p">(</span>
                <span class="p">{</span><span class="s2">"detail"</span><span class="p">:</span> <span class="s2">"Unable to read file."</span><span class="p">},</span> 
                <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span>
            <span class="p">)</span>
</code></pre>
  </div>
  <p>
   This works just fine. But in some cases, I might return the same type of error response in multiple places. For a university that is no longer accepting applications, I might have multiple places where I am essentially passing the same &ldquo;Deadline passed&rdquo; message. Although the response is just one line of code, I have to find it, copy it, and paste it to make sure I&rsquo;m using the same wording, or deal with inconsistency in my exception messages.
  </p>
  <h2>
   Custom Exception Classes for Clean Consistency
  </h2>
  <p>
   For exception conditions that you encounter frequently in your code, you can override DRF&rsquo;s
   <code>
    APIException
   </code>
   class and create your own exceptions! Read more in
   <a href="https://www.django-rest-framework.org/api-guide/exceptions/#apiexception">
    the docs
   </a>
   .
  </p>
  <p>
   Import the
   <code>
    APIException
   </code>
   class and create a new class that inherits from it. Then set the
   <code>
    status_code
   </code>
   ,
   <code>
    default_detail
   </code>
   , and
   <code>
    default_code
   </code>
   attributes.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># exceptions.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.exceptions</span><span class="w"> </span><span class="kn">import</span> <span class="n">APIException</span>


<span class="k">class</span><span class="w"> </span><span class="nc">UnreadableCSVFile</span><span class="p">(</span><span class="n">APIException</span><span class="p">):</span>
    <span class="n">status_code</span> <span class="o">=</span> <span class="mi">400</span>
    <span class="n">default_detail</span> <span class="o">=</span> <span class="s2">"Unable to read file."</span>
    <span class="n">default_code</span> <span class="o">=</span> <span class="s2">"unreadable_csv_file"</span>
</code></pre>
  </div>
  <p>
   The
   <code>
    status_code
   </code>
   attribute defines the HTTP status code you want to send in the response with this exception; refer to the
   <a href="https://www.django-rest-framework.org/api-guide/status-codes/">
    DRF list of status codes
   </a>
   for help. The
   <code>
    default_detail
   </code>
   attribute is the human-readable exception message you want to pass in the response. The
   <code>
    default_code
   </code>
   is a string that represents this specific exception.
  </p>
  <p>
   In your view, instead of manually returning a
   <code>
    Response()
   </code>
   object, raise your custom exception.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># views.py</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">APIView</span>

<span class="n">From</span> <span class="n">my_app</span><span class="o">.</span><span class="n">exceptions</span> <span class="kn">import</span><span class="w"> </span><span class="nn">UnreadableCSVFile</span>

<span class="k">class</span><span class="w"> </span><span class="nc">MyCSVUploadView</span><span class="p">(</span><span class="n">APIView</span><span class="p">):</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="err">&hellip;</span>
        <span class="k">if</span> <span class="n">file_error</span><span class="p">:</span>
            <span class="k">raise</span> <span class="n">UnreadableCSVFile</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   This allows my exceptions to be reusable across my app, which means the language I use in exception messages is always consistent.
  </p>
  <h2>
   Custom Exception Handlers
  </h2>
  <p>
   You can further customize your exceptions by creating a custom handler function (
   <a href="https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling">
    docs on custom exception handling
   </a>
   ). This is especially helpful if there are pieces of data, like the status code, that you want to ensure are included in all of your HTTP responses. This is a slightly different goal than what I refer to above, but it&rsquo;s worth knowing about. Here is the example from the docs:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.views</span><span class="w"> </span><span class="kn">import</span> <span class="n">exception_handler</span>

<span class="k">def</span><span class="w"> </span><span class="nf">custom_exception_handler</span><span class="p">(</span><span class="n">exc</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
    <span class="c1"># Call REST framework's default exception handler first, </span>
    <span class="c1"># to get the standard error response.</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">exception_handler</span><span class="p">(</span><span class="n">exc</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span>

    <span class="c1"># Now add the HTTP status code to the response.</span>
    <span class="k">if</span> <span class="n">response</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">response</span><span class="o">.</span><span class="n">data</span><span class="p">[</span><span class="s1">'status_code'</span><span class="p">]</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span>

    <span class="k">return</span> <span class="n">response</span>
</code></pre>
  </div>
  <p>
   This adds the
   <code>
    status_code
   </code>
   to each exception response sent in your app.
  </p>
  <p>
   You would then add your custom exception handler to the
   <code>
    REST_FRAMEWORK
   </code>
   settings in your
   <code>
    settings.py
   </code>
   so this custom exception handler will be executed every time there is an API exception:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">REST_FRAMEWORK</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">'EXCEPTION_HANDLER'</span><span class="p">:</span> <span class="s1">'my_project.my_app.utils.custom_exception_handler'</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   For my example above, with the unreadable CSV file, the response would look like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">{</span><span class="s2">"status_code"</span>:<span class="w"> </span><span class="m">400</span>,<span class="w"> </span><span class="s2">"detail"</span>:<span class="w"> </span><span class="s2">"Unable to read file."</span><span class="o">}</span>
</code></pre>
  </div>
  <p>
   With the status code returned as part of the response dictionary.
  </p>
  <p>
   <em>
    Thanks to
    <a href="https://twitter.com/fwiles">
     Frank Wiles
    </a>
    for teaching me about custom exceptions, and
    <a href="https://twitter.com/webology">
     Jeff Triplett
    </a>
    for reviewing a draft of this article.
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>SSH_AUTH_SOCK, tmux and you</title><link>http://www.revsys.com/tidbits/ssh_auth_sock-tmux-and-you/</link><description>tmux is awesome. I have used it for years. This post is not about tmux but about solving a problem in the context of tmux.</description><pubDate>Sat, 30 Nov 2019 03:16:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/ssh_auth_sock-tmux-and-you/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Problem Definition
  </h2>
  <p>
   I use
   <a href="https://github.com/tmux/tmux/wiki">
    tmux
   </a>
   (nearly) every day.  One of the roles it fulfills is as a remote session persistence manager. I login to a remote host, connect to the tmux session I've been using for the last week and do
   <em>
    things
   </em>
   .  That tmux session may persist for up to a month.  Often my laptop stays at the office for days at a time.  Assuming there have been no wifi router reboot, ISP hiccup, OS update-related laptop reboot, dog snagging the power adapter out the side of the laptop, fuse on the power strip the power adapter for the laptop is plugged into blowing because the hedgehog is cold and wants to snuggle events, tmux sessions are exactly as they were when last I interacted with them!
  </p>
  <p>
   The one exception:
   <em>
    ssh
   </em>
   --specifically,
   <em>
    ssh-agent
   </em>
   forwarding.  Each request for agent forwarding is assigned a local socket named
   <code>
    /tmp/ssh-blanrndthng/agent.$( pidof local end of the agent link )
   </code>
   .  This path is assigned to the environment variable,
   <code>
    SSH_AUTH_SOCKET
   </code>
   .
  </p>
  <p>
   When a new tmux session or window is created, the main tmux process reads the environment of the initiating process.  Now, the prime directive of programs such as tmux is that it must
   <em>
    persist
   </em>
   the user's session. The best way for it to achieve this goal is to take on some daemon-like characteristics such as dissociating itself from its parent process and becoming a ward of the
   <em>
    init
   </em>
   process.  This facilitates the feature of being able to
   <em>
    disconnect
   </em>
   from a tmux client-session leaving the editor sessions, log tail windows, etc. in whatever state they are currently in.
  </p>
  <div class="codehilite">
   <pre><span></span><code>:#<span class="w"> </span>ps<span class="w"> </span>f<span class="w"> </span>-o<span class="w"> </span>session,pid,ppid,cmd<span class="w"> </span>-s<span class="w"> </span><span class="m">26804</span>,26761
<span class="w"> </span>SESS<span class="w">   </span>PID<span class="w">  </span>PPID<span class="w"> </span>CMD
<span class="m">26761</span><span class="w"> </span><span class="m">26761</span><span class="w">     </span><span class="m">1</span><span class="w"> </span>tmux
<span class="m">26804</span><span class="w"> </span><span class="m">26804</span><span class="w"> </span><span class="m">26761</span><span class="w">  </span><span class="se">\_</span><span class="w"> </span>-bash
<span class="m">26804</span><span class="w"> </span><span class="m">28003</span><span class="w"> </span><span class="m">26804</span><span class="w">      </span><span class="se">\_</span><span class="w"> </span>ps<span class="w"> </span>f<span class="w"> </span>-o<span class="w"> </span>session,pid,ppid,cmd<span class="w"> </span>-s<span class="w"> </span><span class="m">2680</span>
</code></pre>
  </div>
  <p>
   When tmux creates a new session (usually a shell), the shell environment
   <em>
    as that tmux process knows it
   </em>
   is passed to that session which is the state of the environment when the master tmux process was created.
  </p>
  <p>
   If I have an active/connected terminal connection from the laptop to this host, there is no problem.  ssh works as expected.  It is when the user disconnects from all sessions that things get out of sync.   So, let's say, one of the aforementioned events has occurred.  Everything is back up.  Systems are rebooted.  Blah blah blah.  I do the ssh dance and am reconnected to the remote host, reattach the tmux client-session I was working with previously.  Everything works great until
   <em>
    ssh-agent
   </em>
   comes into play:
   <code>
    Error connecting to agent: No such file or directory
   </code>
   .
  </p>
  <p>
   Fuck.
  </p>
  <p>
   <code>
    &lt;Ctrl&gt;-Bc
   </code>
   (new tmux window with properly set SSH_AUTH_SOCK)
  </p>
  <p>
   <code>
    git push
   </code>
  </p>
  <p>
   <code>
    &lt;Ctrl&gt;-Bp
   </code>
   (return to previous window)
  </p>
  <p>
   <code>
    &lt;Ctrl&gt;-D
   </code>
   (exit shell/close window with unset SSH_AUTH_SOCK)
  </p>
  <hr/>
  <p>
   <strong>
    NOTE
   </strong>
  </p>
  <p>
   Absolutely nothing is malfunctioning.  There is nothing that tmux or ssh-agent are doing (or not doing) that they shouldn't be (or should be) doing.   When a new window/session is created, tmux learns the environment of the calling process and passes it on.  It has no mechanism (nor should it) to influence the environment of existing processes.
  </p>
  <hr/>
  <h2>
   Solution
  </h2>
  <p>
   The problem can be summed up as: there is a dynamic element involved when creating a static session.  The solution is to make this element (our erstwhile problem variable,
   <code>
    SSH_AUTH_SOCK
   </code>
   ) another static element for existing or new sessions.
  </p>
  <h3>
   check &amp; repair
  </h3>
  <ul>
   <li>
    is there a functional agent configuration (test against existing
    <code>
     SSH_AUTH_SOCK
    </code>
    present by default when ssh-agent forwarding is used)
   </li>
   <li>
    no
    <ul>
     <li>
      do nothing
     </li>
    </ul>
   </li>
   <li>
    yes
    <ul>
     <li>
      reset
      <code>
       SSH_AUTH_SOCK
      </code>
      to point to the (static) filesystem location (
      <code>
       ~/.ssh/ssh_auth_sock
      </code>
      )
     </li>
     <li>
      is this a functional agent configuration?
      <ul>
       <li>
        yes: do nothing
       </li>
       <li>
        no:
        <code>
         ln -sf ${SSH_AUTH_SOCK} $HOME/.ssh/ssh_auth_sock
        </code>
       </li>
      </ul>
     </li>
    </ul>
   </li>
  </ul>
  <p>
   <code>
    ~/.bashrc_agent
   </code>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># vim: ft=sh</span>

<span class="k">function</span><span class="w"> </span>_check_ssh_agent<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">$(</span><span class="w"> </span>ssh-add<span class="w"> </span>-l<span class="w"> </span>&gt;<span class="p">&amp;</span><span class="w"> </span>/dev/null<span class="w"> </span><span class="k">)</span>
<span class="o">}</span>

<span class="k">function</span><span class="w"> </span>set_ssh_agent<span class="o">()</span><span class="w"> </span><span class="o">{</span>

<span class="w">        </span><span class="nb">local</span><span class="w"> </span><span class="nv">SAS</span><span class="o">=</span><span class="si">${</span><span class="nv">SSH_AUTH_SOCK</span><span class="si">}</span>

<span class="w">        </span>_check_ssh_agent<span class="w"> </span><span class="o">&amp;&amp;</span>
<span class="w">                </span><span class="nb">local</span><span class="w"> </span><span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span>/.ssh/ssh_auth_sock
<span class="w">                </span>_check_ssh_agent<span class="w"> </span><span class="o">||</span>
<span class="w">                        </span>ln<span class="w"> </span>-sf<span class="w"> </span><span class="si">${</span><span class="nv">SAS</span><span class="si">}</span><span class="w"> </span><span class="nv">$HOME</span>/.ssh/ssh_auth_sock

<span class="w">        </span><span class="c1"># recall, "||" and "&amp;&amp;" operate on the 0/non-0 property</span>
<span class="w">        </span><span class="c1"># of the called function's return value. If the check succeeds</span>
<span class="w">        </span><span class="c1"># with the alternative socket path, the "ssh-add" call returns</span>
<span class="w">        </span><span class="c1"># 0, so there is nothing more to do. It is only if the alternative</span>
<span class="w">        </span><span class="c1"># path does not have a functional agent that a non-0 value will</span>
<span class="w">        </span><span class="c1"># be returned.  "&amp;&amp;" proceeds if 0 is returned. "||" proceeds</span>
<span class="w">        </span><span class="c1"># if non-0 is returned, thus, "||" is the correct glyph to</span>
<span class="w">        </span><span class="c1"># use since we have additional work to do.</span>
<span class="o">}</span>

set_ssh_agent
</code></pre>
  </div>
  <p>
   Run the check on login
  </p>
  <p>
   <code>
    ~/.bashrc
   </code>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">[</span>...<span class="o">]</span>
<span class="o">[[</span><span class="w"> </span>-f<span class="w"> </span>~/.bashrc_agent<span class="w"> </span><span class="o">]]</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>.<span class="w"> </span>~/.bashrc_agent
</code></pre>
  </div>
  <p>
   This directive lets ssh client utilities know where to look:
  </p>
  <p>
   <code>
    ~/.ssh/config
   </code>
  </p>
  <div class="codehilite">
   <pre><span></span><code>Host *
  IdentityAgent ~/.ssh/ssh_auth_sock
</code></pre>
  </div>
  <h2>
   Dark Corners
  </h2>
  <p>
   The one bit that popped into my head as I was writing this: This sort of thing should
   <em>
    never be done
   </em>
   with login accounts used by multiple individuals.  The key material belonging to the last individual to login to the account and who is
   <em>
    still logged in
   </em>
   will be what every tmux session for that account has access to.
  </p>
 </div>
</div>
]]>/></item><item><title>How to Add Django Models to the Wagtail Admin</title><link>http://www.revsys.com/tidbits/how-add-django-models-wagtail-admin/</link><description>When working with Wagtail, you might find that you&amp;#x27;re using Wagtail Page models for some of your database models, but regular Django models for others. In this post, learn how to add your Django models to the Wagtail admin to make managing multiple types of models easier.</description><pubDate>Tue, 27 Aug 2019 20:47:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/how-add-django-models-wagtail-admin/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Versions
  </h2>
  <ul>
   <li>
    Python 3.7
   </li>
   <li>
    Django 2.2
   </li>
   <li>
    Wagtail 2.6
   </li>
  </ul>
  <p>
   When working with Wagtail, you might find that you're using Wagtail Page models for some of your database models, but regular Django models for others.
  </p>
  <p>
   A built-in example of this is the Django
   <code>
    User
   </code>
   model. When you log into the Wagtail admin, you can see the Django
   <code>
    User
   </code>
   model in the
   <code>
    Settings
   </code>
   submenu. The
   <code>
    User
   </code>
   model is not a Wagtail model; it's the same
   <code>
    User
   </code>
   model you see in a Django project that doesn't use Wagtail. Wagtail just exposes it to the Admin for you.
  </p>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <img alt="users_admin.png" class="richtext-image full-width" height="360" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/users_admin.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180959Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=93df35fb46a9d154f465ebd7368227870b236e0779d221f6e5826b424448cb58" width="404"/>
 <p>
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We can do the same thing with our Django models: we can expose them to the Wagtail admin so we don't have to maintain two separate admin interfaces to manage our website content.
  </p>
  <p>
   For this example, let's assume we're working with these models:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span> 


<span class="k">class</span><span class="w"> </span><span class="nc">Pizza</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
    <span class="n">toppings</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ManyToManyField</span><span class="p">(</span><span class="s2">"Topping"</span><span class="p">)</span>


<span class="k">class</span><span class="w"> </span><span class="nc">Topping</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</code></pre>
  </div>
  <h2>
   Adding a single model
  </h2>
  <p>
   The
   <a href="http://docs.wagtail.io/en/v2.6.1/reference/contrib/modeladmin/">
    Wagtail docs
   </a>
   are pretty clear on how to accomplish this, but let's walk through the steps.
  </p>
  <p>
   First, make sure
   <code>
    wagtail.contrib.modeladmin
   </code>
   is in your
   <code>
    INSTALLED_APPS
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># settings.py </span>

<span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
    <span class="o">...</span>
    <span class="s2">"wagtail.contrib.modeladmin"</span><span class="p">,</span>
<span class="p">]</span>
</code></pre>
  </div>
  <p>
   Next, in the same app as the model you want to expose to the Wagtail admin, add a file called
   <code>
    wagtail_hooks.py
   </code>
   .
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># wagtail_hooks.py</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.contrib.modeladmin.options</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelAdmin</span><span class="p">,</span> <span class="n">modeladmin_register</span> 

<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Pizza</span>


<span class="k">class</span><span class="w"> </span><span class="nc">PizzaAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
    <span class="n">model</span> <span class="o">=</span> <span class="n">Pizza</span> 
    <span class="n">menu_label</span> <span class="o">=</span> <span class="s2">"Pizza"</span>  
    <span class="n">menu_icon</span> <span class="o">=</span> <span class="s2">"pick"</span> 
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">200</span> 
    <span class="n">add_to_settings_menu</span> <span class="o">=</span> <span class="kc">False</span> 
    <span class="n">exclude_from_explorer</span> <span class="o">=</span> <span class="kc">False</span> 
    <span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"name"</span><span class="p">,)</span>
    <span class="n">list_filter</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"toppings"</span><span class="p">,)</span>
    <span class="n">search_fields</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"name"</span><span class="p">,)</span>


<span class="n">modeladmin_register</span><span class="p">(</span><span class="n">PizzaAdmin</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Let's step through these options in the
   <code>
    ModelAdmin
   </code>
   class:
  </p>
  <ul>
   <li>
    <code>
     model
    </code>
    : The name of the model you're adding.
   </li>
   <li>
    <code>
     menu_label
    </code>
    : Leave this blank to use the
    <code>
     verbose_name_plural
    </code>
    from your model. Give it a value to specify a new label for the Wagtail menu.
   </li>
   <li>
    <code>
     menu_icon
    </code>
    : Every menu item in the Wagtail admin has an icon, and you can specify the one you want to use. Here is a
    <a href="https://thegrouchy.dev/general/2015/12/06/wagtail-streamfield-icons.html">
     list of the available icons
    </a>
    .
   </li>
   <li>
    <code>
     menu_order
    </code>
    : What order you want this model to appear in. 000 is first, 100 is second, etc. Note: if you add multiple models to the admin, you won't get an error if two of them have the same
    <code>
     menu_order
    </code>
    ; Wagtail will just pick for you.
   </li>
   <li>
    <code>
     add_to_settings_menu
    </code>
    : Whether you want this menu item to appear in the
    <strong>
     Settings
    </strong>
    submenu in the Wagtail admin.
   </li>
   <li>
    <code>
     exclude_from_explorer
    </code>
    : Set to True if you
    <strong>
     do not
    </strong>
    want the explorer (the search box in the admin) to return results from this model. Set to False if you
    <strong>
     do
    </strong>
    want the explorer to return results from this model. (It's confusing.)
   </li>
   <li>
    <code>
     list_display
    </code>
    : Same as the Django admin; list the fields you want to display on the listing page for this model in the Wagtail admin.
   </li>
   <li>
    <code>
     list_filter
    </code>
    : Same as the Django admin; supply the fields you want to use to filter in the sidebar of the Wagtail admin.
   </li>
   <li>
    <code>
     search_fields
    </code>
    : Same as the Django admin; supply the fields that you want the explorer to use to return search results.
   </li>
  </ul>
  <p>
   The final step is to register the admin class. Once you've done that and started your server, you'll be able to see your model in the Wagtail admin:
  </p>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <img alt="pizzas_admin.png" class="richtext-image full-width" height="328" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/pizzas_admin.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180959Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=58c41026a3aff67483960f0a06ae5805e259a74ab336b70df1f96a33746a2f91" width="800"/>
 <p>
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Adding related models
  </h2>
  <p>
   In our example models, we have two models:
   <code>
    Pizza
   </code>
   and
   <code>
    Toppings
   </code>
   . We could manually add the
   <code>
    Topping
   </code>
   model to the Wagtail admin and have it appear just below the
   <code>
    Pizza
   </code>
   model. We just learned how!
  </p>
  <p>
   But it's so closely related to the
   <code>
    Pizza
   </code>
   model that it might be nice if we were able to relate those two models together in a submenu, kind of like how
   <strong>
    Settings
   </strong>
   is its own submenu in the admin that contains Users, Redirects, Sites, etc.
  </p>
  <p>
   Go back to
   <code>
    wagtail_hooks.py
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># wagtail_hooks.py</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.contrib.modeladmin.options</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
    <span class="n">ModelAdmin</span><span class="p">,</span> 
    <span class="n">ModelAdminGroup</span><span class="p">,</span> 
    <span class="n">modeladmin_register</span> 
<span class="p">)</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Pizza</span><span class="p">,</span> <span class="n">Topping</span>


<span class="k">class</span><span class="w"> </span><span class="nc">PizzaAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
    <span class="o">...</span>
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">000</span> 
    <span class="o">...</span>


<span class="k">class</span><span class="w"> </span><span class="nc">ToppingAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
    <span class="n">model</span> <span class="o">=</span> <span class="n">Topping</span> 
    <span class="n">menu_label</span> <span class="o">=</span> <span class="s2">"Toppings"</span>  
    <span class="n">menu_icon</span> <span class="o">=</span> <span class="s2">"edit"</span> 
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">100</span> 
    <span class="n">add_to_settings_menu</span> <span class="o">=</span> <span class="kc">False</span> 
    <span class="n">exclude_from_explorer</span> <span class="o">=</span> <span class="kc">False</span> 
    <span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"name"</span><span class="p">,)</span>
    <span class="n">search_fields</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"name"</span><span class="p">,)</span>
</code></pre>
  </div>
  <p>
   Relating our two models together starts off in the same way: we create a class that inherits from
   <code>
    ModelAdmin
   </code>
   for each model and identify the necessary attributes like
   <code>
    model
   </code>
   and
   <code>
    menu_icon
   </code>
   to control things like their listing pages and search behavior.
  </p>
  <p>
   Then, we add a new class that inherits from
   <code>
    ModelAdminGroup
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># wagtail_hooks.py</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.contrib.modeladmin.options</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
    <span class="n">ModelAdmin</span><span class="p">,</span> 
    <span class="n">ModelAdminGroup</span><span class="p">,</span> 
    <span class="n">modeladmin_register</span> 
<span class="p">)</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Pizza</span><span class="p">,</span> <span class="n">Topping</span>


<span class="k">class</span><span class="w"> </span><span class="nc">PizzaAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
    <span class="o">...</span>
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">000</span> 
    <span class="o">...</span>


<span class="k">class</span><span class="w"> </span><span class="nc">ToppingAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
    <span class="o">...</span>
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">100</span> 
    <span class="o">...</span> 

<span class="k">class</span><span class="w"> </span><span class="nc">PizzaGroup</span><span class="p">(</span><span class="n">ModelAdminGroup</span><span class="p">):</span>
    <span class="n">menu_label</span> <span class="o">=</span> <span class="s2">"Pizzas"</span> 
    <span class="n">menu_icon</span> <span class="o">=</span> <span class="s2">"pick"</span>
    <span class="n">menu_order</span> <span class="o">=</span> <span class="mi">500</span> 
    <span class="n">items</span> <span class="o">=</span> <span class="p">(</span><span class="n">PizzaAdmin</span><span class="p">,</span> <span class="n">ToppingAdmin</span><span class="p">)</span>


<span class="n">modeladmin_register</span><span class="p">(</span><span class="n">PizzaGroup</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   In the
   <code>
    PizzaGroup
   </code>
   class, we have some of the same attributes:
  </p>
  <ul>
   <li>
    <code>
     menu_label
    </code>
    : We set what we want this group of related models to be called in the Wagtail admin menu
   </li>
   <li>
    <code>
     menu_icon
    </code>
    : Which icon we want to use for this menu
   </li>
   <li>
    <code>
     menu_order
    </code>
    : Where we want this menu to appear in the sidebar, in relation to the other menu items
   </li>
  </ul>
  <p>
   We also add a new attribute,
   <code>
    items
   </code>
   , where we list which
   <code>
    ModelAdmin
   </code>
   classes we want to be part of this group. In our case, we want
   <code>
    PizzaAdmin
   </code>
   and
   <code>
    ToppingAdmin
   </code>
   to be in this group, so we add those.
  </p>
  <p>
   Note the change we made to
   <code>
    menu_order
   </code>
   in
   <code>
    PizzaAdmin
   </code>
   and
   <code>
    ToppingAdmin
   </code>
   : Now those are set to
   <code>
    000
   </code>
   and
   <code>
    100
   </code>
   . When the
   <code>
    ModelAdmin
   </code>
   classes will be part of a group, set the
   <code>
    menu_order
   </code>
   how you want them to relate to each other, not to the other menu items in the Wagtail admin. Then set the
   <code>
    menu_order
   </code>
   for the
   <code>
    ModelAdminGroup
   </code>
   class to the proper value for the order you want it to appear in the side menu in the admin.
  </p>
  <p>
   Then we register the whole group, instead of the
   <code>
    ModelAdmin
   </code>
   classes individually, to the Wagtail admin. When we reload the admin, we see this:
  </p>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <img alt="pizzas_group_admin.png" class="richtext-image full-width" height="300" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/pizzas_group_admin.width-800.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T180959Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=9454ffc7667d1b896654bf9a56d857ff9605324cab6b92ecae0d2ea5ae33d7b3" width="800"/>
 <p>
 </p>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   On the far left, there is a new menu item
   <strong>
    Pizzas
   </strong>
   that expands a submenu. The submenu contains links to the admin interfaces for
   <strong>
    Pizzas
   </strong>
   and
   <strong>
    Toppings
   </strong>
   !
  </p>
  <p>
   <strong>
    Note
   </strong>
   : If you have the Django admin enabled and have your models already in the Django admin, this doesn't disable them from the regular Django admin. You are free to access your models in both the Wagtail admin and the Django admin, or at this point you can choose to remove your models from the Django admin (or disable the Django admin altogether, if you prefer).
  </p>
  <h2>
   Helpful Links
  </h2>
  <ul>
   <li>
    <a href="http://docs.wagtail.io/en/v2.6.1/reference/contrib/modeladmin/">
     Wagtail docs on ModelAdmin
    </a>
   </li>
   <li>
    <a href="https://thegrouchy.dev/general/2015/12/06/wagtail-streamfield-icons.html">
     A List of Wagtail StreamField Icons
    </a>
   </li>
  </ul>
  <p>
   <em>
    Special thanks to Jeff Triplett and Jacob Burch for their help with this post.
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Using Different Read and Write Serializers in Django REST Framework</title><link>http://www.revsys.com/tidbits/using-different-read-and-write-serializers-django-rest-framework/</link><description>On a recent project, we needed to use different serializers for GET vs POST/PUT/PATCH requests to our API. Read on to learn how we used a mixin to accomplish this goal.</description><pubDate>Tue, 20 Aug 2019 20:47:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/using-different-read-and-write-serializers-django-rest-framework/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <strong>
    Versions
   </strong>
   :
  </p>
  <ul>
   <li>
    Python 3.7
   </li>
   <li>
    Django 2.2
   </li>
   <li>
    Django REST Framework 3.10
   </li>
  </ul>
  <p>
   On a recent project, we needed to use different serializers for GET vs. POST/PUT/PATCH requests to our Django REST Framework API. In our case, this was because the GET serializer contained a lot of nested data; for example, it contained expanded fields from other serializers to foreign-key relationships. The requests to update data via the API, though, didn't need these expanded fields.
  </p>
  <p>
   The first way we approached using different serializers for read and update actions was to override
   <code>
    get_serializer_class()
   </code>
   on each viewset to decide which serializer to return depending on the action in the request. We returned the "read" serializer for
   <code>
    list
   </code>
   and
   <code>
    retrieve
   </code>
   actions, and the "update" serializer for everything else. (The full list of API actions is
   <a href="https://github.com/encode/django-rest-framework/blob/335054a5d36b352a58286b303b608b6bf48152f8/rest_framework/schemas/coreapi.py#L39">
    in the DRF codebase
   </a>
   .) But we wound up repeating ourselves across several viewsets, so we wrote a mixin to take care of some of this work for us!
  </p>
  <p>
   A mixin is a Python class that contains custom attributes and methods (
   <a href="https://easyaspython.com/mixins-for-fun-and-profit-cb9962760556?gi=3875fd6a6ff8">
    more explanation
   </a>
   ). It's not very useful on its own, but when it's inherited into a class, that class has access to the mixin's special attributes and methods.
  </p>
  <p>
   This was our mixin:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">ReadWriteSerializerMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="w">    </span><span class="sd">"""</span>
<span class="sd">    Overrides get_serializer_class to choose the read serializer</span>
<span class="sd">    for GET requests and the write serializer for POST requests.</span>

<span class="sd">    Set read_serializer_class and write_serializer_class attributes on a</span>
<span class="sd">    viewset. </span>
<span class="sd">    """</span>

    <span class="n">read_serializer_class</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="n">write_serializer_class</span> <span class="o">=</span> <span class="kc">None</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_serializer_class</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>        
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"create"</span><span class="p">,</span> <span class="s2">"update"</span><span class="p">,</span> <span class="s2">"partial_update"</span><span class="p">,</span> <span class="s2">"destroy"</span><span class="p">]:</span>
            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_write_serializer_class</span><span class="p">()</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_read_serializer_class</span><span class="p">()</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_read_serializer_class</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="bp">self</span><span class="o">.</span><span class="n">read_serializer_class</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">,</span> <span class="p">(</span>
            <span class="s2">"'</span><span class="si">%s</span><span class="s2">' should either include a `read_serializer_class` attribute,"</span>
            <span class="s2">"or override the `get_read_serializer_class()` method."</span>
            <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span>
        <span class="p">)</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">read_serializer_class</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_write_serializer_class</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="bp">self</span><span class="o">.</span><span class="n">write_serializer_class</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">,</span> <span class="p">(</span>
            <span class="s2">"'</span><span class="si">%s</span><span class="s2">' should either include a `write_serializer_class` attribute,"</span>
            <span class="s2">"or override the `get_write_serializer_class()` method."</span>
            <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span>
        <span class="p">)</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">write_serializer_class</span>
</code></pre>
  </div>
  <p>
   This mixin defines two new attributes,
   <code>
    read_serializer_class
   </code>
   and
   <code>
    write_serializer_class
   </code>
   . Each attribute has a corresponding method to catch the error where the mixin is being used, but those attributes haven't been set. The
   <code>
    get_*_serializer_class()
   </code>
   methods will raise an
   <code>
    AssertionError
   </code>
   if your viewset hasn't set the appropriate attribute or overridden the necessary method.
  </p>
  <p>
   The
   <code>
    get_serializer_class
   </code>
   method makes the final decision on which serializer to use. For the "update" actions to the API, it returns
   <code>
    write_serializer_class
   </code>
   ; otherwise it returns
   <code>
    read_serializer_class
   </code>
   .
  </p>
  <p>
   The mixin gets used in a viewset like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework</span><span class="w"> </span><span class="kn">import</span> <span class="n">viewsets</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">.mixins</span><span class="w"> </span><span class="kn">import</span> <span class="n">ReadWriteSerializerMixin</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">MyModel</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">.serializers</span><span class="w"> </span><span class="kn">import</span> <span class="n">ModelReadSerializer</span><span class="p">,</span> <span class="n">ModelWriteSerializer</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MyModelViewSet</span><span class="p">(</span><span class="n">ReadWriteSerializerMixin</span><span class="p">,</span> <span class="n">viewsets</span><span class="o">.</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">queryset</span> <span class="o">=</span> <span class="n">MyModel</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span> 
    <span class="n">read_serializer_class</span> <span class="o">=</span> <span class="n">ModelReadSerializer</span> 
    <span class="n">write_serializer_class</span> <span class="o">=</span> <span class="n">ModelWriteSerializer</span>
</code></pre>
  </div>
  <p>
   Now the viewset
   <code>
    MyModelViewSet
   </code>
   has access to the attributes and methods from the mixin
   <code>
    ReadWriteSerializerMixin
   </code>
   . This means that when a call is made to the API that uses
   <code>
    MyModelViewSet
   </code>
   , the
   <code>
    get_serializer_class()
   </code>
   method from
   <code>
    ReadWriteSerializerMixin
   </code>
   will automatically be called and will decide, based on the kind of API request being made, which serializer to use. If we needed to make even more granular decisions about the serializer returned (maybe we want to use a more limited serializer for a
   <code>
    list
   </code>
   request and one with more data in a
   <code>
    retrieve
   </code>
   request), then our viewset can override
   <code>
    get_write_serializer_class()
   </code>
   to add that logic.
  </p>
  <p>
   Note: Custom DRF actions will contain actions that aren't part of the DRF list of accepted actions (because they are custom actions you're creating), so when you call
   <code>
    get_serializer_class
   </code>
   from inside your action method, it will return whatever your "default" serializer class is. In the example above, the "default" serializer is the
   <code>
    read_serializer_class
   </code>
   because it's what we return when we fall through the other conditional.
  </p>
  <p>
   Depending on your action, you will want to override
   <code>
    get_serializer_class
   </code>
   to change your default method or explicitly account for your custom action.
  </p>
  <p>
   Mixins are a DRY (Don't Repeat Yourself) way to add functionality that you wind up needing to use across several viewsets. We hope you get to experiment with using them soon!
  </p>
  <p>
   <em>
    Thanks to Jeff Triplett for his help with this post.
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Dataclasses and attrs: when and why</title><link>http://www.revsys.com/tidbits/dataclasses-and-attrs-when-and-why/</link><description>Python 3.7 introduced dataclasses, which design is based on the &amp;quot;attrs&amp;quot; library. This article will show the way I use dataclasses and attrs, why I think you should use both, and why I think attrs is still very relevant.</description><pubDate>Tue, 04 Jun 2019 20:40:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/dataclasses-and-attrs-when-and-why/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Python 3.7 introduced
   <a href="https://docs.python.org/3/library/dataclasses.html">
    dataclasses
   </a>
   (
   <a href="https://www.python.org/dev/peps/pep-0557/">
    PEP557
   </a>
   ). Dataclasses can be a convenient way to generate classes whose primary goal is to contain values.
  </p>
  <p>
   The design of dataclasses is based on the pre-existing
   <code>
    attr.s
   </code>
   library. In fact Hynek Schlawack, the very same author of attrs, helped with the writing of PEP557.
  </p>
  <p>
   Basically dataclasses are a slimmed-down version of attrs. Whether this is an improvement or not really depends on your specific use-case.
  </p>
  <p>
   I think the addition of dataclasses to the standard library makes attrs even more relevant. The way I see it is that one is a subset of the other, and having both options is a good thing. You should probably use both in your project, according to the level of formality you want in that particular piece of code.
  </p>
  <p>
   In this article I will show the way I use dataclasses and attrs, why I think you should use both, and why I think attrs is still very relevant.
  </p>
  <h3>
   What do they do
  </h3>
  <p>
   Both the standard library's dataclasses and the
   <code>
    attrs
   </code>
   library provide a way to define what I'll call "structured data types" (I would put
   <code>
    namedtuple
   </code>
   ,
   <code>
    dict
   </code>
   and
   <code>
    typeddict
   </code>
   in the same family)
  </p>
  <p>
   PS: There's probably some more correct CS term for them, but I didn't go to CS School, so &macr;\
   <em>
    (ツ)
   </em>
   /&macr;
  </p>
  <p>
   They are all variations on the same concept: a class representing a data type containing multiple values, each value addressed by some kind of key.
  </p>
  <p>
   They also do a few more useful things: they provide ordering, serialization, and a nice string representation. But for the most part, the most useful purpose is adding a certain degree of formalization to a group of values that need to be passed around.
  </p>
  <h3>
   An example
  </h3>
  <p>
   I think an example would better illustrate what I use dataclasses and attrs for.
Suppose you want to render a template containing a table. You want to make sure the table has a title, a description, and rows:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">render_document</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">caption</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]):</span>
    <span class="k">return</span> <span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">({</span>
        <span class="s2">"title"</span> <span class="p">:</span> <span class="n">title</span><span class="p">,</span>
        <span class="s2">"caption"</span><span class="p">:</span> <span class="n">caption</span><span class="p">,</span>
        <span class="s2">"data"</span><span class="p">:</span> <span class="n">data</span><span class="p">,</span>
<span class="p">})</span>
</code></pre>
  </div>
  <p>
   Now, suppose you want to render a document, which consists of a title, description, status ("draft", "in review", "approved"), and a list of tables. How would you pass the tables to
   <code>
    render_document
   </code>
   ?
  </p>
  <p>
   You may choose to represent each table as a
   <code>
    dict
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="p">{</span>
    <span class="s2">"title"</span><span class="p">:</span> <span class="s2">"My Table"</span><span class="p">,</span>
    <span class="s2">"caption"</span><span class="p">:</span> <span class="s2">"2019 Earnings"</span><span class="p">,</span>
    <span class="s2">"data"</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span><span class="s2">"Period"</span><span class="p">:</span> <span class="s2">"QT1"</span><span class="p">,</span> <span class="s2">"Europe"</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span> <span class="s2">"USA"</span><span class="p">:</span> <span class="mi">467</span><span class="p">},</span>
        <span class="p">{</span><span class="s2">"Period"</span><span class="p">:</span> <span class="s2">"QT2"</span><span class="p">,</span> <span class="s2">"Europe"</span><span class="p">:</span> <span class="mi">345</span><span class="p">,</span> <span class="s2">"USA"</span><span class="p">:</span> <span class="mi">765</span><span class="p">},</span>
    <span class="p">]</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   But how would you express the type annotation for the
   <code>
    tables
   </code>
   argument so that it's correct, explicit and simple to understand?
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">render_document</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">description</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">tables</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]):</span>
    <span class="k">return</span> <span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">({</span>
        <span class="s2">"title"</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
        <span class="s2">"description"</span><span class="p">:</span> <span class="n">description</span><span class="p">,</span>
        <span class="s2">"status"</span><span class="p">:</span> <span class="n">status</span><span class="p">,</span>
        <span class="s2">"tables"</span><span class="p">:</span> <span class="n">tables</span><span class="p">,</span>
    <span class="p">})</span>
</code></pre>
  </div>
  <p>
   That only gets us to describe the first level if
   <code>
    tables
   </code>
   . It doesn't tell us that a
   <code>
    Table
   </code>
   has a title, or caption. Instead, you could use a dataclass:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@dataclass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Table</span><span class="p">:</span>
    <span class="n">title</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]</span>
    <span class="n">caption</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">""</span>

<span class="k">def</span><span class="w"> </span><span class="nf">render_document</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">description</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">tables</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Table</span><span class="p">]):</span>
    <span class="k">return</span> <span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">({</span>
        <span class="s2">"title"</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
        <span class="s2">"description"</span><span class="p">:</span> <span class="n">description</span><span class="p">,</span>
        <span class="s2">"tables"</span><span class="p">:</span> <span class="n">tables</span><span class="p">,</span>
    <span class="p">})</span>
</code></pre>
  </div>
  <p>
   This way we have type hinting, helping our IDE helping us.
  </p>
  <p>
   But we can go one step further, and also provide type validation at runtime. This is where dataclasses stops, and attrs comes in:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@attr</span><span class="o">.</span><span class="n">s</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Table</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="n">title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">(</span><span class="n">validator</span><span class="o">=</span><span class="n">attr</span><span class="o">.</span><span class="n">validators</span><span class="o">.</span><span class="n">instance_of</span><span class="p">(</span><span class="nb">str</span><span class="p">))</span>  <span class="c1"># don't you pass no bytes!</span>
    <span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">(</span><span class="n">validator</span><span class="o">=...</span><span class="p">)</span>
    <span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">(</span><span class="n">validator</span><span class="o">=</span><span class="n">attr</span><span class="o">.</span><span class="n">validators</span><span class="o">.</span><span class="n">instance_of</span><span class="p">(</span><span class="nb">str</span><span class="p">),</span> <span class="n">default</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">render_document</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">description</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">tables</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Table</span><span class="p">]):</span>
    <span class="k">return</span> <span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">({</span>
        <span class="s2">"title"</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
        <span class="s2">"description"</span><span class="p">:</span> <span class="n">description</span><span class="p">,</span>
        <span class="s2">"tables"</span><span class="p">:</span> <span class="n">tables</span><span class="p">,</span>
    <span class="p">})</span>
</code></pre>
  </div>
  <p>
   Now, suppose we also need to render a "Report", which is a collection of "Document"s. You can probably see where this is going:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@dataclass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Table</span><span class="p">:</span>
    <span class="n">title</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]</span>
    <span class="n">caption</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">""</span>

<span class="nd">@attr</span><span class="o">.</span><span class="n">s</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Document</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="n">status</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">(</span><span class="n">validators</span><span class="o">=</span><span class="n">attr</span><span class="o">.</span><span class="n">validators</span><span class="o">.</span><span class="n">in_</span><span class="p">(</span>
        <span class="p">[</span><span class="s2">"draft"</span><span class="p">,</span> <span class="s2">"in review"</span><span class="p">,</span> <span class="s2">"approved"</span><span class="p">]</span>
    <span class="p">))</span>
    <span class="n">tables</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Table</span><span class="p">]</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="p">[])</span>

<span class="k">def</span><span class="w"> </span><span class="nf">render_report</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">documents</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Document</span><span class="p">]):</span>
    <span class="k">return</span> <span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">({</span>
        <span class="s2">"title"</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
        <span class="s2">"documents"</span><span class="p">:</span> <span class="n">documents</span><span class="p">,</span>
    <span class="p">})</span>
</code></pre>
  </div>
  <p>
   Note how I am validating that
   <code>
    Document.status
   </code>
   is one of the allowed values. This comes particularly handy when you're building abstractions on top of Django models with a field that uses
   <code>
    choices
   </code>
   . Dataclasses can't do that.
  </p>
  <p>
   A couple of patterns I keep finding myself in are the following:
  </p>
  <ol>
   <li>
    Write a function that accepts some arguments
   </li>
   <li>
    Group some of the arguments into a
    <code>
     tuple
    </code>
   </li>
   <li>
    Hm, I want field names -&gt;
    <code>
     namedtuple
    </code>
    .
   </li>
   <li>
    Hm, I want types -&gt;
    <code>
     dataclass
    </code>
    .
   </li>
   <li>
    Hm, I want validation -&gt;
    <code>
     attrs
    </code>
    .
   </li>
  </ol>
  <p>
   Another situation that happens quite often is this:
  </p>
  <ol>
   <li>
    write a function that accepts some arguments
   </li>
   <li>
    add typing so my IDE can help me out
   </li>
   <li>
    oh, by the way, it needs to support a list of those things, not just one at a time!
   </li>
   <li>
    refactor to use dataclasses
   </li>
   <li>
    This argument can only be one of those values, or
    <ol>
     <li>
      I ask myself: How do I make sure other developers are passing the right type and/or values?
     </li>
    </ol>
   </li>
   <li>
    switch to attrs
   </li>
  </ol>
  <p>
   Sometimes I stop at the dataclasses. Lots of times I get to the attrs step.
  </p>
  <p>
   And sometimes, this happens:
1. one half of this legacy codebase uses
   <code>
    -1
   </code>
   as special value for
   <code>
    False
   </code>
   , that other half uses
   <code>
    False
   </code>
   . Switch to
   <code>
    attr.s
   </code>
   so I can use
   <code>
    converter=
   </code>
   to normalize.
  </p>
  <h3>
   Comparison
  </h3>
  <p>
   The two libraries do appear very similar. To get a clearer picture of how they compare, I've made a table of the features I use most:
  </p>
  <table>
   <thead>
    <tr>
     <th>
      feature
     </th>
     <th>
      dataclasses
     </th>
     <th>
      attrs
     </th>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>
      frozen
     </td>
     <td>
      &checkmark;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      defaults
     </td>
     <td>
      &checkmark;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      totuple
     </td>
     <td>
      &checkmark;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      todict
     </td>
     <td>
      &checkmark;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      validators
     </td>
     <td>
      &cross;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      converters
     </td>
     <td>
      &cross;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
    <tr>
     <td>
      slotted classes
     </td>
     <td>
      &cross;
     </td>
     <td>
      &checkmark;
     </td>
    </tr>
   </tbody>
  </table>
  <p>
   As you can see, there's a lot of overlap. But the additional features on
   <code>
    attrs
   </code>
   provide functionality that I need more often than not.
  </p>
  <h3>
   When to use dataclasses
  </h3>
  <p>
   Dataclasses are just about the "shape" of the data.
Choose dataclasses if:
  </p>
  <ul>
   <li>
    You don't care about values in the fields, only their type
   </li>
   <li>
    adding a dependency is not trivial
   </li>
  </ul>
  <h3>
   When to use attrs
  </h3>
  <p>
   attrs is about the shape
   <em>
    and
   </em>
   the values.
Choose attrs if:
  </p>
  <ul>
   <li>
    you want to validate values. A common case would be the equivalent of a ChoiceField.
   </li>
   <li>
    you want to normalize, or sanitize the input
   </li>
   <li>
    whenever you want more formalization than dataclasses alone can offer
   </li>
   <li>
    you are concerned about memory and performances.
    <code>
     attrs
    </code>
    can create
    <a href="https://www.attrs.org/en/stable/examples.html#slots">
     slotted classes
    </a>
    , which are
    <a href="http://threeofwands.com/attrs-ii-slots/">
     optimized by CPython
    </a>
    .
   </li>
  </ul>
  <p>
   I often find myself using dataclasses and later switching to attr.s because the requirements changed or I find out I need to guard against some particular value. I think that's a normal aspect of developing software and what I call "continuous refactoring".
  </p>
  <h3>
   Why I like dataclasses
  </h3>
  <p>
   I'm glad dataclasses have been added to the standard library, and I think it's a beneficial addition. It's a very convenient thing to have at your disposal whenever you need.
  </p>
  <p>
   For one, it will encourage a more structured style of programming from the beginning.
  </p>
  <p>
   But I think the most compelling case is a practical one. Some high-risk corporate environments (eg: financial institutions) require every package to be vetted (with good reason: we've already had incidents of malicious code in libraries). That means that adding attrs is not as simple as adding a line to your
   <code>
    requirements.txt
   </code>
   , and will involve waiting on approval from your corpops team. Those developers can use dataclasses right away and their code will immediately benefit from using more formalized data types.
  </p>
  <h3>
   Why I like attrs
  </h3>
  <p>
   Most people don't work in such strictly-controlled environments.
  </p>
  <p>
   And sure, sometimes you don't need all the features from attrs, but it doesn't hurt having them.
  </p>
  <p>
   More often than not, I end up needing them anyway, as I formalize more and more of my code's API. Dataclasses only gets half-way of where I want to go.
  </p>
  <h2>
   Conclusion
  </h2>
  <p>
   I think dataclasses encompass only a subset of what attrs has to offer. Admittedly, it is a
   <em>
    big
   </em>
   subset. But the features that are not covered are important enough and needed often enough that they make attrs not only still relevant and useful, but also necessary.
  </p>
  <p>
   In my mind, using both allows developers to progressively refactor their code, moving the contracts amongst functions from loosely-defined arguments all the way up to formally described data structures as the requirements of the app stabilize over time.
  </p>
  <p>
   One nice effect of having dataclasses is that now developers are more incentivized to refactor their code toward more formalization. At some point dataclasses is not going to be enough, and that's when developers will refactor to use attrs. In this way, dataclasses actually acts as an
   <em>
    introduction
   </em>
   to attrs. I wouldn't be surprised if attrs becomes
   <em>
    more
   </em>
   popular thanks to dataclasses.
  </p>
  <h2>
   References
  </h2>
  <ul>
   <li>
    <a href="https://docs.python.org/3/library/dataclasses.html">
     Documentation on dataclasses
    </a>
    +
    <a href="https://www.python.org/dev/peps/pep-0557/">
     PEP557
    </a>
   </li>
   <li>
    <a href="https://www.attrs.org/en/stable/index.html">
     Documentation on attrs
    </a>
   </li>
   <li>
    <a href="https://www.attrs.org/en/stable/why.html#data-classes">
     "Why not dataclasses" on attrs' docs
    </a>
   </li>
  </ul>
  <h2>
   Acknowledgments + Thanks
  </h2>
  <p>
   I want to thank the following people for revising drafts and providing input and insights:
  </p>
  <ul>
   <li>
    Hynek Schlawack
   </li>
   <li>
    Jacob Kaplan-Moss
   </li>
   <li>
    Jacob Burch
   </li>
   <li>
    Jeff Triplett
   </li>
  </ul>
 </div>
</div>
]]>/></item><item><title>jetstack/cert-manager on GKE Private Clusters</title><link>http://www.revsys.com/tidbits/jetstackcert-manager-gke-private-clusters/</link><description>how to train your validating admission controller webhook without losing a hand</description><pubDate>Fri, 12 Apr 2019 03:08:23 +0000</pubDate><guid>http://www.revsys.com/tidbits/jetstackcert-manager-gke-private-clusters/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
 </p>
 <p>
  <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">
   Kubernetes Admission Controllers
  </a>
 </p>
 <p>
 </p>
 <p>
  If, dear reader, you are not familiar with this controller-type, I encourage you to hit up a favorite search engine--many much less obtuse descriptions of their use and implementation exist.
 </p>
 <p>
 </p>
 <p>
  This post focuses on their use by
  <a href="https://github.com/jetstack/cert-manager">
   Jetstack's cert-manager
  </a>
  controller and how to make it happy running on a GKE
  <i>
   private
  </i>
  cluster.
 </p>
 <p>
 </p>
 <h2>
  The Point
 </h2>
 <p>
  The
  <i>
   cert-manager
  </i>
  webhook process is an example of a v
  <i>
   alidating admission webhook.
  </i>
  Generically, VA webhooks are a useful tool to enforce policy decisions; a common example is denying submission of manifests requesting container images from non-private registries.
 </p>
 <h3>
  Do One Thing Well
 </h3>
 <p>
  The webhook is responsible for making sure any manifests that match one of its CRDs are syntactically and structurally valid before being submitted to the actual cert-manager controller. This takes the validation load for the controller as well as relieving it of overhead from processing connections that carry invalid manifest data.
 </p>
 <p>
 </p>
 <h2>
  The Context
 </h2>
 <p>
  Google Kubernetes Engine (private)
 </p>
 <p>
 </p>
 <p>
  The Reader's Digest version: communications from the
  <i>
   master
  </i>
  subnet is restricted. Nodes are not granted public addresses. Users are charged for Kubernetes nodes. Master functionality is provided via a shared environment.
 </p>
 <p>
 </p>
 <h2>
  The Problem
 </h2>
 <p>
  <b>
   <i>
    NOTE:
   </i>
  </b>
  cert-manager already in place
 </p>
 <p>
 </p>
 <hr/>
 <p>
  TL;DR
 </p>
 <p>
 </p>
 <p>
  The webhook registration includes connection info for the webhook process. GKE private clusters do not allow connections from the
  <i>
   master network
  </i>
  to the
  <i>
   service network
  </i>
  on port 6443.
 </p>
 <p>
 </p>
 <hr/>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>apiVersion:<span class="w"> </span>certmanager.k8s.io/v1alpha1
kind:<span class="w"> </span>ClusterIssuer
metadata:
<span class="w">  </span>annotations:<span class="w"> </span><span class="o">{}</span>
<span class="w">  </span>name:<span class="w"> </span>something
spec:
<span class="w">  </span>acme:
<span class="w">    </span>ca:<span class="w"> </span><span class="o">{}</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  ... graces us with:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>Internal<span class="w"> </span>error<span class="w"> </span>occurred:<span class="w"> </span>failed<span class="w"> </span>calling<span class="w"> </span>admission<span class="w"> </span>webhook<span class="w"> </span><span class="s2">"clusterissuers.admission.certmanager.k8s.io"</span>:<span class="w"> </span>the<span class="w"> </span>server<span class="w"> </span>is<span class="w"> </span>currently<span class="w"> </span>unable<span class="w"> </span>to<span class="w"> </span>handle<span class="w"> </span>the<span class="w"> </span>request
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  That error message is absolutely correct and just as unhelpful.
 </p>
 <p>
 </p>
 <p>
  The
  <i>
   server
  </i>
  ? Yeah... let me just find... the
  <i>
   server
  </i>
  . Are we talking about... maybe the
  <i>
   API
  </i>
  server? Or the cert-manager controller? Maybe we're just talking about the guy that just brought the check to the table...
 </p>
 <p>
 </p>
 <hr/>
 <p>
 </p>
 <p>
  Thanks to the author of an
  <a href="https://github.com/helm/charts/issues/10869">
   issue for the old cert-manager helm chart
  </a>
  , it is now common(ish?) knowledge that TCP:6443 is the listening port for the cert-manager webhook. The
  <i>
   cert-manager-webhook
  </i>
  pod runs on a non-master node. Because of the environment, user workloads aren't deployable to master nodes because... there aren't any.
 </p>
 <p>
 </p>
 <p>
  The Kube API is still a process. It runs.
 </p>
 <p>
  .
 </p>
 <p>
  .
 </p>
 <p>
  .
 </p>
 <p>
  Somewhere.
 </p>
 <p>
 </p>
 <p>
  It is where the webhook has been registered; the
  <i>
   process
  </i>
  that waits patiently for requests to validate
  <i>
   relevant manifests!
  </i>
  And it will continue to wait. Every time the API receives a properly authz'd connection with a cert-manager-related payload, the aforementioned error will be delivered because the API can't connect to the webhook service.
 </p>
 <p>
 </p>
 <h3>
  Because this needs a bonus...
 </h3>
 <p>
  When a namespace is deleted, the relevant controller goes through a house-keeping process, walking all registered CRD and built-in object-types and removing any of that object before actually deleting the namespace. The admission controllers registered with the API fire during the course of this process. If one of these fails, the namespace remains in a
  <i>
   Terminating
  </i>
  state until the failing webhook is either deregistered or it is able to eventually resolve its requests.
 </p>
 <p>
 </p>
 <p>
  Retrospectively, this makes sense, though, seeing a namespace that was deleted yesterday still present and "terminating" is rather disturbing.
 </p>
 <p>
 </p>
 <h3>
  Because the bonus needs icing...
 </h3>
 <p>
 </p>
 <p>
  The aforementioned namespace problem also rears its head when cordoning a node for upgrades. The node will never reach a state of readiness (anti-readiness) that indicates the instance is ready for destruction. (First noticed with
  <a href="https://github.com/kubernetes/kops">
   kops
  </a>
  )
 </p>
 <h2>
  The Solution
 </h2>
 <p>
 </p>
 <p>
  <i>
   GCE VPC firewall-rules
  </i>
  are created using
  <i>
   either
  </i>
  a source tag, IP range or service account. We know the source range for the
  <i>
   master network
  </i>
  from when the cluster was created (in our case: 172.16.0.0/28). The
  <i>
   target
  </i>
  can only be selected via target tag or serviceaccount.
 </p>
 <p>
 </p>
 <h3>
  obtain the GCE instance name for a cluster node
 </h3>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>gcloud<span class="w"> </span>compute<span class="w"> </span>instances<span class="w"> </span>list
</pre>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <h3>
  display the GCE tags for that node:
 </h3>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>gcloud<span class="w"> </span>compute<span class="w"> </span>instances<span class="w"> </span>describe<span class="w"> </span>--format<span class="o">=</span>json<span class="w"> </span><span class="o">[</span>name<span class="w"> </span>of<span class="w"> </span>instance<span class="o">]</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>.tags.items

<span class="o">[</span>
<span class="w">  </span><span class="s2">"gke-revsys-production-deux-a2f2de43-node"</span>
<span class="o">]</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <h3>
  create the firewall rule:
 </h3>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>gcloud<span class="w"> </span>compute<span class="w"> </span>firewall-rule<span class="w"> </span>create<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--source-ranges<span class="w"> </span><span class="m">172</span>.16.0.0/28<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>--target-tags<span class="w"> </span>gke-revsys-production-deux-a2f2de43-node<span class="w">  </span><span class="se">\</span>
<span class="w">  </span>--allow<span class="w"> </span>TCP:6443
</pre>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <p>
  That's it. With that command, 3 poorly logged, unremarked error states are done away with. I hope this short post is helpful.
 </p>
 <p>
 </p>
 <p>
  Now, on that note, go do something interesting.
 </p>
</div>
]]>/></item><item><title>Helm &amp; RBAC: Polishing the Doorknob</title><link>http://www.revsys.com/tidbits/helm-rbac-polishing-doorknob/</link><description>Because someone has to right this stuff down...</description><pubDate>Wed, 20 Feb 2019 05:59:26 +0000</pubDate><guid>http://www.revsys.com/tidbits/helm-rbac-polishing-doorknob/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
 </p>
 <p>
  Helm. Tiller. You have probably heard these words in reference to the
  <i>
   former's
  </i>
  description as the
  <i>
   Kubernetes Package Manager
  </i>
  . The later is the server/cluster-side element that
  <i>
   helm
  </i>
  communicates with to do its work. The common practice is to create a role binding between
  <i>
   tiller's
  </i>
  service account and the
  <i>
   cluster-admin
  </i>
  role. This allows for installation/management/updating-of/etc of any Kubernetes object.
 </p>
 <p>
 </p>
 <p>
  Like with most RBAC configuration that denies haphazard assignment of the
  <i>
   cluster-admin
  </i>
  role, once it works it makes sense. Getting to that state for
  <i>
   random-kubernetes-thing-X
  </i>
  can be a time-consuming and often frustrating task.
 </p>
 <p>
 </p>
 <p>
  The doc for properly setting up
  <i>
   tiller
  </i>
  in an RBAC-enabled cluster is... elusive. The following RBAC definition was observed on some issue board (gitlab-runner?)--I was fortunate enough to recognize this gem for what it was and wrote it down before loosing track of that particular issue forever.
 </p>
 <p>
 </p>
 <p>
  Whoever's brain is the origin of this knowledge, I owe you a drop of something nice.
 </p>
 <p>
 </p>
 <p>
  Apply this and remove one more thing that runs with needlessly powerful privileges!
 </p>
 <p>
 </p>
 <p>
 </p>
 <hr/>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>apiVersion:<span class="w"> </span>rbac.authorization.k8s.io/v1
kind:<span class="w"> </span>Role
metadata:
<span class="w">  </span>name:<span class="w"> </span>tiller-for-mortals
rules:
<span class="w">  </span>-
<span class="w">    </span>apiGroups:
<span class="w">      </span>-<span class="w"> </span><span class="s2">""</span>
<span class="w">    </span>resources:
<span class="w">      </span>-<span class="w"> </span>pods
<span class="w">    </span>verbs:
<span class="w">      </span>-<span class="w"> </span>list
<span class="w">  </span>-
<span class="w">    </span>apiGroups:
<span class="w">      </span>-<span class="w"> </span><span class="s2">""</span>
<span class="w">    </span>resources:
<span class="w">      </span>-<span class="w"> </span>pods/portforward
<span class="w">    </span>verbs:
<span class="w">      </span>-<span class="w"> </span>create
</pre>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
 <hr/>
 <p>
 </p>
 <p>
  Once this manifest is applied to the namespace occupied by
  <i>
   tiller,
  </i>
  a
  <i>
   rolebinding
  </i>
  is all it takes to have access to
  <i>
   helm/tiller's
  </i>
  functions.
 </p>
 <p>
 </p>
 <hr/>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>$<span class="w"> </span>kubectl<span class="w">  </span>--namespace<span class="o">=</span>tillerhut<span class="w"> </span>create<span class="w"> </span>rolebinding<span class="w"> </span>helm4stephen<span class="w"> </span><span class="se">\</span>
<span class="w">     </span>--service-account<span class="o">=</span>tokens:stephen-at-revsys<span class="w"> </span>--role<span class="o">=</span>tiller-for-mortals
</pre>
 </div>
</div>
<div class="block-content">
 <p>
 </p>
</div>
<div class="block-content">
 <p>
 </p>
 <hr/>
 <p>
 </p>
 <p>
  <b>
   NOTE
  </b>
 </p>
 <p>
 </p>
 <p>
  The constraint is not directly with
  <i>
   tiller
  </i>
  but with the
  <i>
   rolebinding
  </i>
  . This role can (and is) used to bind to a
  <i>
   gitlab-runner
  </i>
  deployment used for CI-driven deployments. The runner doesn't have direct unfettered access to the host cluster--it just has gRPC access to an internal service with unfettered access to the cluster.
 </p>
 <p>
 </p>
 <p>
  Baby steps, dig?
 </p>
 <p>
 </p>
 <p>
  Thanks for reading!
 </p>
 <p>
 </p>
 <p>
  Stephen
 </p>
</div>
]]>/></item><item><title>GKE: part 2</title><link>http://www.revsys.com/tidbits/gke-part-2/</link><description>Accessing a New GKE Cluster</description><pubDate>Mon, 04 Feb 2019 02:31:04 +0000</pubDate><guid>http://www.revsys.com/tidbits/gke-part-2/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   kubectl
  </h2>
  <p>
   <em>
    kubectl
   </em>
   should, ideally, match the server version.  At the very least, the client
binary should usually be within the
   <em>
    minor version
   </em>
   ballpark of the server.
  </p>
  <hr/>
  <p>
   NOTE: Sometimes, in the immortal words of Freddie Childress, "
   <em>
    shit be broke!
   </em>
   "
An
   <a href="https://github.com/kubernetes/kubernetes/issues/64348">
    example
   </a>
   can be found
in the
   <em>
    v1.10.x
   </em>
   family. The only known work-around is to use a client binary
from
   <em>
    v1.11.x
   </em>
   . The takeaway: your milleage may vary, but if using an off-version
and receiving strange results, try using the version-equivalent client.
  </p>
  <hr/>
  <p>
   Client binary downloads for
   <em>
    v1.11.6
   </em>
   can be found
   <a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md#downloads-for-v1116">
    here
   </a>
  </p>
  <h2>
   Authorizing the Client Environment (part 1)
  </h2>
  <p>
   It is neccessary to add the
   <em>
    Kubernetes Engine Cluster-Admin
   </em>
   role to the target (Google)
userid.  This is accomplished via the
   <em>
    IAM &amp; admin
   </em>
   section of the GCP console.
  </p>
  <ul>
   <li>
    click the pencil widget under the
    <em>
     inheritance
    </em>
    column
   </li>
   <li>
    click
    <em>
     Add Another Role
    </em>
   </li>
   <li>
    scroll or search to
    <em>
     Kubernetes Cluster Admin
    </em>
   </li>
  </ul>
  <h2>
   Authenticating the Client Environment
  </h2>
  <p>
   The
   <a href="https://cloud.google.com/sdk/install">
    Google Cloud SDK
   </a>
   is required.
  </p>
  <p>
   Once installed and configured for the desired Google account, the following commands
are used to populate
   <code>
    ~/.kube/config
   </code>
   with the correct bits to authenticate with
the new cluster:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">(</span>revsys<span class="o">)</span><span class="w"> </span><span class="o">[[</span>revsys-prod-cluster:revsys-com-playground<span class="o">]]</span>
gladiatr@s
:#<span class="w"> </span>gcloud<span class="w"> </span>container<span class="w"> </span>clusters<span class="w"> </span>list
NAME<span class="w">                    </span>LOCATION<span class="w">       </span>MASTER_VERSION<span class="w">  </span>MASTER_IP<span class="w">    </span>MACHINE_TYPE<span class="w">   </span>NODE_VERSION<span class="w">      </span>NUM_NODES<span class="w">  </span>STATUS
revsys-production-deux<span class="w">  </span>us-central1-c<span class="w">  </span><span class="m">1</span>.11.6-gke.6<span class="w">    </span><span class="m">23</span>.24.25.26<span class="w">  </span>n1-highmem-2<span class="w">   </span><span class="m">1</span>.11.6-gke.6<span class="w">      </span><span class="m">2</span><span class="w">          </span>RUNNING

:#<span class="w"> </span>gcloud<span class="w"> </span>container<span class="w"> </span>clusters<span class="w"> </span>get-credentials<span class="w"> </span>revsys-production-deux
Fetching<span class="w"> </span>cluster<span class="w"> </span>endpoint<span class="w"> </span>and<span class="w"> </span>auth<span class="w"> </span>data.
kubeconfig<span class="w"> </span>entry<span class="w"> </span>generated<span class="w"> </span><span class="k">for</span><span class="w"> </span>revsys-production-deux.

<span class="o">(</span>revsys<span class="o">)</span><span class="w"> </span><span class="o">[[</span>gke_revsys-150116_us-central1-c_revsys-production-deux:<span class="o">]]</span>
gladiatr@s
<span class="o">[</span>~/git/revsys/revsys/src<span class="o">]</span>
:#
</code></pre>
  </div>
  <p>
   <em>
    Holy progenitor of mythological figure, Batman! That's ugly!
   </em>
  </p>
  <p>
   Later (post v1.9.x?)
   <em>
    kubectl
   </em>
   binaries added the
   <em>
    very welcome
   </em>
   feature of
easily renaming kube contexts!
  </p>
  <hr/>
  <p>
   NOTE:
   <code>
    ~/.kube/config
   </code>
   is a YAML file. The outer structure is a map with most of
the values expressed as lists of single-element maps.
  </p>
  <p>
   skeleton
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">v1</span>
<span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Config</span>
<span class="nt">clusters</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span>
<span class="nt">users</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span>
<span class="nt">contexts</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span>
<span class="nt">current-context</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">str</span>
<span class="nt">preferences</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span><span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">&lt;- I have no idea what this can be used for</span>
</code></pre>
  </div>
  <p>
   meta-outline of a populated context + associated bits
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">v1</span>
<span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Config</span>
<span class="nt">clusters</span><span class="p">:</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cluster-x</span>
<span class="w">    </span><span class="nt">cluster</span><span class="p">:</span>
<span class="w">      </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://api.somewhere.on.the.internets</span>
<span class="w">      </span><span class="nt">certificate-authority-data</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">(base64 PEM certificate string)</span>
<span class="nt">users</span><span class="p">:</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">user-tokens:cluster-x-user</span>
<span class="w">    </span><span class="nt">token</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">(service account token (**no base64**))</span>
<span class="nt">contexts</span><span class="p">:</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cluster-x</span>
<span class="w">    </span><span class="nt">context</span><span class="p">:</span>
<span class="w">      </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">user-tokens:cluster-x-user</span>
<span class="w">      </span><span class="nt">cluster</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cluster-x</span>
<span class="w">      </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">(configured default namespace)(_optional_)</span>
<span class="nt">current-context</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cluster-x</span>
<span class="nt">preferences</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span>
</code></pre>
  </div>
  <p>
   The following command simply changes the name of the context. (No cluster or user
definitions were harmed in the filming of this episode):
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">(</span>revsys<span class="o">)</span><span class="w"> </span><span class="o">[[</span>gke_revsys-150116_us-central1-c_revsys-production:<span class="o">]]</span>
gladiatr@s
<span class="o">[</span>~<span class="o">]</span>
:#<span class="w"> </span>kubectl<span class="w"> </span>config<span class="w"> </span>rename-context<span class="w"> </span>gke_revsys-150116_us-central1-c_revsys-production<span class="w"> </span>revsys-prod-new

<span class="o">(</span>revsys<span class="o">)</span><span class="w"> </span><span class="o">[[</span>revsys-prod-new:<span class="o">]]</span>
gladiatr@s
<span class="o">[</span>~<span class="o">]</span>
:#
</code></pre>
  </div>
  <h2>
   Authorizing the Client Environment (part 2)
  </h2>
  <p>
   GCP IAM integration is ongoing.  Currently, however, assinging the GCP IAM role (kubeneretes-cluster-admin)
only gets you halfway to the proverbial church.
  </p>
  <p>
   To complete the authorization of the target account requires creating a kubernetes
   <em>
    cluster role binding
   </em>
   that grants full access to 100% of the kube API.
  </p>
  <p>
   Without this step, the target user account will be unable to create RBAC
   <em>
    roles
   </em>
   or
   <em>
    cluster roles
   </em>
   . The
next few posts will involve the creation and/or manipulation of such roles.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">kubectl</span><span class="w"> </span><span class="k">create</span><span class="w"> </span><span class="n">clusterrolebinding</span><span class="w"> </span><span class="n">he</span><span class="o">-</span><span class="n">man</span><span class="w"> </span><span class="c1">--clusterrole=cluster-admin --user=googleid@googledomain</span>
</code></pre>
  </div>
  <h3>
   Epilogue
  </h3>
  <p>
   This post covered the steps for setting up a local environment to allow for interacting
with the new GKE cluster.
  </p>
  <p>
   Next up:
   <em>
    the helm package manager
   </em>
  </p>
  <p>
   Thanks for reading!
  </p>
  <p>
   -Stephen
  </p>
 </div>
</div>
]]>/></item><item><title>GKE: part 1</title><link>http://www.revsys.com/tidbits/gke-part-1/</link><description>Beyond wet feet...</description><pubDate>Sun, 03 Feb 2019 13:26:53 +0000</pubDate><guid>http://www.revsys.com/tidbits/gke-part-1/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   The Cluster
  </h2>
  <hr/>
  <p>
   This series is a journal of how we currently configure our GKE clusters. It should not be construed, necessarily, as a best-practices guide but as a written account that will be augmented as knowledge is gained (or perhaps as a series of internal memos that has boundary issues).
  </p>
  <hr/>
  <p>
   The following is a list of values used with a brief explanation where the key's purpose is not obvious:
  </p>
  <ul>
   <li>
    Name (of cluster):
    <em>
     prod-cluster-deux
    </em>
   </li>
   <li>
    Location Type:
    <em>
     Zonal
    </em>
    (default)
    <ul>
     <li>
      This field affects how the cluster is laid out within the chosen availability zone
     </li>
    </ul>
   </li>
   <li>
    Region:
    <em>
     us-central1
    </em>
   </li>
   <li>
    Master Version:
    <em>
     1.11.6-gke.6
    </em>
    <ul>
     <li>
      latest release as of 02/02/2019
     </li>
    </ul>
   </li>
  </ul>
  <hr/>
  <p>
   <strong>
    NOTE
   </strong>
   : Access to the following config keys require clicking the
   <em>
    Advanced Configuration
   </em>
   widget
  </p>
  <hr/>
  <ul>
   <li>
    Availability
    <ul>
     <li>
      Additional Node Locations:
      <em>
       clear
      </em>
      (default)
     </li>
     <li>
      Maintenance Window (beta):
      <em>
       3:00 AM
      </em>
     </li>
    </ul>
   </li>
   <li>
    Networking
    <ul>
     <li>
      VPC-Native:
      <em>
       check
      </em>
     </li>
     <li>
      Network:
      <em>
       default
      </em>
     </li>
     <li>
      Subnet:
      <em>
       default
      </em>
     </li>
     <li>
      Pod Address Range:
      <em>
       blank
      </em>
      (default)
     </li>
     <li>
      Maximum Pods-Per-Node (beta):
      <em>
       110
      </em>
      (default)
     </li>
     <li>
      Enable HTTP Load Balancing:
      <em>
       clear
      </em>
     </li>
     <li>
      Network Security
     </li>
     <li>
      Private Cluster:
      <em>
       check
      </em>
     </li>
     <li>
      Enable Master Authorized Networks:
      <em>
       check
      </em>
      <ul>
       <li>
        add a reasonably stable source IP (
        <code>
         echo -en $( curl -s api.ipify.org )/32
        </code>
        )
       </li>
      </ul>
     </li>
     <li>
      Enable Network Policy:
      <em>
       check
      </em>
      <ul>
       <li>
        There is reason why having a UI toggle for a standard part of the Kube API is provided. There are people, somewhere, that know this reason. I am not among them.
       </li>
      </ul>
     </li>
    </ul>
   </li>
   <li>
    Security
    <ul>
     <li>
      Enable Basic Authentication:
      <em>
       uncheck
      </em>
     </li>
     <li>
      Issue a Client Certificate:
      <em>
       uncheck
      </em>
     </li>
     <li>
      Enable Legacy Authorization:
      <em>
       uncheck
      </em>
     </li>
     <li>
      Enable Binary Authorization (beta):
      <em>
       uncheck
      </em>
     </li>
     <li>
      Enable Application Layer Secrets Encryption (beta):
      <em>
       check
      </em>
     </li>
     <li>
      Unlike AWS, GCP does not generate default cypto keys for this sort of thing.
     </li>
     <li>
      GCP crypto keys are filed under the
      <em>
       IAM &amp; admin
      </em>
      heading.
     </li>
     <li>
      Create a keyring
     </li>
     <li>
      Create a
      <em>
       Symmetric encrypt/decrypt
      </em>
      key
     </li>
     <li>
      Copy the
      <strong>
       key's
      </strong>
      (
      <em>
       not
      </em>
      the keyring's) resource ID
      <ul>
       <li>
        available from the key's hamburger menu
       </li>
      </ul>
     </li>
     <li>
      paste the RID into the
      <em>
       Select a Customer-Managed Key
      </em>
      box (GKE UI)
     </li>
    </ul>
   </li>
   <li>
    Additional Features
    <ul>
     <li>
      Enable Stackdriver Logging Service:
      <em>
       check
      </em>
      (default)
     </li>
     <li>
      Enable Stackdrive Monitoring Service:
      <em>
       check
      </em>
      (default)
     </li>
     <li>
      Try the new Stackdriver beta Monitoring and Logging Experience:
      <em>
       check
      </em>
      (what the hell, right?)
     </li>
     <li>
      Enable Cloud TPU (beta):
      <em>
       uncheck
      </em>
      (default)
     </li>
     <li>
      Enable Kubernetes Alpha Features:
      <em>
       uncheck
      </em>
      (default)
     </li>
     <li>
      Enable Kubernetes Dashboard:
      <em>
       uncheck
      </em>
     </li>
     <li>
      Enable Istio:
      <em>
       uncheck
      </em>
     </li>
     <li>
      Enable Node Auto-Provisioning (beta):
      <em>
       uncheck
      </em>
     </li>
    </ul>
   </li>
  </ul>
  <h2>
   Egress NAT
  </h2>
  <p>
   Without a NAT, private clusters cannot establish connections external to GCP networks
  </p>
  <ul>
   <li>
    Navigate to the GCP
    <em>
     Network Services
    </em>
    -&gt;
    <em>
     Cloud NAT
    </em>
   </li>
   <li>
    <em>
     Create
    </em>
    <ul>
     <li>
      Gateway Name:
      <em>
       prod-cluster-nat
      </em>
     </li>
     <li>
      Select Cloud Router
      <ul>
       <li>
        VPC Network:
        <em>
         default
        </em>
       </li>
       <li>
        Region:
        <em>
         us-central1
        </em>
       </li>
       <li>
        Cloud Router:
        <em>
         create new cloud router
        </em>
       </li>
      </ul>
     </li>
     <li>
      NAT mapping
      <ul>
       <li>
        Source (internal):
        <em>
         Primary and secondary ranges for all subnets
        </em>
       </li>
       <li>
        NAT IP Addresses:
        <em>
         Automatic
        </em>
        (default)
       </li>
      </ul>
     </li>
    </ul>
   </li>
  </ul>
  <h2>
   Epilogue
  </h2>
  <p>
   The next part in this series will cover setting up a local environment to communicate with the new cluster.
  </p>
  <p>
   Thanks for Reading!
  </p>
  <p>
   -Stephen
  </p>
 </div>
</div>
]]>/></item><item><title>Keeping Django Models Ordered</title><link>http://www.revsys.com/tidbits/keeping-django-model-objects-ordered/</link><description>How to smartly keep a set of Django ORM objects in order for drag-n-drop like situations.</description><pubDate>Wed, 28 Nov 2018 15:25:19 +0000</pubDate><guid>http://www.revsys.com/tidbits/keeping-django-model-objects-ordered/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Most of us know how to order objects using the Django ORM using
   <code>
    order_by('field')
   </code>
   which creates a SQL query for us using
   <code>
    ORDER BY field
   </code>
   . That's not the type of
   <em>
    ordering
   </em>
   I'm going to talk
about today.
  </p>
  <p>
   I'm talking about keeping a set of objects in order with respect to each
other, without any gaps. This has come up for us a couple of times in
the last 6 months of client projects, and I've found a good pattern for
dealing with it I wanted to share with you.
  </p>
  <h2>
   Django Drag and Drop Ordering
  </h2>
  <p>
   Think of a drag-n-drop interface your app might need. You have some
subset of objects that are currently in order 1, 2, 3, 4, ..., n and
you want the item in position 4 to move into position 2. The fact that
drag and drop is the UI we're using isn't essential to the
discussion here, just an example. This pattern would also work for any arrow based
   <em>
    nudging
   </em>
   movement or even a rudimentary queue system
that may not have a visible UI to it at all.
  </p>
  <p>
   The naive approach is to loop over all the objects and adjust the order
manually. This honestly works fine for small sets of objects, but
performance will suffer if we're talking about hundreds or thousands of
elements in your set.
  </p>
  <h2>
   Setting the Stage
  </h2>
  <p>
   To give us a concrete example to work with we're going to create a
couple of models. We're going to have a
   <code>
    Task
   </code>
   model that has some
number of
   <code>
    Steps
   </code>
   that need to be completed in order. We'll be
reordering the steps in our UI often so let's make it easy
   <em>
    and
   </em>
   elegant.
  </p>
  <h3>
   Our Models
  </h3>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Task</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Step</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">task</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="n">Task</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'steps'</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">order</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>

    <span class="n">objects</span> <span class="o">=</span> <span class="n">StepManager</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   I'm purposefully keeping the number of fields small for illustration
purposes, but you would probably also want creation/modification times
and booleans for whether or not the Step or Task is completed. They aren't useful for our example here.
  </p>
  <p>
   Where you put the meat of this logic is up to you, but I think it makes
the most sense to do as a
   <a href="https://docs.djangoproject.com/en/dev/topics/db/managers/">
    manager
   </a>
   method.
  </p>
  <h2>
   Ordering Logic
  </h2>
  <p>
   Here is the manager you need, skim it and we'll dive into the details in a second.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span><span class="p">,</span> <span class="n">transaction</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.db.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">F</span>

<span class="k">class</span><span class="w"> </span><span class="nc">StepManager</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Manager</span><span class="p">):</span>
<span class="w">    </span><span class="sd">""" Manager to encapsulate bits of business logic """</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">new_order</span><span class="p">):</span>
<span class="w">        </span><span class="sd">""" Move an object to a new order position """</span>

        <span class="n">qs</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_queryset</span><span class="p">()</span>

        <span class="k">with</span> <span class="n">transaction</span><span class="o">.</span><span class="n">atomic</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">obj</span><span class="o">.</span><span class="n">order</span> <span class="o">&gt;</span> <span class="nb">int</span><span class="p">(</span><span class="n">new_order</span><span class="p">):</span>
                <span class="n">qs</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
                    <span class="n">task</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">task</span><span class="p">,</span>
                    <span class="n">order__lt</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">order</span><span class="p">,</span>
                    <span class="n">order__gte</span><span class="o">=</span><span class="n">new_order</span><span class="p">,</span>
                <span class="p">)</span><span class="o">.</span><span class="n">exclude</span><span class="p">(</span>
                    <span class="n">pk</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">pk</span>
                <span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
                    <span class="n">order</span><span class="o">=</span><span class="n">F</span><span class="p">(</span><span class="s1">'order'</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
                <span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">qs</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
                    <span class="n">task</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">task</span><span class="p">,</span>
                    <span class="n">order__lte</span><span class="o">=</span><span class="n">new_order</span><span class="p">,</span>
                    <span class="n">order__gt</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">order</span><span class="p">,</span>
                <span class="p">)</span><span class="o">.</span><span class="n">exclude</span><span class="p">(</span>
                    <span class="n">pk</span><span class="o">=</span><span class="n">obj</span><span class="o">.</span><span class="n">pk</span><span class="p">,</span>
                <span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
                    <span class="n">order</span><span class="o">=</span><span class="n">F</span><span class="p">(</span><span class="s1">'order'</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span>
                <span class="p">)</span>

            <span class="n">obj</span><span class="o">.</span><span class="n">order</span> <span class="o">=</span> <span class="n">new_order</span>
            <span class="n">obj</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   Assuming we had several steps already created for a task we can move it like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">Step</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">step_3_obj</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This would move the object to the 5th position. In the real world,
you would probably also want to ensure that the new order position is
not below 1 or beyond the total count for the steps to avoid weird gaps.
  </p>
  <p>
   Let's go through this
   <em>
    step by step
   </em>
   (pun intended):
  </p>
  <ul>
   <li>
    After getting our default
    <a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/">
     QuerySet
    </a>
    we start an atomic transaction. We want to renumber the tasks that need updating, including the one moving, all together or not at all.
    This transaction helps ensure that.
   </li>
   <li>
    We then limit our query to Steps which are part of this Task. This
    is a crucial bit and would depend on the nature of your application. If we didn't set this we would be reordering
    Steps without regard for the Tasks they were a part of which would definitely be a bug.
   </li>
   <li>
    Next, we have to make a decision. Is our new order larger or smaller
    then our current order? This determines which items need to move around.
   </li>
   <li>
    If it's larger, we need to move all of the Steps that are less than
    our current order and greater than or equal to our new order.
    Otherwise, we need the ones less than or equal to our new order position and greater than our current position.
   </li>
   <li>
    We then exclude ourselves.
   </li>
   <li>
    Then we issue an update call using
    <a href="https://docs.djangoproject.com/en/dev/ref/models/expressions/#f-expressions">
     an F()
    expression
    </a>
    to either increment or decrement the object's order by 1. F
    expressions allow us to modify each row in our query by a value contained on that row.
   </li>
   <li>
    Finally, we set our object's order directly and save.
   </li>
  </ul>
  <h2>
   Creating new objects
  </h2>
  <p>
   If we're going to have this setup, we also need to handle creating new objects putting them in order next, to do that override the create method on your manager like so:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span><span class="p">,</span> <span class="n">transaction</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.db.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">F</span><span class="p">,</span> <span class="n">Max</span>

<span class="k">class</span><span class="w"> </span><span class="nc">StepManager</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Manager</span><span class="p">):</span>
    <span class="c1"># ... previous code here ...</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">create</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="n">instance</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>

        <span class="k">with</span> <span class="n">transaction</span><span class="o">.</span><span class="n">atomic</span><span class="p">():</span>
            <span class="c1"># Get our current max order number</span>
            <span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
                <span class="n">task</span><span class="o">=</span><span class="n">instance</span><span class="o">.</span><span class="n">task</span>
            <span class="p">)</span><span class="o">.</span><span class="n">aggregate</span><span class="p">(</span>
                <span class="n">Max</span><span class="p">(</span><span class="s1">'order'</span><span class="p">)</span>
            <span class="p">)</span>

            <span class="c1"># Increment and use it for our new object</span>
            <span class="n">current_order</span> <span class="o">=</span> <span class="n">results</span><span class="p">[</span><span class="s1">'order__max'</span><span class="p">]</span>
            <span class="k">if</span> <span class="n">current_order</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
                <span class="n">current_order</span> <span class="o">=</span> <span class="mi">0</span>

            <span class="n">value</span> <span class="o">=</span> <span class="n">current_order</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="n">instance</span><span class="o">.</span><span class="n">order</span> <span class="o">=</span> <span class="n">value</span>
            <span class="n">instance</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>

            <span class="k">return</span> <span class="n">instance</span>
</code></pre>
  </div>
  <p>
   Now we can create new objects normally with
   <code>
    Step.objects.create(task=&lt;task_obj&gt;, name='Testing')
   </code>
   and it will give us the proper order.
  </p>
  <h2>
   Benefits
  </h2>
  <p>
   The most significant benefit to this pattern is that we're pushing most of the
work onto our database to do the incrementing and decrementing of all
the objects. Also, we're doing it with two fairly speedy
   <code>
    UPDATE
   </code>
   queries.
One to reset all of the intervening rows and one last one to set the
model object we're wanting to move.
  </p>
  <p>
   If we had merely looped over our objects, we would be forced to generate
an individual update query per Step which is inefficient and does not
scale well.
  </p>
  <p>
   We've also encapsulated this logic into one spot to stay DRY and make
writing tests for this logic easier.
  </p>
  <h2>
   Production Indexing
  </h2>
  <p>
   There is an index you should consider on this to make your common queries faster.  On your
   <code>
    Step
   </code>
   model you may want:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Step</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="c1"># ... original fields and manager definitions ... </span>

    <span class="k">class</span><span class="w"> </span><span class="nc">Meta</span><span class="p">:</span>
        <span class="n">index_together</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'task'</span><span class="p">,</span> <span class="s1">'order'</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   This will create a multi-column index by Task ID and the order which will make this query fast
   <code>
    Step.objects.filter(task=&lt;TaskObj&gt;).order_by('order')
   </code>
   .
  </p>
  <h2>
   DRF Ordering API
  </h2>
  <p>
   So this is all dandy, but if you're ordering some Django models with a drag and drop interface, you're doing it with Javascript so we need an API.  If we're building an API with Django we're using
   <a href="https://www.django-rest-framework.org/">
    Django REST Framework
   </a>
   so here is an easy way to implement that.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework</span><span class="w"> </span><span class="kn">import</span> <span class="n">viewsets</span><span class="p">,</span> <span class="n">status</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.decorators</span><span class="w"> </span><span class="kn">import</span> <span class="n">action</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">rest_framework.response</span><span class="w"> </span><span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django_filters.rest_framework</span><span class="w"> </span><span class="kn">import</span> <span class="n">DjangoFilterBackend</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">serializers</span>

<span class="k">class</span><span class="w"> </span><span class="nc">StepViewSet</span><span class="p">(</span><span class="n">viewsets</span><span class="o">.</span><span class="n">ModelViewSet</span><span class="p">):</span>
    <span class="n">queryset</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">Step</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">'order'</span><span class="p">)</span>
    <span class="n">serializer_class</span> <span class="o">=</span> <span class="n">serializers</span><span class="o">.</span><span class="n">StepSerializer</span>
    <span class="n">filter_backends</span> <span class="o">=</span> <span class="p">(</span><span class="n">DjangoFilterBackend</span><span class="p">,</span> <span class="p">)</span>
    <span class="n">filter_fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'task'</span><span class="p">,</span> <span class="p">)</span>

    <span class="nd">@action</span><span class="p">(</span><span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">'post'</span><span class="p">],</span> <span class="n">detail</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">):</span>
<span class="w">        </span><span class="sd">""" Move a single Step to a new position """</span>
        <span class="n">obj</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_object</span><span class="p">()</span>
        <span class="n">new_order</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'order'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>

        <span class="c1"># Make sure we received an order </span>
        <span class="k">if</span> <span class="n">new_order</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">Response</span><span class="p">(</span>
                <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'error'</span><span class="p">:</span> <span class="s1">'No order given'</span><span class="p">},</span>
                <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">,</span>
            <span class="p">)</span>

        <span class="c1"># Make sure our new order is not below one</span>
        <span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">new_order</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">Response</span><span class="p">(</span>
                <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'error'</span><span class="p">:</span> <span class="s1">'Order cannot be zero or below'</span><span class="p">},</span>
                <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">,</span>
            <span class="p">)</span>

        <span class="n">models</span><span class="o">.</span><span class="n">Step</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">new_order</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">Response</span><span class="p">({</span><span class="s1">'success'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span> <span class="s1">'order'</span><span class="p">:</span> <span class="n">new_order</span><span class="p">})</span>
</code></pre>
  </div>
  <p>
   Where these endpoints reside is going to depend on how you configure things in your Django project, but assuming you're using a DRF
   <code>
    SimpleRouter
   </code>
   and have things setup on
   <code>
    /api/
   </code>
   your URL patterns are going to be something like:
  </p>
  <ul>
   <li>
    <code>
     GET /api/steps/
    </code>
    for a list of all steps
   </li>
   <li>
    <code>
     GET /api/steps/?task=&lt;task_id&gt;
    </code>
    for all Steps by Task (in Step order)
   </li>
   <li>
    <code>
     POST /api/steps/&lt;step_id&gt;/move/
    </code>
    with a JSON body of
    <code>
     {'order': &lt;int&gt;}
    </code>
    of the that Step's new order position.
   </li>
  </ul>
  <p>
   <strong>
    NOTE
   </strong>
   This API example excludes any authentication and authorization concerns which you will need to address before using in production.  You don't want just anyone reordering your steps!
  </p>
  <p>
   Hopefully, you've found this informative and can put this pattern to use
in your next project. If you need help implementing things of this
nature at your company
   <a href="/contact/">
    reach out to us
   </a>
   to check on our
availability.
  </p>
 </div>
</div>
]]>/></item><item><title>Tips for Using Django's ManyToManyField</title><link>http://www.revsys.com/tidbits/tips-using-djangos-manytomanyfield/</link><description>ManyToManyFields confuse a lot of people. The way you relate objects to each other is just different enough from dealing with ForeignKeys and just uncommon enough in day-to-day Django development that it&amp;#x27;s easy to forget all the little tricks for dealing with them.</description><pubDate>Fri, 31 Aug 2018 20:37:58 +0000</pubDate><guid>http://www.revsys.com/tidbits/tips-using-djangos-manytomanyfield/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Versions:
  </p>
  <ul>
   <li>
    Python 3.7
   </li>
   <li>
    Django 2.1
   </li>
  </ul>
  <p>
   <code>
    ManyToManyField
   </code>
   s confuse a lot of people. The way you relate objects to each other using a many-to-many relationship is just different enough from dealing with
   <code>
    ForeignKey
   </code>
   s and just uncommon enough in day-to-day Django development that it's easy to forget all the little tricks for dealing with them.
  </p>
  <p>
   When should you use a
   <code>
    ManyToManyField
   </code>
   instead of a regular
   <code>
    ForeignKey
   </code>
   ? To remember that, let's think about pizza. A pizza can have many toppings (a Hawaiian pizza usually has Canadian bacon and pineapple), and a topping can go on many pizzas (Canadian bacon also appears on meat lovers' pizzas). Since a pizza can have more than one topping, and a topping can go on more than one pizza, this is a great place to use a
   <code>
    ManyToManyField
   </code>
   .
  </p>
  <p>
   So let's dive in: assume the following models in a
   <code>
    pizzas
   </code>
   app.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span> 


<span class="k">class</span><span class="w"> </span><span class="nc">Pizza</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
    <span class="n">toppings</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ManyToManyField</span><span class="p">(</span><span class="s1">'Topping'</span><span class="p">)</span>

    <span class="k">def</span><span class="w"> </span><span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>


<span class="k">class</span><span class="w"> </span><span class="nc">Topping</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>

    <span class="k">def</span><span class="w"> </span><span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
</code></pre>
  </div>
  <h2>
   Both objects must exist in the database
  </h2>
  <p>
   You have to save a
   <code>
    Topping
   </code>
   in the database before you can add it to a
   <code>
    Pizza
   </code>
   , and vice versa. This is because a
   <code>
    ManyToManyField
   </code>
   creates an invisible "through" model that relates the source model (in this case
   <code>
    Pizza
   </code>
   , which contains the
   <code>
    ManyToManyField
   </code>
   ) to the target model (
   <code>
    Topping
   </code>
   ). In order to create the connection between a pizza and a topping, they both have to be added to this invisible "through" table. From the Django
   <a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ManyToManyField.through">
    docs
   </a>
   :
  </p>
  <p>
   " [T]here is &hellip; an implicit through model class you can use to directly access the table created to hold the association. It has three fields to link the models. If the source and target models differ, the following fields are generated:
  </p>
  <ul>
   <li>
    <code>
     id
    </code>
    : the primary key of the relation.
   </li>
   <li>
    <code>
     &lt;containing_model&gt;_id
    </code>
    : the id of the model that declares the ManyToManyField.
   </li>
   <li>
    <code>
     &lt;other_model&gt;_id
    </code>
    : the id of the model that the ManyToManyField points to."
   </li>
  </ul>
  <p>
   The invisible "through" model that Django uses to make many-to-many relationships work requires the primary keys for the source model and the target model. A primary key doesn't exist until a model instance is saved, so that's why both instances have to exist before they can be related. (You can't add spinach to your pizza if you haven't bought spinach yet, and you can't add spinach to your pizza if you haven't even started rolling out the crust yet either.)
  </p>
  <p>
   See what happens when you try to add a topping to a pizza before you've added that topping to the database:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>from<span class="w"> </span>pizzas.models<span class="w"> </span>import<span class="w"> </span>Pizza,<span class="w"> </span>Topping
&gt;&gt;<span class="w"> </span><span class="nv">hawaiian_pizza</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Pizza.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'Hawaiian'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span><span class="nv">pineapple</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Topping<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'pineapple'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span>hawaiian_pizza.toppings.add<span class="o">(</span>pineapple<span class="o">)</span>
Traceback<span class="w"> </span><span class="o">(</span>most<span class="w"> </span>recent<span class="w"> </span>call<span class="w"> </span>last<span class="o">)</span>:
...
ValueError:<span class="w"> </span>Cannot<span class="w"> </span>add<span class="w"> </span><span class="s2">"&lt;Topping: pineapple&gt;"</span>:<span class="w"> </span>instance<span class="w"> </span>is<span class="w"> </span>on<span class="w"> </span>database<span class="w"> </span><span class="s2">"default"</span>,<span class="w"> </span>
value<span class="w"> </span>is<span class="w"> </span>on<span class="w"> </span>database<span class="w"> </span><span class="s2">"None"</span>
&gt;&gt;<span class="w"> </span>
</code></pre>
  </div>
  <p>
   A
   <code>
    ValueError
   </code>
   is raised because the
   <code>
    pineapple
   </code>
   hasn't yet been saved, so its value on the database doesn't exist yet. But when I save
   <code>
    pineapple
   </code>
   , I can add it to my pizza.
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>pineapple.save<span class="o">()</span><span class="w"> </span>
&gt;&gt;<span class="w"> </span>hawaiian_pizza.toppings.add<span class="o">(</span>pineapple<span class="o">)</span>
&gt;&gt;<span class="w"> </span>hawaiian_pizza.toppings.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pineapple&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   The reverse doesn't work either: I can't create a topping in the database, and then add it to a pizza that hasn't been saved.
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span><span class="nv">pepperoni</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Topping.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'pepperoni'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span><span class="nv">pepperoni_pizza</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Pizza<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'Pepperoni'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span>pepperoni_pizza.toppings.add<span class="o">(</span>pepperoni<span class="o">)</span>
Traceback<span class="w"> </span><span class="o">(</span>most<span class="w"> </span>recent<span class="w"> </span>call<span class="w"> </span>last<span class="o">)</span>:
...
ValueError:<span class="w"> </span><span class="s2">"&lt;Pizza: Pepperoni&gt;"</span><span class="w"> </span>needs<span class="w"> </span>to<span class="w"> </span>have<span class="w"> </span>a<span class="w"> </span>value<span class="w"> </span><span class="k">for</span><span class="w"> </span>field<span class="w"> </span><span class="s2">"id"</span><span class="w"> </span>before<span class="w"> </span>this<span class="w"> </span>many-to-many<span class="w"> </span>
relationship<span class="w"> </span>can<span class="w"> </span>be<span class="w"> </span>used.
</code></pre>
  </div>
  <p>
   This error is more explicit (it states that an id is required) but it's essentially the same error. It's just coming from the other side of the relationship.
  </p>
  <h2>
   To retrieve the stuff in a
   <code>
    ManyToManyField
   </code>
   , you have to use
   <code>
    *_set
   </code>
   ...
  </h2>
  <p>
   Since the field
   <code>
    toppings
   </code>
   is already on the
   <code>
    Pizza
   </code>
   model, getting all the toppings on a specific pizza is pretty straightforward.
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>hawaiian_pizza.toppings.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pineapple&gt;,<span class="w"> </span>&lt;Topping:<span class="w"> </span>Canadian<span class="w"> </span>bacon&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   But if I try to see what pizzas use Canadian bacon, I get an
   <code>
    AttributeError
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>canadian_bacon.pizzas.all<span class="o">()</span>
Traceback<span class="w"> </span><span class="o">(</span>most<span class="w"> </span>recent<span class="w"> </span>call<span class="w"> </span>last<span class="o">)</span>:
...
AttributeError:<span class="w"> </span><span class="s1">'Topping'</span><span class="w"> </span>object<span class="w"> </span>has<span class="w"> </span>no<span class="w"> </span>attribute<span class="w"> </span><span class="s1">'pizzas'</span>
</code></pre>
  </div>
  <p>
   That's because Django automatically refers to the target
   <code>
    ManyToManyField
   </code>
   objects as "sets." The pizzas that use specific toppings are in their own "set":
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>canadian_bacon.pizza_set.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Hawaiian&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <h2>
   &hellip; unless you add the
   <code>
    related_name
   </code>
   option to the field
  </h2>
  <p>
   Adding the
   <code>
    related_name
   </code>
   option to a
   <code>
    ManyToManyField
   </code>
   will let you choose a more intuitive name to use when you want to retrieve the stuff in that field.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Pizza</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="o">...</span>
    <span class="n">toppings</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ManyToManyField</span><span class="p">(</span><span class="s1">'Topping'</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'pizzas'</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The
   <code>
    related_name
   </code>
   should usually be the lowercase, plural form of your model name. This is confusing for some people because shouldn't the
   <code>
    related_name
   </code>
   for
   <code>
    toppings
   </code>
   just be&hellip; toppings?
  </p>
  <p>
   No; the
   <code>
    related_name
   </code>
   isn't referring to how you want to retrieve the stuff
   <em>
    in this field
   </em>
   ; it specifies the term you want to use instead of
   <code>
    *_set
   </code>
   when you're on the target object (which in this case is a topping) and want to see which source objects point to that target (what pizzas use a specific topping).
  </p>
  <p>
   Without a
   <code>
    related_name
   </code>
   , we would retrieve all the pizzas that use a specific topping with
   <code>
    pizza_set
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>canadian_bacon.pizza_set.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Hawaiian&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   Adding a
   <code>
    related_name
   </code>
   of "pizzas" to the
   <code>
    toppings
   </code>
   attribute lets us retrieve all the toppings for a pizza like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>canadian_bacon.pizzas.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Hawaiian&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <h2>
   You can add things from both sides of the relationship.
  </h2>
  <p>
   Earlier, we created a
   <code>
    Pizza
   </code>
   object, and then a
   <code>
    Topping
   </code>
   object, and then ran
   <code>
    hawaiian_pizza.toppings.add(pineapple)
   </code>
   to associate the pineapple topping with the Hawaiian pizza.
  </p>
  <p>
   But we could, instead, add a pizza to a topping.
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span><span class="nv">cheese_pizza</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Pizza.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'Cheese'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span><span class="nv">mozzarella</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Topping.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'mozzarella'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span>mozzarella.pizzas.add<span class="o">(</span>cheese_pizza<span class="o">)</span>
&gt;&gt;<span class="w"> </span>mozzarella.pizzas.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Cheese&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <h2>
   You can query the items in the many-to-many set from both sides
  </h2>
  <p>
   Say we want to find all the pizzas that have toppings that start with the letter "p." We can write that query like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>Pizza.objects.filter<span class="o">(</span><span class="nv">toppings__name__startswith</span><span class="o">=</span><span class="s1">'p'</span><span class="o">)</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Pepperoni<span class="w"> </span>Pizza&gt;,<span class="w"> </span>&lt;Pizza:<span class="w"> </span>Hawaiian<span class="w"> </span>Pizza&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   A Hawaiian pizza contains pineapple, and a pepperoni pizza contains pepperonis. Pineapple and pepperoni both start with the letter "p," so the both of those pizzas are returned.
  </p>
  <p>
   We can do the same from the
   <code>
    Topping
   </code>
   model, to find all the toppings used on pizzas that contain "Hawaiian" in their name:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>Topping.objects.filter<span class="o">(</span><span class="nv">pizzas__name__contains</span><span class="o">=</span><span class="s1">'Hawaiian'</span><span class="o">)</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pineapple&gt;,<span class="w"> </span>&lt;Topping:<span class="w"> </span>Canadian<span class="w"> </span>bacon&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <h2>
   You might need a custom "through" model
  </h2>
  <p>
   Remember when I mentioned the invisible "through" model that Django creates to manage your many-to-many relationships? You might want to keep track of more data about those relationships, and to do that you would use a custom "through" model.
  </p>
  <p>
   The example used in the
   <a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ManyToManyField.through_fields">
    Django docs
   </a>
   is of a Group, Person, and Membership relationship. A group can have many people as members, and a person can be part of many groups, so the
   <code>
    Group
   </code>
   model has a
   <code>
    ManyToManyField
   </code>
   that points to
   <code>
    Person
   </code>
   . Then, a
   <code>
    Membership
   </code>
   model contains
   <code>
    ForeignKey
   </code>
   s to both
   <code>
    Person
   </code>
   and
   <code>
    Group
   </code>
   , and can store extra information about a person's membership in a specific group, like the date they joined, who invited them, etc.
  </p>
  <p>
   But we're not here to talk about people. We are all about pizza.
  </p>
  <p>
   Using our existing models, we can create all kinds of pizzas with a wide range of toppings. But we can't make a pizza like "Super Pepperoni" that contains double the usual amount of pepperonis. We can't add pepperoni to a pizza more than once:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>pepperoni_pizza.toppings.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pepperoni&gt;<span class="o">]</span>&gt;
&gt;&gt;<span class="w"> </span>pepperoni_pizza.toppings.add<span class="o">(</span>pepperoni<span class="o">)</span>
&gt;&gt;<span class="w"> </span>pepperoni_pizza.toppings.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pepperoni&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   Django just ignores us if we try. A "through" model would let us specify a quantity for each topping, enabling us to add "pepperoni" once, but specify that we wanted twice the amount for the Super Pepperoni pizza.
  </p>
  <p>
   <strong>
    Note
   </strong>
   : If you're going to use a "through" model, you have to start with that in mind&hellip; or be willing to either drop your database or do some very advanced database finagling. If you try to add a "through" model later, you will see an error like this one when you run
   <code>
    migrate
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code>ValueError:<span class="w"> </span>Cannot<span class="w"> </span>alter<span class="w"> </span>field<span class="w"> </span>pizzas.Pizza.toppings<span class="w"> </span>into<span class="w"> </span>pizzas.Pizza.toppings<span class="w"> </span>-<span class="w"> </span>
they<span class="w"> </span>are<span class="w"> </span>not<span class="w"> </span>compatible<span class="w"> </span>types<span class="w"> </span><span class="o">(</span>you<span class="w"> </span>cannot<span class="w"> </span>alter<span class="w"> </span>to<span class="w"> </span>or<span class="w"> </span>from<span class="w"> </span>M2M<span class="w"> </span>fields,<span class="w"> </span>or<span class="w"> </span>add<span class="w"> </span>or<span class="w"> </span>remove<span class="w"> </span><span class="nv">through</span><span class="o">=</span><span class="w"> </span>on<span class="w"> </span>M2M<span class="w"> </span>fields<span class="o">)</span>
</code></pre>
  </div>
  <p>
   If you're following along, now is the time to drop your database, delete your migration files, and add this new model:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">ToppingAmount</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>

    <span class="n">REGULAR</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="n">DOUBLE</span> <span class="o">=</span> <span class="mi">2</span>
    <span class="n">TRIPLE</span> <span class="o">=</span> <span class="mi">3</span>
    <span class="n">AMOUNT_CHOICES</span> <span class="o">=</span> <span class="p">(</span>
        <span class="p">(</span><span class="n">REGULAR</span><span class="p">,</span> <span class="s1">'Regular'</span><span class="p">),</span>
        <span class="p">(</span><span class="n">DOUBLE</span><span class="p">,</span> <span class="s1">'Double'</span><span class="p">),</span>
        <span class="p">(</span><span class="n">TRIPLE</span><span class="p">,</span> <span class="s1">'Triple'</span><span class="p">),</span>
    <span class="p">)</span>

    <span class="n">pizza</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="s1">'Pizza'</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'topping_amounts'</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">SET_NULL</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">topping</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="s1">'Topping'</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'topping_amounts'</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">SET_NULL</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">amount</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">choices</span><span class="o">=</span><span class="n">AMOUNT_CHOICES</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">REGULAR</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Now, add the
   <code>
    through
   </code>
   option to the
   <code>
    toppings
   </code>
   field on the
   <code>
    Pizza
   </code>
   model:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Pizza</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
   <span class="o">...</span>
    <span class="n">toppings</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ManyToManyField</span><span class="p">(</span><span class="s1">'Topping'</span><span class="p">,</span> <span class="n">through</span><span class="o">=</span><span class="s1">'ToppingAmount'</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'pizzas'</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   And run
   <code>
    makemigrations
   </code>
   and
   <code>
    migrate
   </code>
   .
  </p>
  <p>
   If we try to add a pizza, a topping, and then associate them the way we used to, we will get an error:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span><span class="nv">super_pep</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Pizza.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'Super Pepperoni'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span><span class="nv">pepperoni</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>Topping.objects.create<span class="o">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">'pepperoni'</span><span class="o">)</span>
&gt;&gt;<span class="w"> </span>super_pep.toppings.add<span class="o">(</span>pepperoni<span class="o">)</span>
Traceback<span class="w"> </span><span class="o">(</span>most<span class="w"> </span>recent<span class="w"> </span>call<span class="w"> </span>last<span class="o">)</span>:
...
AttributeError:<span class="w"> </span>Cannot<span class="w"> </span>use<span class="w"> </span>add<span class="o">()</span><span class="w"> </span>on<span class="w"> </span>a<span class="w"> </span>ManyToManyField<span class="w"> </span>which<span class="w"> </span>specifies<span class="w"> </span>an<span class="w"> </span>intermediary<span class="w"> </span>model.<span class="w"> </span>
Use<span class="w"> </span>pizzas.ToppingAmount<span class="err">'</span>s<span class="w"> </span>Manager<span class="w"> </span>instead.
</code></pre>
  </div>
  <p>
   Using a custom "through" model forces us to use that model to associate the pizza and toppings.
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;&gt;<span class="w"> </span><span class="nv">super_pep_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>ToppingAmount.objects.create<span class="o">(</span><span class="nv">pizza</span><span class="o">=</span>super_pep,<span class="w"> </span><span class="nv">topping</span><span class="o">=</span>pepperoni,<span class="w"> </span><span class="nv">amount</span><span class="o">=</span>ToppingAmount.DOUBLE<span class="o">)</span>
</code></pre>
  </div>
  <p>
   But the benefit is that we can now add some extra information about that relationship, like the fact that the amount of pepperonis on a Super Pepperoni pizza should be double the regular amount.
  </p>
  <p>
   We can still access the toppings from the pizza:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>super_pep.toppings.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Topping:<span class="w"> </span>pepperoni&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   We'll only see pepperoni once, since the amount is on the "through" model. And we can access the pizzas that use a specific topping:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;<span class="w"> </span>pepperoni.pizzas.all<span class="o">()</span>
&lt;QuerySet<span class="w"> </span><span class="o">[</span>&lt;Pizza:<span class="w"> </span>Super<span class="w"> </span>Pepperoni&gt;<span class="o">]</span>&gt;
</code></pre>
  </div>
  <p>
   But now, we can use our "through" model to get all the toppings and their amount for a specific pizza from the
   <code>
    ToppingAmount
   </code>
   model:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">for</span> <span class="n">top_amt</span> <span class="ow">in</span> <span class="n">ToppingAmount</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">pizza</span><span class="o">=</span><span class="n">super_pep</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">top_amt</span><span class="o">.</span><span class="n">topping</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">top_amt</span><span class="o">.</span><span class="n">get_amount_display</span><span class="p">())</span>

<span class="n">pepperoni</span> <span class="n">Double</span>
</code></pre>
  </div>
  <p>
   You can also see the topping amounts from the
   <code>
    Pizza
   </code>
   objects themselves.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">for</span> <span class="n">top_amt</span> <span class="ow">in</span> <span class="n">super_pep</span><span class="o">.</span><span class="n">topping_amounts</span><span class="o">.</span><span class="n">all</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">top_amt</span><span class="o">.</span><span class="n">topping</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">top_amt</span><span class="o">.</span><span class="n">get_amount_display</span><span class="p">())</span> 

<span class="n">pepperoni</span> <span class="n">Double</span>
</code></pre>
  </div>
  <p>
   And you can also access the amount for a specific topping from the
   <code>
    topping
   </code>
   on the
   <code>
    Pizza
   </code>
   object.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">for</span> <span class="n">topping</span> <span class="ow">in</span> <span class="n">super_pep</span><span class="o">.</span><span class="n">toppings</span><span class="o">.</span><span class="n">all</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">top_amt</span> <span class="ow">in</span> <span class="n">topping</span><span class="o">.</span><span class="n">topping_amount</span><span class="o">.</span><span class="n">all</span><span class="p">():</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">topping</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">top_amt</span><span class="o">.</span><span class="n">amount</span><span class="p">,</span> <span class="n">top_amt</span><span class="o">.</span><span class="n">get_amount_display</span><span class="p">())</span>

<span class="n">pepperoni</span> <span class="mi">2</span> <span class="n">Double</span>
</code></pre>
  </div>
  <p>
   You could extend this
   <code>
    ToppingAmount
   </code>
   through model to hold information about the left and right halves of the pizza, or notes about topping preparation ("put peppers under cheese"). You can also add methods to the through model or the source/target models to more easily access some of the topping amount information.
  </p>
  <p>
   A through model is also useful for relationships between players and teams; the through model could contain information about the players' positions, jersey numbers, and dates they joined the team. A through model joining movie theatres and films could contain the number of screens the film is showing on and the start and end run dates. Students' relationships to their Degree Programs could track information like GPA, whether the program is the student's major or minor, whether it's a double major, and start/end semesters the student was in a program.
  </p>
  <hr/>
  <h3>
   Further Reading
  </h3>
  <ul>
   <li>
    The
    <a href="https://docs.djangoproject.com/en/stable/topics/db/examples/many_to_many/">
     many-to-many relationships
    </a>
    section of the Django docs contains a lot of great examples of using
    <code>
     ManyToManyField
    </code>
    .
   </li>
   <li>
    The explanation of
    <a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ManyToManyField">
     <code>
      ManyToManyField
     </code>
    </a>
    in the model field reference.
   </li>
   <li>
    Jacob Kaplan-Moss also has a
    <a href="https://gist.github.com/jacobian/827937">
     great set of examples
    </a>
    of using a many-to-many relationship with a custom "through" model.
   </li>
  </ul>
  <p>
   <em>
    Thank you to
    <a href="https://twitter.com/mvioletmurphy">
     Monique Murphy
    </a>
    and
    <a href="https://twitter.com/webology">
     Jeff Triplett
    </a>
    for their assistance.
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Slugs as primary keys</title><link>http://www.revsys.com/tidbits/slugs-primary-keys/</link><description>A very common pattern in a Django Project is to have some kind of &amp;#x27;model-type&amp;#x27; relationship, where you have some kind of object that can only belong to one of the types defined in the database.</description><pubDate>Mon, 20 Aug 2018 16:39:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/slugs-primary-keys/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h1>
   Slugs as primary keys
  </h1>
  <p>
   A very common pattern I've seen in Django Project is to have some kind of 'model-type' relationship, where you have some kind of object that can only belong to one of the types defined in the database.
  </p>
  <p>
   A typical implementation would look something like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">EventType</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">slug</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">SlugField</span><span class="p">(</span><span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>


<span class="k">class</span><span class="w"> </span><span class="nc">Event</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="nb">type</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="n">EventType</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
    <span class="n">title</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">day</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateField</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   A subtle issue with this implementation is that you may have to query the DB for an
   <code>
    EventType
   </code>
   when creating an
   <code>
    Event
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">work_type</span> <span class="o">=</span> <span class="n">EventType</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">slug</span><span class="o">=</span><span class="s1">'work'</span><span class="p">)</span>
<span class="n">Event</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
    <span class="nb">type</span><span class="o">=</span><span class="n">work_type</span><span class="p">,</span>
    <span class="n">title</span><span class="o">=</span><span class="s1">'release'</span><span class="p">,</span>
    <span class="n">day</span><span class="o">=</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">),</span>
<span class="p">)</span>
</code></pre>
  </div>
  <p>
   In other words, the minimum amount of queries to create an
   <code>
    Event
   </code>
   is 2: a
   <code>
    SELECT
   </code>
   for fetching the type, and the
   <code>
    INSERT
   </code>
   that saves the instance.
  </p>
  <p>
   If somehow you already have the
   <code>
    pk
   </code>
   of the
   <code>
    EventType
   </code>
   (for example, it might come from an API payload or from the URL), then you can easily avoid the lookup by setting the primary key directly into the
   <code>
    type_id
   </code>
   column:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
    <span class="n">type_id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">[</span><span class="s1">'pk'</span><span class="p">]</span>
    <span class="n">Event</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
        <span class="n">type_id</span><span class="o">=</span><span class="n">type_id</span><span class="p">,</span>
        <span class="n">title</span><span class="o">=</span><span class="s1">'release'</span><span class="p">,</span>
        <span class="n">day</span><span class="o">=</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">),</span>
    <span class="p">)</span>
</code></pre>
  </div>
  <p>
   But dealing directly with column names is
   <a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#database-representation">
    discouraged by the docs
   </a>
  </p>
  <blockquote>
   <p>
    However, your code should never have to deal with the database column name
   </p>
  </blockquote>
  <p>
   We can get around this by instantiating an
   <code>
    EventType
   </code>
   instance with just the primary key:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
    <span class="n">type_id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">[</span><span class="s1">'pk'</span><span class="p">]</span>
    <span class="n">Event</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
       <span class="nb">type</span><span class="o">=</span><span class="n">EventType</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">type_id</span><span class="p">),</span>
       <span class="n">title</span><span class="o">=</span><span class="s1">'release'</span><span class="p">,</span>
       <span class="n">day</span><span class="o">=</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">),</span>
    <span class="p">)</span>
</code></pre>
  </div>
  <p>
   But this requires us to know the numerical
   <code>
    id
   </code>
   beforehand.
  </p>
  <p>
   We already have
   <code>
    slug
   </code>
   as source of uniqueness for the
   <code>
    event_eventtypes
   </code>
   table, and it's URL-friendly. We could just use that as the
   <code>
    EventType
   </code>
   primary key.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">EventType</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">slug</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">SlugField</span><span class="p">(</span><span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>


<span class="k">class</span><span class="w"> </span><span class="nc">Event</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="nb">type</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span>
        <span class="n">EventType</span><span class="p">,</span> 
        <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">title</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">day</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateField</span><span class="p">()</span>
</code></pre>
  </div>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
    <span class="n">type_slug</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">[</span><span class="s1">'slug'</span><span class="p">]</span>
    <span class="n">Event</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
       <span class="nb">type</span><span class="o">=</span><span class="n">EventType</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">type_slug</span><span class="p">),</span>
       <span class="n">title</span><span class="o">=</span><span class="s1">'release'</span><span class="p">,</span>
       <span class="n">day</span><span class="o">=</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">),</span>
    <span class="p">)</span>
</code></pre>
  </div>
  <p>
   This also allows to easily create
   <code>
    Event
   </code>
   s of a specific type without having to fetching any
   <code>
    EventType
   </code>
   . This is especially useful in data migrations, or tests:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">types</span> <span class="o">=</span>  <span class="p">[</span><span class="s1">'home'</span><span class="p">,</span> <span class="s1">'work'</span><span class="p">,</span> <span class="s1">'community'</span><span class="p">]</span>
<span class="k">for</span> <span class="n">type_slug</span> <span class="ow">in</span> <span class="n">types</span><span class="p">:</span>
    <span class="n">Event</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
       <span class="nb">type</span><span class="o">=</span><span class="n">EventType</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">type_slug</span><span class="p">),</span>
       <span class="n">title</span><span class="o">=</span><span class="s1">'release'</span><span class="p">,</span>
       <span class="n">day</span><span class="o">=</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">),</span>
    <span class="p">)</span>
</code></pre>
  </div>
  <p>
   In cases like this, you may want to consider using a slug as primary key, rather than the default integer-based default. It's more performant and just as straight forward.
  </p>
  <p>
   There are some caveat to consider, though: You won't be able to modify the slug from the Django admin.
  </p>
  <p>
   Another thing to consider is that changing the type of your primary key is a one-way road: once you made the slug your pk, you won't be able to convert it back to a regular
   <code>
    AutoField
   </code>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Sentinel values in Python</title><link>http://www.revsys.com/tidbits/sentinel-values-python/</link><description>It is often necessary to differentiate between an argument that has not been provided, and an argument provided with the value `None`. For that purpose, we create what&amp;#x27;s called a &amp;#x27;sentinel value&amp;#x27;.</description><pubDate>Thu, 16 Aug 2018 20:35:04 +0000</pubDate><guid>http://www.revsys.com/tidbits/sentinel-values-python/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Sometimes it is necessary to differentiate between an argument that has not
been provided, and an argument provided with the value
   <code>
    None
   </code>
   . For that purpose, we create what's called a 'sentinel value'.
  </p>
  <p>
   For example, let's assume you want to define a Field class. Field instances must have a value or declare a default,
and
   <code>
    None
   </code>
   could be a perfectly valid value to have:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">Field</span><span class="p">:</span>
    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">sentinel</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">default</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="ow">is</span> <span class="n">sentinel</span><span class="p">:</span>
            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"this field has no value!"</span><span class="p">)</span>

<span class="n">eula_accepted</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span>
<span class="n">eula_accepted</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>  <span class="c1"># raises `ValueError`</span>

<span class="n">eula_accepted</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="n">eula_accepted</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>  <span class="c1"># doesn't raise. `None` means the EULA hasn't been neither accepted or reject yet.</span>
</code></pre>
  </div>
  <p>
   The most common approach is to declare the sentinel value with
   <code>
    object()
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">sentinel</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   This approach is the quickest and most common, but it has some issues. To quote
   <a href="https://mail.python.org/pipermail/python-ideas/2018-July/051852.html">
    Joseph Jevnik
   </a>
   :
  </p>
  <blockquote>
   <p>
    One is that they don't repr well so they make debugging harder. Another issue
is that they cannot be pickled or copied. You also cannot take a weak
reference to a sentinel which can break some caching code and makes
them harder to use.
   </p>
  </blockquote>
  <p>
   For example:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">sentinel</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>

<span class="nb">repr</span><span class="p">(</span><span class="n">sentinel</span><span class="p">)</span>
<span class="c1"># '&lt;object object at 0x10823e8d0&gt;'</span>
</code></pre>
  </div>
  <p>
   To work around this issue, some people create their own
   <code>
    Sentinel
   </code>
   class. But I've found a quicker way in the
   <code>
    unittest.mock
   </code>
   module.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">unittest.mock</span><span class="w"> </span><span class="kn">import</span> <span class="n">sentinel</span>

<span class="n">NotSet</span> <span class="o">=</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">NotSet</span>

<span class="nb">repr</span><span class="p">(</span><span class="n">NotSet</span><span class="p">)</span>
<span class="c1"># 'sentinel.NotSet'</span>
</code></pre>
  </div>
  <p>
   If you don't feel like importing from
   <code>
    unittest
   </code>
   in your application code, you could install the
   <code>
    mock
   </code>
   package, or "hide it under the rug" by aliasing somewhere in your code base and importing it from there:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># in `myproject.types`</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">unittest.mock</span><span class="w"> </span><span class="kn">import</span> <span class="n">sentinel</span>
</code></pre>
  </div>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># somewhere else in your project:</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">myproject.types</span><span class="w"> </span><span class="kn">import</span> <span class="n">sentinel</span>
</code></pre>
  </div>
  <p>
   An alternative to
   <code>
    unittest.mock.sentinel
   </code>
   is to declare your own sentinel class and use it as a value:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">NotSet</span><span class="p">:</span>
    <span class="k">pass</span>

<span class="c1"># PS: Remember to use the class _itself_.</span>
<span class="k">def</span><span class="w"> </span><span class="nf">fn</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="n">NotSet</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre>
  </div>
  <p>
   This will give you a not really pretty, but useful enough
   <code>
    repr
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nb">repr</span><span class="p">(</span><span class="n">NotSet</span><span class="p">)</span>
<span class="c1"># "&lt;class '__main__.NotSet'&gt;"</span>
</code></pre>
  </div>
  <p>
   Of course, you could go one step further and declare your own
   <code>
    repr
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">NotSet</span><span class="p">:</span>
    <span class="k">def</span><span class="w"> </span><span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="s1">'NotSet'</span>

<span class="nb">repr</span><span class="p">(</span><span class="n">NotSet</span><span class="p">)</span>
<span class="c1"># 'NotSet'</span>
</code></pre>
  </div>
  <p>
   Of all the options, I think using
   <code>
    unittest.mock.Sentinel
   </code>
   is my favorite. Importing from
   <code>
    unittest
   </code>
   in my application code is a compromise that I'm willing to make in exchange for having something ready to use.
  </p>
 </div>
</div>
]]>/></item><item><title>Celery and Django and Docker: Oh My!</title><link>http://www.revsys.com/tidbits/celery-and-django-and-docker-oh-my/</link><description>In this post, you will learn how to create a Celery task inside a Django project in a Docker container. Sounds awesome, right?</description><pubDate>Thu, 19 Jul 2018 19:37:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/celery-and-django-and-docker-oh-my/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   In this post, you will learn about how to:
  </p>
  <ul>
   <li>
    create a Celery task
   </li>
   <li>
    inside a Django project
   </li>
   <li>
    in a Docker container
   </li>
  </ul>
  <p>
   Versions: Django 1.11, Python 3.6, Celery 4.2.1, Redis 2.10.6, and Docker 17.12.
  </p>
  <p>
   Note that especially for Celery, versions matter a lot. Celery changed the names of many of their settings between versions 3 and 4, so if internet tutorials have been tripping you up, that might be why. Be careful when Googling for advice and always check the version number if something isn&rsquo;t working.
  </p>
  <p>
   If you need a refresher on using Docker with Django, check out
   <a href="https://www.revsys.com/tidbits/brief-intro-docker-djangonauts/">
    A Brief Intro to Docker for Djangonauts
   </a>
   and
   <a href="https://www.revsys.com/tidbits/docker-useful-command-line-stuff/">
    Docker: Useful Command Line Stuff
   </a>
   .
  </p>
  <h2>
   What is Celery?
  </h2>
  <p>
   <a href="http://docs.celeryproject.org/en/latest/index.html">
    Celery
   </a>
   is a tool that helps you manage tasks that should occur outside the request/response cycle. It&rsquo;s not specific to Django.
  </p>
  <p>
   Celery is especially helpful for transforming blocking transactions on your site into non-blocking transactions. For example, you might have a site that takes payment information. Validating credit card information, processing a charge, and producing a receipt might take 3-15 seconds, during which time your user is waiting and other users&rsquo; requests are also held up. Celery can help by offloading that work to different tasks. Instead of waiting until the credit card has been processed to show your user a confirmation page, you can quickly show them a confirmation screen that assures them that a receipt is forthcoming in their email. Then, outside the request/response cycle in a series of Celery tasks, you can validate their credit card, charge it, create a receipt, and email the receipt to the user. This experience is much smoother for your user, a better use of your server resources, and increases the number of requests your website can process for other users.
  </p>
  <p>
   You can use Celery to send email, update your database with side effects from the request that was just processed, query an API and store the result, and a lot more. Any task that takes more than half a second is a great candidate for turning into a Celery task.
  </p>
  <p>
   Another thing Celery is helpful for is scheduling tasks to run at specific times. You might be familiar with cron jobs, which are tasks that run at specific intervals you define. Django doesn&rsquo;t have the cleanest ways of handling scheduling jobs, but using Celery with Django to schedule jobs is pretty smooth. You might set up scheduled Celery tasks to send user notification emails, scrape a website, or process vendor payments.
  </p>
  <p>
   This post focuses on getting a scheduled task to run inside Docker in a Django project.
  </p>
  <h2>
   Setting Up a Task
  </h2>
  <p>
   Assume this project has the following structure:
  </p>
  <div class="codehilite">
   <pre><span></span><code>proj/
&boxvr;&boxh;&boxh; app/
     &boxvr;&boxh;&boxh; __init__.py
     &boxvr;&boxh;&boxh; tasks.py
&boxvr;&boxh;&boxh; proj/
     &boxvr;&boxh;&boxh; __init__.py
     &boxvr;&boxh;&boxh; celery.py
     &boxvr;&boxh;&boxh; settings.py
&boxvr;&boxh;&boxh; docker-compose.yml
&boxvr;&boxh;&boxh; Dockerfile
&boxvr;&boxh;&boxh; manage.py
&boxur;&boxh;&boxh; requirements.txt
</code></pre>
  </div>
  <p>
   You should already have Django specified in your requirements file, and the most recent version of
   <a href="https://www.docker.com/">
    Docker
   </a>
   downloaded onto your computer. Since the Dockerfile takes care of installing packages for us, to access Celery and Redis we need to add the current versions of those libraries to the
   <code>
    requirements.txt
   </code>
   file:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">celery</span><span class="o">==</span><span class="mf">4.2.1</span>
<span class="n">redis</span><span class="o">==</span><span class="mf">2.10.6</span>
</code></pre>
  </div>
  <p>
   Open
   <code>
    proj/celery.py
   </code>
   and add the following code. Most of it is boilerplate that you will see in all Celery configuration files.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">celery</span><span class="w"> </span><span class="kn">import</span> <span class="n">Celery</span>


<span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s1">'DJANGO_SETTINGS_MODULE'</span><span class="p">,</span> <span class="s1">'proj.settings'</span><span class="p">)</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">Celery</span><span class="p">(</span><span class="s1">'proj'</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">config_from_object</span><span class="p">(</span><span class="s1">'django.conf:settings'</span><span class="p">,</span> <span class="n">namespace</span><span class="o">=</span><span class="s1">'CELERY'</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">autodiscover_tasks</span><span class="p">()</span>

<span class="nd">@app</span><span class="o">.</span><span class="n">task</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">debug_task</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'Request: </span><span class="si">{0!r}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="p">))</span>
</code></pre>
  </div>
  <p>
   In this code, you are identifying a default Django settings module to use and doing some configuration setup. You are also setting up Celery to &ldquo;autodiscover&rdquo; tasks from all apps in your project. (This project is, creatively, called
   <code>
    proj
   </code>
   .) Finally, you have a debug task.
  </p>
  <p>
   Now let&rsquo;s create a task. In
   <code>
    app/tasks.py
   </code>
   , add this code:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">celery</span><span class="w"> </span><span class="kn">import</span> <span class="n">shared_task</span>


<span class="nd">@shared_task</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="err">&ldquo;</span><span class="n">Hello</span> <span class="n">there</span><span class="err">!&rdquo;</span><span class="p">)</span> 
</code></pre>
  </div>
  <p>
   The task itself is the function
   <code>
    hello()
   </code>
   , which prints a greeting. The
   <code>
    shared_task
   </code>
   decorator creates an instance of the task for each app in your project, which makes the tasks easier to reuse. There&rsquo;s a great explanation of
   <code>
    shared_task
   </code>
   <a href="https://stackoverflow.com/questions/21233089/how-to-use-the-shared-task-decorator-for-class-based-tasks?answertab=votes#tab-top">
    here
   </a>
   .
  </p>
  <p>
   In
   <code>
    proj/__init__.py
   </code>
   , add the following:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">.celery</span><span class="w"> </span><span class="kn">import</span> <span class="n">app</span> <span class="k">as</span> <span class="n">celery_app</span>

<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'celery_app'</span><span class="p">]</span>
</code></pre>
  </div>
  <p>
   This code ensures that Celery finds the tasks you&rsquo;ve written when your Django application starts.
  </p>
  <p>
   To test that your
   <code>
    hello()
   </code>
   task works, you can run it locally as a regular Python function. Start a Python shell using
   <code>
    docker-compose run web ./manage.py shell
   </code>
   . Run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;&gt;<span class="w"> </span>from<span class="w"> </span>app.tasks<span class="w"> </span>import<span class="w"> </span>hello
&gt;&gt;&gt;<span class="w"> </span>hello<span class="o">()</span>
Hello<span class="w"> </span>there!<span class="w"> </span>
</code></pre>
  </div>
  <p>
   If you would like to test running your task as a Celery task, run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>&gt;&gt;&gt;<span class="w"> </span>hello.delay<span class="o">()</span>
&lt;AsyncResult:<span class="w"> </span>ba845cf3-e60b-4432-a9d8-9943621cb8a0&gt;
</code></pre>
  </div>
  <p>
   Back in your first tab, you will see the output from your task.
   <code>
    delay()
   </code>
   lets Celery execute the task, so instead of seeing the output in your shell like you&rsquo;re used to, you see your output logged to the console where your server is running.
  </p>
  <h2>
   Introducing: Celery Beat
  </h2>
  <p>
   But the task, once found, will only execute once, and we&rsquo;d like to schedule it to happen more frequently, like once a minute. We will use a feature called
   <a href="http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html">
    Celery beat
   </a>
   to schedule our task to run periodically. Celery beat is the Celery scheduler. It executes tasks as often as you tell it to.
  </p>
  <p>
   Open
   <code>
    settings.py
   </code>
   . Before we run our task through Celery, we need to configure some Django settings. All settings specific to Celery should have the
   <code>
    CELERY_
   </code>
   prefix.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">CELERY_BROKER_URL</span> <span class="o">=</span> <span class="s1">'redis://redis:6379'</span>
<span class="n">CELERY_RESULT_BACKEND</span> <span class="o">=</span> <span class="s1">'redis://redis:6379'</span>
<span class="n">CELERY_ACCEPT_CONTENT</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'application/json'</span><span class="p">]</span>
<span class="n">CELERY_TASK_SERIALIZER</span> <span class="o">=</span> <span class="s1">'json'</span>
<span class="n">CELERY_RESULT_SERIALIZER</span> <span class="o">=</span> <span class="s1">'json'</span>
</code></pre>
  </div>
  <p>
   For
   <code>
    CELERY_BROKER_URL
   </code>
   and
   <code>
    CELERY_RESULT_BACKEND
   </code>
   , you may see tutorials that instruct you to set these to something like
   <code>
    redis://localhost:6379
   </code>
   , but you should replace
   <code>
    localhost
   </code>
   with the service name defined in your docker-compose file,
   <code>
    redis
   </code>
   .  (We&rsquo;ll get to that in a moment.)
  </p>
  <p>
   Import
   <code>
    crontab
   </code>
   in your settings file.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">celery.schedules</span><span class="w"> </span><span class="kn">import</span> <span class="n">crontab</span>
</code></pre>
  </div>
  <p>
   Now add the following variable below your other
   <code>
    CELERY_
   </code>
   settings.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">CELERY_BEAT_SCHEDULE</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">'hello'</span><span class="p">:</span> <span class="p">{</span>
        <span class="s1">'task'</span><span class="p">:</span> <span class="s1">'app.tasks.hello'</span><span class="p">,</span>
        <span class="s1">'schedule'</span><span class="p">:</span> <span class="n">crontab</span><span class="p">()</span>  <span class="c1"># execute every minute</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   This code sets up a dictionary,
   <code>
    CELERY_BEAT_SCHEDULE
   </code>
   , that contains the names of your tasks as keys and a dictionary of information about your task and its schedule as the value. In the dictionary that contains the keys &ldquo;task&rdquo; and &ldquo;schedule,&rdquo; the value of &ldquo;task&rdquo; should be a string with the fully qualified path to your task. The value of &ldquo;schedule&rdquo; is the information about how often you want this task to run. The default is to execute every minute;
   <a href="http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html?highlight=crontab%20#crontab-schedules">
    check out the docs
   </a>
   for examples on more complex schedules.
  </p>
  <h2>
   Configuring Celery In Docker
  </h2>
  <p>
   The
   <code>
    Dockerfile
   </code>
   is
   <a href="https://www.revsys.com/tidbits/brief-intro-docker-djangonauts/">
    here
   </a>
   and doesn&rsquo;t need any changes in order to work with Celery.
  </p>
  <p>
   The
   <code>
    docker-compose.yml
   </code>
   file, however, needs some new services:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s">'3'</span>

<span class="nt">services</span><span class="p">:</span>
<span class="w">  </span><span class="nt">db</span><span class="p">:</span>
<span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">postgres:9.6.5</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">postgres_data:/var/lib/postgresql/data/</span>
<span class="w">  </span><span class="nt">redis</span><span class="p">:</span>
<span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">"redis:alpine"</span>
<span class="w">  </span><span class="nt">web</span><span class="p">:</span>
<span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">bash -c "python /code/manage.py migrate --noinput &amp;&amp; python /code/manage.py runserver 0.0.0.0:8000"</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.:/code</span>
<span class="w">    </span><span class="nt">ports</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"8000:8000"</span>
<span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>
<span class="w">  </span><span class="nt">celery</span><span class="p">:</span>
<span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">celery -A proj worker -l info</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.:/code</span>
<span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>
<span class="w">  </span><span class="nt">celery-beat</span><span class="p">:</span>
<span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">celery -A proj beat -l info</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.:/code</span>
<span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>

<span class="nt">volumes</span><span class="p">:</span>
<span class="w">  </span><span class="nt">postgres_data</span><span class="p">:</span>
</code></pre>
  </div>
  <p>
   Let&rsquo;s walk through the services we&rsquo;ve added.
  </p>
  <p>
   <a href="https://redis.io/topics/introduction">
    Redis
   </a>
   is a data store and message broker that works with Celery to manage storing and processing your messages.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">redis</span><span class="p">:</span>
<span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">"redis:alpine"</span>
</code></pre>
  </div>
  <p>
   In your
   <code>
    web
   </code>
   service, add
   <code>
    redis
   </code>
   to the
   <code>
    depends_on
   </code>
   section. This ensures that your
   <code>
    db
   </code>
   and
   <code>
    redis
   </code>
   services will start before the
   <code>
    web
   </code>
   service. (Note: this won't guarantee that the
   <code>
    db
   </code>
   and
   <code>
    redis
   </code>
   services will be fully ready before the
   <code>
    web
   </code>
   service starts; look into
   <a href="https://docs.docker.com/compose/compose-file/#restart">
    <code>
     restart: on-failure
    </code>
   </a>
   and other options for making sure a service doesn't start until other services it needs are ready.)*
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>
</code></pre>
  </div>
  <p>
   In order to have that task execute without needing to explicitly tell it to execute via the command line, we added the
   <code>
    celery
   </code>
   service.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">celery</span><span class="p">:</span>
<span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">celery -A proj worker -l info</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.:/code</span>
<span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>
</code></pre>
  </div>
  <p>
   This code adds a Celery worker to the list of services defined in docker-compose. Now our app can recognize and execute tasks automatically from inside the Docker container once we start Docker using
   <code>
    docker-compose up
   </code>
   .
  </p>
  <p>
   The
   <code>
    celery worker
   </code>
   command starts an instance of the celery worker, which executes your tasks.
   <code>
    -A proj
   </code>
   passes in the name of your project,
   <code>
    proj
   </code>
   , as the app that Celery will run.
   <code>
    -l info
   </code>
   sets the log-level as
   <code>
    info
   </code>
   . The Django docs have
   <a href="https://docs.djangoproject.com/en/1.11/topics/logging/">
    more info on logging
   </a>
   ; the log-level you set won&rsquo;t matter until you have some code to determine how the different levels should be handled.
  </p>
  <p>
   We also added a
   <code>
    celery-beat
   </code>
   service that will run this command automatically inside the Docker container.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">celery-beat</span><span class="p">:</span>
<span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">celery -A proj beat -l info</span>
<span class="w">    </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.:/code</span>
<span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">db</span>
<span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">redis</span>
</code></pre>
  </div>
  <p>
   The command is similar, but instead of
   <code>
    celery -A proj worker
   </code>
   we run
   <code>
    celery -A proj beat
   </code>
   to start the Celery beat service, which will run tasks on the schedule defined in
   <code>
    CELERY_BEAT_SCHEDULE
   </code>
   in
   <code>
    settings.py
   </code>
   .
  </p>
  <p>
   Start Docker with
   <code>
    docker-compose up
   </code>
   . You should see the output from your task appear in the console once a minute (or on the schedule you specified).
  </p>
  <div class="codehilite">
   <pre><span></span><code>celery_1<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="o">[</span><span class="m">2018</span>-02-01<span class="w"> </span><span class="m">22</span>:44:00,957:<span class="w"> </span>WARNING/ForkPoolWorker-1<span class="o">]</span><span class="w"> </span>Hello<span class="w"> </span>there!
</code></pre>
  </div>
  <p>
   And there you have it! For even more fun, you might try:
  </p>
  <ul>
   <li>
    Adding more complex tasks, like tasks that take arguments
   </li>
   <li>
    Setting up different schedules
   </li>
   <li>
    Setting up a non-periodic task
   </li>
  </ul>
  <h2>
   What next?
  </h2>
  <p>
   Review the
   <a href="http://celerytaskschecklist.com/">
    Celery Tasks Checklist
   </a>
   for a great introduction to Celery best practices. In particular, pay attention to:
  </p>
  <ul>
   <li>
    Set a
    <code>
     retry_limit
    </code>
    so that failed tasks don&rsquo;t keep trying forever.
   </li>
   <li>
    Set a high default
    <code>
     task_time_limit
    </code>
    to avoid tasks that block the entire Celery worker.
   </li>
   <li>
    For tasks that need to take in a Django model object as a parameter, pass in a primary key and not the object itself.
   </li>
   <li>
    Do a lot of logging!
   </li>
  </ul>
  <p>
   You will also want to monitor your tasks for success or failure. A great tool for this is
   <a href="https://flower.readthedocs.io/en/latest/">
    Flower
   </a>
   , Celery&rsquo;s monitoring tool. Flower will show you a dashboard of all your workers and tasks and let you drill down into specific tasks, show you task statistics, let you restart workers, and let you rate-limit tasks (among many other things).
  </p>
  <p>
   If you use an error-tracking system like
   <a href="https://rollbar.com/">
    Rollbar
   </a>
   or
   <a href="https://sentry.io/welcome/">
    Sentry
   </a>
   , you can also set Celery up to report exceptions to those services.
  </p>
  <h2>
   Further Reading
  </h2>
  <ul>
   <li>
    <a href="http://celerytaskschecklist.com/">
     Celery Task Checklist
    </a>
   </li>
   <li>
    <a href="https://www.revsys.com/12days/async-workers-celery/">
     Message Queues and Async Workers
    </a>
    , Frank Wiles
   </li>
   <li>
    <a href="http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html">
     Celery Docs - Periodic Tasks
    </a>
   </li>
   <li>
    <a href="http://docs.celeryproject.org/en/latest/django/first-steps-with-django.html">
     Celery Docs - First steps with Django
    </a>
   </li>
   <li>
    <a href="http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_always_eager">
     Celery Docs - task_always_eager
    </a>
   </li>
   <li>
    <a href="http://docs.celeryproject.org/en/latest/userguide/tasks.html#logging">
     Celery Docs - Logging
    </a>
   </li>
   <li>
    <a href="https://medium.com/@yehandjoe/celery-4-periodic-task-in-django-9f6b5a8c21c7">
     Celery 4 Periodic Task in Django
    </a>
    , Yehan Djoehartono
   </li>
   <li>
    <a href="https://realpython.com/blog/python/asynchronous-tasks-with-django-and-celery/#periodic-tasks">
     Asynchronous Tasks with Django and Celery
    </a>
    (Celery 3.1.18)
   </li>
  </ul>
  <p>
   <br/>
   * Thanks to
   <a href="https://www.reddit.com/r/django/comments/9099sq/celery_and_django_and_docker_oh_my/e2pgsth/">
    kurashu89
   </a>
   for their correction on an earlier version of this article.
  </p>
 </div>
</div>
]]>/></item><item><title>pytest support for django-test-plus</title><link>http://www.revsys.com/tidbits/pytest-support-django-test-plus/</link><description>django-test-plus has long been a useful helper library with Django. Now we&amp;#x27;ve added some simple pytest fixture support to make it even more useful!</description><pubDate>Fri, 15 Jun 2018 21:02:34 +0000</pubDate><guid>http://www.revsys.com/tidbits/pytest-support-django-test-plus/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  We're obviously fans of
  <a href="https://github.com/revsys/django-test-plus">
   django-test-plus
  </a>
  , having written it, and have found it very valuable over the last few years at cutting down on all of the boilerplate test code we have to write with our clients.
 </p>
 <p>
 </p>
 <p>
  It even helps sell certain stubborn clients on the idea that testing can be done in a not so painful way!
 </p>
 <p>
  Today we're happy to announce that you can now use django-test-plus as a real pytest fixture. Here's a quick example:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_some_view</span><span class="p">(</span><span class="n">tp</span><span class="p">):</span>
    <span class="n">tp</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'some-view-name'</span><span class="p">)</span>
    <span class="n">tp</span><span class="o">.</span><span class="n">response_200</span><span class="p">()</span>


<span class="k">def</span><span class="w"> </span><span class="nf">test_some_url_stuff</span><span class="p">(</span><span class="n">tp</span><span class="p">):</span>
    <span class="n">expected_url</span> <span class="o">=</span> <span class="s1">'/api/'</span>
    <span class="n">reversed_url</span> <span class="o">=</span> <span class="n">tp</span><span class="o">.</span><span class="n">reverse</span><span class="p">(</span><span class="s1">'api'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">expected_url</span> <span class="o">==</span> <span class="n">reversed_url</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  And you get all of the other test plus methods as part of that "tp" fixture. This is a new experimental feature, so we'd love to hear how we can make this better and more "pytest like".
 </p>
 <p>
 </p>
 <p>
  To start using all you need to do is install it and pull in "tp" as a fixture, you install it with just:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>pip<span class="w"> </span>install<span class="w"> </span>django-test-plus
</pre>
 </div>
</div>
]]>/></item><item><title>Caching uuid's for the win!</title><link>http://www.revsys.com/tidbits/caching-uuids-win/</link><description>More and more often, we see schema designs that use UUIDs as primary keys. That&amp;#x27;s a valid choice if you&amp;#x27;re concerned about sharding and partitioning your database, but it has its own drawbacks, sometimes in unexpected places.</description><pubDate>Tue, 22 May 2018 17:43:28 +0000</pubDate><guid>http://www.revsys.com/tidbits/caching-uuids-win/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   More and more often, we see schema designs that use UUIDs as primary keys. That's a valid choice if you're concerned about sharding and partitioning your database, but it has its own drawbacks, sometimes in unexpected places.
  </p>
  <p>
   If you're working on a system big enough that needs to use UUIDs, chances are that at some point you'll have to turn those UUIDs into strings, perhaps in order to pass them across different systems.
  </p>
  <p>
   In order to serialize the UUID into a string, Python has to format the UUID's
   <code>
    hex
   </code>
   property into a string containing hyphens, as RFC 4122 requires. And in order to do that, it has to slice that
   <a href="https://github.com/python/cpython/blob/3.6/Lib/uuid.py#L226-L229">
    property 5 times
   </a>
   .
  </p>
  <div class="codehilite">
   <pre><span></span><code>    <span class="k">def</span><span class="w"> </span><span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">hex</span> <span class="o">=</span> <span class="s1">'</span><span class="si">%032x</span><span class="s1">'</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">int</span>
        <span class="k">return</span> <span class="s1">'</span><span class="si">%s</span><span class="s1">-</span><span class="si">%s</span><span class="s1">-</span><span class="si">%s</span><span class="s1">-</span><span class="si">%s</span><span class="s1">-</span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span>
            <span class="nb">hex</span><span class="p">[:</span><span class="mi">8</span><span class="p">],</span> <span class="nb">hex</span><span class="p">[</span><span class="mi">8</span><span class="p">:</span><span class="mi">12</span><span class="p">],</span> <span class="nb">hex</span><span class="p">[</span><span class="mi">12</span><span class="p">:</span><span class="mi">16</span><span class="p">],</span> <span class="nb">hex</span><span class="p">[</span><span class="mi">16</span><span class="p">:</span><span class="mi">20</span><span class="p">],</span> <span class="nb">hex</span><span class="p">[</span><span class="mi">20</span><span class="p">:])</span>
</code></pre>
  </div>
  <p>
   This is fasted way to do it, and it isn't usually a bottleneck per-se, as the overhead is minimal.
  </p>
  <p>
   However, it's common for UUIDs to be used in scenarios where there's a lot of data and computation, and in some situations you might find yourself serializing a homogenous set of UUIDs over and over. This might happen in a situation where you have a 'heap' of hotter model instances that ares processed more often than most.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">random</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">uuid</span>


<span class="n">uuids</span> <span class="o">=</span> <span class="p">[</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">99</span><span class="p">)]</span>
<span class="n">normal_distrib</span> <span class="o">=</span> <span class="p">[</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">uuids</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9999</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">uuid</span> <span class="ow">in</span> <span class="n">normal_distrib</span><span class="p">:</span>
    <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">uuid</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</code></pre>
  </div>
  <p>
   In this situation, the overhead adds up, and could become a bottleneck you'd never think about.
  </p>
  <p>
   You could use
   <code>
    uuid.hex
   </code>
   instead of the serialized value, but that would break the RFC and, more pragmatically, it would make the data not really portable across systems or languages.
  </p>
  <p>
   Your best option is going to be the
   <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache">
    <code>
     lru_cache
    </code>
   </a>
   decorator.
  </p>
  <p>
   First, abstract the logic that's calling the serialization into a function that you can decorate:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">some_logic</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
        <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="p">{</span><span class="s1">'uuid'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">instance</span><span class="o">.</span><span class="n">pk</span><span class="p">)})</span>
</code></pre>
  </div>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">functools</span><span class="w"> </span><span class="kn">import</span> <span class="n">lru_cache</span>

<span class="nd">@lru_cache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">post_instance</span><span class="p">(</span><span class="n">instance</span><span class="p">):</span>
    <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="p">{</span><span class="s1">'uuid'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">instance</span><span class="o">.</span><span class="n">pk</span><span class="p">)})</span>


<span class="k">def</span><span class="w"> </span><span class="nf">some_logic</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
        <span class="n">post_instance</span><span class="p">(</span><span class="n">instance</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Note: You might be tempted to abstract the
   <code>
    str
   </code>
   call itself:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@lru_cache</span>
<span class="k">def</span><span class="w"> </span><span class="nf">uuid_to_str</span><span class="p">(</span><span class="n">uuid</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">uuid</span><span class="p">)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">some_logic</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
        <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="p">{</span><span class="s1">'uuid'</span><span class="p">:</span> <span class="n">uuid_to_str</span><span class="p">(</span><span class="n">instance</span><span class="o">.</span><span class="n">pk</span><span class="p">)})</span>
</code></pre>
  </div>
  <p>
   That's not really a good idea. You'll end up caching too much. The cache size will be wasted in calls that are rarely reused, making the more common calls incur in cache misses.
  </p>
  <p>
   Next, add logging to get some insight. This will tell uis if our caching is actually effective, and gives us some indication of how big the cache size should be:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">logging</span>


<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">some_logic</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
        <span class="n">post_instance</span><span class="p">(</span><span class="n">instance</span><span class="p">)</span>
    <span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">post_instance</span><span class="o">.</span><span class="n">cache_info</span><span class="p">())</span>
</code></pre>
  </div>
  <p>
   Briefly run the code against production data, and you'll get something like this in your logs:
  </p>
  <div class="codehilite">
   <pre><span></span><code>CacheInfo(hits=126, misses=8, maxsize=32, currsize=8)
</code></pre>
  </div>
  <p>
   If
   <code>
    hits
   </code>
   is really low, then you know
   <code>
    lru_cache
   </code>
   won't help you with this data. Your dataset of UUIDs is too heterogenous to take advantage of caching.
  </p>
  <p>
   <code>
    currsize
   </code>
   gives you an idea of how big of a cache you might need and what you should set
   <code>
    lru_caches
   </code>
   <code>
    maxsize
   </code>
   argument to.
  </p>
  <p>
   Keep in mind that higher values will use more RAM, and we're not really interested in perfectly caching every single value that's used more than once. We just want to cut down on the most common ones most of the times.
  </p>
  <p>
   As rule of the thumb, if
   <code>
    currsize
   </code>
   is smaller than
   <code>
    512
   </code>
   , I would set
   <code>
    maxsize
   </code>
   to that value plus some room (I like to go to next power of 2). Otherwise, I would cap
   <code>
    maxsize
   </code>
   to
   <code>
    128
   </code>
   . In our cexample, I would use
   <code>
    max_size=16
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="mi">16</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">post_instance</span><span class="p">(</span><span class="n">instance</span><span class="p">):</span>
    <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="p">{</span><span class="s1">'uuid'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">instance</span><span class="o">.</span><span class="n">pk</span><span class="p">)}</span>
</code></pre>
  </div>
 </div>
</div>
]]>/></item><item><title>Testing import file mismatch</title><link>http://www.revsys.com/tidbits/testing-import-file-mismatch/</link><description>Ran into a weird error with coverage, pytest, and Travis today and wanted to document an easy fix when you get a &amp;#x27;import file mistmatch&amp;#x27; error from pip packages you do not control.</description><pubDate>Mon, 21 May 2018 14:08:19 +0000</pubDate><guid>http://www.revsys.com/tidbits/testing-import-file-mismatch/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  So over the weekend I was working on adding pytest support to
  <a href="https://github.com/revsys/django-test-plus">
   django-test-plus
  </a>
  our library which makes testing in Django easier. We
  <b>
   of course
  </b>
  have tests for our testing library, but have up until now used normal Unittest style tests that we're all familiar with.
 </p>
 <p>
  Now that I'm adding full pytest support, I needed to have the tests run using pytest rather than Django's normal test runner. In switching I needed pytest itself, pytest-django, and pytest-cov so we could keep track of our test coverage. Which of these caused the problem isn't really important, but what started happening was this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="o">====================================</span><span class="w"> </span><span class="nv">ERRORS</span><span class="w"> </span><span class="o">====================================</span>
_____________<span class="w"> </span>ERROR<span class="w"> </span>collecting<span class="w"> </span>.eggs/py-1.5.3-py2.7.egg/py/test.py<span class="w"> </span>_____________
import<span class="w"> </span>file<span class="w"> </span>mismatch:
imported<span class="w"> </span>module<span class="w"> </span><span class="s1">'py.test'</span><span class="w"> </span>has<span class="w"> </span>this<span class="w"> </span>__file__<span class="w"> </span>attribute:
<span class="w">  </span>/home/travis/build/revsys/django-test-plus/.eggs/pytest-3.5.1-py2.7.egg/pytest.py
which<span class="w"> </span>is<span class="w"> </span>not<span class="w"> </span>the<span class="w"> </span>same<span class="w"> </span>as<span class="w"> </span>the<span class="w"> </span><span class="nb">test</span><span class="w"> </span>file<span class="w"> </span>we<span class="w"> </span>want<span class="w"> </span>to<span class="w"> </span>collect:
<span class="w">  </span>/home/travis/build/revsys/django-test-plus/.eggs/py-1.5.3-py2.7.egg/py/test.py
HINT:<span class="w"> </span>remove<span class="w"> </span>__pycache__<span class="w"> </span>/<span class="w"> </span>.pyc<span class="w"> </span>files<span class="w"> </span>and/or<span class="w"> </span>use<span class="w"> </span>a<span class="w"> </span>unique<span class="w"> </span>basename<span class="w"> </span><span class="k">for</span><span class="w"> </span>your<span class="w"> </span><span class="nb">test</span><span class="w"> </span>file<span class="w"> </span>modules
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  If you don't speak fluent Python, this is just saying that because of some import magic going on the files 'pytest.py' and 'py/test.py' appear to be the same thing to pytest and it's properly bailing out of the test run so you can fix it.
 </p>
 <p>
  Unfortunately, neither of those are my projects and I didn't have a direct way to fix this. Luckily, I thought of a way around it which was, if we don't generate the bytecode pyc files then it won't see any duplicates.
 </p>
 <p>
  To do this I just needed to add the following to the project's tox.ini:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="o">[</span>tox<span class="o">]</span>
<span class="w">   </span>....<span class="w"> </span>lots<span class="w"> </span>of<span class="w"> </span>other<span class="w"> </span>configuration<span class="w"> </span>...

<span class="nv">setenv</span><span class="w"> </span><span class="o">=</span>
<span class="w">    </span><span class="nv">PYTHONDONTWRITEBYTECODE</span><span class="o">=</span><span class="nb">true</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Hopefully this helps you sort out odd error message should you run into it when using Travis-CI and tox.
 </p>
 <p>
  And more importantly, hopefully Google helps me find this page when I completely forget about this and run into it again next year!
 </p>
</div>
]]>/></item><item><title>Copying Kubernetes Secrets Between Namespaces</title><link>http://www.revsys.com/tidbits/copying-kubernetes-secrets-between-namespaces/</link><description>easy-mode, cross-namespace object copy</description><pubDate>Sun, 29 Apr 2018 19:19:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/copying-kubernetes-secrets-between-namespaces/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It's a fairly long command, but it actually is pretty easy use.  You will however need to have
   <a href="https://mikefarah.gitbook.io/yq/">
    yq
   </a>
   installed as we need to scrub out a few elements from our YAML that are automatically created by Kubernetes.  yq is just like
   <a href="https://stedolan.github.io/jq/">
    jq
   </a>
   but for working with YAML.
  </p>
  <p>
   If we have a secret named
   <code>
    database
   </code>
   in the namespace
   <code>
    app-dev
   </code>
   and we want to copy it, as is, to the
   <code>
    app-production
   </code>
   namespace we need to execute:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>-n<span class="w"> </span>app-dev<span class="w"> </span>get<span class="w"> </span>secret<span class="w"> </span>database<span class="w"> </span>-o<span class="w"> </span>yaml<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
yq<span class="w"> </span><span class="s1">'del(.metadata.creationTimestamp, .metadata.uid, .metadata.resourceVersion, .metadata.namespace)'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
kubectl<span class="w"> </span>apply<span class="w"> </span>--namespace<span class="w"> </span>app-production<span class="w"> </span>-f<span class="w"> </span>-
</code></pre>
  </div>
  <h2>
   Breakdown
  </h2>
  <p>
   What we're doing here is grabbing the current secret in YAML format.   We then use
   <code>
    yq
   </code>
   to remove the uid, namespace, creationTimestamp, and resourceVersion elements from the metadata stanza so it can be applied into a new namespace.
  </p>
  <h2>
   just
  </h2>
  <p>
   If you're a fan of the tool
   <a href="https://just.systems/man/en/">
    just
   </a>
   like we are, you can drop in this command into your
   <code>
    Justfile
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="x"># Copy a secret from one namespace to another</span>
<span class="x">copy-secret from-namespace secret-name to-namespace:</span>
<span class="x">   kubectl -n </span><span class="cp">{{</span><span class="nv">from-namespace</span><span class="cp">}}</span><span class="x"> get secret </span><span class="cp">{{</span><span class="nv">secret-name</span><span class="cp">}}</span><span class="x"> -o yaml| yq 'del(.metadata.creationTimestamp, .metadata.uid, .metadata.resourceVersion, .metadata.namespace)' | kubectl apply --namespace </span><span class="cp">{{</span><span class="nv">to-namespace</span><span class="cp">}}</span><span class="x"> -f -</span>
</code></pre>
  </div>
  <p>
   This can then be used quickly like:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>just<span class="w"> </span>copy-secret<span class="w"> </span>app-dev<span class="w"> </span>pg<span class="w"> </span>app-production
</code></pre>
  </div>
 </div>
</div>
<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Original Deprecated Example
  </h2>
  <p>
   <strong>
    UPDATE:
   </strong>
   Unfortunately, the
   <code>
    --export
   </code>
   has been deprecated by the kubectl team so this original advice below no longer works with modern Kubernetes clusters.
  </p>
  <p>
   The secret resource-type is unique--it cannot be accessed from pods outside of
its namespace.  A simple way of copying common secret data (e.g.: docker registry credentials) between namespaces is provided by the
   <code>
    --export
   </code>
   flag of
   <code>
    kubectl get
   </code>
   . Pipe its output to
   <code>
    kubectl apply -n &lt;target namespace&gt; -f -
   </code>
   , and you are done!
  </p>
 </div>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>kubectl<span class="w"> </span>get<span class="w"> </span>secret<span class="w"> </span>gitlab-registry<span class="w"> </span>--namespace<span class="o">=</span>revsys-com<span class="w"> </span>--export<span class="w"> </span>-o<span class="w"> </span>yaml<span class="w"> </span><span class="p">|</span><span class="se">\</span>
<span class="w">   </span>kubectl<span class="w"> </span>apply<span class="w"> </span>--namespace<span class="o">=</span>devspectrum-dev<span class="w"> </span>-f<span class="w"> </span>-
</pre>
 </div>
</div>
]]>/></item><item><title>What the, Kubernetes! -- part 1</title><link>http://www.revsys.com/tidbits/what-the--kubernetes-1/</link><description>Overcoming the read-only configMap with Helm</description><pubDate>Fri, 30 Mar 2018 21:53:56 +0000</pubDate><guid>http://www.revsys.com/tidbits/what-the--kubernetes-1/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Greetings, and welcome to the first edition of,
   <em>
    What the, Kubernetes
   </em>
   !
  </p>
  <p>
   Today's topics:
   <a href="https://github.com/kubernetes/kubernetes/issues/60813">
    CVE-2017-1002101
   </a>
   , init-containers and YOU!
  </p>
  <h2>
   The context
  </h2>
  <p>
   Upgrading a cluster instance group from v1.7.13 to v1.7.14 introduced me to
the first-run attempt at solving the problem outlined in the CVE.
  </p>
  <p>
   The solution to the vulnerability (for the most part affecting untrusted, multi-tenant
clusters) involved forcing all
   <b>
    configMap
   </b>
   and
   <b>
    secret
   </b>
   bind-mounts to read-only.
  </p>
  <p>
   The tool we'll use in this post is
   <a href="https://github.com/kubernetes/helm/releases/tag/v2.7.2">
    Helm 2.7.2
   </a>
   .
  </p>
  <h2>
   The Problem
  </h2>
  <p>
   A CI component that was running successfully on v1.7.13 after the upgrade to v1.7.14.
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pod<span class="w"> </span>-l<span class="w"> </span><span class="nv">app</span><span class="o">=</span>docker-ci
NAME<span class="w">                         </span>READY<span class="w">     </span>STATUS<span class="w">             </span>RESTARTS<span class="w">   </span>AGE
docker-ci-4050235671-n487p<span class="w">   </span><span class="m">0</span>/1<span class="w">       </span>CrashLoopBackOff<span class="w">   </span><span class="m">4</span><span class="w">          </span>1m

$<span class="w"> </span>kubectl<span class="w"> </span>logs<span class="w"> </span>docker-ci-4050235671-n487p

<span class="o">[</span>...<span class="o">]</span>
<span class="nv">time</span><span class="o">=</span><span class="s2">"2018-03-26T04:57:21Z"</span><span class="w"> </span><span class="nv">level</span><span class="o">=</span>info<span class="w"> </span><span class="nv">msg</span><span class="o">=</span><span class="s2">"containerd successfully booted in 0.016248s"</span><span class="w"> </span><span class="nv">module</span><span class="o">=</span>containerd
Error<span class="w"> </span>starting<span class="w"> </span>daemon:<span class="w"> </span>Error<span class="w"> </span>saving<span class="w"> </span>key<span class="w"> </span>file:<span class="w"> </span>open<span class="w"> </span>/etc/docker/.tmp-key.json853378281:<span class="w"> </span>read-only<span class="w"> </span>file<span class="w"> </span>system
</code></pre>
  </div>
  <p>
   Whether or not the problem presents is dependent on the workload. It affects docker and
   <a href="https://minio.io/">
    minio
   </a>
   but not gitlab-ci-runner. 
The explanation is simple: it depends on the programmer who wrote the code.
  </p>
  <hr/>
  <p>
   <code>
    chdir(2)
   </code>
   : it's a syscall, not a law.
  </p>
  <hr/>
  <p>
   Contrary to
   <a href="https://github.com/kubernetes/kubernetes/pull/58720">
    kube issue #58720
   </a>
   , most temp file writes to one of these mounts
are performed by programs during initialization and are gone before initialization is complete.
  </p>
  <p>
   Regardless, since I've written exactly zero lines of Kubernetes code, I will leave public declaration
of opinion on the quality of this fix to others in favor of presenting a solution that can help mitigate
the results of this change.
  </p>
  <h2>
   The Solution
  </h2>
  <ul>
   <li>
    initContainer
    <ul>
     <li>
      Mount a shared
      <b>
       emptyDir
      </b>
      volume on
      <code>
       /etc/docker
      </code>
     </li>
     <li>
      Mount the configMap/secret to an alternate directory (
      <code>
       /etc/docker_
      </code>
      )
     </li>
     <li>
      Copy the contents to the expected/configured location
     </li>
     <li>
      exit
     </li>
    </ul>
   </li>
   <li>
    runtime container
    <ul>
     <li>
      Mount the shared
      <b>
       emptyDir
      </b>
      volume on
      <code>
       /etc/docker
      </code>
     </li>
     <li>
      Initialize
      <b>
       dockerd
      </b>
      normally
     </li>
    </ul>
   </li>
  </ul>
  <p>
   One functional loss incurred with this method: the files in the target directory
will not be magically updated. A mounted
   <b>
    configMap
   </b>
   or
   <b>
    secret
   </b>
   will eventually reflect 
changes made to the source configMap/secret. Replicating such functionality could be done with a side-car
container (a runtime, rather than an init-container) that would monitor the API event
bus for changes to the configMap of interest; copying new data to the shared volume
when necessary.
  </p>
  <h2>
   The Hiccup
  </h2>
  <p>
   Init-containers aren't new. They have, however, had a rough start. When deploying to a v1.5-1.7 cluster, 
it is necessary to use the
   <code>
    beta.kubernetes.io/init-containers
   </code>
   annotation to avoid
   <a href="https://github.com/kubernetes/kubernetes/issues/45627">
    issue #45627
   </a>
   . Post v1.8, it will all be just another
part of
   <code>
    .spec.template.spec[]Map
   </code>
  </p>
  <h2>
   What the...
  </h2>
  <p>
   <b>
    NOTE:
   </b>
   if you are uninterested in the why, this subsection can be skipped.
  </p>
  <p>
   I'm not sure when init-containers entered the codebase. The feature graduated to beta status in kube v1.5 and ostensibly
to GA status in v1.6.
  </p>
  <ul>
   <li>
    beta feature annotation:
    <code>
     spec.template.spec.metadata.annotations["beta.kubernetes.io/init-containers"]
    </code>
    .
   </li>
   <li>
    GA spec path:
    <code>
     spec.templates.spec.initContainers[]
    </code>
    .
   </li>
  </ul>
  <p>
   The theory goes as follows:
  </p>
  <ul>
   <li>
    <b>
     v1.5
    </b>
    : use the beta annotation
   </li>
   <li>
    <b>
     v1.6
    </b>
    through
    <b>
     v1.7
    </b>
    :  GA/beta deprecation phase; either the annotation or spec form are valid
   </li>
   <li>
    <b>
     v1.8
    </b>
    : full GA; annotation is removed.
   </li>
  </ul>
  <p>
   The full story:
   <a href="https://github.com/kubernetes/kubernetes/issues/45627">
    issue #45627
   </a>
  </p>
  <p>
   The reality of how init-containers are processed on v1.6-v1.7:
  </p>
  <ul>
   <li>
    Initial Deployment Received
    <ul>
     <li>
      Does the deployment define
      <code>
       .spec.template.spec.initContainers[]
      </code>
      ?
     </li>
     <li>
      No
      <ul>
       <li>
        Does the deployment define beta annotation init-containers?
       </li>
       <li>
        Yes
        <ul>
         <li>
          ingest annotation JSON
         </li>
         <li>
          sync data from annotation ingestion to
          <code>
           ...spec.initContainers[]
          </code>
         </li>
        </ul>
       </li>
       <li>
        No (well, then)
       </li>
      </ul>
     </li>
     <li>
      Yes
      <ul>
       <li>
        Does the deployment define beta annotation init-containers?
       </li>
       <li>
        Yes
        <ul>
         <li>
          Are the
          <code>
           ...spec.initContainers[]
          </code>
          sync'd with the beta annotation
         </li>
         <li>
          Yes
          <ul>
           <li>
            Capital! Nothing to see here. Carry on!
           </li>
          </ul>
         </li>
         <li>
          No
          <ul>
           <li>
            Hrmph! We know what's really making the wheels turn here!
           </li>
           <li>
            <code>
             ...spec.initContainers[]
            </code>
            dropped into
            <code>
             /dev/null
            </code>
            .
           </li>
           <li>
            re-synchronize
            <code>
             ...spec.initContainers[]
            </code>
            to reflect the beta annotation.
           </li>
          </ul>
         </li>
        </ul>
       </li>
      </ul>
     </li>
    </ul>
   </li>
  </ul>
  <p>
   Summary: kube v1.6 through v1.7 the
   <b>
    spec
   </b>
   definition never has primacy with the scheduler except on
   <b>
    initial Deployment
   </b>
   . When such a manifest is received, kube converts the
   <code>
    ....initContainers[]
   </code>
   spec structure into a JSON string and stores it as a beta annotation value. On subsequent updates, modifications to
   <code>
    ....initContainers[]
   </code>
   not only have no effect, but also
   <b>
    overwrite
   </b>
   <code>
    ....initContainers[]
   </code>
   with the existing (deserialized) annotation structure.  The only way around this situation is to
   <b>
    only
   </b>
   use the annotation form on
   <b>
    Deployment
   </b>
   update.  The API will entice you to
   <code>
    ...spec.initContainers[]
   </code>
   by deserializing your annotation value to its GA spec location.  Be strong!  Until v1.8, define init-containers as if you were still on v1.5--pretend
   <code>
    ...spec.initContainers[]
   </code>
   doesn't exist until then!
  </p>
  <h2>
   The Helm Chart
  </h2>
  <p>
   So we need a method  that will allow for gradual cut-over to v1.8 without having to manage separate charts.
  </p>
  <p>
   The application used for this demonstration is Docker.
   <b>
    dockerd
   </b>
   is one of those binaries that
uses its config directory for pre-initialization scratch space.
  </p>
  <h3>
   Configuration
  </h3>
  <p>
   The fix for the read-only configuration path can be seen towards the bottom of
   <code>
    ./values.yaml
   </code>
   with the keys for 
driving our init-container named templates above
   <code>
    Env
   </code>
   .
  </p>
  <hr/>
  <p>
   <b>
    NOTE
   </b>
   : The following chart files have been pruned for this post.
   <a href="https://gitlab.com/revsys/helm-charts/tree/master/docker">
    The unpruned version
   </a>
  </p>
  <p>
   All indentation is at 2-space increments. Look for any lines in the chart with the 
term
   <code>
    indent
   </code>
   for adjustment if you adapt this to a different indentation interval.
  </p>
  <hr/>
  <p>
   <b>
    <code>
     values.yaml
    </code>
   </b>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">2</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">3 Image</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker</span>
<span class="w">  </span><span class="nt">4 ImageTag</span><span class="p">:</span><span class="w"> </span><span class="nl">&amp;itag</span><span class="w"> </span><span class="s">"18-dind"</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">5</span>
<span class="w">  </span><span class="nt">6 deploymentEnvironment</span><span class="p">:</span><span class="w"> </span><span class="nl">&amp;env</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">demo</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">7</span>
<span class="w">  </span><span class="nt">8 Plug</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">9</span>
<span class="w w-Error"> </span><span class="nt">10 NodeSelectors</span><span class="p">:</span><span class="w">  </span><span class="p p-Indicator">[]</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">11</span>
<span class="w"> </span><span class="nt">12 InitCommands</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">13   -</span>
<span class="w"> </span><span class="nt">14     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">config</span>
<span class="w"> </span><span class="nt">15     command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cp /etc/docker_/config.json /etc/docker/</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">16</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">17</span>
<span class="w"> </span><span class="nt">18 Env</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">19   -</span>
<span class="w"> </span><span class="nt">20     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">DOCKER_HOST</span>
<span class="w"> </span><span class="nt">21     value</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">localhost:49152</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">22   -</span>
<span class="w"> </span><span class="nt">23     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">IMAGE_TAG</span>
<span class="w"> </span><span class="nt">24     value</span><span class="p">:</span><span class="w"> </span><span class="nv">*itag</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">52</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">53</span><span class="w"> </span><span class="c1"># Volumes</span>
<span class="w"> </span><span class="nt">54 Volumes</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">55   -</span>
<span class="w"> </span><span class="nt">56     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-config</span>
<span class="w"> </span><span class="nt">57     configMap</span><span class="p">:</span>
<span class="w"> </span><span class="nt">58       name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-config</span>
<span class="w"> </span><span class="nt">59       items</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">60         -</span>
<span class="w"> </span><span class="nt">61           key</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">config</span>
<span class="w"> </span><span class="nt">62           path</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">config.json</span>
<span class="w"> </span><span class="nt">63           mode</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0600</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">64   -</span>
<span class="w"> </span><span class="nt">65     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-config-directory</span>
<span class="w"> </span><span class="nt">66     emptyDir</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">67</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">68</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">69</span>
<span class="w"> </span><span class="nt">70 VolumeMounts</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">71   -</span>
<span class="w"> </span><span class="nt">72     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-config</span>
<span class="w"> </span><span class="nt">73     mountPath</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/docker_</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">74   -</span>
<span class="w"> </span><span class="nt">75     name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-config-directory</span>
<span class="w"> </span><span class="nt">76     mountPath</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/docker</span>
</code></pre>
  </div>
  <p>
   <br/>
  </p>
  <hr/>
  <p>
   <b>
    Q
   </b>
   :
   <em>
    Dear Stephen: Why are your init-container commands listed in
    <code>
     values.yaml
    </code>
    ?
   </em>
  </p>
  <p>
   <b>
    A
   </b>
   : I am glad you asked!  As template markup gets thicker, readability decreases. Having
critical aspects of a deployment hidden within a tangle of unrelated symbols and formatting
has the danger of obscuring what the target workload is. I've been meaning to work out the
gotpl incantations to make this happen and this series seemed to be a perfect reason
to do it!
  </p>
  <hr/>
  <p>
   <br/>
  </p>
  <p>
   <b>
    <code>
     ./settings/one
    </code>
   </b>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">  </span><span class="mi">1</span>
<span class="w">  </span><span class="mi">2</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="mi">3</span><span class="w">   </span><span class="nt">"log-driver"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gcplogs"</span><span class="p">,</span>
<span class="w">  </span><span class="mi">4</span><span class="w">   </span><span class="nt">"group"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span>
<span class="w">  </span><span class="mi">5</span><span class="w">   </span><span class="nt">"iptables"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w">  </span><span class="mi">6</span><span class="w">   </span><span class="nt">"ip-masq"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
<span class="w">  </span><span class="mi">7</span><span class="w"> </span><span class="p">}</span>
</code></pre>
  </div>
  <p>
   <br/>
  </p>
  <h3>
   Named Templates (
   <a href="https://github.com/kubernetes/helm/blob/master/docs/chart_template_guide/named_templates.md">
    doc link
   </a>
   )
  </h3>
  <hr/>
  <h3>
   op.ed time
  </h3>
  <p>
   The following is the meat of the presented solution. It involves Helm. Helm is a
   <b>
    templating
   </b>
   utility that is working its 
way towards fulfilling its stated goal of being a package manager for Kubernetes.
  </p>
  <p>
   Because, um... well.. Kubernetes and uh... Golang, Helm, unsurprisingly, uses Go templates. If its notably inelegant
appearance displeases you, well, the large pile of sand is over there. And here is your mallet. And you were born
with the other critical piece to that puzzle. Go for it!
  </p>
  <p>
   For everyone else, without further ado, third party plugins or wrapper scripts, I give you...
  </p>
  <hr/>
  <h3>
   The Meat (or salty, smokey-flavored tempeh)
  </h3>
  <p>
   <b>
    NOTE
   </b>
   : the filenames prefixed with an underscore signal to helm that the contents are not Kube manifests.
  </p>
  <p>
   First up: the
   <code>
    InitMethod
   </code>
   template. See
   <a href="https://en.wikipedia.org/wiki/Reverse_Polish_notation">
    this
   </a>
   if
you are unfamiliar with postfix notation (reference to lines 9 and 13).
  </p>
  <p>
   Within this wee mess we have a thing that, when included in another template, will emit a term,
   <code>
    annotation
   </code>
   or
   <code>
    spec
   </code>
   ,  indicative of the form supported by the target kube cluster.
  </p>
  <p>
   <br/>
  </p>
  <p>
   <b>
    <code>
     ./templates/_helpers.yaml
    </code>
   </b>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">1</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="nv">vim</span><span class="o">:</span> <span class="nv">set</span> <span class="nv">filetype</span><span class="o">=</span><span class="nv">sls</span> <span class="nv">sw</span><span class="o">=</span><span class="m">2</span> <span class="nv">ts</span><span class="o">=</span><span class="m">2</span><span class="o">:</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">2</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">3</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">4</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">define</span> <span class="s2">"InitMethod"</span> -<span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">5</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">major</span> <span class="o">:=</span> <span class="nv">.Capabilities.KubeVersion.Major</span> -<span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">6</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">minor_</span> <span class="o">:=</span> <span class="o">(</span> <span class="nv">splitList</span> <span class="s2">"+"</span> <span class="nv">.Capabilities.KubeVersion.Minor</span> <span class="o">)</span> -<span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">7</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">minor</span> <span class="o">:=</span> <span class="nv">index</span> <span class="err">$</span><span class="nv">minor_</span> <span class="m">0</span> -<span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">8</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="k">and</span> <span class="o">(</span><span class="nv">lt</span> <span class="o">(</span><span class="nv">int</span> <span class="err">$</span><span class="nv">major</span><span class="o">)</span> <span class="m">2</span><span class="o">)</span> <span class="o">(</span><span class="nv">lt</span> <span class="o">(</span><span class="nv">int</span> <span class="err">$</span><span class="nv">minor</span><span class="o">)</span> <span class="m">8</span><span class="o">)</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">9</span><span class="w">     </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">printf</span> <span class="s2">"annotation"</span> -<span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">10</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="k">else</span> -<span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">11</span><span class="w">     </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="o">(</span><span class="nv">eq</span> <span class="o">(</span><span class="nv">int</span> <span class="err">$</span><span class="nv">major</span><span class="o">)</span> <span class="m">1</span><span class="o">)</span> <span class="k">and</span> <span class="o">(</span><span class="nv">ge</span> <span class="o">(</span><span class="nv">int</span> <span class="err">$</span><span class="nv">minor</span><span class="o">)</span> <span class="m">8</span><span class="o">)</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">12</span><span class="w">       </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">printf</span> <span class="s2">"spec"</span> -<span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">13</span><span class="w">     </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> -<span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="k">else</span> <span class="k">if</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">14</span><span class="w">   </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> -<span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="k">if</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">15</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> -<span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="nv">define</span> <span class="o">*/</span><span class="cp">}}</span>
</code></pre>
  </div>
  <p>
   <br/>
  </p>
  <p>
   Once your eyes are able to blur past the template markup, it is quite straightforward:
  </p>
  <ul>
   <li>
    InitMethod
    <ul>
     <li>
      what version of Kubernetes are we talking to?
      <ul>
       <li>
        less than v1.8: we use the annotation form
       </li>
       <li>
        v1.8 and beyond: use the spec form
       </li>
      </ul>
     </li>
    </ul>
   </li>
  </ul>
  <hr/>
  <p>
   NOTE: GKE decided to augment the kube version with a "+".  Lines 6-7 are required to deal with this anomaly
  </p>
  <hr/>
  <p>
   <b>
    <code>
     ./templates/_init-containers.yaml
    </code>
   </b>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="nv">vim</span><span class="o">:</span> <span class="nv">set</span> <span class="nv">filetype</span><span class="o">=</span><span class="nv">sls</span> <span class="nv">sw</span><span class="o">=</span><span class="m">2</span> <span class="nv">ts</span><span class="o">=</span><span class="m">2</span><span class="o">:</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">2</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">3</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">define</span> <span class="s2">"InitSpec"</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">4</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="nv">eq</span> <span class="o">(</span><span class="nv">include</span> <span class="s2">"InitMethod"</span> <span class="err">.</span><span class="o">)</span> <span class="s2">"spec"</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">5</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">env</span> <span class="o">:=</span> <span class="nv">.Values.Env</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">6</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">volumes</span> <span class="o">:=</span> <span class="nv">.Values.VolumeMounts</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">7</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">image</span> <span class="o">:=</span> <span class="o">(</span> <span class="nv">printf</span> <span class="s2">"%s:%s"</span> <span class="nv">.Values.Image</span> <span class="nv">.Values.ImageTag</span> <span class="o">)</span> <span class="cp">}}</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">8 initContainers</span><span class="p p-Indicator">:</span>
<span class="w">  </span><span class="l l-Scalar l-Scalar-Plain">9</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">range</span> <span class="nv">.Values.initCommands</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">10   -</span>
<span class="w"> </span><span class="nt">11     name</span><span class="p">:</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">.name</span> <span class="cp">}}</span>
<span class="w"> </span><span class="nt">12     image</span><span class="p">:</span><span class="w"> </span><span class="cp">{{</span> <span class="err">$</span><span class="nv">image</span> <span class="cp">}}</span>
<span class="w"> </span><span class="nt">13     command</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"/bin/sh"</span><span class="p p-Indicator">,</span><span class="w"> </span><span class="s">"-c"</span><span class="p p-Indicator">]</span>
<span class="w"> </span><span class="nt">14     args</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">15       -</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">.command</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="w"> </span><span class="nt">16     env</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">17</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">toYaml</span> <span class="err">$</span><span class="nv">env</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">8</span> <span class="cp">}}</span>
<span class="w"> </span><span class="nt">18     volumeMounts</span><span class="p">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">19</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">toYaml</span> <span class="err">$</span><span class="nv">volumes</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">8</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">20</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="nv">range</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">21</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="k">if</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">22</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span><span class="w"> </span><span class="cp">{{</span><span class="o">/*</span> <span class="nv">define</span> <span class="o">*/</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">23</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">24</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">25</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">26</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">define</span> <span class="s2">"InitAnnotation"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">27</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="nv">eq</span> <span class="o">(</span><span class="nv">include</span> <span class="s2">"InitMethod"</span> <span class="err">.</span><span class="o">)</span> <span class="s2">"annotation"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">28</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">env</span> <span class="o">:=</span> <span class="nv">.Values.Env</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">29</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">volumes</span> <span class="o">:=</span> <span class="nv">.Values.VolumeMounts</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">30</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="err">$</span><span class="nv">image</span> <span class="o">:=</span> <span class="o">(</span> <span class="nv">printf</span> <span class="s2">"%s:%s"</span> <span class="nv">.Values.Image</span> <span class="nv">.Values.ImageTag</span> <span class="o">)</span> <span class="cp">}}</span>
<span class="w"> </span><span class="nt">31 pod.beta.kubernetes.io/init-containers</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">|</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">32   [</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">33</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">range</span> <span class="err">$</span><span class="nv">ic_index</span><span class="o">,</span> <span class="err">$</span><span class="nv">ic</span> <span class="o">:=</span> <span class="nv">.Values.initCommands</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">34 </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="err">$</span><span class="nv">ic_index</span> <span class="cp">}}</span><span class="l l-Scalar l-Scalar-Plain">,</span><span class="cp">{{</span><span class="nv">end</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">35     {</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">36       "name"</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">.name</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span><span class="err">,</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">37       "image"</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="cp">{{</span> <span class="err">$</span><span class="nv">image</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span><span class="err">,</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">38       "command"</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"/bin/sh"</span><span class="p p-Indicator">,</span><span class="w"> </span><span class="s">"-c"</span><span class="p p-Indicator">]</span><span class="err">,</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">39       "args"</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">.command</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span><span class="w"> </span><span class="p p-Indicator">]</span><span class="err">,</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">40       "env"</span><span class="p p-Indicator">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">41         [</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">42</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">range</span> <span class="err">$</span><span class="nv">ev_index</span><span class="o">,</span> <span class="err">$</span><span class="nv">ev</span> <span class="o">:=</span> <span class="err">$</span><span class="nv">env</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">43 </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="err">$</span><span class="nv">ev_index</span><span class="cp">}}</span><span class="l l-Scalar l-Scalar-Plain">,</span><span class="cp">{{</span><span class="nv">end</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">44</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">toJson</span> <span class="err">$</span><span class="nv">ev</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">12</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">45</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">46         ],</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">47       "volumeMounts"</span><span class="p p-Indicator">:</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">48         [</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">49</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">range</span> <span class="err">$</span><span class="nv">vm_index</span><span class="o">,</span> <span class="err">$</span><span class="nv">vm</span> <span class="o">:=</span> <span class="err">$</span><span class="nv">volumes</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">50 </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="err">$</span><span class="nv">vm_index</span> <span class="cp">}}</span><span class="l l-Scalar l-Scalar-Plain">,</span><span class="cp">{{</span><span class="nv">end</span><span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">51</span><span class="w"> </span><span class="cp">{{</span> <span class="nv">toJson</span> <span class="err">$</span><span class="nv">vm</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">12</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">52</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">53         ]</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">54     }</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">55</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">56   ]</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">57</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">58</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">59</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">60</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">define</span> <span class="s2">"InitContainers"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">61</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="nv">eq</span> <span class="o">(</span> <span class="nv">include</span> <span class="s2">"InitMethod"</span> <span class="o">)</span> <span class="s2">"annotation"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">62</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">include</span> <span class="s2">"InitAnnotation"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">63</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">64</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="nv">eq</span> <span class="o">(</span> <span class="nv">include</span> <span class="s2">"InitMethod"</span> <span class="o">)</span> <span class="s2">"spec"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">65</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">include</span> <span class="s2">"InitSpec"</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">66</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">67</span><span class="w"> </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
</code></pre>
  </div>
  <p>
   See lines 4 &amp; 27 for how
   <b>
    InitMethod
   </b>
   is called.
  </p>
  <p>
   <br/>
  </p>
  <h3>
   The Cheese (or congealed soy paste cheese analog)
  </h3>
  <ul>
   <li>
    InitSpec
   </li>
   <li>
    InitAnnotation
   </li>
  </ul>
  <p>
   What follows are the templates that inject the appropriate init-containers definition when included by a Deployment manifest. They can be included as part of a Chart's boilerplate (if one is so inclined) as they do not add to the manifest's structure if no init-container commands are defined to drive them.
  </p>
  <p>
   Encapsulating the if/else logic within the helper templates allows for a
   <b>
    Deployment
   </b>
   manifest template to get away with only two template-related statements.  Only the form that provides full functionality actually renders anything; therefore, the desire for a chart that is version-agnostic (viz. init-containers)
  </p>
  <p>
   <br/>
  </p>
  <p>
   <b>
    <code>
     templates/deployment.yaml
    </code>
   </b>
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="x">  1 apiVersion: extensions/v1beta1</span>
<span class="x">  2 kind: Deployment</span>
<span class="x">  3 metadata:</span>
<span class="x">  4   name: </span><span class="cp">{{</span><span class="nv">.Values.Plug</span><span class="cp">}}</span><span class="x">-</span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x">  5   namespace: </span><span class="cp">{{</span><span class="nv">.Release.Namespace</span><span class="cp">}}</span>
<span class="x">  6   labels:</span>
<span class="x">  7     app: </span><span class="cp">{{</span><span class="nv">.Values.Plug</span><span class="cp">}}</span>
<span class="x">  8     env: </span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x">  9     imageTag: </span><span class="cp">{{</span><span class="nv">.Values.ImageTag</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 10     heritage: </span><span class="cp">{{</span><span class="nv">.Release.Service</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 11     release: </span><span class="cp">{{</span> <span class="nv">.Release.Name</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 12     chart: </span><span class="cp">{{</span><span class="nv">.Chart.Name</span><span class="cp">}}</span><span class="x">-</span><span class="cp">{{</span><span class="nv">.Chart.Version</span><span class="cp">}}</span>
<span class="x"> 13 spec:</span>
<span class="x"> 14   selector:</span>
<span class="x"> 15     matchLabels:</span>
<span class="x"> 16       app: </span><span class="cp">{{</span><span class="nv">.Values.Plug</span><span class="cp">}}</span><span class="x">-</span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x"> 17       env: </span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x"> 18       imageTag: </span><span class="cp">{{</span><span class="nv">.Values.ImageTag</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 19       release: </span><span class="cp">{{</span> <span class="nv">.Release.Name</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 20   template:</span>
<span class="x"> 21     metadata:</span>
<span class="x"> 22       labels:</span>
<span class="x"> 23         app: </span><span class="cp">{{</span><span class="nv">.Values.Plug</span><span class="cp">}}</span><span class="x">-</span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x"> 24         env: </span><span class="cp">{{</span><span class="nv">.Values.deploymentEnvironment</span><span class="cp">}}</span>
<span class="x"> 25         imageTag: </span><span class="cp">{{</span><span class="nv">.Values.ImageTag</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 26         release: </span><span class="cp">{{</span> <span class="nv">.Release.Name</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 27       annotations:</span>
<span class="x"> 28         chksum/config: </span><span class="cp">{{</span> <span class="nv">include</span> <span class="o">(</span><span class="nv">print</span> <span class="err">$</span><span class="nv">.Template.BasePath</span>  <span class="s2">"/configmap.yaml"</span><span class="o">)</span> <span class="err">.</span> <span class="o">|</span> <span class="nf">sha256sum</span> <span class="o">|</span> <span class="nf">quote</span> <span class="cp">}}</span>
<span class="x"> 29 </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">include</span> <span class="s2">"InitAnnotation"</span> <span class="err">.</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">8</span> <span class="cp">}}</span>
<span class="x"> 30     spec:</span>
<span class="x"> 31 </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">include</span> <span class="s2">"InitSpec"</span> <span class="err">.</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">6</span> <span class="cp">}}</span>
<span class="x"> 32 </span><span class="cp">{{</span><span class="o">-</span> <span class="k">if</span> <span class="nv">.Values.NodeSelectors</span> <span class="cp">}}</span>
<span class="x"> 33       nodeSelector:</span>
<span class="x"> 34 </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">toYaml</span> <span class="nv">.Values.NodeSelectors</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">10</span> <span class="cp">}}</span>
<span class="x"> 35 </span><span class="cp">{{</span><span class="o">-</span> <span class="nv">end</span> <span class="cp">}}</span>
<span class="x"> 36       volumes:</span>
<span class="x"> 37 </span><span class="cp">{{</span> <span class="nv">toYaml</span> <span class="nv">.Values.Volumes</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">8</span> <span class="cp">}}</span>
<span class="x"> 38       containers:</span>
<span class="x"> 39         -</span>
<span class="x"> 40           name: docker</span>
<span class="x"> 41           image: </span><span class="cp">{{</span><span class="nv">.Values.Image</span><span class="cp">}}</span><span class="x">:</span><span class="cp">{{</span><span class="nv">.Values.ImageTag</span><span class="cp">}}</span>
<span class="x"> 42           command:</span>
<span class="x"> 43             - /usr/local/bin/dockerd</span>
<span class="x"> 44           args:</span>
<span class="x"> 45             - --config-file=/etc/docker/config.json</span>
<span class="x"> 46             - -H</span>
<span class="x"> 47             - 0.0.0.0:49152</span>
<span class="x"> 48             - --dns</span>
<span class="x"> 49             - 8.8.8.8</span>
<span class="x"> 50             - --insecure-registry&bull;</span>
<span class="x"> 51             - registry--ci.ci</span>
<span class="x"> 52           securityContext:</span>
<span class="x"> 53             privileged: true</span>
<span class="x"> 54           ports:</span>
<span class="x"> 55             -</span>
<span class="x"> 56              protocol: TCP</span>
<span class="x"> 57              containerPort: 49152</span>
<span class="x"> 58           volumeMounts:</span>
<span class="x"> 59 </span><span class="cp">{{</span> <span class="nv">toYaml</span> <span class="nv">.Values.VolumeMounts</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">12</span> <span class="cp">}}</span>
<span class="x"> 60           env:</span>
<span class="x"> 61 </span><span class="cp">{{</span> <span class="nv">toYaml</span> <span class="nv">.Values.Env</span> <span class="o">|</span> <span class="nf">indent</span> <span class="m">12</span> <span class="cp">}}</span>
</code></pre>
  </div>
  <p>
   <br/>
  </p>
  <h2>
   Conclusion
  </h2>
  <p>
   The original target example for this post was to have been demonstrating a
   <a href="https://djangoproject.com">
    Django
   </a>
   and
   <a href="https://celeryproject.org">
    Celery
   </a>
   application. Once I encountered CVE-2017-1002101 I decided to refocus my initial foray towards the simpler and much more immediate problem domain. This example doesn't have complex requirements for making it work. As long as the init-container image has a functioning
   <code>
    cp
   </code>
   binary it will foot the bill--a valid argument can be presented that the post-initial-deployment functionality of this init-container has no effect on the long-term viability of that
   <b>
    Deployment
   </b>
   (as long as the command is entered correctly the first time).
  </p>
  <p>
   Because python runtimes (e.g.: Django, Celery,
   <a href="https://gunicorn.org">
    Gunicorn
   </a>
   )  directly consume application code, it is critical those runtimes' environments are always in sync. The next post will cover such a deployment and will include the methods demonstrated today.
  </p>
  <p>
   Thanks for reading!
  </p>
 </div>
</div>
]]>/></item><item><title>Using Private Packages in Python</title><link>http://www.revsys.com/tidbits/using-private-packages-python/</link><description>With companies moving to microservices, it's becoming common to have a system scattered across multiple repositories. It's frequent to abstract common patterns and code into private repositories that are then included in each service. But using packages from private repos with Python can be tricky. This guide will guide you through the necessary steps.</description><pubDate>Thu, 21 Dec 2017 17:17:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/using-private-packages-python/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   With companies moving to microservices, it's becoming common to have a system scattered across multiple repositories. It's frequent to abstract common patterns and code into private repositories that are then included in each service.
  </p>
  <p>
   But using packages from private repos with Python can be tricky. This guide will walk you through the necessary steps to correctly use private packages with
   <code>
    pip
   </code>
   or
   <code>
    pipenv
   </code>
   . We have also built
   <a href="https://revsys.com/pip-private-packages/">
    a little app
   </a>
   that will generate the right incantations for you to copy and paste.
  </p>
  <h2>
   Figuring out the URL
  </h2>
  <p>
   First, you need to choose which format you want to use: tarball (ie:
   <code>
    .zip
   </code>
   ) or vcs (most likely
   <code>
    git
   </code>
   ). I prefer just downloading the tarball because it doesn't require any additional vcs software. But if use
   <code>
    pipenv
   </code>
   , you'll have to use vcs (see below).
  </p>
  <h3>
   Authentication
  </h3>
  <p>
   In order to have access to the private repo or tarball, you'll need an authentication token. GitHub and other providers allow you to create a private access token for your user, but because we are going to hardcode such token in code, I strongly recommend to create a 'bot' user and use its access token.
  </p>
  <ul>
   <li>
    Github: access token at
    <code>
     https://github.com/settings/tokens
    </code>
    . You can select just the
    <code>
     repo
    </code>
    scopes.
   </li>
   <li>
    Githost: deploy token at
    <code>
     https://&lt;myorg&gt;.githost.io/&lt;org&gt;/&lt;project&gt;/settings/repository
    </code>
    . You can just select
    <code>
     read_repository
    </code>
    scope.
   </li>
   <li>
    Gitlab: deploy token at
    <code>
     https://gitlab.com/profile/&lt;org&gt;/&lt;project&gt;/settings/repository
    </code>
    . You can just select
    <code>
     read_repository
    </code>
    scope.
   </li>
   <li>
    Bitbucket: access token at
    <code>
     https://bitbucket.org/account/user/&lt;botusername&gt;/app-passwords
    </code>
    . You can just select
    <code>
     repositories/read
    </code>
    scope.
   </li>
  </ul>
  <h3>
   Versioning
  </h3>
  <p>
   You will also need to decide which version of your package you want to install. We'll call this the
   <code>
    ref
   </code>
   . You could target a branch, a commit, or a tag/version. I prefer to use version because it gives me a human-readable point of reference.
  </p>
  <h2>
   Building the url
  </h2>
  <p>
   Once you have your access token and your ref, the next step is figuring out a URL at which
   <code>
    pip
   </code>
   can download or clone the code.
  </p>
  <h3>
   Github
  </h3>
  <ul>
   <li>
    Tarball URL will be
    <code>
     https://&lt;access_token&gt;@github.com/&lt;myorg&gt;/&lt;myproject&gt;/archive/&lt;ref&gt;.zip
    </code>
   </li>
   <li>
    VCS Tarball will be
    <code>
     git+https://&lt;access_token&gt;@github.com/&lt;myorg&gt;/&lt;myproject&gt;.git@&lt;ref&gt;
    </code>
   </li>
  </ul>
  <h3>
   Githost
  </h3>
  <ul>
   <li>
    Tarball URL will be
    <code>
     https://&lt;myorg&gt;.githost.io/&lt;myteam&gt;/&lt;myproject&gt;/repository/archive.zip?private_token=&lt;access_token&gt;&amp;ref=&lt;ref&gt;
    </code>
   </li>
   <li>
    VCS Tarball will be
    <code>
     git+https://&lt;token_username&gt;:&lt;deploy_token@&lt;myorg&gt;.githost.io/&lt;myteam&gt;/&lt;myproject&gt;.git@&lt;ref&gt;
    </code>
   </li>
  </ul>
  <h3>
   Gitlab
  </h3>
  <ul>
   <li>
    Tarball URL will be
    <code>
     https://gitlab.com/&lt;myteam&gt;/&lt;myproject&gt;/repository/archive.zip?private_token=&lt;access_token&gt;&amp;ref=&lt;ref&gt;
    </code>
   </li>
   <li>
    VCS Tarball will be
    <code>
     git+https://&lt;token_username&gt;:&lt;deploy_token&gt;@gitlab.com/&lt;myorg&gt;/&lt;myproject&gt;.git@&lt;ref&gt;
    </code>
   </li>
  </ul>
  <h3>
   Bitbucket
  </h3>
  <ul>
   <li>
    Tarball URL will be
    <code>
     https://&lt;botusername&gt;:&lt;access_token&gt;@bitbucket.org/&lt;myorg&gt;/&lt;myproject&gt;/get/&lt;ref&gt;.zip
    </code>
   </li>
   <li>
    VCS Tarball will be
    <code>
     git+https://&lt;botusername&gt;:&lt;access_token&gt;@bitbucket.org/&lt;myorg&gt;/&lt;myproject&gt;.git@&lt;ref&gt;
    </code>
   </li>
  </ul>
  <h2>
   Specifying the dependency
  </h2>
  <h3>
   <code>
    pip
   </code>
   requirements file
  </h3>
  <p>
   Once you have the URL, you can just add it to you requirements files, or use with
   <code>
    pip install
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code>$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span><span class="s2">"&lt;URL&gt;"</span><span class="w">  </span><span class="c1"># You'll need the quotes to escape the URL</span>
</code></pre>
  </div>
  <h3>
   <code>
    Pipfile
   </code>
  </h3>
  <p>
   To add the URL to you
   <code>
    Pipfile
   </code>
   , use this syntax:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">[packages]</span>
<span class="na">revsys-teams = {file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"&lt;URL&gt;"</span><span class="na">}</span>
</code></pre>
  </div>
  <h3>
   Private dependency of a private package
  </h3>
  <p>
   If you need to specify your private package as a dependency of another package, you'll have to add the URL in your the dependency's
   <code>
    setup.py
   </code>
   . Note that the exact syntax of the specification is a little tricky:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">setuptools</span><span class="w"> </span><span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">find_packages</span>

<span class="n">setup</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="s1">'upstreamprivatepackage'</span><span class="p">,</span>
    <span class="n">install_requires</span><span class="o">=</span><span class="p">[</span>
        <span class="c1"># The 'version' is required. Doesn't have to be an actual version of the package.</span>
        <span class="c1"># but it must match what's in the `egg` fragment of its entry in `dependency_links`.</span>
        <span class="s2">"&lt;myproject&gt;==0.0.1"</span><span class="p">,</span>
    <span class="p">],</span>
    <span class="n">dependency_links</span><span class="o">=</span><span class="p">[</span>
        <span class="c1"># there must be a version identifier in the `egg=` fragment and it must match what's</span>
        <span class="c1"># in `install_requires`</span>
        <span class="s1">'&lt;url&gt;#egg=&lt;myproject&gt;-0.0.1'</span><span class="p">,</span>
    <span class="p">],</span>
<span class="p">)</span>
</code></pre>
  </div>
  <p>
   When installing the upstream package, you'll also have to tell
   <code>
    pip
   </code>
   to actually parse the entries in
   <code>
    dependency_links
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="c1"># `pip` has to be run with the `PIP_PROCESS_DEPENDENCY_LINKS` env var or with `--process-dependency-links` option.</span>
<span class="c1"># As of v9.0.1, `pip` will print a big red scary deprecation warning, but that's the only way.</span>
<span class="nv">PIP_PROCESS_DEPENDENCY_LINKS</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>...

<span class="c1"># or</span>
pip<span class="w"> </span>install<span class="w"> </span>...<span class="w"> </span>--process-dependency-links
</code></pre>
  </div>
 </div>
</div>
]]>/></item><item><title>Docker: Useful Command Line Stuff</title><link>http://www.revsys.com/tidbits/docker-useful-command-line-stuff/</link><description>Let’s talk about what it’s like to actually use Docker in your day-to-day.</description><pubDate>Tue, 05 Dec 2017 02:43:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/docker-useful-command-line-stuff/</guid><content:encoded<![CDATA[<div class="block-content">
 <p data-block-key="k891j">
  In my&nbsp;
  <a href="https://www.revsys.com/tidbits/brief-intro-docker-djangonauts/">
   first post about Docker
  </a>
  , I took you through what goes in the&nbsp;
  <b>
   Dockerfile
  </b>
  &nbsp;and&nbsp;
  <b>
   docker-compose.yml
  </b>
  &nbsp;files. In this post, let&rsquo;s talk about what it&rsquo;s like to actually use Docker.
 </p>
 <p data-block-key="k79df">
  This post assumes that the following are true for you:
 </p>
 <ul>
  <li data-block-key="u70xb">
   You have worked through the&nbsp;
   <a href="https://docs.docker.com/get-started/">
    Get Started with Docker
   </a>
   &nbsp;tutorial
   <br/>
  </li>
  <li data-block-key="kjhgr">
   You have a Django project with a Dockerfile and docker-compose.yml file (see my&nbsp;
   <a href="https://www.revsys.com/tidbits/brief-intro-docker-djangonauts/">
    first post
   </a>
   &nbsp;for help setting these up)
   <br/>
  </li>
  <li data-block-key="6uayz">
   You can run&nbsp;
   <b>
    docker-compose build
   </b>
   &nbsp;and&nbsp;
   <b>
    docker-compose up
   </b>
   , start your server (if that isn&rsquo;t part of your docker-compose.yml file already), and see a Django blue screen of success (or other &ldquo;YAY!&rdquo; indication) when you look at your web page in a browser
  </li>
 </ul>
 <p data-block-key="grazi">
  To see what my Dockerfile and docker-compose.yml file look like for the purposes of this post, head over to my&nbsp;
  <a href="https://www.revsys.com/tidbits/brief-intro-docker-djangonauts/">
   first post
  </a>
  .
 </p>
 <h2 data-block-key="s6yyk">
  On a New Day
 </h2>
 <p data-block-key="q2c6e">
  If you've changed anything in your Dockerfile or requirements, then you will want to rebuild your image. This will retrieve any updates from the Python image you are using, re-install your requirements if they have changed, and make sure your image is up to date. If not much has changed in your Dockerfile or requirements, this will only take a few seconds. If you have made more substantial changes, this might take a little longer.
 </p>
 <p data-block-key="uijc0">
  Open Terminal or your command line, navigate to the directory that contains your Dockerfile, and run this command:
 </p>
 <p data-block-key="4q9q0">
  <br/>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">build</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="veewf">
  We&rsquo;re using&nbsp;
  <b>
   docker-compose build
  </b>
  &nbsp;instead of&nbsp;
  <b>
   docker build .
  </b>
  &nbsp;because once you start using Compose, you should consistently use the Compose commands. I&rsquo;ve found through some trial and error that the two commands don&rsquo;t always have the same result, and that can be frustrating to debug.
 </p>
 <p data-block-key="otljt">
  You will see a lot of output that looks something like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">build</span>
<span class="n">db</span> <span class="n">uses</span> <span class="n">an</span> <span class="n">image</span><span class="p">,</span> <span class="n">skipping</span>
<span class="n">Building</span> <span class="n">web</span>
<span class="n">Step</span> <span class="mi">1</span><span class="o">/</span><span class="mi">10</span> <span class="p">:</span> <span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.6.2</span>
<span class="o">---&gt;</span> <span class="mi">26</span><span class="n">acbad26a2c</span>
<span class="err">&hellip;</span>
<span class="n">Step</span> <span class="mi">7</span><span class="o">/</span><span class="mi">10</span> <span class="p">:</span> <span class="n">RUN</span> <span class="n">pip</span> <span class="n">install</span> <span class="o">-</span><span class="n">r</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span>
<span class="o">---&gt;</span> <span class="n">Using</span> <span class="n">cache</span>
<span class="o">---&gt;</span> <span class="mi">6494267</span><span class="n">dadea</span>
<span class="n">Step</span> <span class="mi">8</span><span class="o">/</span><span class="mi">10</span> <span class="p">:</span> <span class="n">COPY</span> <span class="o">.</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>
<span class="o">---&gt;</span> <span class="n">d80b24eb2470</span>
<span class="n">Step</span> <span class="mi">9</span><span class="o">/</span><span class="mi">10</span> <span class="p">:</span> <span class="n">WORKDIR</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>
<span class="o">---&gt;</span> <span class="n">c0e80ec3605d</span>
<span class="n">Removing</span> <span class="n">intermediate</span> <span class="n">container</span> <span class="mi">8</span><span class="n">d15670dbe57</span>
<span class="n">Step</span> <span class="mi">10</span><span class="o">/</span><span class="mi">10</span> <span class="p">:</span> <span class="n">EXPOSE</span> <span class="mi">8000</span>
<span class="o">---&gt;</span> <span class="n">Running</span> <span class="ow">in</span> <span class="n">aec283f5123c</span>
<span class="o">---&gt;</span> <span class="n">c8f944861cbf</span>
<span class="n">Removing</span> <span class="n">intermediate</span> <span class="n">container</span> <span class="n">aec283f5123c</span>
<span class="n">Successfully</span> <span class="n">built</span> <span class="n">c8f944861cbf</span>
<span class="n">Successfully</span> <span class="n">tagged</span> <span class="n">my_app_web</span><span class="p">:</span><span class="n">latest</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="h5ah1">
  You can see from this output that Docker treats each line in your Dockerfile as its own step. Step 1 retrieves the Python image you specified, Step 7 installs your requirements, and so on. This is what folks mean when they describe Docker as layered. Each line in your Dockerfile is a layer. Docker will skip any step that was complete the last time you built the image&hellip; as long as all the steps above it have stayed the same. If I changed the Python image that I&rsquo;m basing this image on, then each step below that would be re-run in its entirely. If I change the WORKDIR on line 9, then steps 9 and 10 both have to be re-run.&nbsp;
  <a href="https://twitter.com/webology">
   Jeff Triplett
  </a>
  &nbsp;says, &ldquo;I think of layers like dominoes. If you have 10 lines and you push the fourth one over, lines 4-10 are going to fall over and have to be rebuilt.&rdquo; Read more about images and layers in the&nbsp;
  <a href="https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/">
   Docker docs
  </a>
  .
 </p>
 <p data-block-key="r61fl">
  Assuming your image built with no errors, you now have a container! Docker indicates that to you with the final lines:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">Successfully</span> <span class="n">built</span> <span class="n">c8f944861cbf</span>
<span class="n">Successfully</span> <span class="n">tagged</span> <span class="n">my_app_web</span><span class="p">:</span><span class="n">latest</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="1uh3g">
  Docker successfully built an image for you, gave it an ID, labeled that image with whatever you told it to name your image (
  <b>
   my_app_web
  </b>
  &nbsp;in this example), and tagged it &ldquo;latest.&rdquo;
 </p>
 <p data-block-key="hcqfs">
  Now you&rsquo;re ready to run your container(s). Run this command:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">up</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="9gbjo">
  You will see some output and see your server start (as long as starting your server is part of your docker-compose.yml file).
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">Starting</span> <span class="n">my_app_db_1</span> <span class="o">...</span>
<span class="n">Starting</span> <span class="n">my_app_db_1</span> <span class="o">...</span> <span class="n">done</span>
<span class="n">Recreating</span> <span class="n">my_app_web_1</span> <span class="o">...</span>
<span class="n">Recreating</span> <span class="n">my_app_web_1</span> <span class="o">...</span> <span class="n">done</span>
<span class="n">Attaching</span> <span class="n">to</span> <span class="n">my_app_db_1</span><span class="p">,</span> <span class="n">my_app_web_1</span>
<span class="n">db_1</span>   <span class="o">|</span> <span class="n">LOG</span><span class="p">:</span>  <span class="n">database</span> <span class="n">system</span> <span class="n">was</span> <span class="n">shut</span> <span class="n">down</span> <span class="n">at</span> <span class="mi">2017</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">19</span> <span class="mi">00</span><span class="p">:</span><span class="mi">59</span><span class="p">:</span><span class="mi">19</span> <span class="n">UTC</span>
<span class="n">db_1</span>   <span class="o">|</span> <span class="n">LOG</span><span class="p">:</span>  <span class="n">MultiXact</span> <span class="n">member</span> <span class="n">wraparound</span> <span class="n">protections</span> <span class="n">are</span> <span class="n">now</span> <span class="n">enabled</span>
<span class="n">db_1</span>   <span class="o">|</span> <span class="n">LOG</span><span class="p">:</span>  <span class="n">autovacuum</span> <span class="n">launcher</span> <span class="n">started</span>
<span class="n">db_1</span>   <span class="o">|</span> <span class="n">LOG</span><span class="p">:</span>  <span class="n">database</span> <span class="n">system</span> <span class="ow">is</span> <span class="n">ready</span> <span class="n">to</span> <span class="n">accept</span> <span class="n">connections</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">Performing</span> <span class="n">system</span> <span class="n">checks</span><span class="o">...</span>
<span class="n">web_1</span>  <span class="o">|</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">System</span> <span class="n">check</span> <span class="n">identified</span> <span class="n">no</span> <span class="n">issues</span> <span class="p">(</span><span class="mi">0</span> <span class="n">silenced</span><span class="p">)</span><span class="o">.</span>
<span class="n">web_1</span>  <span class="o">|</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">October</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">2017</span> <span class="o">-</span> <span class="mi">01</span><span class="p">:</span><span class="mi">08</span><span class="p">:</span><span class="mi">50</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">Django</span> <span class="n">version</span> <span class="mf">1.11.5</span><span class="p">,</span> <span class="n">using</span> <span class="n">settings</span> <span class="s1">'my_app.settings.local'</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">Starting</span> <span class="n">development</span> <span class="n">server</span> <span class="n">at</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="mf">0.0.0.0</span><span class="p">:</span><span class="mi">8000</span><span class="o">/</span>
<span class="n">web_1</span>  <span class="o">|</span> <span class="n">Quit</span> <span class="n">the</span> <span class="n">server</span> <span class="k">with</span> <span class="n">CONTROL</span><span class="o">-</span><span class="n">C</span><span class="o">.</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="440gn">
  Remember in our docker-compose.yml file when we asked Docker to not start our web service until our db service had successfully started? If you look at the top lines, you can see Docker is doing as we asked:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">Starting</span> <span class="n">my_app_db_1</span> <span class="o">...</span>
<span class="n">Starting</span> <span class="n">my_app_db_1</span> <span class="o">...</span> <span class="n">done</span>
<span class="n">Recreating</span> <span class="n">my_app_web_1</span> <span class="o">...</span>
<span class="n">Recreating</span> <span class="n">my_app_web_1</span> <span class="o">...</span> <span class="n">done</span>
</pre>
 </div>
</div>
<div class="block-content">
 <h2 data-block-key="z6o7x">
  Running manage.py commands
 </h2>
 <p data-block-key="kk7hg">
  Once you&rsquo;re using Compose, you can run your normal manage.py commands without any extra work or setup in the docker-compose.yml file. We set docker-compose.yml up to automatically start our server, but running the server isn&rsquo;t the only thing we do with manage.py.
 </p>
 <p data-block-key="bors4">
  To access your manage.py commands, enter:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">run</span> <span class="o">-</span><span class="n">rm</span> <span class="n">web</span> <span class="o">./</span><span class="n">manage</span><span class="o">.</span><span class="n">py</span> <span class="p">[</span><span class="n">command</span><span class="p">]</span> <span class="p">[</span><span class="n">arguments</span><span class="p">]</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="u5f54">
  <br/>
  <b>
   docker-compose run
  </b>
  &nbsp;tells Docker that we&rsquo;re about to run a command. The&nbsp;
  <b>
   -rm
  </b>
  &nbsp;option will shut down this container when we&rsquo;re finished with it.&nbsp;
  <b>
   web
  </b>
  &nbsp;identifies the service we want to run; manage.py commands will generally be in the web service.
 </p>
 <p data-block-key="adqos">
  The rest is the standard way to run manage.py commands:&nbsp;
  <b>
   ./manage.py [command]
  </b>
  . Since the Dockerfile defines our WORKDIR as /code/, we can run&nbsp;
  <b>
   manage.py
  </b>
  &nbsp;from the current directory.&nbsp;For example, to run tests, we would run:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">run</span> <span class="o">-</span><span class="n">rm</span> <span class="n">web</span> <span class="o">./</span><span class="n">manage</span><span class="o">.</span><span class="n">py</span> <span class="n">test</span> <span class="p">[</span><span class="n">app</span><span class="p">]</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="kqk7u">
  The output is what we expect:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">Starting</span> <span class="n">my_app_db_1</span> <span class="o">...</span> <span class="n">done</span>
<span class="n">Creating</span> <span class="n">test</span> <span class="n">database</span> <span class="k">for</span> <span class="n">alias</span> <span class="s1">'default'</span><span class="o">...</span>
<span class="n">System</span> <span class="n">check</span> <span class="n">identified</span> <span class="n">no</span> <span class="n">issues</span> <span class="p">(</span><span class="mi">0</span> <span class="n">silenced</span><span class="p">)</span><span class="o">.</span>
<span class="o">...............</span>
<span class="o">----------------------------------------------------------------------</span>
<span class="n">Ran</span> <span class="mi">15</span> <span class="n">tests</span> <span class="ow">in</span> <span class="mf">0.577</span><span class="n">s</span>

<span class="n">OK</span>
<span class="n">Destroying</span> <span class="n">test</span> <span class="n">database</span> <span class="k">for</span> <span class="n">alias</span> <span class="s1">'default'</span><span class="o">...</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="g4ze7">
  On the top line we see Docker start the db service, but the other output is probably familiar to you.
 </p>
 <p data-block-key="tw9jd">
  To make migrations, we would run:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span><span class="o">-</span><span class="n">compose</span> <span class="n">run</span> <span class="o">-</span><span class="n">rm</span> <span class="n">web</span> <span class="o">./</span><span class="n">manage</span><span class="o">.</span><span class="n">py</span> <span class="n">makemigrations</span>
</pre>
 </div>
</div>
<div class="block-content">
 <h2 data-block-key="lra9o">
  Your container's bash prompt
 </h2>
 <p data-block-key="s1psw">
  You can also run commands from Docker&rsquo;s bash shell. This is handy when you&rsquo;ve run&nbsp;
  <b>
   docker-compose up
  </b>
  &nbsp;and you don&rsquo;t want to stop your server while you do something else.
 </p>
 <p data-block-key="db4bq">
  But in order to enter the bash shell of a container, you need the container id. While your server is running, open a new Terminal tab. Run the command&nbsp;
  <b>
   docker ps
  </b>
  &nbsp;to get your running containers and their IDs. You will see something like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span> <span class="n">ps</span>
<span class="n">CONTAINER</span> <span class="n">ID</span>        <span class="n">IMAGE</span>               <span class="n">COMMAND</span>                  <span class="n">CREATED</span>             <span class="n">STATUS</span>              <span class="n">PORTS</span>                    <span class="n">NAMES</span>
<span class="p">[</span><span class="n">container_1_id</span><span class="p">]</span>        <span class="n">my_app_web</span>             <span class="s2">"python /code/mana..."</span>   <span class="mi">21</span> <span class="n">minutes</span> <span class="n">ago</span>      <span class="n">Up</span> <span class="mi">21</span> <span class="n">minutes</span>       <span class="mf">0.0.0.0</span><span class="p">:</span><span class="mi">8000</span><span class="o">-&gt;</span><span class="mi">8000</span><span class="o">/</span><span class="n">tcp</span>   <span class="n">my_app_web_1</span>
<span class="p">[</span><span class="n">container_2_id</span><span class="p">]</span>        <span class="n">postgres</span><span class="p">:</span><span class="mf">9.6.5</span>      <span class="s2">"docker-entrypoint..."</span>   <span class="mi">9</span> <span class="n">hours</span> <span class="n">ago</span>         <span class="n">Up</span> <span class="mi">21</span> <span class="n">minutes</span>       <span class="mi">5432</span><span class="o">/</span><span class="n">tcp</span>                 <span class="n">my_app_db_1</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="7fq18">
  Expand your Terminal window so you can see everything lined up pretty. You can see both of your containers,&nbsp;
  <b>
   my_app_web_1&nbsp;
  </b>
  and&nbsp;
  <b>
   my_app_db_1
  </b>
  , listed. Copy the container ID of your web container.
 </p>
 <p data-block-key="gibvp">
  Now run this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="n">docker</span> <span class="n">exec</span> <span class="o">-</span><span class="n">it</span> <span class="p">[</span><span class="n">container_id</span><span class="p">]</span> <span class="n">bash</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="lx8sd">
  <a href="https://docs.docker.com/engine/reference/commandline/exec/#usage">
   <b>
    exec
   </b>
  </a>
  <b>
   &nbsp;
  </b>
  is the command used to run a command in a running container. The&nbsp;
  <i>
   -it
  </i>
  &nbsp;option combines two options,&nbsp;
  <b>
   -i
  </b>
  &nbsp;and&nbsp;
  <b>
   -t
  </b>
  , which together make the shell interactive and set up a&nbsp;
  <a href="https://en.wikipedia.org/wiki/Pseudoterminal">
   pseudo-TTY
  </a>
  . &nbsp;Identify your container by its ID, then type&nbsp;
  <b>
   bash
  </b>
  &nbsp;to indicate that you want a bash prompt. Your Terminal window will looks a little different:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>$<span class="w"> </span>docker<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>-it<span class="w"> </span><span class="o">[</span>container_id<span class="o">]</span><span class="w"> </span>bash
root@<span class="o">[</span>container_id<span class="o">]</span>:/code#
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="xf8w7">
  You&rsquo;re now inside your container&rsquo;s bash shell. If you set your&nbsp;
  <b>
   WORKDIR
  </b>
  &nbsp;as&nbsp;
  <b>
   /code/
  </b>
  &nbsp;in your&nbsp;Dockerfile, then you will automatically be in the /code/ directory. If you&nbsp;
  <b>
   cd ..
  </b>
  &nbsp;and then&nbsp;
  <b>
   ls
  </b>
  , you can see what other directories live in your container. Feel free to poke around.
 </p>
 <p data-block-key="nan5z">
  You can also enter a container&rsquo;s bash prompt with a Compose command:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>$<span class="w"> </span>docker-compose<span class="w"> </span>run<span class="w"> </span><span class="o">[</span>service<span class="o">]</span><span class="w"> </span>bash
Starting<span class="w"> </span>my_app_db_1<span class="w"> </span>...<span class="w"> </span><span class="k">done</span>
root@<span class="o">[</span>container_id<span class="o">]</span>:/code#
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="zd055">
  The difference between this command and&nbsp;
  <b>
   docker exec -it [container_id] bash
  </b>
  &nbsp;is that the&nbsp;
  <b>
   exec
  </b>
  &nbsp;command puts you into a bash prompt inside a container that was already running. The docker-compose command starts a brand new container and puts your into a bash prompt inside&nbsp;that&nbsp;container. You can see this happen in the output: the docker-compose command will print something like &ldquo;Starting my_app_db_1,&rdquo; to indicate it&rsquo;s starting a new db service, since the new web service will require one. The difference probably won&rsquo;t matter when things are going well, but it&rsquo;s good to know.
 </p>
 <p data-block-key="3h66b">
  Once you are in your bash prompt, you can enter the Python shell:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>root@<span class="o">[</span>container_id<span class="o">]</span>:/code#<span class="w"> </span>./manage.py<span class="w"> </span>shell
Python<span class="w"> </span><span class="m">3</span>.6.2<span class="w"> </span><span class="o">(</span>default,<span class="w"> </span>Sep<span class="w"> </span><span class="m">13</span><span class="w"> </span><span class="m">2017</span>,<span class="w"> </span><span class="m">14</span>:26:54<span class="o">)</span>
Type<span class="w"> </span><span class="s1">'copyright'</span>,<span class="w"> </span><span class="s1">'credits'</span><span class="w"> </span>or<span class="w"> </span><span class="s1">'license'</span><span class="w"> </span><span class="k">for</span><span class="w"> </span>more<span class="w"> </span>information
IPython<span class="w"> </span><span class="m">6</span>.2.1<span class="w"> </span>--<span class="w"> </span>An<span class="w"> </span>enhanced<span class="w"> </span>Interactive<span class="w"> </span>Python.<span class="w"> </span>Type<span class="w"> </span><span class="s1">'?'</span><span class="w"> </span><span class="k">for</span><span class="w"> </span>help.

In<span class="w"> </span><span class="o">[</span><span class="m">1</span><span class="o">]</span>:
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="m7e6f">
  To exit your bash prompt (whichever way you got into it), just type&nbsp;
  <b>
   exit
  </b>
  &nbsp;or&nbsp;
  <b>
   ctrl + d
  </b>
  .
 </p>
 <h2 data-block-key="b9mz8">
  Going Home
 </h2>
 <p data-block-key="cnnqo">
  I&rsquo;m not sure how neat or messy you like to leave your desktop at the end of the workday. But if you like to button things up, you might want to stop your containers from running all weekend when you leave the office on Friday. To do this, run:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>$<span class="w"> </span>docker-compose<span class="w"> </span>down
</pre>
 </div>
</div>
<div class="block-content">
 <p data-block-key="xg8y5">
  This will stop your running containers. If you haven&rsquo;t been using the&nbsp;
  <b>
   -rm
  </b>
  &nbsp;option when running management commands, you will probably see more container notifications than you expected to.
  <br/>
 </p>
 <p data-block-key="bj1ne">
  Resources
 </p>
 <p data-block-key="7wxad">
 </p>
 <p data-block-key="ll9kp">
 </p>
 <p data-block-key="2jav4">
 </p>
 <ul>
  <li data-block-key="p52n2">
   <a href="https://docs.docker.com/compose/overview/">
    Overview of Docker Compose
   </a>
   <br/>
  </li>
  <li data-block-key="f12di">
   <a href="/tidbits/brief-intro-docker-djangonauts/">
    A Brief Intro to Docker for Djangonauts
   </a>
   <br/>
  </li>
 </ul>
 <p data-block-key="25u6j">
 </p>
 <p data-block-key="4ehlx">
 </p>
 <p data-block-key="jc5ri">
 </p>
 <p data-block-key="isa49">
  <i>
   Thanks to&nbsp;
  </i>
  <a href="https://twitter.com/fxdgear">
   <i>
    Nick Lang
   </i>
  </a>
  <i>
   &nbsp;for advice on this post, and to&nbsp;
  </i>
  <a href="https://twitter.com/fwiles">
   <i>
    Frank Wiles
   </i>
  </a>
  <i>
   &nbsp;and&nbsp;
  </i>
  <a href="https://twitter.com/webology">
   <i>
    Jeff Triplett
   </i>
  </a>
  <i>
   &nbsp;for their feedback on drafts and their patient debugging help.
  </i>
 </p>
</div>
]]>/></item><item><title>A Brief Intro to Docker for Djangonauts</title><link>http://www.revsys.com/tidbits/brief-intro-docker-djangonauts/</link><description>Lacey didn&amp;#x27;t have the opportunity to work with Docker at her last job. In this tidbit she steps you through getting started with Docker for Django developers.</description><pubDate>Tue, 05 Dec 2017 01:41:27 +0000</pubDate><guid>http://www.revsys.com/tidbits/brief-intro-docker-djangonauts/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  I&rsquo;ll be honest: I was pretty trepidatious about using Docker. It wasn&rsquo;t something we used at my last job and most tutorials felt like this comic by Van Oktop.
 </p>
 <p>
 </p>
 <img alt="How to Draw a Horse" class="richtext-image left" height="750" src="https://sfo2.digitaloceanspaces.com/revsys-com-prod-media/images/howtodrawahorse.width-500.width-500.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=X4TI6FSBIKO5PE7QO3JH%2F20260401%2Fsfo2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260401T181000Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=a1fd0adea3126aa55d4e77f102141d36dc0c59e757ec5052534b36fc69301411" width="417"/>
 <p>
  This post won&rsquo;t teach you everything you need to know about Docker. But if you&rsquo;re getting started with Docker and feeling a little lost, hopefully this will help demystify it a bit.
 </p>
 <h2>
  What is Docker?
 </h2>
 <p>
  But first, some frequently asked (by me to my colleagues) questions:
 </p>
 <p>
 </p>
 <ul>
  <li>
   <b>
    So&hellip; what&rsquo;s Docker?
   </b>
   Glad you asked! Docker helps you run different projects with different dependencies totally separately from each other, without needing to download a bunch of stuff onto your machine that you may never need again.
   <br/>
  </li>
  <li>
   <b>
    How is that different from virtualenv
   </b>
   ? A virtual environment does some of this. You can use different versions of Python, Django, etc. in different projects when you run each project in its own virtualenv. Docker has the added benefit of isolating your database, caching service like Redis, and other tools as well. For my current project, I&rsquo;m running a Postgres database and I didn&rsquo;t have to download Postgres or configure it locally at all!
   <br/>
  </li>
  <li>
   <b>
    So do you use Docker alongside virtualenv?
   </b>
   Not quite. You use Docker containers instead of virtual environments. If you&rsquo;re committed to Docker, you don&rsquo;t need to worry about virtualenvs anymore. (They can still be useful&hellip; but that&rsquo;s another post.)
   <br/>
  </li>
 </ul>
 <h2>
  A few Docker definitions
 </h2>
 <p>
 </p>
 <p>
 </p>
 <ul>
  <li>
   <a href="https://www.docker.com/">
    <b>
     Docker
    </b>
   </a>
   : a software container platform. In practice, this means that Docker is something you download onto your machine. You will run Docker for your projects the way you used to use virtual environments, but you will write a little extra code to set up your stuff in Docker.
   <br/>
  </li>
  <li>
   <b>
    Image
   </b>
   : a &ldquo;lightweight, stand-alone, executable package that includes everything needed to run a piece of software.&rdquo; You will set up a specific image for each project you work on that will tell Docker which packages your project needs, where your code lives, etc.
   <br/>
  </li>
  <li>
   <b>
    Container
   </b>
   : &ldquo;a runtime instance of an image.&rdquo; Containers are running copies of images, and are what your code will actually run in. This part is closest to what used to be the virtual environment.
   <br/>
  </li>
  <li>
   <b>
    Dockerfile
   </b>
   : the name of the file that contains the instructions for setting up your image.
   <br/>
  </li>
  <li>
   <b>
    docker-compose.yml
   </b>
   : the file where you can set up your database, automatically start your server when you start your container, and cool stuff like that.
   <br/>
  </li>
 </ul>
 <h2>
  Stop reading
 </h2>
 <p>
 </p>
 <p>
  I highly recommend working through the
  <a href="https://docs.docker.com/get-started/">
   Get started with Docker
  </a>
  tutorial on the Docker website. It will introduce you to the parts of a Dockerfile and the basics of how Docker works. The rest of this post assumes you&rsquo;ve done the tutorial and are ready to use Docker with a Django project.
  <br/>
 </p>
 <h2>
  Setting up a new project in Docker
 </h2>
 <p>
  You should not consider these instructions as set in stone. It&rsquo;s what made it easiest for me to get set up and verify that everything was working with Docker.
 </p>
 <p>
 </p>
 <p>
  First, download Docker and complete the
  <a href="https://docs.docker.com/get-started/">
   Get started with Docker
  </a>
  tutorial.
 </p>
 <p>
  Follow your normal process for starting a new project, including using
  <a href="https://github.com/pydanny/cookiecutter-django">
   cookie-cutter
  </a>
  and
  <a href="https://tutorial.djangogirls.org/en/installation/#set-up-virtualenv-and-install-django">
   creating a virtual environment
  </a>
  . (You&rsquo;ll discard this virtual environment later.) Create a
  <b>
   requirements.txt
  </b>
  file and add the packages you need. Inside your virtual environment, run
  <b>
   pip install -r requirements.txt
  </b>
  . Then run
  <b>
   ./manage.py runserver
  </b>
  and make sure you have the blue screen of success in your browser. Yay! Make your initial commit.
 </p>
 <p>
 </p>
 <h3>
  Dockerfile
 </h3>
 <p>
 </p>
 <p>
  In the same directory as your manage.py file, create a file called
  <b>
   Dockerfile
  </b>
  . Remember that a Dockerfile contains the instructions for creating your image. It should look something like this (but yours might not need everything mine does, and yours might include some instructions that mine does not):
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.6</span>

<span class="n">ENV</span> <span class="n">PYTHONUNBUFFERED</span> <span class="mi">1</span>
<span class="n">ENV</span> <span class="n">DJANGO_ENV</span> <span class="n">dev</span>
<span class="n">ENV</span> <span class="n">DOCKER_CONTAINER</span> <span class="mi">1</span>

<span class="n">COPY</span> <span class="o">./</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span>
<span class="n">RUN</span> <span class="n">pip</span> <span class="n">install</span> <span class="o">-</span><span class="n">r</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span>

<span class="n">COPY</span> <span class="o">.</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>
<span class="n">WORKDIR</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>

<span class="n">EXPOSE</span> <span class="mi">8000</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Let's break this down:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.6</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  You don&rsquo;t need to create your Docker image from scratch. You can base your image off of code in another image in the Docker Hub, a repository of existing Docker images.
 </p>
 <p>
  On this line, I&rsquo;ve told Docker to base my image off of the Python 3.6 image, which (you guessed it) contains Python 3.6. Pointing to Python 3.6 versus 3.6.x ensures that we get the latest 3.6.x version, which will include bug fixes and security updates for that version of Python.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">ENV</span> <span class="n">PYTHONUNBUFFERED</span> <span class="mi">1</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  <b>
   ENV
  </b>
  creates an environment variable called
  <b>
   PYTHONUNBUFFERED
  </b>
  and sets it to
  <b>
   1
  </b>
  (which, remember, is &ldquo;truthy&rdquo;). All together, this statement means that Docker won&rsquo;t buffer the output from your application; instead, you will get to see your output in your console the way you&rsquo;re used to.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">ENV</span> <span class="n">DJANGO_ENV</span> <span class="n">dev</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  If you use multiple environment-based settings.py files, this creates an environment variable called
  <b>
   DJANGO_ENV
  </b>
  and sets it to the development environment. You might call that "test" or "local" or something else.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">ENV</span> <span class="n">DOCKER_CONTAINER</span> <span class="mi">1</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This creates an environment variable called
  <b>
   DOCKER_CONTAINER
  </b>
  that you can use in settings.py to load different databases depending on whether you&rsquo;re running your application inside a Docker container.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">COPY</span> <span class="o">./</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Remember that "." means &ldquo;the current directory,&rdquo; so this line copies your project&rsquo;s requirements.txt file into a new directory in Docker called
  <b>
   /code/
  </b>
  .
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">RUN</span> <span class="n">pip</span> <span class="n">install</span> <span class="o">-</span><span class="n">r</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">requirements</span><span class="o">.</span><span class="n">txt</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Just like in a regular virtual environment, you need to install your required packages.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">COPY</span> <span class="o">.</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This line copies the rest of the code in your current directory "." (your project code) into the /code/ directory.
 </p>
 <p>
  Each Docker container will already contain some subdirectories, so a good practice is to put your project code into its own directory.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">WORKDIR</span> <span class="o">/</span><span class="n">code</span><span class="o">/</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  You&rsquo;re probably used to running things like
  <b>
   ./manage.py runserver
  </b>
  . But when you run that command in your Docker container, you&rsquo;re likely to forget that your code doesn&rsquo;t live in the current directory (.) anymore; it lives in /code/. This line tells Docker that you want your &ldquo;working directory&rdquo; to be
  <b>
   /code/
  </b>
  so you can still continue to run commands from the current directory to your heart&rsquo;s content.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">EXPOSE</span> <span class="mi">8000</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  In order to runserver like a champ, your Docker container will need access to port 8000. This bestows that access.
 </p>
 <p>
  Huzzah! Your first Dockerfile is ready to go.
 </p>
 <p>
  Deactivate your virtual environment. In Terminal or your command line, run
  <b>
   docker build .
  </b>
  from the same directory that contains your Dockerfile. You will see a lot of output in the console.
 </p>
 <p>
  Your Dockerfile defines the rules and instructions for your image, and "docker build ." actually creates your image. You can&rsquo;t run containers until you have a valid image to base them on. Assuming you had no errors when you ran
  <b>
   docker build .
  </b>
  , you will now have a functioning container!
 </p>
 <h3>
  Docker Compose
 </h3>
 <p>
  If you are not on a Mac, install
  <a href="https://docs.docker.com/compose/install/">
   Docker Compose
  </a>
  . (Mac users: Docker Compose ships with Docker, so you&rsquo;re good to go!)
 </p>
 <p>
  Docker Compose lets you run more than one container in a Docker application. It&rsquo;s especially useful if you want to have a database, like Postgres, running in a container alongside your web app. (Docker&rsquo;s
  <a href="https://docs.docker.com/compose/overview/">
   overview of Compose
  </a>
  is helpful.) Compose allows you to define several services that will make up your app and run them all together. Examples of services you might define include:
  <br/>
 </p>
 <p>
 </p>
 <ul>
  <li>
   <b>
    web
   </b>
   : defines your web service
   <br/>
  </li>
  <li>
   <b>
    db
   </b>
   : your database
   <br/>
  </li>
  <li>
   <b>
    redis
   </b>
   or another caching service
  </li>
 </ul>
 <p>
 </p>
 <p>
  Compose can also help you relate those services to each other. For example, you likely don&rsquo;t want your web service to start running until your db is ready, right?
 </p>
 <p>
  Create a new file called
  <b>
   docker-compose.yml
  </b>
  in the same directory as your Dockerfile. While Dockerfile doesn&rsquo;t have an extension, the docker-compose file is written in YAML, so it has the extension
  <b>
   .yml
  </b>
  . Mine defines two services,
  <b>
   web
  </b>
  and
  <b>
   db
  </b>
  , and looks like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">version</span><span class="p">:</span> <span class="s1">'3'</span>

<span class="n">services</span><span class="p">:</span>
  <span class="n">db</span><span class="p">:</span>
    <span class="n">image</span><span class="p">:</span> <span class="n">postgres</span><span class="p">:</span><span class="mf">9.6.5</span>
    <span class="n">volumes</span><span class="p">:</span>
      <span class="o">-</span> <span class="n">postgres_data</span><span class="p">:</span><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">postgresql</span><span class="o">/</span><span class="n">data</span><span class="o">/</span>
  <span class="n">web</span><span class="p">:</span>
    <span class="n">build</span><span class="p">:</span> <span class="o">.</span>
    <span class="n">command</span><span class="p">:</span> <span class="n">bash</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"python /code/manage.py migrate --noinput &amp;&amp; python /code/manage.py runserver 0.0.0.0:8000"</span>
    <span class="n">volumes</span><span class="p">:</span>
      <span class="o">-</span> <span class="o">.</span><span class="p">:</span><span class="o">/</span><span class="n">code</span>
    <span class="n">ports</span><span class="p">:</span>
      <span class="o">-</span> <span class="s2">"8000:8000"</span>
    <span class="n">depends_on</span><span class="p">:</span>
      <span class="o">-</span> <span class="n">db</span>

<span class="n">volumes</span><span class="p">:</span>
  <span class="n">postgres_data</span><span class="p">:</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Just like we did with the Dockerfile, let&rsquo;s go through the parts of this docker-compose.yml file.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">version</span><span class="p">:</span> <span class="s1">'3'</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This line defines the version of Compose we want to use. We&rsquo;re using version 3, the
  <a href="https://docs.docker.com/compose/compose-file/compose-versioning/">
   most recent version
  </a>
  .
 </p>
 <p>
 </p>
 <p>
  .
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">services</span><span class="p">:</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Indented under this line, we will define the services we want our image to run in separate containers when we run our project.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">db</span><span class="p">:</span>
    <span class="n">image</span><span class="p">:</span> <span class="n">postgres</span><span class="p">:</span><span class="mf">9.6.5</span>
    <span class="n">volumes</span><span class="p">:</span>
      <span class="o">-</span> <span class="n">postgres_data</span><span class="p">:</span><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">postgresql</span><span class="o">/</span><span class="n">data</span><span class="o">/</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This is where Compose gets exciting: this section sets up the
  <b>
   db
  </b>
  service as a Postgres database and instructs Compose to pull version 9.6.5 of Postgres from the image that already exists in Docker Hub. This means that I don&rsquo;t need to download Postgres on my computer
  <i>
   at all
  </i>
  in order to use it as my local database.
 </p>
 <p>
  Upgrading Postgres from one minor version to another while keeping your data requires running some extra scripts, pgdump and pgrestore, and can get a little complicated. If you don&rsquo;t want to mess with this, set your Postgres image to a specific version (like 9.6.5). You will probably want to upgrade the Postgres version eventually, but this will save you from having to upgrade with every minor version release.
 </p>
 <p>
  <b>
   volumes
  </b>
  tells Compose where in the container I would like it to store my data: in
  <b>
   /var/lib/postgresql/data/
  </b>
  . Remember when I said that each container had its own set of subdirectories and that is why you needed to copy your application code into a directory named
  <b>
   /code/
  </b>
  ?
  <b>
   /var/
  </b>
  is one of those other subdirectories. A volume also lets your data persist beyond the lifecycle of a specific container.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">web</span><span class="p">:</span>
    <span class="n">build</span><span class="p">:</span> <span class="o">.</span>
    <span class="n">command</span><span class="p">:</span> <span class="n">bash</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"python /code/manage.py migrate --noinput &amp;&amp; python /code/manage.py runserver 0.0.0.0:8000"</span>
    <span class="n">volumes</span><span class="p">:</span>
      <span class="o">-</span> <span class="o">.</span><span class="p">:</span><span class="o">/</span><span class="n">code</span>
    <span class="n">ports</span><span class="p">:</span>
      <span class="o">-</span> <span class="s2">"8000:8000"</span>
    <span class="n">depends_on</span><span class="p">:</span>
      <span class="o">-</span> <span class="n">db</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This section sets up the
  <b>
   web
  </b>
  service, the one that will run my application code.
  <b>
   build: .
  </b>
  tells Compose to build the image from the current directory.
  <b>
   command: bash -c "python /code/manage.py migrate --noinput"
  </b>
  will automatically run migrations when I run the container and hide the output from me in the console.
  <b>
   &amp;&amp; python /code/manage.py runserver 0.0.0.0:8000
  </b>
  will start the server when I run the container. (The
  <b>
   &amp;&amp;
  </b>
  lets us put two commands on one line.)
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">volumes</span><span class="p">:</span>
      <span class="o">-</span> <span class="o">.</span><span class="p">:</span><span class="o">/</span><span class="n">code</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This section sets up another volume for the code in your current directory and where your code will live in the Docker container (/code/).
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">ports</span><span class="p">:</span>
      <span class="o">-</span> <span class="s2">"8000:8000"</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Here we map our own port 8000 to the port 8000 in the Docker container. A more technical explanation is, &ldquo;We map port 8000 to the host&rsquo;s port 8000, meaning that our app server will be reachable in the host via `127.0.0.1:8000` once it&rsquo;s running;&rdquo; thanks to
  <a href="http://www.eidel.io/2017/07/10/dockerizing-django-uwsgi-postgres/">
   Oliver Eidel
  </a>
  for that!
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">depends_on</span><span class="p">:</span>
      <span class="o">-</span> <span class="n">db</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  The
  <b>
   depends_on
  </b>
  statement declares that our web service depends on our db service, so Compose will get the db service up and running before it tries to run the web service.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">volumes</span><span class="p">:</span>
  <span class="n">postgres_data</span><span class="p">:</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Finally, Compose has a rule where you have to list your named volumes in a top-level
  <b>
   volumes
  </b>
  key, so we have done that.
 </p>
 <p>
  Save the docker-compose.yml file.
  <br/>
 </p>
 <p>
  In Terminal or your console and from the same directory that contains your Dockerfile and docker-compose.yml file, run
  <b>
   docker-compose up
  </b>
  .
  <br/>
 </p>
 <p>
  Assuming you have no errors, navigate to
  <b>
   http://localhost:8000/
  </b>
  in a browser and see your blue screen of success once again!
  <br/>
 </p>
 <p>
  Ready for your next step? Check out
  <a href="/tidbits/docker-useful-command-line-stuff/">
   Docker: Useful Command Line Stuff
  </a>
  next!
 </p>
 <h2>
  Resources
 </h2>
 <p>
 </p>
 <p>
 </p>
 <ul>
  <li>
   <a href="https://docs.docker.com/get-started/">
    Get started with Docker
   </a>
   <br/>
  </li>
  <li>
   <a href="https://docs.docker.com/compose/gettingstarted/">
    Get started with Docker Compose
   </a>
   <br/>
  </li>
  <li>
   <a href="https://docs.docker.com/compose/django/">
    Quickstart: Compose and Django
   </a>
   <br/>
  </li>
  <li>
   <a href="https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/">
    Best practices for writing Dockerfiles
   </a>
  </li>
  <li>
   <a href="http://www.eidel.io/2017/07/10/dockerizing-django-uwsgi-postgres/">
    Dockerizing Django, uWSGI and Postgres the Serious Way
   </a>
   , Oliver Eidel
   <br/>
  </li>
 </ul>
 <p>
  <i>
   Thanks to Frank Wiles and Jeff Triplett for reviewing drafts of this post.
  </i>
 </p>
</div>
]]>/></item><item><title>Optimized Python</title><link>http://www.revsys.com/tidbits/optimized-python/</link><description>Smaller and faster Python 3.6.x images for you to take advantage of.</description><pubDate>Sat, 02 Dec 2017 16:50:15 +0000</pubDate><guid>http://www.revsys.com/tidbits/optimized-python/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  Turns out there are some optimizations you can do when compiling Python 3.6 and Python 3.7 that give you some significant speed improvements without any real downside.
 </p>
 <p>
  We've paired with with Google's base Debian image that rips out systemd and all it's dependencies, which you weren't going to be using in a Docker container anyway, which yields a MUCH smaller image.
 </p>
 <p>
  How do you use it? Pretty simple just start your Dockerfile with:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span>FROM<span class="w"> </span>revolutionsystems/python:3.7.1-wee-optimized-lto
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  And rebuild your image.
 </p>
 <p>
  How much faster is it? Depending on the benchmark in question it's up to 19% faster and nearly 75% smaller than the official Python images.
 </p>
 <p>
  You can get more details on benchmarks and how all this works on the
  <a href="https://hub.docker.com/r/revolutionsystems/python/">
   dockerhub page
  </a>
  . Information on
  <a href="https://github.com/revsys/optimized-python-docker">
   build details
  </a>
  is also available.
 </p>
 <p>
  Enjoy!
 </p>
 <p>
  Need help effectively using Docker with Python or Django? We can help!
  <a href="https://www.revsys.com/contact/">
   Contact us
  </a>
  or
  <a href="mailto:sales@revsys.com">
   send us a quick email
  </a>
  .
 </p>
</div>
]]>/></item><item><title>Python 3 run command over SSH</title><link>http://www.revsys.com/tidbits/python-3-run-command-over-ssh/</link><description>Sometimes you just need to run a command on a remote host with ssh and Python 3 without any real fuss. Here is the simplest way we have found to do that and retrieve the output. </description><pubDate>Wed, 28 Dec 2016 17:35:49 +0000</pubDate><guid>http://www.revsys.com/tidbits/python-3-run-command-over-ssh/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  <a href="http://www.fabfile.org/">
   Fabric
  </a>
  is great, but isn't 100% compatible with Python 3. &nbsp;There is
  <a href="https://github.com/mathiasertl/fabric/">
   Fabric3
  </a>
  , which does work, but sometimes you don't want to have to issue
  <i>
   `fab &lt;something&gt;`
  </i>
  &nbsp;but instead want to issue a command as part of a larger bit of Python. &nbsp;
 </p>
 <p>
  I ran into this situation today where I wanted to issue a few commands over ssh to a remote host as part of a
  <a href="http://click.pocoo.org/6/">
   Click
  </a>
  command I'm building to do some ops automation here at REVSYS.&nbsp;
 </p>
 <p>
  It's probably not perfect in terms of error handling, but it sure is simple! Here is all you need to do:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">fabric.api</span><span class="w"> </span><span class="kn">import</span> <span class="n">env</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">fabric.operations</span><span class="w"> </span><span class="kn">import</span> <span class="n">run</span> <span class="k">as</span> <span class="n">fabric_run</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">fabric.context_managers</span><span class="w"> </span><span class="kn">import</span> <span class="n">settings</span><span class="p">,</span> <span class="n">hide</span>

<span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">command</span><span class="p">):</span>
<span class="w">    </span><span class="sd">""" Run a command on the host. Assumes user has SSH keys setup """</span>
    <span class="n">env</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="n">user</span>
    <span class="n">env</span><span class="o">.</span><span class="n">use_ssh_config</span> <span class="o">=</span> <span class="kc">True</span>

    <span class="k">with</span> <span class="n">settings</span><span class="p">(</span><span class="n">hide</span><span class="p">(</span><span class="s1">'everything'</span><span class="p">),</span> <span class="n">host_string</span><span class="o">=</span><span class="n">host</span><span class="p">):</span>
        <span class="n">results</span> <span class="o">=</span> <span class="n">fabric_run</span><span class="p">(</span><span class="n">command</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">results</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  You'll need to install the Fabric3 library with a simple:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">Fabric3</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  And away you go! All this is doing is re-using Fabric3's run() function while silencing all of the normal Fabric style output and forcing the user, host, and to use the calling environment's ssh configuration settings (like your ~/.ssh/config). &nbsp;
 </p>
 <p>
  We could accomplish this more directly with Paramiko or work some subprocess magic to call our ssh, but this was the simplest and fastest way I solved the ssh problem today.
 </p>
 <p>
  Hope you find this little trick useful! Happy Hacking!&nbsp;
 </p>
</div>
]]>/></item><item><title>Python: atexit</title><link>http://www.revsys.com/tidbits/python-atexit/</link><description>Python's atexit handler is one of those things people should use more often.  Some developers aren't even aware that it exists and that makes me sad. </description><pubDate>Thu, 29 Sep 2016 18:47:09 +0000</pubDate><guid>http://www.revsys.com/tidbits/python-atexit/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  Did you know Python can automatically do things for you when the process exits? The
  <a href="https://docs.python.org/3/library/atexit.html#atexit-example">
   atexit
  </a>
  &nbsp;is one of those handy, but often forgotten about, aspects of Python. &nbsp;I had cause to use it yet again today with a certain process that was being naughty. &nbsp;
 </p>
 <p>
  The idea is simple, atexit&nbsp;instructs Python to run your function just before the process exits. &nbsp;So when would you want to do that? I'm sure there are other situations, but I typically end up using them when:
 </p>
 <p>
 </p>
 <ul>
  <li>
   I want to log both process startup AND exit
  </li>
  <li>
   Alert someone that a process has died, especially cron jobs that are easily forgotten about
  </li>
 </ul>
 <p>
  A quick example to show you the idea:
 </p>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">atexit</span>


<span class="nd">@atexit</span><span class="o">.</span><span class="n">register</span>
<span class="k">def</span><span class="w"> </span><span class="nf">closing</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">"=== Closing down ==="</span><span class="p">)</span>


<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">"whee!"</span><span class="p">)</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Using the atexit decorator, we wrap a little function that just prints out that we're closing down. Our while loop, er I mean our
  <i>
   whee loop,
  </i>
  will run until we interrupt the process with a Ctrl-C. &nbsp;The output looks like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">whee</span><span class="o">^</span><span class="n">Cwhee</span><span class="err">!</span>
<span class="n">whee</span><span class="err">!</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s2">"example.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">10</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">"whee!"</span><span class="p">)</span>
<span class="ne">KeyboardInterrupt</span>
<span class="o">===</span> <span class="n">Closing</span> <span class="n">down</span> <span class="o">===</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  We see our usual KeyboardInterrupt traceback and then our custom closing message.&nbsp;
 </p>
 <p>
  You can also register your atexit functions without the decorator so you can pass args and kwargs to it to be flexible. &nbsp;
 </p>
 <p>
  Seems pretty perfect, doesn't it? It's not all roses. &nbsp;Python only calls these atexit&nbsp;functions in a couple of scenarios, specifically if:
 </p>
 <p>
 </p>
 <ul>
  <li>
   sys.exit() is called
  </li>
  <li>
   The process completes and is exiting normally
  </li>
 </ul>
 <p>
  However, it&nbsp;
  <b>
   does not
  </b>
  &nbsp;call your atexit handles if Python is killed by a signal, when Python itself has a fatal internal error, or if os._exit() is called directly. &nbsp;So it's not useful to detect kills or kill -9s for sure, but in most other usual exit scenarios it can be handy.&nbsp;
 </p>
 <p>
  Hope you find a use for it!&nbsp;
 </p>
 <p>
 </p>
</div>
]]>/></item><item><title>The Vagary of AWS Availability Zones</title><link>http://www.revsys.com/tidbits/vagary-aws-availability-zones/</link><description>What are Amazon availability zones anyway?</description><pubDate>Tue, 30 Aug 2016 16:56:16 +0000</pubDate><guid>http://www.revsys.com/tidbits/vagary-aws-availability-zones/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  In daily parlance, &ldquo;zone&rdquo; is used in a variety of contexts:
 </p>
 <p>
 </p>
 <ol>
  <li>
   the neutral zone (Romulan or Klingon--take your pick)
   <br/>
  </li>
  <li>
   the demilitarized zone
   <br/>
  </li>
  <li>
   a safe zone
  </li>
  <li>
   zoned-out
  </li>
  <li>
   in the zone
  </li>
  <li>
   the "friend" zone
  </li>
  <li>
   land/property-use zoning
  </li>
 </ol>
 <p>
 </p>
 <p>
  The commonality these contexts share is the idea of a specific space; &nbsp;whether&nbsp;physical or metaphysical, virtual or real, a &ldquo;zone&rdquo; defines a discrete space&nbsp;for a specific use or set of uses (or non-use in the case of #4).
 </p>
 <p>
  I hadn&rsquo;t thought much about whether or not the term &lsquo;availability zone&rsquo; was etymologically&nbsp;accurate.&nbsp;
 </p>
 <p>
  On some level (Amazon NOCs), I imagine it is precise enough. From the perspective of an external consumer of AWS resources--I imagine most people will go through life without giving it a second thought. &nbsp;Apparently, I was not destined to walk amongst them.
 </p>
 <p>
  My goal was simple: Using the
  <a href="https://github.com/boto/boto3">
   Python boto3
  </a>
  &nbsp;and
  <a href="https://github.com/boto/botocore">
   botocore
  </a>
  &nbsp;modules, I wanted to be able to specify a
  <a href="https://en.wikipedia.org/wiki/Supernetwork">
   supernet
  </a>
  , an AWS region, a
  <a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks">
   CIDR mask&nbsp;
  </a>
  and a
  <a href="https://en.wikipedia.org/wiki/Amazon_Virtual_Private_Cloud">
   VPC
  </a>
  &nbsp;id and have it do the math and create the desired subnets&nbsp;that would subsequently be attached to the target region&rsquo;s availability zones.
 </p>
 <p>
  What I learned:
 </p>
 <p>
 </p>
 <ul>
  <li>
   the concept of "zone" in AWS-land is fluid. &nbsp;Something I noticed: the AZ is the only AWS thing that does not have an associated ID (at least not available to mortals)
  </li>
  <li>
   the EC2 API will give you a list of possible availability zones. They may all be active or... not. (e.g:
   <i>
    us-east-1
   </i>
   &nbsp;currently has five zones. Four of them exist. The letter associated with the dead zone is chosen by Amazon when you create your account)
  </li>
 </ul>
 <p>
 </p>
 <p>
  When you ask for the list of an AZ within us-east-1:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="p">[</span>
<span class="w">    </span><span class="s2">"AvailabilityZones"</span><span class="p">,</span>
<span class="w">    </span><span class="p">[</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="s2">"State"</span><span class="o">:</span><span class="w"> </span><span class="s2">"available"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"RegionName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"Messages"</span><span class="o">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w">            </span><span class="s2">"ZoneName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1a"</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="s2">"State"</span><span class="o">:</span><span class="w"> </span><span class="s2">"available"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"RegionName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"Messages"</span><span class="o">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w">            </span><span class="s2">"ZoneName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1b"</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="s2">"State"</span><span class="o">:</span><span class="w"> </span><span class="s2">"available"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"RegionName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"Messages"</span><span class="o">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w">            </span><span class="s2">"ZoneName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1c"</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="s2">"State"</span><span class="o">:</span><span class="w"> </span><span class="s2">"available"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"RegionName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"Messages"</span><span class="o">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w">            </span><span class="s2">"ZoneName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1d"</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="s2">"State"</span><span class="o">:</span><span class="w"> </span><span class="s2">"available"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"RegionName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span>
<span class="w">            </span><span class="s2">"Messages"</span><span class="o">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w">            </span><span class="s2">"ZoneName"</span><span class="o">:</span><span class="w"> </span><span class="s2">"us-east-1e"</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">]</span>
<span class="p">]</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Today there are only 4 active zones. &nbsp;Which one is the Dead Zone(tm)? &nbsp;The solution: simple brute&nbsp;force!
 </p>
 <p>
 </p>
 <ol>
  <li>
   Get the list of possible AZs for the region
  </li>
  <li>
   Create a dummy VPC using a moderate sized IPv4 supernet (/21)
  </li>
  <li>
   Divide the supernet into several subnets -- there are 8 /24 subnets in a /21 network so unless Amazon rolls out a region with more than 8 AZs, we're covered
  </li>
  <li>
   Start creating subnets on the dummy VPC
  </li>
  <li>
   The response from Amazon is a dictionary with a ResponseMetadata key. &nbsp;If
   <i>
    response['ResponseMetadata']['HttpStatusCode']
   </i>
   , you have a live AZ
  </li>
  <li>
   Cache this on a per-account basis.
  </li>
 </ol>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">boto3</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">botocore.exceptions</span><span class="w"> </span><span class="kn">import</span> <span class="n">ClientError</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">ipaddr</span><span class="w"> </span><span class="kn">import</span> <span class="n">IPNetwork</span>    <span class="c1"># https://github.com/google/ipaddr-py</span>

<span class="k">def</span><span class="w"> </span><span class="nf">verified_az_list</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="err">&rsquo;</span><span class="n">us</span><span class="o">-</span><span class="n">east</span><span class="o">-</span><span class="mi">1</span><span class="err">&rsquo;</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">keyid</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">profile</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">supernet</span><span class="o">=</span><span class="err">&rsquo;</span><span class="mf">10.255.248.0</span><span class="o">/</span><span class="mi">21</span><span class="err">&rsquo;</span><span class="p">):</span>
    <span class="n">retval</span> <span class="o">=</span> <span class="p">[]</span>
	
    <span class="n">rc_boto</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">Session</span><span class="p">(</span><span class="n">aws_access_key_id</span><span class="o">=</span><span class="n">keyid</span><span class="p">,</span>         <span class="c1"># 1</span>
                       <span class="n">aws_secret_access_key</span><span class="o">=</span><span class="n">key</span><span class="p">,</span>
                       <span class="n">region_name</span><span class="o">=</span><span class="n">region</span><span class="p">)</span>

    <span class="n">mc_ec2</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">client</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">ec2</span><span class="err">&rsquo;</span><span class="p">)</span>

    <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">az_dat</span><span class="p">,),</span> <span class="n">stat</span> <span class="o">=</span> <span class="n">mc</span><span class="o">.</span><span class="n">describe_availability_zones</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>

    <span class="n">az_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span><span class="p">[</span><span class="s1">'ZoneName'</span><span class="p">]</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">az_dat</span><span class="p">]</span>

    <span class="n">resp</span> <span class="o">=</span> <span class="n">mc_ec2</span><span class="o">.</span><span class="n">create_vpc</span><span class="p">(</span>                                <span class="c1"># 2</span>
        <span class="n">CidrBlock</span><span class="o">=</span><span class="n">supernet</span><span class="p">,</span> <span class="n">InstanceTenancy</span><span class="o">=</span><span class="s1">'default'</span>
    <span class="p">)</span>

    <span class="k">if</span> <span class="n">resp</span><span class="p">[</span><span class="s1">'ResponseMetadata'</span><span class="p">][</span><span class="s1">'HTTPStatusCode'</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
        <span class="k">return</span> <span class="kc">False</span>

    <span class="n">vpc_id</span> <span class="o">=</span> <span class="n">resp</span><span class="p">[</span><span class="s1">'Vpc'</span><span class="p">][</span><span class="s1">'VpcId'</span><span class="p">]</span>

    <span class="n">subnets</span> <span class="o">=</span> <span class="p">[</span>                 <span class="c1"># for list comp. haters: using the ipaddr.IPNetwork.exploded </span>
        <span class="n">subnet</span><span class="o">.</span><span class="n">exploded</span>         <span class="c1"># property method to extract the CIDR string--we only need one</span>
        <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">IPNetwork</span><span class="p">(</span>     <span class="c1"># subnet-per-reported AZ</span>
           <span class="n">supernet</span> 
        <span class="p">)</span><span class="o">.</span><span class="n">subnet</span><span class="p">(</span>
            <span class="n">new_prefix</span><span class="o">=</span><span class="mi">28</span>
        <span class="p">)[:</span><span class="nb">len</span><span class="p">(</span><span class="n">az_list</span><span class="p">)]</span>
    <span class="p">]</span>

    <span class="c1"># THE BRUTALITY</span>
    <span class="k">for</span> <span class="n">az</span><span class="p">,</span> <span class="n">subnet</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">az_list</span><span class="p">,</span> <span class="n">subnets</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">subnet_dat</span><span class="p">),</span> <span class="n">stat</span> <span class="o">=</span> <span class="n">mc_ec2</span><span class="o">.</span><span class="n">create_subnet</span><span class="p">(</span>
                <span class="n">VpcId</span><span class="o">=</span><span class="n">vpc_id</span><span class="p">,</span>
                <span class="n">CidrBlock</span><span class="o">=</span><span class="n">subnet</span><span class="p">,</span>
                <span class="n">AvailabilityZone</span><span class="o">=</span><span class="n">az</span>
            <span class="p">)</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>

            <span class="n">retval</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">az</span><span class="p">)</span>
        <span class="k">except</span> <span class="n">ClientError</span><span class="p">:</span>
            <span class="k">pass</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">mc_ec2</span><span class="o">.</span><span class="n">delete_subnet</span><span class="p">(</span><span class="n">SubnetId</span><span class="o">=</span><span class="n">subnet_dat</span><span class="p">[</span><span class="s1">'SubnetId'</span><span class="p">])</span>

    <span class="n">mc_ec2</span><span class="o">.</span><span class="n">delete_vpc</span><span class="p">(</span><span class="n">VpcId</span><span class="o">=</span><span class="n">vpc_id</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">retval</span>
</pre>
 </div>
</div>
<div class="block-content">
 <h2>
  to be fair...
 </h2>
 <p>
 </p>
 <p>
  From Amazon&rsquo;s very own &ldquo;
  <a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions-availability-zones">
   What is Amazon EC2?
  </a>
  &rdquo; document:
 </p>
 <p>
  <i>
   An Availability Zone is represented by a region code followed by a letter identifier;&nbsp;for example, us-east-1a. To ensure that resources are distributed across the Availability&nbsp;Zones for a region, we independently map Availability Zones to identifiers for each account.&nbsp;For example, your Availability Zone us-east-1a might not be the same location as us-east-1a&nbsp;for another account. There's no way for you to coordinate Availability Zones between accounts.
  </i>
 </p>
 <p>
  I used to thoughtlessly cause the Death of Trees by printing off, binding then&nbsp;storing such manuals on the back of toilets for perusal when entrapped by biological necessity. &nbsp;I don&rsquo;t do that anymore--the ascension of tablets and smartphones have made reaching for 3-ring binders filled with pounds of&nbsp;API documentation a rare compulsion. &nbsp;The result: I don&rsquo;t think I&rsquo;ve given the EC2 introductory manual more&nbsp;than a passing glance until recently.
 </p>
 <p>
  P.S.
 </p>
 <p>
  To the keepers of Amazon&rsquo;s AWS documentation: your mobile user experience is rotten. Save a forest;&nbsp;give us a nice mobile-friendly doc option! &nbsp;(PDF -&gt; MOBI doesn&rsquo;t count! :P )
 </p>
 <p>
 </p>
</div>
]]>/></item><item><title>Python 12-factor apps with envparse</title><link>http://www.revsys.com/tidbits/python-12-factor-apps-envparse/</link><description>Getting your configuration from the system environment for your Python and/or Django apps is often the best way to provide security and flexibility. envparse makes it easy. </description><pubDate>Sat, 27 Aug 2016 18:39:41 +0000</pubDate><guid>http://www.revsys.com/tidbits/python-12-factor-apps-envparse/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  Making&nbsp;
  <a href="https://12factor.net/">
   12-Factor apps
  </a>
  &nbsp;is all the rage these days and not without good reasons. Using the system environment to get your app's configuration is probably the most flexible way to pass configuration information once you get used to it.
 </p>
 <p>
  One mental barrier I know I had was I didn't want to
  <i>
   <b>
    always
   </b>
  </i>
  &nbsp;have to pass a bunch of variables just to run my code. The ideal setup is:
 </p>
 <p>
 </p>
 <ul>
  <li>
   Load the env vars from a file in the current directory or any directory above
   <br/>
  </li>
  <li>
   Have any variables from the actual environment included and give the ability to override them on a per execution basis on the command line
  </li>
 </ul>
 <p>
  Luckily, the
  <a href="https://pypi.python.org/pypi/envparse/0.2.0">
   envparse
  </a>
  &nbsp;library for Python gives us this pretty easily. &nbsp;There are two ways you can use it. &nbsp;The "standard" way or more manual way. &nbsp;Or you can define up front a schema of variables you expect and ensure they are cast to the types you need.&nbsp;
 </p>
 <p>
  Let's explore the standard way first.&nbsp;
 </p>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">envparse</span><span class="w"> </span><span class="kn">import</span> <span class="n">env</span>

<span class="n">env</span><span class="o">.</span><span class="n">read_envfile</span><span class="p">()</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  That's all you need to do to load up the environment. The library will walk your file system path looking for a .env file, if it finds one it will load it. &nbsp;If it doesn't it will issue a warning you can safely ignore.&nbsp;
 </p>
 <p>
  You're on your own for validation, however. &nbsp;If you go to access a variable that doesn't exist you'll get a KeyError.&nbsp;
 </p>
 <p>
  Let's assume we have the following in a .env&nbsp;in the current directory:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="nv">AWS_ACCESS_KEY</span><span class="o">=</span><span class="s1">'blahblahblahblah'</span>
<span class="nv">AWS_SECRET_KEY</span><span class="o">=</span><span class="s1">'WeAreNotThatDumb'</span>
<span class="nv">CONSULTING_LEVEL</span><span class="o">=</span><span class="s1">'Pro'</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  If we then called our script like this:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">OTHER</span><span class="o">=</span><span class="s1">'bar'</span> <span class="n">CONSULTING_LEVEL</span><span class="o">=</span><span class="s1">'Expert'</span> <span class="n">python</span> <span class="n">envparse</span><span class="o">-</span><span class="n">example</span><span class="o">.</span><span class="n">py</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Now let's look at envparse-example.py, what do you think it would output?
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">envparse</span><span class="w"> </span><span class="kn">import</span> <span class="n">env</span>

<span class="n">env</span><span class="o">.</span><span class="n">read_envfile</span><span class="p">()</span>

<span class="nb">print</span><span class="p">(</span><span class="s1">'ACCESS ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_ACCESS_KEY'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'SECRET ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_SECRET_KEY'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'CONSULTING_LEVEL ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'CONSULTING_LEVEL'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'OTHER ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'OTHER'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">'foo'</span><span class="p">))</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  You win a cookie or something if you correctly guessed:
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="n">ACCESS</span> <span class="o">=</span> <span class="n">AKIAJI4KRV67OPXQZS7Q</span>
<span class="n">SECRET</span> <span class="o">=</span> <span class="n">EEhwUE1d4</span><span class="o">+</span><span class="n">CLvw6l7EdJzsMwqGgMjMwMgCjwTB3a</span>
<span class="n">CONSULTING_LEVEL</span> <span class="o">=</span> <span class="n">Expert</span>
<span class="n">OTHER</span> <span class="o">=</span> <span class="n">bar</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  I want to point out two things about this. First, as you probably already understood the fact we have 'CONSULTING_LEVEL' set in both the file and on the command line, the command line version takes precedence.&nbsp;
 </p>
 <p>
  The second thing I want to point out is the last line in the source above. &nbsp;We overrode 'OTHER' on the command line, but if we hadn't it would be set to the default of 'foo'. &nbsp;This helps allow you to set sane defaults in the absence of the variable being set anywhere at all.&nbsp;
 </p>
 <h2>
  envparse&nbsp;using a Schema
 </h2>
 <p>
  Now that we've covered the more manual and direct way of using envparse, let's play with the Schema support real quick.&nbsp;
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">envparse</span><span class="w"> </span><span class="kn">import</span> <span class="n">Env</span>

<span class="n">env</span> <span class="o">=</span> <span class="n">Env</span><span class="p">(</span>
    <span class="n">AWS_ACCESS_KEY</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">AWS_SECRET_KEY</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">CONSULTING_LEVEL</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">OTHER</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">cast</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">'foo'</span><span class="p">)</span>

<span class="p">)</span>
<span class="n">env</span><span class="o">.</span><span class="n">read_envfile</span><span class="p">()</span>

<span class="nb">print</span><span class="p">(</span><span class="s1">'ACCESS ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_ACCESS_KEY'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'SECRET ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_SECRET_KEY'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'CONSULTING_LEVEL ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'CONSULTING_LEVEL'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'OTHER ='</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'OTHER'</span><span class="p">))</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Assuming the same .env file and overrides on the command line this will produce the exact same results. The different here is we import 'Env' (note the capitalization) and set the variables we expect to have.&nbsp;
 </p>
 <p>
  If something is missing from our environment, envparse&nbsp;raises a envparse.ConfigurationError rather than getting the more generic KeyError when we go to access to a missing value.
 </p>
 <p>
  Using a schema like this also gives us options for settings defaults as part of the definition rather than when calling for the value itself. &nbsp;envparse has a few other useful options for casting more complex types and giving you the ability to pre or post process the values when they are read which can be useful to help normalize values. &nbsp;
 </p>
 <h2>
  Using envparse with Click
 </h2>
 <p>
  I'm a huge fan of
  <a href="http://click.pocoo.org/6/">
   Click
  </a>
  &nbsp;and while it has great support for
  <a href="http://click.pocoo.org/6/options/#values-from-environment-variables">
   pulling values out of the environment for you
  </a>
  , it doesn't support using a .env&nbsp;file directly. &nbsp;Lucky for us it's straightforward to use envparse with Click. If you need options that can come from a .env file, the environment directly, or passed on the command line you can do this:
 </p>
 <p>
  <br/>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">click</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">envparse</span><span class="w"> </span><span class="kn">import</span> <span class="n">Env</span>

<span class="n">env</span> <span class="o">=</span> <span class="n">Env</span><span class="p">(</span>
    <span class="n">AWS_ACCESS_KEY</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">AWS_SECRET_KEY</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">CONSULTING_LEVEL</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">OTHER</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">cast</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">'foo'</span><span class="p">)</span>

<span class="p">)</span>
<span class="n">env</span><span class="o">.</span><span class="n">read_envfile</span><span class="p">()</span>

<span class="nd">@click</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s1">'--access-key'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_ACCESS_KEY'</span><span class="p">))</span>
<span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s1">'--secret-key'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">env</span><span class="o">.</span><span class="n">str</span><span class="p">(</span><span class="s1">'AWS_SECRET_KEY'</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">testing</span><span class="p">(</span><span class="n">access_key</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">access_key</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">secret_key</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
    <span class="n">testing</span><span class="p">()</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  The trick here is to use envparse to read in your expected values, whether they come from a .env file or are passed on in via the environment it doesn't matter. &nbsp;We then use the value from envparse to set the default for Click. &nbsp;If we need to, we can still override this on the command line and everyone is happy.
 </p>
 <p>
  Hope this helps you make your Python app's configuration more flexible!&nbsp;
 </p>
</div>
]]>/></item><item><title>reverse()'s soulmate resolve()</title><link>http://www.revsys.com/tidbits/django-resolve-to-view/</link><description>Have a URL but can't figure out what view it is supposed to use? Use resolve() to easily and quickly determine it just as Django does.</description><pubDate>Thu, 11 Aug 2016 12:29:46 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-resolve-to-view/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  One common practice in Django is reversing URL:
 </p>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">django.urls</span><span class="w"> </span><span class="kn">import</span> <span class="n">reverse</span>

<span class="n">reverse</span><span class="p">(</span><span class="s1">'my_url_name'</span><span class="p">,</span> <span class="n">kwargs</span><span class="o">=</span><span class="p">{</span><span class="s1">'pk'</span><span class="p">:</span> <span class="mi">1</span><span class="p">})</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  But very often, in large projects, it's hard to remember exactly what the name of the URL is. Furthermore, you maybe using 3rd party libraries that include URLs you want to refer to.
 </p>
 <p>
  If you know what the URL looks like, but you can't remember the name or its arguments, a quick and easy way to find them out is use the `resolve` function:
 </p>
 <p>
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">django.urls</span><span class="w"> </span><span class="kn">import</span> <span class="n">resolve</span>

<span class="n">resolve</span><span class="p">(</span><span class="s1">'/myurl/2'</span><span class="p">)</span>
<span class="c1"># ResolverMatch(func=myapp.views.MyModelViewSet, args=(), kwargs={u'pk': u'2'}, url_name=mymodel-detail, app_name=None, namespaces=['api'])</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  As you can see, the output includes all you need to&nbsp;reverse&nbsp;the URL, including arguments,&nbsp;app_name&nbsp;and&nbsp;namespaces.
 </p>
</div>
<div class="block-html">
 <div class="block-content">
  <div class="rich-text">
   <h6>
    UPDATE
   </h6>
   <p>
    The
    <code>
     django-extensions
    </code>
    package provides another easy way to find out which view. After installing it, you can simply use its
    <code>
     show_urls
    </code>
    and
    <code>
     grep
    </code>
    ping for the URL you're looking for:
   </p>
  </div>
 </div>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="err">$</span> <span class="o">./</span><span class="n">manage</span><span class="o">.</span><span class="n">py</span> <span class="n">show_urls</span> <span class="o">|</span> <span class="n">grep</span> <span class="s2">"/myurl/&lt;pk&gt;"</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  Thanks to
  <b>
   @bmihelac
  </b>
  &nbsp;for the suggestion!
 </p>
 <p>
  <br/>
 </p>
</div>
]]>/></item><item><title>Extend Django Templates Smartly</title><link>http://www.revsys.com/tidbits/extend-django-templates-smartly/</link><description>You can use a variable to set the name of the template you are extending when using Django templates.</description><pubDate>Mon, 08 Aug 2016 16:55:49 +0000</pubDate><guid>http://www.revsys.com/tidbits/extend-django-templates-smartly/</guid><content:encoded<![CDATA[<div class="block-content">
 <p>
  One of my all favorite template tricks goes back to my LJ World days.
 </p>
 <p>
  Most people don't realize that the&nbsp;
  <a href="https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#extends">
   extends
  </a>
  &nbsp;tag accepts a variable and not just a hard-coded literal value. This allows for magical things to happen when combined with a default.
  <br/>
 </p>
 <p>
  The following block extends a base template named "base.html" if the&nbsp;
  <b>
   base_template
  </b>
  &nbsp;is not set.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="cp">{%</span> <span class="k">extends</span> <span class="nv">base_template</span><span class="o">|</span><span class="nf">default</span><span class="s2">:"base.html"</span> <span class="cp">%}</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  For any page that you want to programmatically override the base template, you may do so by adding a&nbsp;base_template context variable to your views context values which are exposed to your template.
 </p>
</div>
<div class="block-code">
 <div class="codehilite">
  <pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">django.views.generic</span><span class="w"> </span><span class="kn">import</span> <span class="n">TemplateView</span>


<span class="k">class</span><span class="w"> </span><span class="nc">MarketingView</span><span class="p">(</span><span class="n">TemplateView</span><span class="p">):</span>

    <span class="n">template_name</span> <span class="o">=</span> <span class="s1">'marketing_landing.html'</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">get_context_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="n">context</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">MarketingView</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">get_context_data</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="n">context</span><span class="p">[</span><span class="s1">'base_template'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'base_marketing.html'</span>
        <span class="k">return</span> <span class="n">context</span>
</pre>
 </div>
</div>
<div class="block-content">
 <p>
  This simple trick opens the door to do fun things with your website based on the day of the year or something useful like A/B test two different versions of your website's landing page. The possibilities are endless.
 </p>
</div>
]]>/></item><item><title>Django Birthday: Recap</title><link>http://www.revsys.com/tidbits/django-birthday/</link><description>Our recap of the Django&amp;#x27;s 10th Birthday Party</description><pubDate>Tue, 14 Jul 2015 21:02:13 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-birthday/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Happy 10th Birthday Django!
  </h2>
  <p>
   <img alt="" src="http://photos.frankwiles.com/Events/Django-Birthday/i-pRMN7Sr/0/M/150711jasondailey841-M.jpg"/>
  </p>
  <p>
   Whew, what a conference! The talks were great, the venue was comfortable, and the BBQ was amazing. But that was only part of it. Many of the talks focused on the Django community and you could feel it in the room. We laughed from our bellies and teared up each time someone mentioned
   <a href="https://www.djangoproject.com/weblog/2013/sep/16/announcing-malcolm-tredinnick-memorial-prize/">
    Malcolm
   </a>
   .
  </p>
  <p>
   I had the honor of giving the closing address, which was really just a thinly veiled way for me to have a Steve Jobs "oh and one more thing" moment. We had secretly conspired with The World Company, the place where Django was born, to open up the building so all of the attendees could get a quick tour and see
   <strong>
    EXACTLY
   </strong>
   where Django originated. Liberty Hall, where we held the conference, is conveniently across the alley from the Journal-World building so we all stood up, grabbed our bags, and walked over. The photo above is most of us in front of the News Center building where Django was born.
  </p>
  <p>
   The night before the conference we had a very loosely planned evening of dinner and drinks which also included a quick Skype session with the Django Girls group in Australia to kick off the celebration. It was a great relaxed way to catch up with old friends.
  </p>
  <h2>
   Videos
  </h2>
  <p>
   The
   <a href="http://pyvideo.org/category/71/django-birthday">
    videos are up on PyVideo.org
   </a>
   for you all to enjoy. As always
   <a href="http://nextdayvideo.com/">
    Next Day Video
   </a>
   had them up so fast it was a constant stream of "OMG my talk is already up" Tweets.
  </p>
  <h2>
   Photos
  </h2>
  <p>
   Even before Adrian mentioned in his talk about taking more photos of your projects for nostalgia purposes, we had it in our plans for Django Birthday. There is no telling if Django will make it to 2025, but assuming it does we have plenty of ammo for the next birthday party. I took a bunch of photos and we hired
   <a href="http://www.daileyimages.com/">
    Dailey Images
   </a>
   to ensure we captured all of the right moments.
  </p>
  <p>
   Some initial quickly edited shots from Jason Dailey, the photos I took, and some from my friend Joe Griffin are in my
   <a href="http://photos.frankwiles.com/Events/Django-Birthday/">
    Django Birthday
   </a>
   photo album.
  </p>
  <p>
   Kenneth Love also setup an
   <a href="https://www.icloud.com/sharedalbum/#B04GWZuqDGv43mF">
    iCloud album
   </a>
   of some of his shots. Including one of the newspaper the next day with a front page photo of the block party.
  </p>
  <h2>
   The Party
  </h2>
  <p>
   <img alt="" src="http://photos.frankwiles.com/Events/Django-Birthday/i-nTMNj9g/0/X3/Django_July11_2015-9-X3.jpg"/>
  </p>
  <p>
   Eldarion and RevSys sponsored the party with Lincoln Loop sponsoring the birthday cake.
  </p>
  <p>
   While it was a bit warmer than I would like, the party was epic. We had all of the things you would expect at a 10 year old geeky kids birthday party. Snow cones, balloon animals, Gizmo the robot wandering around, and of course a bouncy castle. The Batmobile even made an appearance, but that wasn't planned... it's just
   <a href="https://www.google.com/webhp?sourceid=chrome-instant&amp;ion=1&amp;espv=2&amp;es_th=1&amp;ie=UTF-8#q=%23LFK&amp;es_th=1">
    #LFK
   </a>
  </p>
  <p>
   For the more adult of us, we also had 3 musical performances and started the night off with Adrian himself playing Django Reinhardt. Yep that's right, the guy who created Django, playing Django, at Django's 10th birthday.
  </p>
  <p>
   Our friends at
   <a href="http://www.thesandbar.com">
    The SandBar
   </a>
   were awesome enough to help us throw this block party and showed everyone an amazing time. We had the usual things you would expect, but also a special Python/Django themed craft cocktail tent and this awesome shrimp &amp; rice in a pineapple food option that I have to try to recreate at home now.
  </p>
  <p>
   When we started planning this party Jacob made me promise to not have any ponies, but I couldn't resist one. So in addition to our big cakes for all attendees we had the baker create a small cutting cake and let Adrian have the honor of cutting the pony in half.
  </p>
  <p>
   I want to personally thank everyone who made the trip out to our little weird bit of the world. I hope you enjoyed yourselves and come back again very soon. Things went so well, I'm not sure I can wait until 2025. We may have to celebrate Django's Sweet 16 or something in the mean time.
  </p>
  <h2>
   Many thanks to our great sponsors for helping us put on a great and fun event!
  </h2>
  <ul>
   <li>
    <a href="http://eldarion.com/">
     Eldarion
    </a>
   </li>
   <li>
    <a href="https://p.ota.to/">
     Potato
    </a>
   </li>
   <li>
    <a href="http://plushrugs.com/">
     PlushRugs.com
    </a>
   </li>
   <li>
    <a href="https://lincolnloop.com/">
     Lincoln Loop
    </a>
   </li>
   <li>
    <a href="https://reelio.com/">
     Reelio
    </a>
   </li>
   <li>
    <a href="https://teamtreehouse.com/">
     Treehouse
    </a>
   </li>
  </ul>
  <p>
   And our travel sponsors:
  </p>
  <ul>
   <li>
    <a href="https://www.python.org/psf/">
     The Python Software Foundation
    </a>
   </li>
   <li>
    <a href="https://www.djangoproject.com/">
     The Django Software Foundation
    </a>
   </li>
  </ul>
 </div>
</div>
]]>/></item><item><title>Django Birthday Party</title><link>http://www.revsys.com/tidbits/django-birthday-party/</link><description>Django is turning 10 years old this summer and we&amp;#x27;re throwing it a birthday party in the city where it was born.</description><pubDate>Wed, 17 Jun 2015 23:01:07 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-birthday-party/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   So as you may know, Django is turning 10 years old this summer and we&rsquo;re throwing it a
   <a href="https://djangobirthday.com">
    birthday party
   </a>
   . It&rsquo;s a one day tech conference and one day of sprints mere feet from where Adrian, Simon, Jacob, and Wilson toiled away on &ldquo;
   <em>
    the CMS
   </em>
   &rdquo;, as it was called then. That code became the first of a few amazing refreshes of
   <a href="http://www.lawrence.com">
    Lawrence.com
   </a>
   and the
   <em>
    very first
   </em>
   Django website.
  </p>
  <p>
   We&rsquo;ve got an amazing line up of
   <a href="https://djangobirthday.com/speakers/">
    speakers
   </a>
   of course. And we want to celebrate Django obviously, but secretly we have another small motivation. See we love Lawrence, despite the fact it&rsquo;s surrounded by Kansas, and we&rsquo;ve grown tired of answering questions along the lines of &ldquo;Ummm why are you in Kansas?&rdquo; So we&rsquo;re going to expose our Django community friends to as much as this area has to offer to help explain why we choose to live here. And who knows maybe one of them will fall in love, as we have, and build their next startup here.
  </p>
  <h2>
   So what can you expect?
  </h2>
  <p>
   Most everyone agrees the best part of Lawrence is our downtown, so we&rsquo;re having the event downtown and encouraging everyone to stay in or near the area. This makes everything special in Lawrence within walking distance. There&rsquo;s great local restaurants, quirky stores, and being a college town lots of interesting places to grab a libation should you so choose.
  </p>
  <p>
   We&rsquo;re having the event at
   <a href="https://www.google.com/search?q=liberty+hall+lawrence&amp;espv=2&amp;biw=1439&amp;bih=1010&amp;source=lnms&amp;tbm=isch&amp;sa=X&amp;ei=MfOBVZLRFpSZoQTFhoDIDw&amp;ved=0CAcQ_AUoAg">
    Liberty Hall
   </a>
   which has acted as a community-meeting house and venue since 1856! If you&rsquo;re interested you can
   <a href="http://www.libertyhall.net/about/history">
    read more about its history
   </a>
   . Lawrence is weird and funky, which is reflected in the wide variety of entertainers that have performed there. Some highlights include: Oscar Wilde, Tina Turner, Wu Tang Clan, Henry Rollins, Tori Amos, The Civil Wars, and for its 100th anniversary party
   <a href="http://www.pitch.com/FastPitch/archives/2012/06/22/the-flaming-lips-circus-last-night-at-liberty-hall">
    The Flaming Lips
   </a>
   which I&rsquo;m quite sad to have missed. Lawrence has a long and great music history, but much of that happened in the 90s at
   <a href="https://en.wikipedia.org/wiki/Outhouse_(venue)">
    The Outhouse
   </a>
   (which is now sadly a strip club so we
   <strong>
    won&rsquo;t
   </strong>
   be going there) and another venue down the street named the
   <a href="http://thebottlenecklive.com/about/">
    Bottleneck
   </a>
   .
  </p>
  <p>
   Kansas is known for its BBQ, so of course we&rsquo;re having Kansas City&rsquo;s best
   <a href="http://www.joeskc.com/about/">
    Oklahoma Joe&rsquo;s
   </a>
   cater lunch. In 2009, Anthony Bourdain listed Joe&rsquo;s in his &ldquo;Thirteen Places to Eat Before You Die&rdquo;. Yeah it&rsquo;s that good.
  </p>
  <h2>
   But it&rsquo;s a party right?
  </h2>
  <p>
   Oh yes it&rsquo;s a party all right! We&rsquo;ve gotten special permission from the City of Lawrence to close down a block of downtown and hold a block party! We&rsquo;ll have a few live bands, including Adrian, food, beer, a special cocktail tent for our more refined guests, snow cones, and some other fun family friendly things for the kids.
  </p>
  <p>
   See many in Lawrence don&rsquo;t know about Django and we want to change that. By having a block party all of Lawrence can come down and help celebrate, instead of having it just be a small gathering of us geeks.
  </p>
  <p>
   Oh and there will be cake of course&hellip; it&rsquo;s not a birthday party without cake.
  </p>
  <h2>
   Many thanks to our great sponsors for helping us put on a great and fun event!
  </h2>
  <ul>
   <li>
    <a href="https://p.ota.to/">
     Potato
    </a>
   </li>
   <li>
    <a href="http://plushrugs.com/">
     PlushRugs.com
    </a>
   </li>
   <li>
    <a href="https://lincolnloop.com/">
     Lincoln Loop
    </a>
   </li>
   <li>
    <a href="https://reelio.com/">
     Reelio
    </a>
   </li>
   <li>
    <a href="https://teamtreehouse.com/">
     Treehouse
    </a>
   </li>
  </ul>
  <p>
   And our travel sponsors:
  </p>
  <ul>
   <li>
    <a href="https://www.python.org/psf/">
     The Python Software Foundation
    </a>
   </li>
   <li>
    <a href="https://www.djangoproject.com/">
     The Django Software Foundation
    </a>
   </li>
  </ul>
 </div>
</div>
]]>/></item><item><title>Improved Django Tests</title><link>http://www.revsys.com/tidbits/django-test-plus/</link><description>Improve your Django Tests using django-test-plus&amp;#x27; helpful additions to the standard Django TestCase</description><pubDate>Fri, 29 May 2015 12:20:52 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-test-plus/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   You write tests. Or at least you should be writing tests! As Jacob always says,
   <em>
    "Code without tests is broken as designed."
   </em>
  </p>
  <p>
   Unfortunately you have to write a ton of boilerplate code to test even the simplest of things. This isn't true of just Django, but most languages and frameworks. There are lots of tools like
   <a href="https://factoryboy.readthedocs.org/en/latest/">
    Factory Boy
   </a>
   and
   <a href="http://pytest.org/">
    pytest
   </a>
   that help, but we can do better.
  </p>
  <p>
   If you're looking for how to get started writing Django tests I'd suggest reading the
   <a href="https://docs.djangoproject.com/en/dev/topics/testing/">
    excellent testing documentation
   </a>
   first and also consider picking up Harry Percival's excellent book
   <a href="http://www.amazon.com/gp/product/1449364829/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=390957&amp;creativeASIN=1449364829&amp;linkCode=as2&amp;tag=revosystblog-20&amp;linkId=VG3GUGRNAQXLZRVR">
    Test-Driven Development with Python
   </a>
   <img alt="" src="http://ir-na.amazon-adsystem.com/e/ir?t=revosystblog-20&amp;l=as2&amp;o=1&amp;a=1449364829"/>
  </p>
  <p>
   After the 400th time of copying over the same base test methods into various personal and client projects, I finally got around to packaging up and releasing all of our useful test additions into a module
   <a href="https://pypi.python.org/pypi/django-test-plus">
    django-test-plus
   </a>
   . I confess, I did this so I didn't have to copy it around anymore as much as wanting to share it with the world. My laziness is your gain. Come be lazy with me!
  </p>
  <h2>
   Let's have some testing fun
  </h2>
  <p>
   I think this module is best explained by examples. So let's just dive right in. First off you need to install django-test-plus with:
  </p>
  <div class="codehilite">
   <pre><span></span><code>pip install django-test-plus
</code></pre>
  </div>
  <h3>
   Then instead of this...
  </h3>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.test</span><span class="w"> </span><span class="kn">import</span> <span class="n">TestCase</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.core.urlresolvers</span><span class="w"> </span><span class="kn">import</span> <span class="n">reverse</span>

<span class="k">class</span><span class="w"> </span><span class="nc">MyViewTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">test_some_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">url</span> <span class="o">=</span> <span class="n">reverse</span><span class="p">(</span><span class="s1">'my-url-name'</span><span class="p">)</span>
        <span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>
</code></pre>
  </div>
  <h3>
   Do this...
  </h3>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">test_plus.test</span><span class="w"> </span><span class="kn">import</span> <span class="n">TestCase</span>

<span class="k">class</span><span class="w"> </span><span class="nc">MyViewTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>

    <span class="k">def</span><span class="w"> </span><span class="nf">test_some_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'my-url-name'</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">response_200</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   For the sake of brevity, I'll be excluding the imports and test class in the rest of these examples and just focus on the methods themselves.
  </p>
  <h3>
   Or even this...
  </h3>
  <p>
   Getting a named URL and checking that status is 200 is such a common pattern, it has it's own helper. This will get the named URL, check that the status is 200 and return the response for other checks.
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_some_view(self):
    response = self.get_check_200('my-url-name')
</code></pre>
  </div>
  <h3>
   Instead of...
  </h3>
  <div class="codehilite">
   <pre><span></span><code>def test_with_reverse_args(self):
    url = reverse('some-url', kwargs={
        'year': 2015, 
        'slug': 'my-this-is-getting-long'
    })
    response = self.client.get(url)
</code></pre>
  </div>
  <h3>
   Do this...
  </h3>
  <div class="codehilite">
   <pre><span></span><code>def test_with_reverse_args(self):
    response = self.get('some-url', year=2015, slug='much-better')
</code></pre>
  </div>
  <h2>
   HTTP Posts work the same way
  </h2>
  <h3>
   Instead of...
  </h3>
  <div class="codehilite">
   <pre><span></span><code>def post_with_reverse_args(self):
    url = reverse('some-url', kwargs={
        'year': 2015, 
        'slug': 'my-this-is-getting-long'
    })
    data = {'body': 'long-way'}
    response = self.client.post(url. data=data)
</code></pre>
  </div>
  <p>
   `
  </p>
  <h3>
   Save your keystrokes for another day with...
  </h3>
  <div class="codehilite">
   <pre><span></span><code>def test_post_better(self):
    response = self.post('some-url', 
        year=2015, 
        slug='much-better', 
        data={'body': 'lazy-way'}
    )
</code></pre>
  </div>
  <h3>
   Sometimes you still need reverse...
  </h3>
  <p>
   So it's included for you. No need to import it yourself.
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_reversing(self):
    response = self.get('my-named-url')
    test_url = self.reverse('some-other-url', pk=12)
    self.assertEqual(response.context['next'], test_url)
</code></pre>
  </div>
  <p>
   That's better, but that's still a bit rough on the hands. Let's make it a touch better with this version which uses our
   <code>
    get_context()
   </code>
   method:
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_reversing(self):
    response = self.get('my-named-url')
    test_url = self.reverse('some-other-url', pk=12)
    self.assertEqual(self.get_context('next'), test_url)
</code></pre>
  </div>
  <p>
   We keep the last response received in
   <code>
    self.last_response
   </code>
   for you so there isn't a reason to have to pass it around all over the place.
  </p>
  <p>
   Often you need to test the values of several context variables, so let's make that a bit easier.
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_several_values(self):
    self.get('my-view')
    self.assertContext('key1', 'value1')
    self.assertContext('key2', 'value2')
    self.assertContext('key3', False)
</code></pre>
  </div>
  <h2>
   What about other statuses?
  </h2>
  <p>
   Don't worry we've got you covered there are several
   <code>
    response_XXX()
   </code>
   methods to test for other common status code, for example:
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_not_found(self):
    response = self.get('no-there')
    self.response_404(respones)
</code></pre>
  </div>
  <h2>
   Authentication and Users
  </h2>
  <p>
   When testing out Django views you often need to make some users, login in as them, and poke around. Let's make that easier too!
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_needs_login(self):
    # Make a user 
    user = self.make_user('testuser')

    # Make sure we protected the view
    self.assertLoginRequired('my-protected-view')

    with self.login(user):
          self.get('my-protected-view')
          self.assertContext('secret', True)
</code></pre>
  </div>
  <h3>
   Performance
  </h3>
  <p>
   It's easy to make a few template changes that seem inconsequential only to have your database query count blow up to the size of Warren Buffett's checking account. Django provides the
   <code>
    assertNumQueries
   </code>
   assertion context to check the query count, but that is a static count. Often the results are slightly variable, so django-test-plus as
   <code>
    assertNumQueriesLessThan
   </code>
   .
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">def</span><span class="w"> </span><span class="n">test_does_not_get_crazy</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="w">    </span><span class="n">with</span><span class="w"> </span><span class="bp">self</span><span class="o">.</span><span class="n">assertNumQueriesLessThan</span><span class="p">(</span><span class="mi">25</span><span class="p">):</span>
<span class="w">        </span><span class="n">variable_query_count_function</span><span class="p">()</span>
</code></pre>
  </div>
  <h3>
   Ultimate Lazy
  </h3>
  <p>
   Maybe it's a toy project or you're adding tests to a project that previously didn't have any. Some tests are better than no tests right? So we provide a quick view check with
   <code>
    assertGoodView
   </code>
   :
  </p>
  <div class="codehilite">
   <pre><span></span><code>def test_better_than_nothing(self):
    self.assertGoodView('first-view')
    self.assertGoodView('second-view')
    self.assertGoodView('something-else')
</code></pre>
  </div>
  <p>
   What's this do? It gets the view at the named URL, ensures the status code is 200 and tests that the number of queries run is less than 50. Oh and it returns the response if you want it for other purposes. Not amazing, but better than having no test coverage.
  </p>
  <h2>
   The future
  </h2>
  <p>
   I've been using various versions of this module for years, but there is always room for improvement. Happy to take pull requests submissions for new methods that will be generally useful. Or maybe one day I'll be less lazy and see about merging these into Django core.
  </p>
  <p>
   Hope you like the module and find it useful. Happy Testing!
  </p>
 </div>
</div>
]]>/></item><item><title>Django Performance: 4 Simple Things</title><link>http://www.revsys.com/tidbits/django-performance-simple-things/</link><description>Four simple steps you can take to improve the performance of your Django applications</description><pubDate>Wed, 06 May 2015 15:13:51 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-performance-simple-things/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Optimizing for performance often comes with a bunch of headaches and hard problems, but it doesn&rsquo;t always have to be that way.
  </p>
  <p>
   Below are four simple things you can do to quickly and easily improve the performance of your sites. They&rsquo;re so simple you should make them part of your standard setup.
  </p>
  <h2>
   Persistent database connections
  </h2>
  <p>
   New in Django 1.6 Django has built-in database connection persistence. Most everyone uses PostgreSQL for their production systems, and while connecting to PG isn&rsquo;t particularly slow, it is something we can optimize.
  </p>
  <p>
   Without persistent connections every request to your site also makes a connection to your database, authenticates the user, etc. This can easily take as much as 20&ndash;75ms if your database is on another host even on a fast network.
  </p>
  <p>
   To setup connection persistence you add the
   <code>
    CONN_MAX_AGE
   </code>
   parameter to your
   <code>
    DATABASES
   </code>
   setting to look like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'whoohoodb',
        'CONN_MAX_AGE': 600,
    }
}
</code></pre>
  </div>
  <p>
   With this,we&rsquo;ve setup persistent connections to stay alive for 10 minutes at a time. This helps to cut down on any possible memory leaks or a flaky connection causing a long term problem. You can certainly use a longer value, but I wouldn&rsquo;t go much beyond an hour as there will be little benefit.
  </p>
  <p>
   You can read more details about
   <a href="https://docs.djangoproject.com/en/1.8/ref/databases/#persistent-connections">
    persistent database connections
   </a>
   in the Django documentation.
  </p>
  <h2>
   Template Loading
  </h2>
  <p>
   By default Django comes configured to use the two standard template loaders:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">TEMPLATE_LOADERS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span>
<span class="w">    </span><span class="s1">'django.template.loaders.filesystem.Loader'</span><span class="p">,</span>
<span class="w">    </span><span class="s1">'django.template.loaders.app_directories.Loader'</span><span class="p">,</span>
<span class="p">)</span>
</code></pre>
  </div>
  <p>
   These loaders search the file system and parse your templates on every request. Kinda surprising that it works as quickly as it does right?
  </p>
  <p>
   You can turn on cached loading, so Django only has to find and parse your templates one time, by simply changing your configuration to be:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">TEMPLATE_LOADERS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span>
<span class="w">    </span><span class="p">(</span><span class="s1">'django.template.loaders.cached.Loader'</span><span class="p">,</span><span class="w"> </span><span class="p">(</span>
<span class="w">        </span><span class="s1">'django.template.loaders.filesystem.Loader'</span><span class="p">,</span>
<span class="w">        </span><span class="s1">'django.template.loaders.app_directories.Loader'</span><span class="p">,</span>
<span class="w">    </span><span class="p">)),</span>
<span class="p">)</span>
</code></pre>
  </div>
  <p>
   Now, don&rsquo;t be silly and turn on cached loading in your development environments as it will get annoying. You&rsquo;ll be forced to restart runserver (or whatever you&rsquo;re using locally) on each and every template change.
  </p>
  <h2>
   Optimize Django Sessions
  </h2>
  <p>
   If you poll Django users nearly 82% of them have no idea where their sessions are stored. Ok, I admit I made that number up, but in our experience it&rsquo;s pretty true.
  </p>
  <p>
   By default, Django stores your user sessions in your database and expects you to occasionally prune out old entries. Very few people do this.
  </p>
  <p>
   So on each request you&rsquo;re doing a SQL query to get the session data and another to grab the User object information. If you&rsquo;re the sort of person who&rsquo;s concerned about performance you likely already have memcached or Redis setup to cache some things. You can switch to storing your sessions in your cache and easily remove a SQL query from every single request to your site with:
  </p>
  <div class="codehilite">
   <pre><span></span><code>SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
</code></pre>
  </div>
  <p>
   Now this assumes you can safely lose any data you store in your user sessions. If that&rsquo;s not the case, you can still get some benefit from using:
  </p>
  <div class="codehilite">
   <pre><span></span><code>SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
</code></pre>
  </div>
  <p>
   This backend caches your data, but also writes any changes to your database for long term storage.
  </p>
  <h3>
   select_related() and prefetch_related()
  </h3>
  <p>
   The first two tips above were super easy, right? Unfortunately this last one, while still easy compared to totally changing up your data storage, does require a bit more than a single configuration setting change.
  </p>
  <p>
   <code>
    select_related()
   </code>
   and
   <code>
    prefetch_related()
   </code>
   aren&rsquo;t in everyone&rsquo;s bag of tricks but they are incredibly useful for cutting down on the number of SQL queries your ORM code is firing.
  </p>
  <p>
   The situation that usually happens is you have some
   <code>
    BlogPost
   </code>
   model with a ForeignKey to User. Then in a list view you use
   <code>
    queryset = BlogPost.objects.active
   </code>
   . Then in your templates you have something like:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">&lt;ul&gt;</span>
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">post</span> <span class="k">in</span> <span class="nv">object_list</span> <span class="cp">%}</span>
<span class="w">  </span><span class="nt">&lt;li&gt;</span><span class="cp">{{</span> <span class="nv">post.title</span> <span class="cp">}}</span><span class="w"> </span>-<span class="w"> </span><span class="cp">{{</span> <span class="nv">post.user.email</span> <span class="cp">}}</span><span class="nt">&lt;/li&gt;</span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
<span class="nt">&lt;/ul&gt;</span>
</code></pre>
  </div>
  <p>
   This ends up doing the expected SELECT against the blog_post table, but then is forced in the template loop to do another SELECT against auth_user
   <strong>
    for each and every post in the list
   </strong>
   .
  </p>
  <p>
   Instead, we can ask the Django ORM to go ahead and JOIN in the auth_user table to our results so that
   <code>
    object.user
   </code>
   is already an object for us. Taking us from
   <code>
    BlogPost.objects.active().count()
   </code>
   queries down to a single query.
  </p>
  <p>
   We can fix it by simply changing the view&rsquo;s queryset to be:
  </p>
  <div class="codehilite">
   <pre><span></span><code>queryset = BlogPost.objects.select_related().active()
</code></pre>
  </div>
  <p>
   While not a simple settings change, still pretty simple to accomplish.
  </p>
  <p>
   <code>
    prefetch_related
   </code>
   is the same mechanism, but for ManyToManyField relationships. Prefetch can&rsquo;t use a join here, so the
   <em>
    join
   </em>
   happens in Python at the ORM level. This can be a HUGE performance win for situations where you have a small number of rows in your ManyToManyField() vs a large number of rows in the model itself. However, in general, it&rsquo;s less useful and thus used less often that
   <code>
    select_related()
   </code>
   .
  </p>
  <p>
   So when do you use these you ask? The easiest thing to do is fire up
   <a href="https://django-debug-toolbar.readthedocs.org/en/1.3/">
    django-debug-toolbar
   </a>
   with your project and some representative data. By representative I mean you shouldn&rsquo;t have a single BlogPost and a single User, try more like 100+ of each.
  </p>
  <p>
   Then all you need to do is browse around your app locally with the debug toolbar open looking specifically at the query counts and times. If you see anything over 5&ndash;10 queries going on this is your clue to investigate.
  </p>
  <p>
   Most of the time with 100s of objects in your database you&rsquo;ll stumble across a view that is doing 200&ndash;500+ queries where one of these two options will be a huge win for you. Change your queryset, refresh, and make sure that both your query counts decreased but also your overall time. Occasionally you&rsquo;ll have a situation where it&rsquo;s actually faster to do the extra queries vs using joins it happens rarely, but still worth checking the overall query time to make sure you aren&rsquo;t making things worse.
  </p>
 </div>
</div>
]]>/></item><item><title>Wagtail 1.0 (beta) best Django CMS?</title><link>http://www.revsys.com/tidbits/wagtail-best-django-cms/</link><description>Is Wagtail the best Django CMS? Review of Wagtail 1.0 features and our experiences using it.</description><pubDate>Wed, 29 Apr 2015 15:25:21 +0000</pubDate><guid>http://www.revsys.com/tidbits/wagtail-best-django-cms/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Now that the
   <a href="http://wagtail.io">
    Wagtail CMS
   </a>
   is gearing up for its 1.0 release, I wanted to take some time to introduce you to the all around best and most flexible Django CMS currently available. Wagtail has been around for a while, but doesn&rsquo;t seem to get the attention I believe it deserves.
  </p>
  <p>
   We&rsquo;ve used Wagtail recently on a number of projects, and the overall experience has been great. It strikes the right balance of making the easy things easy and the hard things not only possible, but relatively easy as well.
  </p>
  <h2>
   Feature Highlights
  </h2>
  <ul>
   <li>
    Non-technical end-user ease. Custom admin with great UI/UX
   </li>
   <li>
    Plays nicely alongside any other Django apps on your site
   </li>
   <li>
    Easy admin customization and branding
   </li>
   <li>
    Flexibility of CMS models for more structured data beyond just having a &ldquo;Page&rdquo;
   </li>
   <li>
    Built in Form builder system
   </li>
   <li>
    Great Image and Document/File support and UI
   </li>
   <li>
    StreamField for ultimate flexibility allowing you to define and organize small blocks of content
   </li>
   <li>
    Ability to organize admin tabs and field layouts
   </li>
   <li>
    Control/Flexibility of what page models can be added below certain URLs
   </li>
   <li>
    Hooks into ElasticSearch for searching
   </li>
   <li>
    Compatible with Varnish and static site generators to help with performance at scale
   </li>
  </ul>
  <h2>
   Admin Interface
  </h2>
  <p>
   Let&rsquo;s face it, the Django admin leaves a lot to be desired. It&rsquo;s very CRUD-oriented and confusing for all but the most technical of users. Even giving it a facelift with packages like
   <a href="http://djangosuit.com/">
    Django Suit
   </a>
   , or swapping it out entirely for something like
   <a href="http://grappelliproject.com/">
    Grappelli
   </a>
   isn&rsquo;t really what your end users want. Don&rsquo;t get me wrong: both of these packages are great and you should check out, but they both simply can&rsquo;t get past all of the hurdles and pain that come with attempting to customize the Django admin beyond a certain point.
  </p>
  <p>
   Wagtail comes out of the box with it&rsquo;s own custom admin interface that is specifically geared toward a typical CMS workflow. Check out this great
   <a href="https://player.vimeo.com/video/86036070">
    promo video
   </a>
   about Wagtail and you&rsquo;ll see what I mean. No seriously, go watch it. I&rsquo;ll wait.
  </p>
  <p>
   Isn&rsquo;t that great looking? My first thought when seeing the Wagtail video for the first time was &ldquo;Nice, but I bet customizing it is a huge pain in the&hellip;&rdquo;. Thankfully, I gave it a whirl anyway and came to find that customizing the Wagtail admin is actually pretty simple.
  </p>
  <p>
   There is a great
   <a href="http://docs.wagtail.io/en/latest/editor_manual/index.html">
    editor&rsquo;s guide in the docs
   </a>
   that is all most end users need to get started. So far in our use, the only thing that confuses users is the Explorer, Root pages, and the hierarchical nature of pages in general. Even those are small issues as one quick chat with the user and they grok it and are on their way.
  </p>
  <p>
   Oh and a huge bonus the admin is surprisingly usable on both mobile and tablets!
  </p>
  <p>
   <img alt="" src="http://media.revsys.com/images/blog/wagtail/wagtail-admin-ui.png"/>
  </p>
  <h3>
   Ways to customize the Wagtail Admin
  </h3>
  <p>
   There are a few ways you can customize the admin. First off, you can determine what fields are visible to your users and on what tab of the interface with just a
   <a href="http://docs.wagtail.io/en/latest/pages/editing_api.html">
    bit of configuration
   </a>
   . Consider this the bare bones entry level of &ldquo;customization&rdquo; you&rsquo;ll be doing.
  </p>
  <p>
   Customizing the
   <em>
    branding
   </em>
   of the admin is also a very frequent need. Techies often don&rsquo;t see the point, but if you can put on your end user hat for a moment it seems weird and often confusing to come to a login page for www.revsys.com that reads &ldquo;Welcome to the Wagtail CMS Admin&rdquo;.
  </p>
  <p>
   If you install
   <a href="https://pypi.python.org/pypi/django-overextends">
    django-overextends
   </a>
   you can
   <a href="http://docs.wagtail.io/en/latest/howto/custom_branding.html">
    easily customize
   </a>
   the logo, login message, and welcome messages used by the CMS to match your user&rsquo;s expectations.
  </p>
  <p>
   For me, these two customization options are what I expect from a CMS. However, Wagtail goes a step further and gives you
   <a href="http://docs.wagtail.io/en/latest/reference/hooks.html#hooks">
    hooks
   </a>
   to allow for much richer customizations. You can do things like:
  </p>
  <ul>
   <li>
    Add items to the Wagtail User Bar that appears for logged in users on the right side of the page much like Django Debug Toolbar
   </li>
   <li>
    Add or remove panels from the main Wagtail admin homepage
   </li>
   <li>
    Add or remove summary items (Pages, Documents, Images, etc) from the homepage
   </li>
   <li>
    Use hooks for taking behind the scenes actions or if you want your own customized Responses after creating, editing, or deleting at Page
   </li>
   <li>
    Add your own admin menu items, which can go to any Django views or offsite URLs you desire.
   </li>
  </ul>
  <p>
   <img alt="" src="http://media.revsys.com/images/blog/wagtail/wagtail-admin-sidebar.png"/>
  </p>
  <p>
   I used that last ability to add admin menu items with great success on
   <a href="http://www.tedxlawrence.com">
    TEDxLawrence.com
   </a>
   . We needed a way for our Speaker Committee to view the speaker submissions, vote, and make comments. Instead of attempting to shoe horn all of this into a Django Admin or even Wagtail Admin universe, I simply linked off to entirely customized Class Based Views to give me complete end to end control.
  </p>
  <h2>
   Wagtail Pages
  </h2>
  <p>
   Most content management systems operate around the concept of a
   <em>
    page
   </em>
   that usually has a title, some sort of short description of the page, and then the page content itself. Many give you nice WYSIWYG editing tools to make things like adding headers, lists, bold, and italics relatively easy.
  </p>
  <p>
   The problem comes when what you are wanting to represent on a
   <em>
    page
   </em>
   doesn&rsquo;t fit cleanly in this data model. Do you just shove it into the content field? Maybe your CMS has some clunky mechanism to relate additional content to a page or via some plugin system. Or maybe you&rsquo;re just out of luck, punt, and load some stuff with javascript from the template.
  </p>
  <p>
   With Wagtail you build your own models that inherit from it&rsquo;s Page model. This gives you the ability to customize specific fields for specific data and ultimately removes a lot of the usual shenanigans one goes through to fit your data concepts into your CMS&rsquo; vision of the world.
  </p>
  <p>
   This probably works best as an example. Let&rsquo;s build two different types of pages. A simple blog type page and a more complex Staff Member page one might use for individual staff members.
  </p>
  <p>
   Our simple page can look like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.wagtailcore.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Page</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.wagtailcore.fields</span><span class="w"> </span><span class="kn">import</span> <span class="n">RichTextField</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">wagtail.wagtailadmin.edit_handlers</span><span class="w"> </span><span class="kn">import</span> <span class="n">FieldPanel</span>

<span class="k">class</span><span class="w"> </span><span class="nc">BlogPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
    <span class="n">sub_title</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">published</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateField</span><span class="p">()</span>
    <span class="n">author</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">summary</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">RichTextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">body</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">RichTextField</span><span class="p">()</span>
    <span class="n">closing_content</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">RichTextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>

    <span class="n">content_panels</span> <span class="o">=</span> <span class="p">[</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">title</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">sub_title</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">published</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">author</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">summary</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">body</span><span class="err">&rsquo;</span><span class="p">),</span>
        <span class="n">FieldPanel</span><span class="p">(</span><span class="err">&lsquo;</span><span class="n">closing_content</span><span class="err">&rsquo;</span><span class="p">)</span>
    <span class="p">]</span>
</code></pre>
  </div>
  <p>
   Wagtail automatically sets up some fields for you, like title, the slug of the page, start/end visibility times, and SEO/meta related fields so you just need to define the fields you want beyond those.
  </p>
  <p>
   Here we&rsquo;ve defined some additional structured information we want on a blog post. A possible sub_title and summary information, an author, the date the entry was published, and the usual
   <em>
    body
   </em>
   field. We&rsquo;ve also added an additional closing_content field we might use for a ending call to action or other content that we want highlighted and shown below the post.
  </p>
  <p>
   All you need to do is add this to a Django app&rsquo;s models.py, run makemigrations and migrate and you&rsquo;re good to go.
  </p>
  <p>
   Now let&rsquo;s make a slightly more complicated Staff Page:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nx">DEPARTMENT_CHOICES</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">(</span>
<span class="w">    </span><span class="p">(</span><span class="err">&lsquo;</span><span class="nx">admin</span><span class="err">&rsquo;</span><span class="p">,</span><span class="w"> </span><span class="err">&lsquo;</span><span class="nx">Administration</span><span class="err">&rsquo;</span><span class="p">),</span>
<span class="w">    </span><span class="p">(</span><span class="err">&lsquo;</span><span class="nx">accounting</span><span class="err">&rsquo;</span><span class="p">,</span><span class="w"> </span><span class="err">&lsquo;</span><span class="nx">Accounting</span><span class="err">&rsquo;</span><span class="p">),</span>
<span class="w">    </span><span class="p">(</span><span class="err">&lsquo;</span><span class="nx">marketing</span><span class="err">&rsquo;</span><span class="p">,</span><span class="w"> </span><span class="err">&lsquo;</span><span class="nx">Marketing</span><span class="err">&rsquo;</span><span class="p">),</span>
<span class="w">    </span><span class="p">(</span><span class="err">&lsquo;</span><span class="nx">sales</span><span class="err">&rsquo;</span><span class="p">,</span><span class="w"> </span><span class="err">&lsquo;</span><span class="nx">Sales</span><span class="err">&rsquo;</span><span class="p">),</span>
<span class="w">    </span><span class="p">(</span><span class="err">&lsquo;</span><span class="nx">engineer</span><span class="err">&rsquo;</span><span class="p">,</span><span class="w"> </span><span class="err">&lsquo;</span><span class="nx">Engineering</span><span class="err">&rsquo;</span><span class="p">),</span>
<span class="p">)</span>

<span class="kd">class</span><span class="w"> </span><span class="nx">StaffPage</span><span class="p">(</span><span class="nx">Page</span><span class="p">):</span>
<span class="w">    </span><span class="nx">first_name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">CharField</span><span class="p">(</span><span class="nx">max_length</span><span class="p">=</span><span class="mi">50</span><span class="p">)</span>
<span class="w">    </span><span class="nx">last_name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">CharField</span><span class="p">(</span><span class="nx">max_length</span><span class="p">=</span><span class="mi">50</span><span class="p">)</span>
<span class="w">    </span><span class="nx">active</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">BooleanField</span><span class="p">(</span><span class="k">default</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>
<span class="w">    </span><span class="nx">start_date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">DateField</span><span class="p">()</span>
<span class="w">    </span><span class="nx">end_date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">DateField</span><span class="p">(</span><span class="nx">blank</span><span class="p">=</span><span class="nx">True</span><span class="p">,</span><span class="w"> </span><span class="nx">null</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>
<span class="w">    </span><span class="nx">department</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">CharField</span><span class="p">(</span><span class="nx">max_length</span><span class="p">=</span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="nx">choices</span><span class="p">=</span><span class="nx">DEPARTMENT_CHOICES</span><span class="p">)</span>
<span class="w">    </span><span class="nx">email</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">EmailField</span><span class="p">()</span>
<span class="w">    </span><span class="nx">twitter</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">CharField</span><span class="p">(</span><span class="nx">max_length</span><span class="p">=</span><span class="mi">50</span><span class="p">)</span>
<span class="w">    </span><span class="nx">short_bio</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">RichTextField</span><span class="p">(</span><span class="nx">blank</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>
<span class="w">    </span><span class="nx">bio</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">RichTextField</span><span class="p">(</span><span class="nx">blank</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>
<span class="w">    </span><span class="nx">education</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">RichTextField</span><span class="p">(</span><span class="nx">blank</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>
<span class="w">    </span><span class="nx">work_history</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">RichTextField</span><span class="p">(</span><span class="nx">blank</span><span class="p">=</span><span class="nx">True</span><span class="p">)</span>

<span class="w">    </span><span class="err">#</span><span class="w"> </span><span class="nx">Panel</span><span class="w"> </span><span class="nx">options</span><span class="w"> </span><span class="nx">left</span><span class="w"> </span><span class="nx">out</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">brevity</span>
</code></pre>
  </div>
  <p>
   As you can see the StaffPage model has quite a bit more fields, most of them optional, which allows the Staff member to update their information over time and not get strangled into putting &lsquo;Bio coming soon&rsquo; into an otherwise required field.
  </p>
  <p>
   Pretty simple right? You&rsquo;re probably thinking there is some catch, luckily you&rsquo;re wrong. It&rsquo;s really pretty much that simple. Easy things easy right?
  </p>
  <h2>
   Harder things in Wagtail
  </h2>
  <p>
   So what are the
   <em>
    hard
   </em>
   things in Wagtail? Well it&rsquo;s mostly just getting familiar with the system in general. A few things that may trip you up are:
  </p>
  <ul>
   <li>
    You can&rsquo;t have a field named
    <em>
     url
    </em>
    on your derived models as Wagtail uses that field name in the parent Page model. Unfortunately if you do add one, which I&rsquo;ve done more times than I care to admit, you get a not very useful error &ldquo;
    <em>
     can&rsquo;t set attribute
    </em>
    &rdquo; and not much else to go on.
   </li>
   <li>
    On many listing type pages it&rsquo;s fine to simply show all of the items, with pagination, in some sort of chronological order. Other times users want to be able to manually curate what shows up on given pages. Wagtail makes this relatively easy as you can define a ForeignKey relationship using a through type model to other pages and use a
    <a href="http://docs.wagtail.io/en/latest/pages/editing_api.html#pages-and-page-derived-models">
     PageChoosePanel
    </a>
    to give the user a nice interface for doing this. The user can also manually order them right in the admin, no additional work necessary.
   </li>
   <li>
    Limiting which pages can be created as children (aka beneath) a page can be handled by setting a list named
    <em>
     parent_page_types
    </em>
    on the child model. Then it can only be added below pages of those defined types. On complex sites with lots of different page types this helps keep the Page Choosing and Creation option choices to a manageable level for the users. And it also obviously helps to keep users from creating the wrong types of pages in the wrong parts of the site.
   </li>
   <li>
    Wagtail currently doesn&rsquo;t have a great story around building navigation menus for you, but there are a dozen reusable Django apps to help handle that. Often a site&rsquo;s menu remains relatively static and isn&rsquo;t edited day-to-day anyway.
   </li>
   <li>
    Supporting multiple sites with the same CMS. This isn&rsquo;t technically hard, but more conceptually difficult to grok. Wagtail supports having multiple sites, via it&rsquo;s wagtailsites app. The way this works is you simply set the Root page for each hostname and it basically takes it from there. However, in most circumstances it&rsquo;s probably easier and cleaner to just have two different instances of Wagtail and use different databases.
   </li>
  </ul>
  <h2>
   Images and Documents
  </h2>
  <p>
   Documents are files of any type you want to be able to upload into the system. This handles any sort of situations where a user needs to upload a PDF, Excel, or Word document and be able to link to it from any other bit of content.
  </p>
  <p>
   Images are exactly what you think, however you can define your own base model for this if you choose and attach additional fields for things like copyright, license, attribution, or even something like EXIF data if you wanted.
  </p>
  <p>
   With both Documents and Images having tagging support via
   <a href="https://django-taggit.readthedocs.org/en/latest/">
    django-taggit
   </a>
   and a really nicely designed look and UX for them in the admin interface.
  </p>
  <p>
   And yes, before you ask, it has built in support for flexible thumbnails in your templates andthe ability for you to mainually define the main focal point in the image to avoid cropping things weirdly.
  </p>
  <p>
   <img alt="" src="http://media.revsys.com/images/blog/wagtail/wagtail-image-ui.png"/>
  </p>
  <h2>
   Form Builder interface
  </h2>
  <p>
   Wagtail also has a nice
   <a href="http://docs.wagtail.io/en/latest/form_builder.html">
    form builder
   </a>
   built into it that can easily suffice for your typical contact form scenarios or more complicated collection needs.
  </p>
  <p>
   Much like Pages, you simply subclass from Wagtail and define what fields you want to collect. On your model you can also override the
   <em>
    process_form_submission
   </em>
   method to do more complex validation or in a more common case to email the interested parties that there is a new submission.
  </p>
  <p>
   One great feature of the form builder that is also built in, is the viewing and downloading interface. Viewing the data that has come in is great, but you just know your users are going to want to pull it out and use it for some other purpose. Wagtail smartly anticipates this and allows the user to download the submitted data, by date range, as a CSV file anytime they want.
  </p>
  <h2>
   Snippets
  </h2>
  <p>
   <a href="http://docs.wagtail.io/en/latest/snippets.html">
    Wagtail Snippets
   </a>
   are reusable bits of content that aren&rsquo;t full web pages. Often these are used for things like sidebar content, advertisements, or calls to action.
  </p>
  <p>
   Unlike with Pages or Forms, you don&rsquo;t subclass a model but instead define a model and simply register it as a snippet. You&rsquo;re then free to give your users the option of attaching snippets of the types you want to other pages. Or if you just want to give them the ability to edit things like footer content for example, you can just manually include the snippet data and the Snippet admin UI is really just becomes their editing interface.
  </p>
  <h2>
   Best Feature? Content Streams
  </h2>
  <p>
   While being able to define your own Page types with their own fields goes a long way, it&rsquo;s quite a stretch from truly free form content. New in version 1.0 is Wagtail&rsquo;s killer feature the
   <a href="http://docs.wagtail.io/en/latest/pages/streamfield.html">
    StreamField
   </a>
   .
  </p>
  <p>
   Users want free form content while developers, designers, and even ops want nicely structured data. StreamFields satisfies both camps.
  </p>
  <p>
   When you define a StreamField on a Page you set what types of
   <em>
    blocks
   </em>
   are available to be added into that stream. A block can be something as simple as a CharField of text or as complicated as a Staff type record like above using
   <a href="http://docs.wagtail.io/en/latest/pages/streamfield.html#structural-block-types">
    structural block types
   </a>
   .
  </p>
  <p>
   The end user can then add say few headers of various types, some rich text content blocks and have it all interspersed with a few images and code blocks. Each of these block types you define can then be styled differently CSS and/or have their markup structured entirely differently if needed.
  </p>
  <p>
   Prior to this feature being added to 1.0, I had to resort to complicated Page relationships that weren&rsquo;t actually pages we intended to make visible on the site. We just subverted the Page Choosing features of Wagtail to give the users the flexibility they needed and keep it all in the same admin interface.
  </p>
  <p>
   Here is what the admin UI looks like for StreamFields. Here we've defined a field named Body that has header, content, and code block types. Each of these
   <em>
    lines
   </em>
   are different blocks. The top and bottom being headers. As you can see you can simply click the plus icons to add new blocks in between others or use the arrows on the right to move block around. They are currently a bit hard to see due to a CSS bug I anticipate being fixed quickly.
  </p>
  <p>
   <img alt="" src="http://media.revsys.com/images/blog/wagtail/wagtail-admin-stream.png"/>
  </p>
  <h2>
   Wagtail&rsquo;s Future
  </h2>
  <p>
   I think Wagtail has a
   <strong>
    VERY
   </strong>
   bright future in general and especially inside the Django community. However, like any 1.0 product there are definitely some things I would like to see in future versions. The two main things I hope to see are:
  </p>
  <ul>
   <li>
    A community collection of high quality and flexible common Page and Block types to make most sites more of a Lego exercise than a coding one.
   </li>
   <li>
    The ability to more easily customize and control the Publish/Moderate/Save as Draft options that appear at the bottom of the screen while editing content. On many smaller sites or those with a flat workflow it should be trivial to make &lsquo;Publish&rsquo; or &lsquo;Submit for Moderation&rsquo; be the default action presented to the user.
   </li>
  </ul>
 </div>
</div>
]]>/></item><item><title>REVSYS Roundup - March 2015</title><link>http://www.revsys.com/tidbits/revsys-roundup-march-2015/</link><description>March 2015 update on happenings around Revolution Systems including our new team member Stephen Spencer</description><pubDate>Thu, 26 Mar 2015 15:48:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/revsys-roundup-march-2015/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Revolution Systems has been having a great time recently and wanted to highlight a few things that have been happening in our world.
  </p>
  <h3>
   <a href="https://www.tedxlawrence.com">
    TEDxLawrence 2015
   </a>
  </h3>
  <p>
   We were happy to have partnered with TEDxLawrence to put on the first ever TEDx event in Lawrence. Frank helped organize and MC the event as a member of the steering committee. We also built the website using our new favorite Django based CMS
   <a href="https://wagtail.io/">
    Wagtail
   </a>
   . The videos of the awesome collection of speakers we had will be up in a few weeks on
   <a href="https://www.tedx.com">
    TED.com
   </a>
   we'll tweet about that when it happens.
  </p>
  <h3>
   <a href="https://www.kansaslinuxfest.us/">
    Kansas Linux Fest
   </a>
  </h3>
  <p>
   Frank also had the pleasure of giving the ending keynote on the first day of the conference. Frank talked on his experience working in devops environments, tips and tricks and advice in his talk
   <a href="/talks/2015/smells-like-teen-systems-devops-nirvana/">
    Smells like Teen Systems: Advice for raising healthy happy systems and getting to DevOps Nirvana
   </a>
   . Frank tried to make it entertaining so prepare yourself for lots of cheesy 90s music references.
  </p>
  <p>
   It's great to have local Open Source event just a block down the street from our offices. We hope this event will grow much larger in the future and based on the quality of the program put on we have no doubt that it will.
  </p>
  <h3>
   <a href="https://www.pycon.us">
    PyCon 2015
   </a>
  </h3>
  <p>
   We're of course sponsoring PyCon again this year and nearly all of the REVSYS team will be there. Be sure to swing by our booth in the expo hall to chat. Frank will be manning the booth most of the time and he will run out of new Reddit posts to keep him occupied if you don't.
  </p>
  <h3>
   <a href="https://www.devspectrum.com">
    Spectrum
   </a>
  </h3>
  <p>
   We've been hard at work on our
   <em>
    hopefully soon to be released
   </em>
   desktop developer tool
   <a href="https://www.devspectrum.com">
    Spectrum
   </a>
   . We had hoped to get a beta released to coincide with PyCon, but unfortunately we've been too busy helping our clients to make that happen.
  </p>
  <p>
   Spectrum is a local desktop app that gives you powerful filtering for your applications logs. In the vein of hosted logging services such as Loggly or Splunk, or self-hosted solutions like Kibana, but specifically targeted at the needs of a developer working locally with features like:
  </p>
  <ul>
   <li>
    Inject logs via a local syslog daemon, REST API, or simply tailing local files to support as many local development scenarios as possible
   </li>
   <li>
    Send a firehose of logs to Spectrum and instantly show or hide log entries by things like facility, priority, or simple string matching. No longer will you have to adjust the verbosity of your logs and then recreate a scenario or error just to see the logs you need
   </li>
   <li>
    Separate project level settings to avoid having to change your setup as you move between projects
   </li>
  </ul>
  <p>
   We started building Spectrum because relaying a firehose of logs to any of the hosted or self-hosted services we tried simply had too much lag time between the generation of the logs and when they were visible to the developer to make it truly productive. We're building Spectrum using
   <a href="https://github.com/atom/atom-shell">
    Atom Shell
   </a>
   and
   <a href="https://facebook.github.io/react/">
    ReactJS
   </a>
   so it will be available for Windows, OSX, and Linux developers and stupid stupid fast.
  </p>
  <p>
   Follow
   <a href="https://twitter.com/devspectrum">
    @devspectrum
   </a>
   on Twitter or signup for the
   <a href="https://eepurl.com/bc4YKH">
    Spectrum mailing list
   </a>
   for announcements and early beta discounts!
  </p>
  <h3>
   And our biggest news so far in 2015,
   <a href="/about/bio/stephen-spencer">
    we added another team member!
   </a>
  </h3>
  <p>
   In January we were pleased to add our friend
   <a href="/about/bio/stephen-spencer">
    Stephen Spencer
   </a>
   to the RevSys team. While most of the team are 80% dev and 20% ops in experience, Stephen is exactly the opposite which helps not only round out our skill sets in general but also drastically increases our capacity to take on more
   <a href="https://www.revsys.com/services/operations/">
    operations and devops
   </a>
   projects without overloading Frank's already busy schedule.
  </p>
  <p>
   Stephen has a ton of experience with tools we like such as
   <a href="https://saltstack.com/">
    SaltStack
   </a>
   and in fact is the guy who got Frank his first *NIX job and one of his original Unix/Linux mentors. Give a shoutout to
   <a href="https://twitter.com/gladiatr">
    Stephen on Twitter
   </a>
   to welcome him to our team!
  </p>
 </div>
</div>
]]>/></item><item><title>Loading Django FileField and ImageFields from the file system</title><link>http://www.revsys.com/tidbits/loading-django-files-from-code/</link><description>How to load files into Django models, not via a file upload, but from a script or Python shell.</description><pubDate>Wed, 03 Dec 2014 21:44:12 +0000</pubDate><guid>http://www.revsys.com/tidbits/loading-django-files-from-code/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I don&rsquo;t know about you, but I run into a situation every so often where I need to programmatically push a file from the local file system or remotely via a URL into a Django model. Using a Python shell, a one off script, or a Django management command.
  </p>
  <p>
   This should be easy, but with a typical web app you do it infrequently. Mostly I find we do it when we&rsquo;re converting someone&rsquo;s site from something else to Django and need to associate an image with another Content item for example.
  </p>
  <p>
   If you start Googling around for answers to how to do this you get the default docs for
   <a href="https://docs.djangoproject.com/en/dev/topics/http/file-uploads/">
    File Uploads
   </a>
   ,
   <a href="https://docs.djangoproject.com/en/dev/ref/files/file/">
    File objects
   </a>
   , and
   <a href="https://docs.djangoproject.com/en/dev/topics/files/">
    Managing Files
   </a>
   which are all great. However, they don&rsquo;t
   <em>
    exactly
   </em>
   spell out how you&rsquo;re supposed to do this.
  </p>
  <p>
   My first inclination is to just assign the model field to a file, but this is missing the relative file path information Django needs.
  </p>
  <p>
   I took the time to write this post mostly for my own benefit so I don&rsquo;t have to figure this out yet again in the future, but hopefully you&rsquo;ll get some use out of it as well.
  </p>
  <h2>
   Steps for loading Django ImageField with a local file
  </h2>
  <p>
   Here is a quick example showing you the moving pieces. The steps you need to perform are:
  </p>
  <ol>
   <li>
    Retrieve the file (if remote) and store it locally
   </li>
   <li>
    Open that file as a normal Python file object
   </li>
   <li>
    Covert that open file to a Django
    <a href="https://docs.djangoproject.com/en/dev/ref/files/file/">
     File
    </a>
   </li>
   <li>
    Attach it to your model field
   </li>
  </ol>
  <h2>
   Example Model
  </h2>
  <p>
   Suppose we have a model like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">django.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">models</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Company</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">logo</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ImageField</span><span class="p">()</span>
</code></pre>
  </div>
  <p>
   Let&rsquo;s create an entry for RevSys, pulling the logo in with requests:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">requests</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">django.core.files</span><span class="w"> </span><span class="kn">import</span> <span class="n">File</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Company</span>

<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="err">&ldquo;</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">media</span><span class="o">.</span><span class="n">revsys</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">img</span><span class="o">/</span><span class="n">revsys</span><span class="o">-</span><span class="n">logo</span><span class="o">.</span><span class="n">png</span><span class="err">&rdquo;</span><span class="p">)</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="err">&ldquo;</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">revsys</span><span class="o">-</span><span class="n">logo</span><span class="o">.</span><span class="n">png</span><span class="err">&rdquo;</span><span class="p">,</span> <span class="err">&ldquo;</span><span class="n">wb</span><span class="err">&rdquo;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>

<span class="n">reopen</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="err">&ldquo;</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">revsys</span><span class="o">-</span><span class="n">logo</span><span class="o">.</span><span class="n">png</span><span class="err">&rdquo;</span><span class="p">,</span> <span class="err">&ldquo;</span><span class="n">rb</span><span class="err">&rdquo;</span><span class="p">)</span>
<span class="n">django_file</span> <span class="o">=</span> <span class="n">File</span><span class="p">(</span><span class="n">reopen</span><span class="p">)</span>

<span class="n">revsys</span> <span class="o">=</span> <span class="n">Company</span><span class="p">()</span>
<span class="n">revsys</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="err">&ldquo;</span><span class="n">Revolution</span> <span class="n">Systems</span><span class="err">&rdquo;</span>
<span class="n">revsys</span><span class="o">.</span><span class="n">logo</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="err">&ldquo;</span><span class="n">revsys</span><span class="o">-</span><span class="n">logo</span><span class="o">.</span><span class="n">png</span><span class="err">&rdquo;</span><span class="p">,</span> <span class="n">django_file</span><span class="p">,</span> <span class="n">save</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   The last line here is the important bit.
   <code>
    save
   </code>
   is given three arguments here:
  </p>
  <ol>
   <li>
    The relative path and filename for inside MEDIA_ROOT
   </li>
   <li>
    The open file using Django&rsquo;s File object
   </li>
   <li>
    Whether or not we want to save the revsys
    <code>
     Company
    </code>
    instance after the image is saved.
   </li>
  </ol>
  <p>
   Now if you&rsquo;re doing this for a bunch of images, you&rsquo;ll want to parse your URLs or filenames to build the paths for you. Something like this would be a good starting point, note this is using Python 3:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">urllib.parse</span><span class="w"> </span><span class="kn">import</span> <span class="n">urlparse</span>
<span class="c1"># from urlparse import urlparse for Python 2.7</span>

<span class="n">url</span> <span class="o">=</span> <span class="err">&ldquo;</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">media</span><span class="o">.</span><span class="n">revsys</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">img</span><span class="o">/</span><span class="n">revsys</span><span class="o">-</span><span class="n">logo</span><span class="o">.</span><span class="n">png</span><span class="err">&rdquo;</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">urlparse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">path</span><span class="p">)</span>
<span class="c1"># This returns &lsquo;revsys-logo.png&rsquo; from the URL</span>
<span class="err">&hellip;</span>

<span class="n">revsys</span><span class="o">.</span><span class="n">logo</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">django_file</span><span class="p">,</span> <span class="n">save</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   Hope this helps!
  </p>
 </div>
</div>
]]>/></item><item><title>Recommended Django Project Layout</title><link>http://www.revsys.com/tidbits/recommended-django-project-layout/</link><description>Our recommendation for the optimal Django project layout for your files, settings, and templates.</description><pubDate>Fri, 21 Nov 2014 22:11:53 +0000</pubDate><guid>http://www.revsys.com/tidbits/recommended-django-project-layout/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   What&rsquo;s the optimal layout for your Django applications, settings files, and various other associated directories?
  </h2>
  <p>
   When Django 1.4 was released it included an
   <a href="https://docs.djangoproject.com/en/dev/releases/1.4/#updated-default-project-layout-and-manage-py">
    updated project layout
   </a>
   which went a long way to improving the default Django project&rsquo;s layout, but here are some tips for making it even better.
  </p>
  <p>
   This is a question we get asked all of the time so I wanted to take a bit of time and write down exactly how we feel about this subject so we can easily refer clients to this document. Note that this was written using Django version 1.7.1, but can be applied to any Django version after 1.4 easily.
  </p>
  <h2>
   Why this layout is better
  </h2>
  <p>
   The project layout we&rsquo;re recommending here has several advantages namely:
  </p>
  <ul>
   <li>
    Allows you to pick up, repackage, and reuse individual Django applications for use in other projects. Often it isn&rsquo;t clear as you are building an app whether or not it is even a candidate for reuse. Building it this way from the start makes it much easier if that time comes.
   </li>
   <li>
    Encourages designing applications for reuse
   </li>
   <li>
    Environment specific settings. No more
    <code>
     if DEBUG==True
    </code>
    nonsense in a single monolithic settings file. This allows to easily see which settings are shared and what is overridden on a per environment basis.
   </li>
   <li>
    Environment specific PIP requirements
   </li>
   <li>
    Project level templates and static files that can, if necessary, override app level defaults.
   </li>
   <li>
    Small more specific test files which are easier to read and understand.
   </li>
  </ul>
  <p>
   Assuming you have two apps
   <em>
    blog
   </em>
   and
   <em>
    users
   </em>
   and 2 environments dev and prod your project layout should be structured like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>myproject/
    manage.py
    myproject/
        __init__.py
        urls.py
        wsgi.py
        settings/
            __init__.py
            base.py
            dev.py
            prod.py
    blog/
        __init__.py
        models.py
        managers.py
        views.py
        urls.py
        templates/
            blog/
                base.html
                list.html
                detail.html
        static/
           &hellip;
        tests/
            __init__.py
            test_models.py
            test_managers.py
            test_views.py
    users/
        __init__.py
        models.py
        views.py
        urls.py
        templates/
            users/
                base.html
                list.html
                detail.html
        static/
            &hellip;
        tests/
            __init__.py
            test_models.py
            test_views.py
     static/
         css/
             &hellip;
         js/
             &hellip;
     templates/
         base.html
         index.html
     requirements/
         base.txt
         dev.txt
         test.txt
         prod.txt
</code></pre>
  </div>
  <p>
   The rest of this article explains how to move a project to this layout and why this layout is better.
  </p>
  <h2>
   Current Default Layout
  </h2>
  <p>
   We&rsquo;re going to call our example project
   <strong>
    foo
   </strong>
   , yes I realize it&rsquo;s a very creative name. We&rsquo;re assuming here that we&rsquo;re going to be launching
   <strong>
    foo.com
   </strong>
   but while we like to have our project names reflect the ultimate domain(s) the project will live on this isn&rsquo;t by any means required.
  </p>
  <p>
   If you kick off your project using
   <code>
    django-admin.py startproject foo
   </code>
   you get a directory structure like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>    foo/
        manage.py
        foo/
           __init__.py
           settings.py
           urls.py
           wsgi.py
</code></pre>
  </div>
  <p>
   This layout is a great starting place, we have a top level directory
   <em>
    foo
   </em>
   which contains our manage.py the
   <em>
    project
   </em>
   directory
   <em>
    foo/foo/
   </em>
   inside it. This is the directory you would check into your source control system such as git.
  </p>
  <p>
   You should think of this
   <em>
    foo/foo/
   </em>
   subdirectory as being
   <strong>
    the project
   </strong>
   where everything else is either a Django application or ancillary files related to the project.
  </p>
  <h2>
   Fixing Settings
  </h2>
  <p>
   We&rsquo;re on a mission to fix your bad settings files here. We show this layout to new clients and I&rsquo;m constantly surprised how few people know this is even possible to do. I blame the fact that while everyone knows that settings are just Python code, they don&rsquo;t think about them
   <em>
    as
   </em>
   Python code.
  </p>
  <p>
   So let&rsquo;s fix up our settings. For our foo project we&rsquo;re going to have 4 environments: dev, stage, jenkins, and production. So let&rsquo;s give each it&rsquo;s own file. The process to do this is:
  </p>
  <ol>
   <li>
    In
    <em>
     foo/foo/
    </em>
    make a settings directory and create an empty
    <code>
     __init__.py
    </code>
    file inside it.
   </li>
   <li>
    Move
    <em>
     foo/foo/settings.py
    </em>
    into
    <em>
     foo/foo/settings/base.py
    </em>
   </li>
   <li>
    Create the individual
    <em>
     dev.py
    </em>
    ,
    <em>
     stage.py
    </em>
    ,
    <em>
     jenkins.py
    </em>
    , and
    <em>
     prod.py
    </em>
    files in
    <em>
     foo/foo/settings/
    </em>
    . Each of these 4 environment specific files should simply contain the following:
    <div class="codehilite">
     <pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">base</span><span class="w"> </span><span class="kn">import</span> <span class="o">*</span>
</code></pre>
    </div>
   </li>
  </ol>
  <p>
   So why is this important? Well for local development you want
   <code>
    DEBUG=True
   </code>
   , but it&rsquo;s pretty easy to accidentally push out production code with it on, so just open up
   <em>
    foo/foo/settings/prod.py
   </em>
   and after the initial import from base just add
   <code>
    DEBUG=False
   </code>
   . Now if your production site is safe from that silly mistake.
  </p>
  <p>
   What else can you customize? Well it should be pretty obvious you&rsquo;ll likely have staging, jenkins, and production all pointing at different databases, likely even on different hosts. So adjust those settings in each environment file.
  </p>
  <h2>
   Using these settings
  </h2>
  <p>
   Using these settings is easy, no matter which method you typically use. To use the OS&rsquo;s environment you just do:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="n">DJANGO_SETTINGS_MODULE</span><span class="o">=</span><span class="err">&ldquo;</span><span class="n">foo</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">jenkins</span><span class="err">&rdquo;</span>
</code></pre>
  </div>
  <p>
   And boom, you&rsquo;re now using the jenkins configuration.
  </p>
  <p>
   Or maybe you prefer to pass them in as a commandline option like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>./manage.py migrate &mdash;settings=foo.settings.production
</code></pre>
  </div>
  <p>
   Same if you&rsquo;re using gunicorn:
  </p>
  <div class="codehilite">
   <pre><span></span><code>gunicorn -w 4 -b 127.0.0.1:8001 &mdash;settings=foo.settings.dev
</code></pre>
  </div>
  <h2>
   What else should be customized about settings?
  </h2>
  <p>
   Another useful tip with Django settings is to change several of the default settings
   <em>
    collections
   </em>
   from being tuples to being lists. For example
   <code>
    INSTALLED_APPS
   </code>
   , by changing it from:
  </p>
  <div class="codehilite">
   <pre><span></span><code>INSTALLED_APPS = (
   &hellip;
)
</code></pre>
  </div>
  <p>
   to:
  </p>
  <div class="codehilite">
   <pre><span></span><code>INSTALLED_APPS = [
    &hellip;
]
</code></pre>
  </div>
  <p>
   In
   <em>
    foo/settings/base.py
   </em>
   we can now more easily add and remove apps based on each environment specific settings file. For example, maybe you only want django-debug-toolbar installed in dev, but not your other environments.
  </p>
  <p>
   This trick is also often useful for the
   <code>
    TEMPLATE_DIRS
   </code>
   and
   <code>
    MIDDLEWARE_CLASSES
   </code>
   settings.
  </p>
  <p>
   Another useful trick we often use is to break up your apps into two lists, one your prerequisites and another for your actual project applications. So like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">PREREQ_APPS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">django</span><span class="o">.</span><span class="n">contrib</span><span class="o">.</span><span class="n">auth</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">django</span><span class="o">.</span><span class="n">contrib</span><span class="o">.</span><span class="n">contenttypes</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&hellip;</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">debug_toolbar</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">imagekit</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">haystack</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="p">]</span>

<span class="n">PROJECT_APPS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">homepage</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">users</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="w">   </span><span class="err">&lsquo;</span><span class="n">blog</span><span class="err">&rsquo;</span><span class="p">,</span>
<span class="p">]</span>

<span class="n">INSTALLED_APPS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PREREQ_APPS</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">PROJECT_APPS</span>
</code></pre>
  </div>
  <p>
   Why is this useful? For one it helps better distinguish between Django core apps, third party apps, and your own internal project specific applications. However,
   <code>
    PROJECT_APPS
   </code>
   often comes in handy as a list of your specific apps for things like testing and code coverage. You have a list of
   <em>
    your
   </em>
   apps, so you can easily and automagically make sure their tests are run and coverage is recorded just for them, not including any third party apps, without having to maintain the list in two separate places.
  </p>
  <h2>
   Fixing requirements
  </h2>
  <p>
   Most projects have a single
   <code>
    requirements.txt
   </code>
   file that is installed like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>pip install -r requirements.txt
</code></pre>
  </div>
  <p>
   This is sufficient for small simple projects, but a little known feature of requirements files is that you can use the
   <code>
    -r
   </code>
   flag to include other files. So we can have a base.txt of all the common requirements and then if we need to be able to run tests have a specific
   <em>
    requirements/test.txt
   </em>
   that looks like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>-r base.txt
pytest==2.5.2
coverage==3.7.1
</code></pre>
  </div>
  <p>
   I&rsquo;ll admit this is not a
   <em>
    HUGE
   </em>
   benefit, but it does help separate out what is a requirement in which environment. And for the truly performance conscience it reduces your pip install time in production a touch by not installing a bunch of things that won&rsquo;t actually be used in production.
  </p>
  <h2>
   Test Files
  </h2>
  <p>
   Why did we separate out the tests files so much? One main reason, if you&rsquo;re writing enough tests a single tests.py file per application will end up being one huge honking file. This is bad for readability, but also just for the simple fact you have to spend time scrolling around a lot in your editor.
  </p>
  <p>
   You&rsquo;ll also end up with less merge conflicts when working with other developers which is a nice side benefit. Small files are your friends.
  </p>
  <h2>
   URLs
  </h2>
  <p>
   For small projects it&rsquo;s tempting to put all of your url definitions in
   <em>
    foo/urls.py
   </em>
   to keep them all in one place. However, if your goal is clarity and reusability you want to define your urls in each app and include them into your main project. So instead of:
  </p>
  <div class="codehilite">
   <pre><span></span><code>urlpatterns = patterns(&lsquo;&rsquo;,
    url(r&rsquo;^$&rsquo;, HomePageView.as_view(), name=&lsquo;home&rsquo;),
    url(r&rsquo;^blog/$&rsquo;, BlogList.as_view(), name=&lsquo;blog_list&rsquo;),
    url(r&rsquo;^blog/(?P&lt;pk&gt;\d+)/$&rsquo;, BlogDetail.as_view(), name=&lsquo;blog_detail&rsquo;),
    &hellip;
    url(r&rsquo;^user/list/$&rsquo;, UserList.as_view(), name=&lsquo;user_list&rsquo;),
    url(r&rsquo;^user/(?P&lt;username&gt;\w+)/$&rsquo;, UserDetail.as_view(), name=&lsquo;user_detail&rsquo;),
)
</code></pre>
  </div>
  <p>
   you should do this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>urlpatterns = patterns(&lsquo;&rsquo;,
    url(r&rsquo;^$&rsquo;, HomePageView.as_view(), name=&lsquo;home&rsquo;),
    url(r&rsquo;^blog/&lsquo;, include(&lsquo;blog.urls&rsquo;)),
    url(r&rsquo;^user/&lsquo;, include(&lsquo;user.urls&rsquo;)),
)
</code></pre>
  </div>
  <h2>
   Templates and static media
  </h2>
  <p>
   Having per app
   <em>
    templates/
   </em>
   and
   <em>
    static/
   </em>
   directories gives us the ability to reuse an application basically
   <strong>
    as is
   </strong>
   in another project.
  </p>
  <p>
   We get the default templates the app provides and any associated static media like special Javascript for that one cool feature all in one package.
  </p>
  <p>
   However, it
   <em>
    also
   </em>
   gives us the ability to override those templates on a per project basis in the main
   <em>
    foo/templates/
   </em>
   directory. By adding a
   <code>
    templates/blog/detail.html
   </code>
   template we override, or mask, the default
   <code>
    blog/templates/blog/detail.html
   </code>
   template.
  </p>
  <h2>
   Reusing a Django application
  </h2>
  <p>
   So assuming you&rsquo;ve been using this layout for awhile, one day you&rsquo;ll realize that your new project needs a blog and the one from your
   <em>
    foo
   </em>
   project would be perfect for it. So you copy and paste the files in&hellip;
   <em>
    pssst WRONG!
   </em>
   . Now you have two copies of the application out there. Bug fixes or new features in one have to manually be moved between the projects, and that assumes you even remember to do that.
  </p>
  <p>
   Instead, make a new repo for your blog and put the
   <em>
    foo/blog/
   </em>
   directory in it. And adjust both your existing foo project and your new project to pip install it.
  </p>
  <p>
   They can still both track different versions of the app, if necessary, or keep up to date and get all of your bug fixes and new features as they develop. You still can override the templates and static media as you need to on a per project basis, so there really isn&rsquo;t any real issues doing this.
  </p>
  <h2>
   Additional Resources
  </h2>
  <p>
   Our friends Danny and Audrey over at
   <a href="http://www.cartwheelweb.com">
    CartWheel Web
   </a>
   reminded us about
   <a href="https://github.com/audreyr/cookiecutter">
    Cookie Cutter
   </a>
   and specifically Danny's
   <a href="https://github.com/pydanny/cookiecutter-django">
    cookiecutter-django
   </a>
   as useful tools for making your initial project creations easier and repeatable.
  </p>
  <p>
   Also, if you're looking for all around great Django tips and best practices, you can't go wrong with their book
   <a href="http://www.amazon.com/gp/product/098146730X/ref=as_li_qf_sp_asin_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=098146730X&amp;linkCode=as2&amp;tag=revosystblog-20">
    Two Scoops of Django: Best Practices For Django 1.6
   </a>
   <img alt="" src="http://ir-na.amazon-adsystem.com/e/ir?t=revosystblog-20&amp;l=as2&amp;o=1&amp;a=098146730X"/>
   which we recommend to all of our clients.
  </p>
  <h2>
   Feedback
  </h2>
  <p>
   We hope you find this improved project layout useful. If you find any bugs, have a suggestion, or just want to chat feel free to reach out to us. Thanks for reading!
  </p>
 </div>
</div>
]]>/></item><item><title>Upgrade salt-master and minions on Ubuntu servers</title><link>http://www.revsys.com/tidbits/upgrade-salt-master-and-minions-ubuntu-servers/</link><description>Quickly, safely and easily upgrade SaltStack entirely from your Salt master</description><pubDate>Sun, 16 Nov 2014 17:58:49 +0000</pubDate><guid>http://www.revsys.com/tidbits/upgrade-salt-master-and-minions-ubuntu-servers/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Step by step guide to safely upgrading your SaltStack master and minions
  </h2>
  <p>
   We&rsquo;ve noticed a bit of confusion and understandable trepidation around upgrading SaltStack with some of our customers. Salt is pretty darn important to your stack, so keeping it working is essential. To help, we&rsquo;ve written up this HOWTO guide for easily and safely upgrading your master and minions.
  </p>
  <p>
   We mostly work with Ubuntu and Debian based systems, so while the steps to upgrade are the same on other Linux systems the exact commands will differ.
  </p>
  <p>
   This guide also assumes you&rsquo;ve got the
   <a href="http://ppa.launchpad.net/saltstack/">
    Salt Stack PPA
   </a>
   configured in your apt sources.
  </p>
  <h3>
   Step 1 - Update your apt repositories
  </h3>
  <p>
   First you need to make sure your apt repositories are up to date, so you get the latest stable versions. Easiest way to do this is via salt itself:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo salt &lsquo;*&rsquo; cmd.run &ldquo;aptitude update&rdquo;
</code></pre>
  </div>
  <h2>
   Step 2 - Upgrade your master
  </h2>
  <p>
   Upgrading the master first ensures you don&rsquo;t run into any version compatibility issues between your master and minions. So ssh into your master and run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo apt-get upgrade salt-master
</code></pre>
  </div>
  <h3>
   Step 3 - Upgrade your minions
  </h3>
  <p>
   Before we attempt to upgrade, let&rsquo;s take a quick look at the existing versions we have running. This might surprise you, I definitely found a couple of cloud instances that were running older version of salt-minion that I somehow had not upgraded in the past. So get a list of what version of salt your minions are running issue this salt command:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo salt &lsquo;*&rsquo; test.version
</code></pre>
  </div>
  <p>
   And you&rsquo;ll get a nice display of every version currently in use. Another useful option here is the command 'manage.versions' which shows you a list of up to date minions vs those that need updating. Here is how you run it:
  </p>
  <div class="codehilite">
   <pre><span></span><code>salt-run manage.versions
</code></pre>
  </div>
  <p>
   Now that we know our base line of versions, let&rsquo;s upgrade them all:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo salt '*' pkg.install salt-minion refresh=True
</code></pre>
  </div>
  <p>
   This is the
   <em>
    correct
   </em>
   way to do it with Salt, but if you run into any trouble you can always issue the usual Ubuntu commands directly like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo salt &lsquo;*&rsquo; cmd.run &ldquo;apt-get -y install salt-minion&rdquo;
</code></pre>
  </div>
  <h3>
   Step 4 - Verify everything worked
  </h3>
  <p>
   Everything should be upgraded now and running the latest version of salt-minion. You can verify this by running the test.version command again:
  </p>
  <div class="codehilite">
   <pre><span></span><code>sudo salt &lsquo;*&rsquo; test.version
</code></pre>
  </div>
  <p>
   If you see some minions aren&rsquo;t using the latest version you may need to manually intervene to see what is stopping apt from upgrading things for you.
  </p>
  <p>
   Hope this help make keeping your SaltStack ecosystem in sync and upgraded to the latest stable versions!
  </p>
 </div>
</div>
]]>/></item><item><title>Ultimate Front End Development Setup</title><link>http://www.revsys.com/tidbits/ultimate-front-end-development-setup/</link><description>A quick howto guide on setting up modern front-end development tools to improve your day to day work flow using Gulp and Livereload</description><pubDate>Tue, 21 Oct 2014 15:09:39 +0000</pubDate><guid>http://www.revsys.com/tidbits/ultimate-front-end-development-setup/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   A quick howto guide on setting up modern front-end development tools to improve your day to day work flow.
  </p>
  <p>
   You'll learn how to:
  </p>
  <ul>
   <li>
    Setup
    <a href="http://gulpjs.com">
     gulp
    </a>
    to orchestrate everything
   </li>
   <li>
    Install
    <a href="http://gulpjs.com/plugins/">
     gulp plugins
    </a>
    to combine, minify and otherwise transform your files
   </li>
   <li>
    Use
    <a href="http://bower.io">
     bower
    </a>
    a package manager for "web things". It helps you install, track and manage CSS frameworks, JS libraries and other tools
   </li>
   <li>
    Add LiveReload so your browser window(s) auto-refresh when you make changes to anything
   </li>
   <li>
    Integrate all of this with a Django project with django-compressor and django-bower
   </li>
  </ul>
  <p>
   I've always struggled working on frontend tasks. I know CSS well enough, but it can be a pain to get it just right. I'm more comfortable with Javascript. However, I tend to end up with spaghetti code all over the place and a dozen randomly included files and things quickly become insane.
  </p>
  <p>
   So I've spent some time over the last several weeks trying to up my frontend development game. As it turns out, many of the pain points I experienced are now solved really well.
  </p>
  <h2>
   What is gulp?
  </h2>
  <p>
   Gulp is a node module that runs and executes various tasks based on rules you've setup. It has its own plugin ecosystem for doing common tasks. To get started today we're going to be using:
  </p>
  <ul>
   <li>
    <a href="https://www.npmjs.org/package/gulp-watch">
     gulp-watch
    </a>
    to spy on various files and directory trees an execute the tasks we define when files changes
   </li>
   <li>
    <a href="https://www.npmjs.org/package/gulp-sass">
     gulp-sass
    </a>
    to compile our
    <a href="http://sass-lang.com/">
     SASS
    </a>
    files into the CSS we will serve to the browser
   </li>
   <li>
    <a href="https://www.npmjs.org/package/gulp-minify-css">
     gulp-minify-css
    </a>
    to minify the CSS we build from SASS
   </li>
   <li>
    <a href="https://www.npmjs.org/package/gulp-rename">
     gulp-rename
    </a>
    to handle renaming files as we process them
   </li>
   <li>
    <a href="https://www.npmjs.org/package/gulp-gzip">
     gulp-gzip
    </a>
    to store gzip compressed versions of files
   </li>
   <li>
    <a href="https://www.npmjs.org/package/gulp-livereload">
     gulp-livereload
    </a>
    to make all our browser windows update as we make changes
   </li>
  </ul>
  <h2>
   What does bower do for us?
  </h2>
  <p>
   Bower is a package manager for web things. With a couple of easy commands you can install things like jQuery, Zurb Foundation or Bootstrap into your project. It also helps track and install the dependencies. It's CPAN, PyPI, gems or npm for "the web" rather than a single language.
  </p>
  <h2>
   Initial Setup
  </h2>
  <p>
   We need to install a few things before we can continue.
  </p>
  <p>
   First, let's install gulp and bower system wide as once you start using these tools you're going to want to use them in all your web projects:
  </p>
  <div class="codehilite">
   <pre><span></span><code>npm install --global gulp bower
</code></pre>
  </div>
  <p>
   Now move into your project directory, this is so npm and bower will install everything else to your project and not system wide. After you're there, install the gulp plugins we will be using:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">npm</span><span class="w"> </span><span class="n">install</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">watch</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">sass</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">minify</span><span class="o">-</span><span class="n">css</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">rename</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">gzip</span><span class="w"> </span><span class="n">gulp</span><span class="o">-</span><span class="n">livereload</span>
</code></pre>
  </div>
  <h2>
   Setup Zurb Foundation
  </h2>
  <p>
   Before we get too far into setting up Gulp we need something for it to do. So let's install
   <a href="http://foundation.zurb.com/">
    Zurb Foundation 5
   </a>
   and set things up to customize it with SASS:
  </p>
  <div class="codehilite">
   <pre><span></span><code>bower install foundation
</code></pre>
  </div>
  <p>
   Now let's create a directory named
   <code>
    scss
   </code>
   and create two files inside of it. One named
   <code>
    _main_settings.scss
   </code>
   is going to be where we override any of Foundation's default settings. For example purposes we just want to change something here, so let's go ahead and make Foundation's grid rows be 100% wide. To do that put this inside the file:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">$</span><span class="nt">row-width</span><span class="o">:</span><span class="w"> </span><span class="nt">100</span><span class="o">%;</span>
</code></pre>
  </div>
  <p>
   And we also need a "main" SASS file to tie everything together so inside
   <code>
    scss/main.scss
   </code>
   place the following SASS code:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="err">@</span><span class="n">charset</span><span class="w"> </span><span class="s1">'UTF-8'</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"main_settings"</span><span class="p">;</span>

<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/grid"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/accordion"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/alert-boxes"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/block-grid"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/breadcrumbs"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/button-groups"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/buttons"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/clearing"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/dropdown"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/dropdown-buttons"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/flex-video"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/forms"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/icon-bar"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/inline-lists"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/joyride"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/keystrokes"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/labels"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/magellan"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/orbit"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/pagination"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/panels"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/pricing-tables"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/progress-bars"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/range-slider"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/reveal"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/side-nav"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/split-buttons"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/sub-nav"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/switches"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/tables"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/tabs"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/thumbs"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/tooltips"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/top-bar"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/type"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/offcanvas"</span><span class="p">;</span>
<span class="cp">@import</span><span class="w"> </span><span class="s2">"../bower_components/foundation/scss/foundation/components/visibility"</span><span class="p">;</span>
</code></pre>
  </div>
  <p>
   This just instructs SASS to use our settings files and load all of the various components of Foundation. In a real project you won't likely be using all of these features so you can comment them out and drastically reduce the size of the CSS you send to the browser.
  </p>
  <h2>
   Setup our gulpfile.js
  </h2>
  <p>
   You control Gulp by writing out a gulpfile.js. Put this in the root of your project, I'll explain what is going on below:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">var</span><span class="w"> </span><span class="n">gulp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">sass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-sass'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">watch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-watch'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">minifycss</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-minify-css'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">rename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-rename'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">gzip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-gzip'</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">livereload</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s1">'gulp-livereload'</span><span class="p">);</span>

<span class="k">var</span><span class="w"> </span><span class="n">gzip_options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">threshold</span><span class="p">:</span><span class="w"> </span><span class="s1">'1kb'</span><span class="p">,</span>
<span class="w">    </span><span class="n">gzipOptions</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">level</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">};</span>

<span class="o">/*</span><span class="w"> </span><span class="n">Compile</span><span class="w"> </span><span class="n">Our</span><span class="w"> </span><span class="n">Sass</span><span class="w"> </span><span class="o">*/</span>
<span class="n">gulp</span><span class="o">.</span><span class="n">task</span><span class="p">(</span><span class="s1">'sass'</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">gulp</span><span class="o">.</span><span class="n">src</span><span class="p">(</span><span class="s1">'scss/*.scss'</span><span class="p">)</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">sass</span><span class="p">())</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">gulp</span><span class="o">.</span><span class="n">dest</span><span class="p">(</span><span class="s1">'static/stylesheets'</span><span class="p">))</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">rename</span><span class="p">({</span><span class="n">suffix</span><span class="p">:</span><span class="w"> </span><span class="s1">'.min'</span><span class="p">}))</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">minifycss</span><span class="p">())</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">gulp</span><span class="o">.</span><span class="n">dest</span><span class="p">(</span><span class="s1">'static/stylesheets'</span><span class="p">))</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">gzip</span><span class="p">(</span><span class="n">gzip_options</span><span class="p">))</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">gulp</span><span class="o">.</span><span class="n">dest</span><span class="p">(</span><span class="s1">'static/stylesheets'</span><span class="p">))</span>
<span class="w">        </span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">livereload</span><span class="p">());</span>
<span class="p">});</span>

<span class="o">/*</span><span class="w"> </span><span class="n">Watch</span><span class="w"> </span><span class="n">Files</span><span class="w"> </span><span class="n">For</span><span class="w"> </span><span class="n">Changes</span><span class="w"> </span><span class="o">*/</span>
<span class="n">gulp</span><span class="o">.</span><span class="n">task</span><span class="p">(</span><span class="s1">'watch'</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">livereload</span><span class="o">.</span><span class="n">listen</span><span class="p">();</span>
<span class="w">    </span><span class="n">gulp</span><span class="o">.</span><span class="n">watch</span><span class="p">(</span><span class="s1">'scss/*.scss'</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="s1">'sass'</span><span class="p">]);</span>

<span class="w">    </span><span class="o">/*</span><span class="w"> </span><span class="n">Trigger</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">live</span><span class="w"> </span><span class="n">reload</span><span class="w"> </span><span class="n">on</span><span class="w"> </span><span class="n">any</span><span class="w"> </span><span class="n">Django</span><span class="w"> </span><span class="n">template</span><span class="w"> </span><span class="n">changes</span><span class="w"> </span><span class="o">*/</span>
<span class="w">    </span><span class="n">gulp</span><span class="o">.</span><span class="n">watch</span><span class="p">(</span><span class="s1">'**/templates/*'</span><span class="p">)</span><span class="o">.</span><span class="n">on</span><span class="p">(</span><span class="s1">'change'</span><span class="p">,</span><span class="w"> </span><span class="n">livereload</span><span class="o">.</span><span class="n">changed</span><span class="p">);</span>

<span class="p">});</span>

<span class="n">gulp</span><span class="o">.</span><span class="n">task</span><span class="p">(</span><span class="s1">'default'</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="s1">'sass'</span><span class="p">,</span><span class="w"> </span><span class="s1">'watch'</span><span class="p">]);</span>
</code></pre>
  </div>
  <p>
   The first few lines simple include all of the various plugins we will be using. We then set some common configuration information for the gzip plugin. Here we're saying we don't want to bother compressing anything smaller than 1kb and to use gzip's maximum compression. Gulp is incredibly fast, most everything happens in milliseconds so there isn't any reason to skimp here.
  </p>
  <p>
   We then define two tasks,
   <code>
    sass
   </code>
   and
   <code>
    watch
   </code>
   . The
   <code>
    sass
   </code>
   command is used by
   <code>
    watch
   </code>
   command, so let's look at that first.
  </p>
  <p>
   The
   <code>
    watch
   </code>
   looks for any file that matches
   <code>
    scss/*.scss
   </code>
   it will run that file through the
   <code>
    sass
   </code>
   command. We also we setup a livereload server which can be used with the
   <a href="http://livereload.com/">
    LiveReload
   </a>
   desktop app or in my case the Chrome extension from their
   <a href="http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions-">
    browser extensions
   </a>
   page.
  </p>
  <p>
   Finally we have another rule that looks for any files that look like Django templates and trigger a live reload. Why is that awesome? Well you can open up multiple browsers, say one desktop and one mobile sized, and as you make edits to your Django templates and/or CSS your browser windows will automatically refresh. Saving you from having to lift your hands off the keyboard, move your mouse
   <strong>
    all
   </strong>
   the way over to the windows and refreshing each manually. This feature alone saves me time and likely a bit of carpal tunnel.
  </p>
  <p>
   Our
   <code>
    sass
   </code>
   command is a bit more complicated. Gulp processes files as pipes, so it doesn't have to write any intermediary files to disk which is part of how it achieves such great speed. If you read the source of that task from top down here is what is going on:
  </p>
  <ul>
   <li>
    Look for any files matching 'scss/*.scss'
   </li>
   <li>
    Compile them with libsass
   </li>
   <li>
    Write the results to this point to 'static/stylesheets'. In our case, with our original being main.scss it will write 'static/stylesheets/main.css'
   </li>
   <li>
    Now rename the stream to append '.min' to the end of the filename and before the extension
   </li>
   <li>
    Minify the CSS at this point
   </li>
   <li>
    Write the results again into 'static/stylesheets' this time creating 'static/stylesheets/main.min.css'
   </li>
   <li>
    Compress the stream using the gzip options we setup up top
   </li>
   <li>
    Yet again, write the results to 'static/stylesheets' this time creating 'static/stylesheets/main.min.css.gz'
   </li>
   <li>
    Trigger a live reload
   </li>
  </ul>
  <p>
   Our final task is the 'default' task. With Gulp you can call tasks individually on the command line, but anything you define in your 'default' task will be run continuously in the foreground after just running gulp without any arguments.
  </p>
  <h2>
   Django Integration
  </h2>
  <p>
   Up to this point everything we've configured is backend agnostic. You could use everything we've setup with a totally static site, Rails, or whatever your preferred backend stack is. Here at RevSys we primarily use Django, so being able to integrate this with Django is pretty important otherwise it wouldn't be useful to us.
  </p>
  <p>
   Luckily Django is totally agnostic about your frontend setup. What we have configured already would work just fine with a Django site. We're building our CSS and JS as combined files and all one would need to do is include them using a standard Django {% static %} template tag. However, I'm a big performance nerd and one of my favorite features of django-compressor is it's default configuration that creates hashed file names based on underlying CSS and JS it is compressing.
  </p>
  <p>
   When you're using django-compressor you control what is combined and compressed using template tags like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>{%<span class="w"> </span>load<span class="w"> </span>compress<span class="w"> </span>%}
<span class="nt">&lt;head&gt;</span>
<span class="w">    </span>{%<span class="w"> </span>compress<span class="w"> </span>css<span class="w"> </span>%}
<span class="w">    </span><span class="nt">&lt;link</span><span class="w"> </span><span class="na">rel=</span><span class="s">"stylesheet"</span><span class="w"> </span><span class="na">href=</span><span class="s">"{% static "</span><span class="err">stylesheets/main.css"</span><span class="w"> </span><span class="err">%}"</span><span class="w"> </span><span class="nt">/&gt;</span>
<span class="w">    </span><span class="nt">&lt;link</span><span class="w"> </span><span class="na">rel=</span><span class="s">"stylesheet"</span><span class="w"> </span><span class="na">href=</span><span class="s">"{% static "</span><span class="err">stylesheets/some-other.css"</span><span class="w"> </span><span class="err">%}"</span><span class="w"> </span><span class="nt">/&gt;</span>
<span class="w">    </span>{%<span class="w"> </span>endcompress<span class="w"> </span>%}
<span class="nt">&lt;/head&gt;</span>
</code></pre>
  </div>
  <p>
   With this configuration compressor will combine, minify, etc., based on the rules you have setup in your Django settings, into a single file like:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="p">&lt;</span><span class="nx">link</span><span class="w"> </span><span class="nx">rel</span><span class="p">=</span><span class="s">"stylesheet"</span><span class="w"> </span><span class="nx">href</span><span class="p">=</span><span class="s">"/static/CACHE/css/ed3523606236.css"</span><span class="w"> </span><span class="k">type</span><span class="p">=</span><span class="s">"text/css"</span><span class="w"> </span><span class="o">/</span><span class="p">&gt;</span>
</code></pre>
  </div>
  <p>
   Note that because gulp is combining and minifing our CSS for us, all we really want django-compressor to do for us is create this hashed file name. So to setup django-compressor all we need to do is 'pip install django-compressor', add 'compressor' to our INSTALLED_APPS and add 'compressor.finders.CompressorFinder' to the STATICFILES_FINDERS setting. By default compressor doesn't compress when DEBUG=True, so I also suggest adding COMPRESS_ENABLED=True to your local dev settings to help ensure your local environment mimics production as much as possible.
  </p>
  <p>
   We are however also using bower, so to make Django's static files system be able to find the bower components we need to 'pip install django-bower', add 'djangobower' to INSTALLED_APPS and add 'djangobower.finders.BowerFinder' to STATICFILES_FINDERS. So ultimately we end up with this in settings:
  </p>
  <div class="codehilite">
   <pre><span></span><code>INSTALLED_APPS = [
    <span class="k">...</span> your other django apps ...
    'django.contrib.staticfiles',
    'djangobower',
    'compressor',
]

COMPRESS_ENABLED = True

STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'djangobower.finders.BowerFinder',
    'compressor.finders.CompressorFinder',
)
</code></pre>
  </div>
  <p>
   We can then easily setup far future Expires headers for anything living in /static/CACHE/ to a date way into the future without any worries we might serve up old CSS to our users. A typical nginx configuration, setting the expire header for 90 days into the future, looks like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code>location /static/CACHE {
    alias /home/site/static/CACHE;
    expires 90d;
}
</code></pre>
  </div>
  <h2>
   Why not just use django-compressor for all of this?
  </h2>
  <p>
   One of the draw backs of django-compressor is when you have several transformations going on it can get a bit slow. All of the SASS compilation, concatination, minification and whatever else you have configured it can take a half second to a second or two for pages to be rendered after a CSS or JS change when working locally with Django's runserver.
  </p>
  <p>
   This doesn't sound like much or anything to be concerned about, but having worked with this new setup for a few days the perceived daily performance difference is pretty impressive. Add in the immediately useful LiveReload features that are easy to achieve with gulp and I can attest this setup helps you stay in a useful flow state more easily.
  </p>
  <p>
   Switching over to this setup might take a half day of time at most, but I would wager you will gain that back in spades in less than 2 months of using it. Happy hacking!
  </p>
 </div>
</div>
]]>/></item><item><title>Python Dev Tip: DRY your shell with PYTHONSTARTUP</title><link>http://www.revsys.com/tidbits/python-tip-shell-pythonstartup/</link><description>Simple way to use virtaualenvwrapper&amp;#x27;s postactivate and deactivate hooks to execute arbitrary Python code for any of your Python shells while working on that project.</description><pubDate>Sat, 12 Jul 2014 20:08:55 +0000</pubDate><guid>http://www.revsys.com/tidbits/python-tip-shell-pythonstartup/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <h2>
   Do you find yourself re-doing the same Python shell stuff?
  </h2>
  <p>
   I know I do. I'm constantly doing it and replaying some of my IPython history isn't cutting it for me anymore. IPython has
   <a href="http://ipython.org/ipython-doc/dev/config/intro.html#profiles">
    profiles
   </a>
   but they're not terribly easy to get working with Django and your typical
   <code>
    manage.py shell
   </code>
   command.
  </p>
  <h2>
   Site wide is easy, but what about per project?
  </h2>
  <p>
   You can load things and execute code for
   <strong>
    ALL
   </strong>
   your Python shells system wide pretty easily, but what about when you need different things loaded depending on the project?
  </p>
  <p>
   Turns out it's pretty easy to do with an environment variable and
   <a href="http://virtualenvwrapper.readthedocs.org/en/latest/">
    virtualenvwrapper
   </a>
   . If you aren't already using virtualenvwrapper you should really check it out. I use it even outside of Python projects just to have an easy mechanism for different "shell environments" to have certain things executed when I start and finish working on a project.
  </p>
  <h2>
   The situation I was in...
  </h2>
  <p>
   While the issue that lead me to figure this out was a Django project, nothing about this technique is Django specific. I have a pretty common workflow when doing green field Django development where I setup Fabric tasks to rebuild my local dev database and repopulate it with certain database objects I need to make the site functional enough for testing. For example, I typically setup a User object with username 'frank' (I know big surprise there) that is a superuser. So if I launch a shell 9 out of 10 times I probably need my
   <em>
    frank
   </em>
   User object for something.
  </p>
  <p>
   This is complicated however by the fact that on some projects I work on the User model is customized to not have a username so in some projects I need to run:
  </p>
  <div class="codehilite">
   <pre><span></span><code>frank = User.objects.get(username='frank')
</code></pre>
  </div>
  <p>
   And in others I need:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">frank</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">User</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="n">email</span><span class="o">=</span><span class="s1">'frank@revsys.com'</span><span class="p">)</span>
</code></pre>
  </div>
  <p>
   What I realized was Python honors and executes the code in any file you specify in the
   <code>
    PYTHONSTARTUP
   </code>
   environment variable in your shell. So if you adjust your virtualenv's
   <a href="http://virtualenvwrapper.readthedocs.org/en/latest/plugins.html#plugins-post-activate">
    postactivate and postdeactivate
   </a>
   hooks to set the path to code you want run on a per project basis. For example:
  </p>
  <h3>
   In ~/.virtualenvs/projectX/bin/postactivate
  </h3>
  <div class="codehilite">
   <pre><span></span><code><span class="w">  </span><span class="c1">#!/bin/bash</span>
<span class="w">  </span><span class="k">export</span><span class="w"> </span><span class="n">PYTHONSTARTUP</span><span class="o">=/</span><span class="n">Users</span><span class="o">/</span><span class="n">frank</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">projectX</span><span class="o">/</span><span class="n">startup</span><span class="o">.</span><span class="n">py</span>
<span class="w">  </span><span class="k">export</span><span class="w"> </span><span class="n">DJANGO_SETTINGS_MODULE</span><span class="o">=</span><span class="s2">"projectX.settings.dev"</span>
<span class="w">  </span><span class="n">cd</span><span class="w"> </span><span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">frank</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">projectX</span>
</code></pre>
  </div>
  <h3>
   In ~/.virtualenvs/projectX/bin/postdeactivate
  </h3>
  <div class="codehilite">
   <pre><span></span><code>  #!/bin/bash
  unset $DJANGO_SETTINGS_MODULE
  unset $PYTHONSTARTUP
</code></pre>
  </div>
  <h2>
   So what can you do with this?
  </h2>
  <p>
   Well you can execute any arbitrary Python code so you could could do a lot, even go so far as to hit your time tracking software's API to log the time, but realize anything you run will slow down the startup time of each Python shell you launch so keep it to what you really need. On this particular project I kept it to a few small things:
  </p>
  <div class="codehilite">
   <pre><span></span><code>  <span class="c1"># Import datetime and some of my commonly used Models </span>
  <span class="kn">import</span><span class="w"> </span><span class="nn">datetime</span>
  <span class="kn">from</span><span class="w"> </span><span class="nn">django.contrib.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_user_model</span>
  <span class="kn">from</span><span class="w"> </span><span class="nn">app1.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">ThatOneModel</span>
  <span class="kn">from</span><span class="w"> </span><span class="nn">app2.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">TheOtherCommonModel</span>

  <span class="k">try</span><span class="p">:</span>
      <span class="n">User</span> <span class="o">=</span> <span class="n">get_user_model</span><span class="p">()</span>
      <span class="n">frank</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="s1">'frank'</span><span class="p">)</span>
  <span class="k">except</span> <span class="n">User</span><span class="o">.</span><span class="n">DoesNotExist</span><span class="p">:</span>
      <span class="c1"># Frank not being in the DB shouldn't raise an error</span>
      <span class="k">pass</span>
</code></pre>
  </div>
  <p>
   Hope this helps you stay in flow more and delay your carpal tunnel a few days at least!
  </p>
 </div>
</div>
]]>/></item><item><title>Django Debugging Bookmarklet Trick</title><link>http://www.revsys.com/tidbits/django-debugging-bookmarklet-trick/</link><description>A little bookmarklet that redirects you from the current page to the same path on http://localhost:8000/. Helpful when debugging Django.</description><pubDate>Wed, 19 Feb 2014 14:28:41 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-debugging-bookmarklet-trick/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   So this is one of those little things where you think, why the hell didn't I think of this years ago?
  </p>
  <p>
   I'm sure many Django developers end up doing this on a fairly regular basis. You're working on a site, go to the production/staging version, navigate around and then want to view the same URL path but on your local development server.
  </p>
  <p>
   If you're like me you cut-n-paste the current URL into a new tab and manually remove the host and replace it with
   <code>
    http://localhost:8000
   </code>
   . That way you have a browser tab of the real site and a tab with the local version for comparision. I've done this at least a billion times. It's a silly little annoyance because it's easy to mess up the URL leaving off the slash after 8000 for example.
  </p>
  <p>
   This morning while working on a site I realized,
   <strong>
    WTF
   </strong>
   I can do this with a bookmarklet! They're super easy as it turns out.
  </p>
  <h2>
   Version that replaces the current tab:
  </h2>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">javascript</span><span class="o">:(</span><span class="nt">function</span><span class="o">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">window.location.replace("</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">localhost</span><span class="o">:</span><span class="mi">8000</span><span class="err">"</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">window</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">pathname</span><span class="p">);</span><span class="w"> </span><span class="p">}</span><span class="o">())</span>
</code></pre>
  </div>
  <h2>
   Version that creates a new tab:
  </h2>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">javascript</span><span class="o">:(</span><span class="nt">function</span><span class="o">()</span><span class="w"> </span><span class="p">{</span><span class="w">  </span><span class="err">window.open("</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">localhost</span><span class="o">:</span><span class="mi">8000</span><span class="err">"</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">window</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">pathname</span><span class="p">,</span><span class="w"> </span><span class="s1">'_blank'</span><span class="p">);</span><span class="w"> </span><span class="p">}</span><span class="o">())</span>
</code></pre>
  </div>
  <p>
   I still can't quite believe I didn't think to do this long ago! I wonder how many hours of time I would have saved over the years? I've included two links below that you can create your own bookmarks from, you'll obviously need to edit them if you don't use the Django defaults of
   <em>
    localhost:8000
   </em>
   .
  </p>
  <p>
   Simply drag which ever version you prefer into your bookmarks and you're ready to go.
  </p>
  <p>
   <a href="javascript:(function() { window.location.replace(" title='http://localhost:8000" + window.location.pathname); }()) "Make localhost'>
    Make localhost:8000
   </a>
  </p>
  <p>
   <a href="javascript:(function() {  window.open(" title="http://localhost:8000&quot; + window.location.pathname, '_blank'); }()) &quot;Make localhost tab">
    Make localhost:8000 tab
   </a>
  </p>
  <p>
   You could easily modify these to create bookmarklets that take you to your staging or QA system and even when working with development frameworks other than Django.
  </p>
  <p>
   Hope this saves you as much time as it is going to save me!
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   <a href="http://jacobian.org">
    Jacob
   </a>
   sent me a great additional add-on tip to this.
   <a href="http://softwareas.com/how-to-use-different-favicons-for-development-staging-and-production/">
    Michael Mahemoff
   </a>
   has a good tip on how to change the Favicon in your browser between different environments like staging and production. So you can combine these two techniques for maximal win!
  </p>
 </div>
</div>
]]>/></item><item><title>Setting up Mailgun with SaltStack</title><link>http://www.revsys.com/tidbits/configure-mailgun-saltstack/</link><description>How to easily configure email relaying for Ubuntu servers with Mailgun, SaltStack, and Postfix</description><pubDate>Sat, 15 Feb 2014 17:37:13 +0000</pubDate><guid>http://www.revsys.com/tidbits/configure-mailgun-saltstack/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Here at RevSys we've been moving away from Chef and using
   <a href="http://www.saltstack.com/community/">
    SaltStack
   </a>
   for all our new server setups. Salt is still pretty new and while I run into bugs every now and again, they're releasing new versions with fixes at an impressive rate.
  </p>
  <p>
   Today I want to show you how to setup a new server with
   <a href="http://www.postfix.org">
    Postfix
   </a>
   and
   <a href="http://www.mailgun.com">
    Mailgun
   </a>
   in a reusable way. This tutorial assumes that you already have a working Salt setup.
  </p>
  <p>
   The steps are pretty easy, we need to:
  </p>
  <ol>
   <li>
    Install postfix and sasl
   </li>
   <li>
    Configure postfix to use our Mailgun account for it's SMTP relay server
   </li>
  </ol>
  <p>
   We'll make this salt state easily reusable by having the Mailgun credentials and optional email alias configurable with Salt Pillar data.
  </p>
  <h3>
   Setup Pillar Data
  </h3>
  <p>
   So first we'll setup the Pillar data. For the purposes of the tutorial we'll assume we are setting up the server 'example.revsys.com'.
  </p>
  <p>
   We'll add:
  </p>
  <p>
   mailgun_username: example@example.mailgun.org mailgun_password: MySillySecretPassword mailgun_alias_file: example/templates/example_mail_aliases `
  </p>
  <p>
   To the pillar data. This could be a separate per server pillar file that /srv/pillar/example.sls or a more reusable /srv/pillar/mailgun.sls depending on how you want to do it.
  </p>
  <h3>
   Setup Salt State File
  </h3>
  <p>
   Then we create a mailgun state in /srv/salt/mailgun/init.sls:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">postfix</span><span class="o">:</span>
<span class="w">    </span><span class="n">pkg</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">installed</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">names</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">postfix</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">libsasl2</span><span class="o">-</span><span class="n">modules</span>
<span class="w">    </span><span class="n">service</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">running</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">require</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">pkg</span><span class="o">:</span><span class="w"> </span><span class="n">postfix</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">watch</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">file</span><span class="o">:</span><span class="w"> </span><span class="sr">/etc/postfix/</span><span class="n">main</span><span class="o">.</span><span class="na">cf</span>

<span class="sr">/etc/postfix/</span><span class="n">main</span><span class="o">.</span><span class="na">cf</span><span class="o">:</span>
<span class="w">    </span><span class="n">file</span><span class="o">.</span><span class="na">managed</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">source</span><span class="o">:</span><span class="w"> </span><span class="n">salt</span><span class="o">://</span><span class="n">mailgun</span><span class="sr">/templates/</span><span class="n">main</span><span class="o">.</span><span class="na">cf</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">user</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">group</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">mode</span><span class="o">:</span><span class="w"> </span><span class="mi">644</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">require</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">pkg</span><span class="o">:</span><span class="w"> </span><span class="n">postfix</span>

<span class="sr">/etc/postfix/s</span><span class="n">asl_passwd</span><span class="o">:</span>
<span class="w">    </span><span class="n">file</span><span class="o">.</span><span class="na">managed</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">source</span><span class="o">:</span><span class="w"> </span><span class="n">salt</span><span class="o">://</span><span class="n">mailgun</span><span class="sr">/templates/s</span><span class="n">asl_passwd</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">user</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">group</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">mode</span><span class="o">:</span><span class="w"> </span><span class="mi">600</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">template</span><span class="o">:</span><span class="w"> </span><span class="n">jinja</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">context</span><span class="o">:</span>
<span class="w">            </span><span class="n">username</span><span class="o">:</span><span class="w"> </span><span class="o">{{</span><span class="w"> </span><span class="n">pillar</span><span class="o">[</span><span class="s1">'mailgun_username'</span><span class="o">]</span><span class="w"> </span><span class="o">}}</span>
<span class="w">            </span><span class="n">password</span><span class="o">:</span><span class="w"> </span><span class="o">{{</span><span class="w"> </span><span class="n">pillar</span><span class="o">[</span><span class="s1">'mailgun_password'</span><span class="o">]</span><span class="w"> </span><span class="o">}}</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">require</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">pkg</span><span class="o">:</span><span class="w"> </span><span class="n">postfix</span>
<span class="w">    </span><span class="n">cmd</span><span class="o">.</span><span class="na">wait</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">name</span><span class="o">:</span><span class="w"> </span><span class="n">postmap</span><span class="w"> </span><span class="sr">/etc/postfix/s</span><span class="n">asl_passwd</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">user</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">watch</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">file</span><span class="o">:</span><span class="w"> </span><span class="sr">/etc/postfix/s</span><span class="n">asl_passwd</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">watch_in</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">service</span><span class="o">:</span><span class="w"> </span><span class="n">postfix</span>
<span class="o">{%</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">pillar</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s1">'mailgun_alias_file'</span><span class="o">,</span><span class="w"> </span><span class="n">False</span><span class="o">)</span><span class="w"> </span><span class="o">%}</span>
<span class="sr">/etc/</span><span class="n">aliases</span><span class="o">:</span>
<span class="w">    </span><span class="n">file</span><span class="o">.</span><span class="na">managed</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">source</span><span class="o">:</span><span class="w"> </span><span class="n">salt</span><span class="o">://{{</span><span class="w"> </span><span class="n">pillar</span><span class="o">[</span><span class="s1">'mailgun_alias_file'</span><span class="o">]</span><span class="w"> </span><span class="o">}}</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">user</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">group</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">mode</span><span class="o">:</span><span class="w"> </span><span class="mi">600</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">template</span><span class="o">:</span><span class="w"> </span><span class="n">jinja</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">require</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">pkg</span><span class="o">:</span><span class="w"> </span><span class="n">postfix</span>
<span class="w">    </span><span class="n">cmd</span><span class="o">.</span><span class="na">wait</span><span class="o">:</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">name</span><span class="o">:</span><span class="w"> </span><span class="n">newaliases</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">user</span><span class="o">:</span><span class="w"> </span><span class="n">root</span>
<span class="w">        </span><span class="o">-</span><span class="w"> </span><span class="n">watch</span><span class="o">:</span>
<span class="w">            </span><span class="o">-</span><span class="w"> </span><span class="n">file</span><span class="o">:</span><span class="w"> </span><span class="sr">/etc/</span><span class="n">aliases</span>
<span class="o">{%</span><span class="w"> </span><span class="n">endif</span><span class="w"> </span><span class="o">%}</span>
</code></pre>
  </div>
  <h3>
   How this works
  </h3>
  <p>
   What it does is pretty simple. We install postfix and sasl. Then push out a main.cf template that adds the configuration necessary to have postfix use mailgun as an SMTP relay. Those configuration options are well documented by Rackspace in this post on
   <a href="https://community.rackspace.com/products/f/28/t/63">
    Configuring Postfix for Mailgun and Sendgrid
   </a>
   . You just need to add this to bottom of the default main.cf:
  </p>
  <div class="codehilite">
   <pre><span></span><code>smtp_sasl_auth_enable = yes
relayhost = smtp.mailgun.org
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd
</code></pre>
  </div>
  <p>
   Our Salt State then creates the file /etc/postfix/sasl_password using a template that looks like:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="x">smtp.mailgun.org </span><span class="cp">{{</span> <span class="nv">username</span> <span class="cp">}}</span><span class="x">:</span><span class="cp">{{</span> <span class="nv">password</span> <span class="cp">}}</span>
</code></pre>
  </div>
  <p>
   Which is built using the Pillar data we created above and can customize on a per host/role basis if necessary. And then optionally we can define an aliases file which will replace /etc/aliases and run the standard newaliases command. We're using cmd.wait here to only rebuild the sasl_passwd map and optional aliases if those files have changed. The watch_in command then restarts postfix if we add or change the sasl_password.
  </p>
  <h3>
   Targeting
  </h3>
  <p>
   To actually deploy this again a server then all you need to do is adjust your /srv/salt/top.sls to include the state like so:
  </p>
  <div class="codehilite">
   <pre><span></span><code>base:
     'example.revsys.com':
        - mailgun
</code></pre>
  </div>
  <p>
   Obviously along with whatever other targeting configuration you have setup already.
  </p>
  <p>
   Hope this helps you get SMTP relaying with Mailgun up and running a bit faster!
  </p>
 </div>
</div>
]]>/></item><item><title>$90 million is that the best we can do?</title><link>http://www.revsys.com/tidbits/90-million-best-we-can-do/</link><description>Government contracts are broken and make it less likely smart tech companies will even participate.</description><pubDate>Tue, 14 Jan 2014 21:12:39 +0000</pubDate><guid>http://www.revsys.com/tidbits/90-million-best-we-can-do/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Yesterday one of our team posted a link in our work chat channel to a
   <a href="http://blogs.law.harvard.edu/philg/2014/01/12/90-million-in-maintenance-per-year-for-healthcare-gov/">
    post by Philip Greenspun
   </a>
   on how Accenture recently landed a $90 million dollar per year contract to take care of
   <a href="http://healthcare.gov">
    healthcare.gov
   </a>
   , joking that we should have bid on it.
  </p>
  <p>
   I don't think anyone can dispute that the original contractors bungled the job severely. [1] However, before I rant on why this contract disgusts me let's get a few things out of the way:
  </p>
  <ul>
   <li>
    We all know that working inside a big bureaucracy slows everything down and basically sucks. Design by committee. A dozen or so project managers. Pre-meetings to get ready for the real meeting, etc.
   </li>
   <li>
    While I've only had a few small dealings with
    <a href="http://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act">
     HIPAA
    </a>
    I'm sure it was a big pain, for better or worse.[2]
   </li>
   <li>
    The process required coordinating data, feeds, and integrations with several different government and private health insurance systems to one degree or another. Legacy systems, speaking legacy protocols, on legacy hardware. Not an easy task, but as someone who has presided over dozens and dozens of weird systems integrations not exactly Nobel Prize worthy stuff.
   </li>
   <li>
    They had to deal with ancient IE browsers and a bunch of other crap those of us working with more modern tech don't bother with any longer. Grandma isn't likely running the latest Chrome.
   </li>
   <li>
    It was a project with a high public profile and was assured to get traffic that by comparison makes most large startup launches look like my personal blog. And no matter how awesome it might have gone, it was going to get some level of complaints.
   </li>
  </ul>
  <h3>
   Some cost comparisons
  </h3>
  <p>
   All that aside, I'm confident $90 million is easily more than double what this should cost. Let's look at some quick comparisons:
  </p>
  <p>
   We sent
   <a href="http://en.wikipedia.org/wiki/Mars_Exploration_Rover">
    two robots to Mars
   </a>
   for $820 million dollars. When they performed better than expected we extended their missions over 5 years for about $100 million. Apparently it costs roughly twice as much to travel to and explore Mars with
   <em>
    freaking robots controlled from earth
   </em>
   per year as it does to signup a few million people on a website.
  </p>
  <p>
   While on the topic of Space and not to sound like an Elon Musk fan boy, but a
   <a href="http://en.wikipedia.org/wiki/Falcon_9">
    SpaceX Falcon 9
   </a>
   rocket only costs $57 million.
  </p>
  <p>
   So let's break it down a bit in actual costs for something like this.
  </p>
  <ul>
   <li>
    Let's assume they need a lot more hardware than I think, let's call it $10 million a year in hardware costs[3].
   </li>
   <li>
    Toss in another $10 million a year to cover bureaucracy, lawyers, lobbyists, etc.
   </li>
   <li>
    Add in another $10 million a year in consultants and anything else I'm forgetting for good measure.
   </li>
   <li>
    Average US programmer salary in 2011 was $72k, so let's call it $250k/engineer/year to round up because
    <em>
     fuck it why not
    </em>
    . So another $40 million of the budget would get you 160 extremely highly paid engineers[4].
   </li>
  </ul>
  <p>
   And we're
   <em>
    <strong>
     still
    </strong>
   </em>
   at only $70 million with these inflated estimates.
  </p>
  <h3>
   So what's wrong?
  </h3>
  <p>
   When I read Greenspun's post it reminded me of a conversation I had a few years ago with a colleague. He's tangentially involved in state level healthcare policy and mentioned there were a few contracts out for bid that he thought RevSys might be interested in. The exact details escape me, but one involved a few web forms for doctors and hospitals in the state to check on the enrollment of patients in some Medicare program. It would have involved integrating a few legacy state systems, but otherwise pretty straight forward. He asked how much something like this would cost and I ball parked it around $200-300k assuming the integrations would be painful and that the project would eat up tons of time in meetings. His response floored me, he laughed and said "Oh you'd need to bid at least a few million or they won't take you seriously."
  </p>
  <p>
   The whole process of bidding and then winning/running a project with the government is a feat unto itself. I've been loosely involved with a few proposals, usually as the "tech expert" and not the party actually putting together the full proposal materials, and I can tell you it eats up an inordinate amount of time and energy just to bid on the project.
  </p>
  <p>
   The process almost assures that the company best at the
   <em>
    process
   </em>
   wins rather than the one that might deliver the best or most cost effective technological solution. Luckily I've never been a part of a winning proposal as I imagine the day-to-day to be equally grueling and professionally unfulfilling.
  </p>
  <p>
   When the government announced they were bringing in "tech's best and brightest" I rolled my eyes, assuming they would fail miserably at getting decent talent. However they brought in Red Hat, which was a pleasant surprise. But as many others have commented on, it was too little and way too late.
  </p>
  <p>
   Most projects like this aren't sexy. They certainly aren't pushing the envelope tech challenges. So perhaps they can't attract even the Top 10% of tech talent either because the problem is boring or they're too busy trying to strike it rich with a startup. But what about the next 10-20% below them on the tech totem pole?
  </p>
  <p>
   Is $90 million the best we can do? Should I be happy it wasn't $120 million? Does it really all boil down to boring problems and the burden of the process? We all know it's broken. The question is can it be fixed and how do we go about it?
  </p>
  <p>
   Until then I'm sure our company and many companies like us will, as Jeff Hammerbacher was quoted as saying continue "thinking about how to make people click ads" [5].
  </p>
  <p>
   If you have ideas on how to fix this mess you can find me on Twitter
   <a href="http://twitter.com/fwiles">
    @fwiles
   </a>
   or via email
   <a href="mailto:frank@revsys.com">
    frank@revsys.com
   </a>
   .
  </p>
  <h3>
   Footnotes
  </h3>
  <p>
   [1] Besides the widely reported outages and bugs the site has experienced it
   <strong>
    STILL
   </strong>
   gets a low C rating on simple performance tools such as YSlow. Doesn't take a lot of imagination to think how badly the internals were constructed in terms of performance.
  </p>
  <p>
   [2] What I mean here is that with most laws and even corporate policies the original intention is usually good. The spirit if you will, but you quickly get mired in details and wording and  CYA behavior.
  </p>
  <p>
   [3] I'd ball park it in the $2-3 million dollar range and I consider that an inflated estimate. $10 million would work out to 475 c3.8xlarge On Demand AWS instances running for the entire year. Which would be about 51k ECUs, 27 TBs of RAM, and 148 TBs of
   <em>
    mirrored
   </em>
   SSD instance storage! Even if we shift some of those monies around to S3, EBS, and traffic charges it's still a metric ton of horsepower. This works out to be roughly
   <a href="http://highscalability.com/blog/2013/4/15/scaling-pinterest-from-0-to-10s-of-billions-of-page-views-a.html">
    2 Pinterests
   </a>
   or about
   <a href="http://highscalability.com/blog/2011/12/6/instagram-architecture-14-million-users-terabytes-of-photos.html">
    4 circa 2011 Instagrams
   </a>
   in terms of hardware.
  </p>
  <p>
   [4] From
   <a href="http://money.usnews.com/careers/best-jobs/computer-programmer/salary">
    US News
   </a>
   . If we used the actual average you could have a tech staff of 555 people FYI.
  </p>
  <p>
   [5] The full quote is "The best minds of my generation are thinking about how to make people click ads. That sucks." from a
   <a href="http://www.businessweek.com/magazine/content/11_17/b4225060960537.htm">
    Business Week
   </a>
   article.
   <em>
    [ CYA]: Cover Your Ass
   </em>
   [ECU]: Elastic Compute Units
  </p>
 </div>
</div>
]]>/></item><item><title>Grove isn't dead, it's only resting</title><link>http://www.revsys.com/tidbits/grove-lives/</link><description>Grove isn&amp;#x27;t dead, it&amp;#x27;s only resting. We&amp;#x27;ve acquired Grove!</description><pubDate>Fri, 28 Sep 2012 21:54:28 +0000</pubDate><guid>http://www.revsys.com/tidbits/grove-lives/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   The news is out: we
   <a href="https://grove.io/blog/grove-here-stay">
    acquired Grove
   </a>
   today, and we're taking over operations immediately.
  </p>
  <p>
   We think
   <a href="https://grove.io">
    Grove
   </a>
   is an incredibly cool tool: it's an IRC server masquerading as a web-based chat application. It's got a really slick web UI that anyone can use, but behind the scenes is IRC, a proven technology trusted by &uuml;ber-geeks everywhere. We love the way Grove bridges the new and the old.
  </p>
  <p>
   We're first and foremost Grove users, so we think we've got a good grasp of what Grove's other users want. In the short term our focus is going to be on fixing connectivity and improving site performance, but after that we've got some big ideas we're planning to implement. And of course we'd love to hear
   <em>
    your
   </em>
   ideas for what Grove should do; let us know at team@grove.io.
  </p>
  <p>
   We're really glad that we could give Grove a home at Revsys. We think it's a great match for our company, and we can't wait to start working on our big ideas.
  </p>
  <p>
   If you're already a Grove user, I hope you're as excited as we are to hear that it's got a bright future. And if you're not already using Grove,
   <a href="https://grove.io">
    why not check it out
   </a>
   ? We think you'll love it as much as we do.
  </p>
 </div>
</div>
]]>/></item><item><title>Three things you should never put in your database</title><link>http://www.revsys.com/tidbits/three-things-you-should-never-put-your-database/</link><description>There are several things that are not usually appropriate to put into a relational database</description><pubDate>Tue, 01 May 2012 21:24:03 +0000</pubDate><guid>http://www.revsys.com/tidbits/three-things-you-should-never-put-your-database/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   As I've said in a few talks, the best way to improve your systems is by first not doing "dumb things". I don't mean you or your development staff is "dumb", it's easy to overlook the implications of these types of decisions and not realize how bad they are for maintainability let alone scaling. As a consultant I see this stuff all of the time and I have yet to ever see it work out well for anyone.
  </p>
  <h2>
   Images, files, and binary data
  </h2>
  <p>
   Your database supports BLOBs so it must be a good idea to shove your files in there right? No it isn't! Hell it isn't even very convenient to use with many DB language bindings.
  </p>
  <p>
   There are a few of problems with storing files in your database:
  </p>
  <ul>
   <li>
    read/write to a DB is always slower than a filesystem
   </li>
   <li>
    your DB backups grow to be huge and more time consuming
   </li>
   <li>
    access to the files now requires going through your app and DB layers
   </li>
  </ul>
  <p>
   The last two are the real killers. Storing your thumbnail images in your database? Great now you can't use nginx or another lightweight web server to serve them up.
  </p>
  <p>
   Do yourself a favor and store a simple relative path to your files on disk in the database or use something like S3 or any CDN instead.
  </p>
  <h2>
   Ephemeral data
  </h2>
  <p>
   Usage statistics, metrics, GPS locations, session data anything that is only useful to you for a short period of time or frequently changes. If you find yourself DELETEing an hour, day, or weeks worth of some table with a cron job, you're using the wrong tool for the job.
  </p>
  <p>
   Use
   <a href="http://redis.io">
    redis
   </a>
   ,
   <a href="https://github.com/etsy/statsd">
    statsd
   </a>
   /
   <a href="http://graphite.wikidot.com/">
    graphite
   </a>
   ,
   <a href="http://basho.com/products/riak-overview/">
    Riak
   </a>
   anything else that is better suited to that type of work load. The same advice goes for aggregations of ephemeral data that doesn't live for very long.
  </p>
  <p>
   Sure it's possible to use a
   <a href="http://en.wikipedia.org/wiki/Backhoe">
    backhoe
   </a>
   to plant some tomatoes in the garden, but it's far faster to grab the shovel in the garage than schedule time with a backhoe and have it arrive at your place and dig. Use the right tool(s) for the job at hand.
  </p>
  <h2>
   Logs
  </h2>
  <p>
   This one seems ok on the surface and the "I might need to use a complex query on them at some point in the future" argument seems to win people over. Storing your logs in a database isn't a HORRIBLE idea, but storing them in the same database as your other production data is.
  </p>
  <p>
   Maybe you're conservative with your logging and only emit one log line per web request normally. That is still generating a log INSERT for every action on your site that is competing for resources that your users could be using. Turn up your logging to a verbose or debug level and watch your production database catch on fire!
  </p>
  <p>
   Instead use something like
   <a href="http://splunk.com">
    Splunk
   </a>
   ,
   <a href="http://loggly.com">
    Loggly
   </a>
   or plain old rotating flat files for your logs. The few times you need to inspect them in odd ways, even to the point of having to write a bit of code to find your answers, is easily outweighed by the constant resources it puts on your system.
  </p>
  <p>
   But wait, you're a unique snowflake and your problem is SO different that it's ok for you to do one of these three.
   <strong>
    No you aren't and no it really isn't
   </strong>
   . Trust me.
  </p>
 </div>
</div>
]]>/></item><item><title>PEP712 - Proposal to make unittest2 more accurate</title><link>http://www.revsys.com/tidbits/pep712-proposal-make-unittest2-more-accurate/</link><description/><pubDate>Thu, 20 Oct 2011 19:35:56 +0000</pubDate><guid>http://www.revsys.com/tidbits/pep712-proposal-make-unittest2-more-accurate/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <table>
   <thead>
    <tr>
     <th>
      PEP:
     </th>
     <th>
      712
     </th>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>
      Title:
     </td>
     <td>
      Proposal to make unittest2 more accurate
     </td>
    </tr>
    <tr>
     <td>
      Version:
     </td>
     <td>
      a58437babcaa
     </td>
    </tr>
    <tr>
     <td>
      Last-Modified:
     </td>
     <td>
      2011-10-20T14:40:01.661119 (Thu, 20 Oct 2011)
     </td>
    </tr>
    <tr>
     <td>
      Authors:
     </td>
     <td>
      Frank Wiles
      <a href="mailto:frank@revsys.com">
       frank@revsys.com
      </a>
      , Jacob Kaplan-Moss
      <a href="mailto:jacob@jacobian.org">
       jacob@jacobian.org
      </a>
      , Jeff Triplett
      <a href="mailto:jeff@revsys.com">
       jeff@revsys.com
      </a>
     </td>
    </tr>
    <tr>
     <td>
      Status:
     </td>
     <td>
      Draft
     </td>
    </tr>
    <tr>
     <td>
      Type:
     </td>
     <td>
      Humor
     </td>
    </tr>
    <tr>
     <td>
      Created:
     </td>
     <td>
      20-Oct-2011
     </td>
    </tr>
    <tr>
     <td>
      Python-Version
     </td>
     <td>
      2.7
     </td>
    </tr>
   </tbody>
  </table>
  <h2>
   Introduction
  </h2>
  <p>
   This PEP describes a proposal to make unittest2 output more accurate and fun.
  </p>
  <h2>
   The Proposed Solution
  </h2>
  <p>
   Upon having more than 7 failing tests in a test run replace all 'F' character output with 'U's for the remainder of the test run.
  </p>
  <h2>
   Rationale
  </h2>
  <p>
   This output formatting more accurately describes the mental state of the developer.
  </p>
  <h2>
   Example
  </h2>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">.....</span><span class="c">FFFFFFFUUUUUUUUUUUUUUUUU</span>
<span class="c">CK</span>
<span class="nb">----------------------------------------------------------------------</span>
<span class="c">Ran 25 tests in 3</span><span class="nt">.</span><span class="c">14159s</span>
<span class="c">FAILED (failures=19)</span>
</code></pre>
  </div>
  <p>
   `
  </p>
  <h2>
   Optional Add-On
  </h2>
  <p>
   Replace next passing test after 19 failures with 'CK'.
  </p>
  <h2>
   Reference Implementations
  </h2>
  <p>
   See Reddit.com
  </p>
  <h2>
   References
  </h2>
  <p>
   <a href="http://twitter.com/#!/f7u12">
    @f7u12
   </a>
   <br/>
   <a href="http://ohinternet.com/F7u12">
    F7U12
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>longjmp() 2011 - Impromptu Party</title><link>http://www.revsys.com/tidbits/longjmp-2011-impromptu-party/</link><description/><pubDate>Fri, 22 Apr 2011 18:30:35 +0000</pubDate><guid>http://www.revsys.com/tidbits/longjmp-2011-impromptu-party/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Lawrence has been having a bunch of downtown block parties lately, which are always great fun. The last couple of days the Kansas Relays have been going on and the long jump event was setup directly outside our
   <a href="http://www2.kusports.com/photos/galleries/2011/apr/21/kansas-relays-downtown-long-jump-hammer-throw/82886/">
    office windows
   </a>
   . Since we had such a great view we quickly through together a little party and invited all of our geeky friends to come watch, we had a pretty good turn out (free beer works apparently). Here are
   <a href="http://bit.ly/hxSA8V">
    some photos
   </a>
   I took from our office. You can find more professional quality photos of the event
   <a href="http://www2.kusports.com/photos/galleries/2011/apr/21/kansas-relays-downtown-long-jump-hammer-throw/">
    here
   </a>
   .
  </p>
  <p>
   Thanks to everyone for coming out!
  </p>
 </div>
</div>
]]>/></item><item><title>We're hiring!</title><link>http://www.revsys.com/tidbits/were-hiring/</link><description/><pubDate>Thu, 07 Apr 2011 21:52:42 +0000</pubDate><guid>http://www.revsys.com/tidbits/were-hiring/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <strong>
    UPDATE:
   </strong>
   I completely forgot to update this post until now, but we have filled this position.
  </p>
  <p>
   We're looking for a full time developer. If you're reading this you probably know what we do, so you'll obviously need to know Django pretty well. Knowledge of PostgreSQL and devops skills are a big plus. We would prefer if you lived in Lawrence, Kansas, obviously, but a few visits here may be fine. Only because we get lonely.
  </p>
  <p>
   Lawrence is an awesome town, so don't let the surroundings (the rest of Kansas) scare you off. It's the hometown of Django and basketball. People often compare it to a smaller version of Austin or Portland, OR. For example, check out the local music happening this weekend on the great
   <a href="http://www.lawrence.com">
    Lawrence.com
   </a>
   .
  </p>
  <p>
   Interested? Email
   <a href="mailto:frank@revsys.com">
    frank@revsys.com
   </a>
   with your resume and code samples. Links to public repositories are ideal, also feel free to talk yourself up with respect to Open Source contributions. Docs, code, answering questions on a mailing list, your StackOverflow account, etc. are all good things to bring to our attention. We probably shouldn't need to tell you this, but if you've contributed code to Django you might want to lead with that!
  </p>
 </div>
</div>
]]>/></item><item><title>Django 1.3 is out - time to upgrade!</title><link>http://www.revsys.com/tidbits/django-13-upgrade/</link><description/><pubDate>Wed, 23 Mar 2011 19:41:25 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-13-upgrade/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Nearly a year in the making, Django 1.3 is now shipping. It includes a ton of bugfixes along with a bunch of major new features:
  </p>
  <blockquote>
   <ul>
    <li>
     Class-based views.
    </li>
    <li>
     Better support of Python&rsquo;s logging tools.
    </li>
    <li>
     A new tool to help with handling static files.
    </li>
    <li>
     Greatly improved testing utilities via the unittest2 library.
    </li>
    <li>
     Configurable on-delete behavior.
    </li>
    <li>
     And more!
    </li>
   </ul>
  </blockquote>
  <p>
   To help people get a jump on upgrading, I'll be
   <a href="http://www.eventbrite.com/event/1008507473">
    holding a webinar next week
   </a>
   . We'll talk about the new features, go over the steps to follow for a safe and easy upgrade, and cover the "gotchas" to avoid as you upgrade.
  </p>
  <p>
   <a href="http://www.eventbrite.com/event/1008507473">
    You should join us
   </a>
   &mdash; March 31 from 1-3 (central). It'll be a blast.
  </p>
 </div>
</div>
]]>/></item><item><title>The big secret project I've been working on...</title><link>http://www.revsys.com/tidbits/big-secret-project-ive-been-working/</link><description/><pubDate>Mon, 07 Feb 2011 17:07:11 +0000</pubDate><guid>http://www.revsys.com/tidbits/big-secret-project-ive-been-working/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   With the
   <a href="https://www.storymarket.com/blog/2011/feb/08/announcing-storymarket-the-new-network-for-findin/">
    official press release
   </a>
   out the door, I can finally start talking about the project that has been consuming most of my time for the last several months!
  </p>
  <p>
   <a href="https://www.storymarket.com">
    StoryMarket
   </a>
   is an online system for making finding, buying, selling and sharing content a la carte easy. It's primarily focused towards journalists and the news industry, but we expect there to be a large contingent of bloggers and other new media content creators from all sectors. StoryMarket was developed as a partnership between Revolution Systems and
   <a href="http://www2.ljworld.com/about/">
    The World Company
   </a>
   , publisher of the
   <a href="http://www.ljworld.com">
    Lawrence Journal-World
   </a>
   the same great company that brought us our beloved
   <a href="http://www.djangoproject.com">
    Django
   </a>
   .
  </p>
  <p>
   The news industry is in trouble, with declining revenues and increasing costs, the old content syndication models are proving to be more than smaller organizations can bear. You can think of it as a mashup of eBay or Etsy and iTunes with a strong social component similar to LinkedIn. Sellers are given extremely flexible tools for managing their individual prices and rights. From being really open and cheap, to being really restrictive and expensive and everything in between.
  </p>
  <p>
   For example, I could give this particular blog post a price of $20 to all StoryMarket users, $10 for users in the 'Django' group, $5 to my Mom, and make it free to
   <a href="http://twitter.com/#!/holdenweb">
    Steve Holden
   </a>
   (because you have to keep the Steve Holden happy). As for rights, I could choose to only allow it to be used in print and not online, or vice versa. I could also exclude certain competitors from even being able to view the content inside of StoryMarket, let alone be able to purchase the rights to republish it.
  </p>
  <p>
   But it's not limited to textual content. StoryMarket also supports images/photos, audio, video, and data sets. The uses of each should be obvious with the exception of maybe data sets, organizations could for example share polling data in say CSV format between each other. Or demographic data they've collected, anything that might be useful to another party.
  </p>
  <p>
   What I think will be most interesting to watch about this project is the "long tail" of the content. If the Lawrence Journal-World writes a story about a new organic beer being made at the local
   <a href="http://www.freestatebrewing.com/">
    FreeState Brewery
   </a>
   , many other publishers wouldn't think there would be a secondary market for that story, being so locally focused. But they would be wrong, beer related blogs might want to re-run the story,
   <a href="http://www.treehugger.com/">
    treehugger.com
   </a>
   might want it, and so might
   <a href="http://en.wiktionary.org/wiki/zymurgy">
    zymurgy
   </a>
   related magazines.
  </p>
  <p>
   Or imagine a college football game and the thousands of press photos taken at one. Only a couple make it into print or online, but I would wager most of them are in focus. I'm sure these other shots, even if not the best or most interesting shots of the game, might find buyers in the featured player's home town paper.
  </p>
  <p>
   Many of you might be saying, "But doesn't the industry already do this today?". They do, but not with the frequency they should be due to all the friction in the process. You have to hunt down someone to talk with at the original organization, wander your way through voicemail hell, and hope they even respond. Then comes the price negotiation, faxing a contract around, etc, etc. Could be days before you're given the rights you wanted last week. With StoryMarket we make this a simple e-commerce transaction so if you find a story you want at 3:14 AM on a Sunday morning, you can have it live on YOUR site a few moments later.
  </p>
  <p>
   So why is this project of interest to the Django community? Three main reasons, jump out:
  </p>
  <ol>
   <li>
    Larger funded projects like this help employ Django developers. While I did most of the original development, we've been really lucky to snag two huge Django stars
    <a href="http://twitter.com/#!/malcolmt/">
     Malcolm Tredinnick
    </a>
    and
    <a href="http://cartwheelweb.com/">
     Danny Greenfeld
    </a>
    to help us finish up the last bits before our public beta launch. And we managed to entice the awesome
    <a href="http://gregnewman.org/journal/">
     Greg Newman
    </a>
    to do the design.
   </li>
   <li>
    Projects like this also bring small hidden benefits to the community as a whole. Because of this project there are a couple of new re-useable Django Apps (
    <a href="https://github.com/revsys/django-tos">
     django-tos
    </a>
    ,
    <a href="https://github.com/frankwiles/django-app-metrics">
     django-app-metrics
    </a>
    , and
    <a href="https://github.com/frankwiles/django-taggit-suggest">
     django-taggit-suggest
    </a>
    ) out there for everyone to use and build upon. We've also contributed back several bug fixes to various Open Source projects we use in StoryMarket.
   </li>
   <li>
    While Django is used for all sorts of projects these days, it's very heavily used in the news industry from which it came. By helping keep journalism strong and vibrant we're also indirectly helping Django itself.
   </li>
  </ol>
  <p>
   I think it's important for all commercial projects like this to highlight their contributions back to our community. Not only for the warm fuzzy feelings, but to help other developers out there convince their superiors that it is ok and/or even very useful to Open Source the generic reusable aspects of their work. This needs to become more common place and the best way to do that is to talk about it publicly.
  </p>
  <p>
   So what app/libraries/patches has your company released lately? Go shout their praises so we all know about it!
  </p>
 </div>
</div>
]]>/></item><item><title>Giving Back to the Community</title><link>http://www.revsys.com/tidbits/giving-back-community/</link><description>REVSYS sponsors Read The Docs and PyCon 2011</description><pubDate>Mon, 31 Jan 2011 22:02:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/giving-back-community/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We're big fans of open source, so we like to give back to the community whenever we can. Having a strong community is actually personally and professionally selfish: a vibrant community not only helps ensure we keep getting to eat on a regular basis, but it also means there is more useful code available for us to use.
  </p>
  <p>
   Neither Jacob nor myself would be in the position we're in if it wasn't for the open source community. So even though we're a small company, in 2011 we're trying to really step up the ways we give back to the community.
  </p>
  <h4>
   Office Hours
  </h4>
  <p>
   We'll be holding another one of our IRC "office hours" in #revsys on Freenode this coming Friday, February 4th, from 3-5pm Central. We held our
   <a href="/officehours/2010/nov/05/">
    first a few months ago
   </a>
   and it was not only successful, but fun so we're doing it again. We hope to have enough time to do this at least a few times a year.
  </p>
  <h4>
   Sponsoring
   <a href="https://us.pycon.org/2011/home/">
    PyCon
   </a>
  </h4>
  <p>
   Where would we be without Python? Sure we could have spent this money on a new hot tub for the office, or perhaps adding another runway to the RevSys HQ airstrip, but we figured helping
   <a href="http://us.pycon.org/2011/home/">
    PyCon
   </a>
   be awesome might be a better use of our money. So we signed up as a sponsor, joining some other great companies (and few of our clients).
  </p>
  <h4>
   Sponsoring
   <a href="https://readthedocs.org">
    readthedocs.org
   </a>
  </h4>
  <p>
   It's no secret that we're
   <a href="https://jacobian.org/writing/great-documentation/">
    huge documentation nerds
   </a>
   . We've been really enjoying the Sphinx documentation toolkit, so we were super excited when we saw
   <a href="https://readthedocs.org">
    Read the Docs
   </a>
   launch after the 2010
   <a href="https://djangodash.com/">
    DjangoDash
   </a>
   . If you haven't checked it out, you really should: It makes it trivial to setup a spot on the web for your docs. It even gives you free searching and doc versions based on tags in your repository.
  </p>
  <p>
   We think this is a great project that
   <a href="https://ericholscher.com/">
    Eric
   </a>
   ,
   <a href="https://charlesleifer.com/">
    Charles
   </a>
   , and
   <a href="https://bobbygrace.info/">
    Bobby
   </a>
   put together, so when they approached us to help cover their server costs we said "hell yes." We're looking forward to the new features they're working on, and we're honored to be able to help out.
  </p>
 </div>
</div>
]]>/></item><item><title>2010: A Year in Review</title><link>http://www.revsys.com/tidbits/2010-year-review/</link><description>A review of what REVSYS did in 2010</description><pubDate>Sat, 01 Jan 2011 00:48:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/2010-year-review/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   2010 was a great year for
   <a href="https://www.revsys.com/">
    REVSYS
   </a>
   and for Jacob and I, both personally and professionally. Here are some of the highlights:
  </p>
  <ul>
   <li>
    Jacob gave a ton of training classes around the US in cities such as NYC, Boston, Los Angles, and Atlanta. Between his training schedule and speaking engagements he managed to log close to 60,000 miles of travel hitting 31 cities in 4 countries this year. I'd list links to all of Jacob's talks this year, but unfortuantely our webserver doesn't have enough storage for that large of a list! (Ok I'm lying, I'm just lazy and running late for a New Year's Eve party.)
   </li>
   <li>
    We gave a half day tutorial at
    <a href="https://www.oscon.com/oscon2010">
     OSCON
    </a>
    on
    <a href="https://www.oscon.com/oscon2010/public/schedule/detail/14167">
     deploying Django
    </a>
    .
   </li>
   <li>
    I gave a
    <a href="https://djangocon.blip.tv/file/4134070/">
     talk
    </a>
    at
    <a href="https://djangocon.us/">
     DjangoCon
    </a>
    and we helped sponsor the conference this year. It was also the first time I got to meet Steve Holden
   </li>
   <li>
    We were able to work with some of the best developers other there this year on projects, people such as
    <a href="https://twitter.com/malcolmt">
     Malcolm
    </a>
    ,
    <a href="https://pydanny.blogspot.com/">
     Danny
    </a>
    ,
    <a href="https://toastdriven.com/">
     Daniel
    </a>
    , and
    <a href="https://www.travisswicegood.com">
     Travis
    </a>
    . Danny even camped out with us at Chez REVSYS for a few months while he taste tested Lawrence.
   </li>
   <li>
    We refreshed our site with the amazing help of
    <a href="http://gregnewman.org/journal/">
     Greg Newman
    </a>
    and
    <a href="http://edfrazierwriter.com/">
     Ed Frazier
    </a>
    .
   </li>
   <li>
    Jacob bought a farm. Note: "a farm" not bought the proverbial "farm".
   </li>
   <li>
    I fell in love with some new bits of tech, most notably Redis (thanks to
    <a href="https://simonwillison.net/2009/Oct/22/redis/">
     Simon
    </a>
    ), virtualenv and virtualenvwrapper. Being a consultant and juggling many projects at once the simple 'workon' aspect of virtualenvwrapper alone is enough of a reason to switch from buildout. Tip, it's even great to use on entirely non-Python related projects to create any sort of easily switchable shell environment.
   </li>
  </ul>
  <p>
   So what's in store for next year? Well we've got a big announcement coming out soon about a project I've been working on for most of 2010. We're helping sponsor PyCon this year, so Jacob and I will be there. I'll also likely be at both the US and EU DjangoCons this year and I'm sure Jacob will be as well (travel/schedule/farming permitting).
  </p>
  <p>
   Thanks all around to our clients and the wonderful Open Source communities we're lucky to be a part of. 2010 was a great year because of you!
  </p>
 </div>
</div>
]]>/></item><item><title>Office hours transcript posted</title><link>http://www.revsys.com/tidbits/office-hours-transcript/</link><description>Transcript of the REVSYS Office Hours from November 2010</description><pubDate>Sun, 07 Nov 2010 23:05:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/office-hours-transcript/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We held our first open "office hours" this past Friday, November 5th. We've taken the IRC transcript, organized and cleaned it up a bit, and
   <a href="https://www.revsys.com/officehours/2010/nov/05/">
    posted it online
   </a>
   for those who couldn't make it.
  </p>
  <p>
   We had about fifty people show in IRC where they asked questions on a wide variety of topics including deployment, NoSQL, cloud computing, and contributing to Django. It was a blast! A few of my favorite questions:
  </p>
  <blockquote>
   <ul>
    <li>
     <a href="https://www.revsys.com/officehours/2010/nov/05/#question5">
      "How can we 1.5 thousand Django sites" (on a single server)?
     </a>
    </li>
    <li>
     <a href="https://www.revsys.com/officehours/2010/nov/05/#question19">
      "What is the stack that you all are recommending/using these days?"
     </a>
    </li>
    <li>
     <a href="https://www.revsys.com/officehours/2010/nov/05/#question34">
      "How do you handle patching existing [open source] projects?"
     </a>
    </li>
   </ul>
  </blockquote>
  <p>
   We had a great time with this, and will
   <em>
    certainly
   </em>
   be doing it again in the future. If you'd like to hear about future office hours,
   <a href="https://twitter.com/revsys">
    follow us on Twitter
   </a>
   .
  </p>
  <p>
   Also, if you'd like a chance to ask questions like this in person, you might want to attend
   <a href="https://revsys.eventbrite.com/">
    one of my Django classes
   </a>
   in LA and Boston next month.
  </p>
 </div>
</div>
]]>/></item><item><title>Two new Django classes: deployment and the ecosystem</title><link>http://www.revsys.com/tidbits/django-classes-dec/</link><description>Django Training Classes </description><pubDate>Thu, 04 Nov 2010 23:16:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-classes-dec/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Want to buff up your Django chops? Learn some cool new tricks for the new year? You should come to one of my new Django classes next month! I'll be teaching two new one-day classes, both of which evolved from common questions we get here at Revsys. Each class is going to be offered twice, once in LA and once in Boston.
  </p>
  <p>
   For the impatient here are some links and quick info:
  </p>
  <blockquote>
   <ul>
    <li>
     <a href="https://deploydjango-la.eventbrite.com/">
      Django Deployment Workshop - Los Angeles
     </a>
     - December 6, 2010.
    </li>
    <li>
     <a href="https://deploydjango-bos.eventbrite.com/">
      Django Deployment Workshop - Boston
     </a>
     - December 10, 2010.
    </li>
    <li>
     <a href="https://djangoeco-la.eventbrite.com/">
      The Best of the Django Ecosystem - Los Angeles
     </a>
     - December 7, 2010.
    </li>
    <li>
     <a href="https://djangoeco-bos.eventbrite.com/">
      The Best of the Django Ecosystem - Boston
     </a>
     - December 9, 2010.
    </li>
   </ul>
  </blockquote>
  <p>
   For more details on each class, read on...
  </p>
  <p>
   The first class attempts to answer this question: "So... I've written this cool Django site, and it works on my laptop... but now what?"
  </p>
  <p>
   We've increasingly been spending our time helping clients learn how to successfully deploy Django, so I've taken the things we've learned and turned them into the
   <strong>
    Django Deployment Workshop
   </strong>
   . You're invited to bring your own Django sites and I'll show you how to put them into production. At the end of the class, you'll walk away with a production environment that could easily serve most small-to-medium Django sites.
  </p>
  <p>
   To sign up:
  </p>
  <blockquote>
   <ul>
    <li>
     <a href="https://deploydjango-la.eventbrite.com/">
      Django Deployment Workshop - Los Angeles
     </a>
     - December 6, 2010.
    </li>
    <li>
     <a href="https://deploydjango-bos.eventbrite.com/">
      Django Deployment Workshop - Boston
     </a>
     - December 10, 2010.
    </li>
   </ul>
  </blockquote>
  <p>
   The second class focuses on Django's app ecosystem. Our clients tell us that they love all the apps available out there, but that they have a really hard time finding the
   <em>
    best
   </em>
   ones and figuring out how to use them. So I've put together
   <strong>
    The Best of the Django Ecosystem
   </strong>
   , a class that tries to answer both questions. We'll look at some of the coolest tools out there in the ecosystem (including
   <a href="http://south.aeracode.org/">
    South
   </a>
   ,
   <a href="http://celeryproject.org/">
    Celery
   </a>
   , and
   <a href="http://haystacksearch.org/">
    Haystack
   </a>
   ) and learn how to integrate them with an existing site. Again, you're invited to bring your existing Django site; I'll give plenty of time and hands-on help getting these apps wired up..
  </p>
  <p>
   Dates and details:
  </p>
  <blockquote>
   <ul>
    <li>
     <a href="https://djangoeco-la.eventbrite.com/">
      The Best of the Django Ecosystem - Los Angeles
     </a>
     - December 7, 2010.
    </li>
    <li>
     <a href="https://djangoeco-bos.eventbrite.com/">
      The Best of the Django Ecosystem - Boston
     </a>
     - December 9, 2010.
    </li>
   </ul>
  </blockquote>
  <p>
   <strong>
    Early-bird registration is open now
   </strong>
   . You'll save over $100 if you register before November 22nd, so get on it. There's also a combo ticket available in both cities if you'd like to attend both classes and group rates if you'd like to bring the whole team.
  </p>
  <p>
   Oh, and to answer the inevitable FAQ: yes, we'd love to bring these classes to other cities! We're looking at a bunch of locations for 2011, and if you'd like to throw your hometown in the ring we've got a
   <a href="https://spreadsheets.google.com/a/jacobian.org/viewform?hl=en&amp;formkey=cmJSYWNvZEZqN085QjBCaUtKRTh4bUE6MA..">
    quick little survey
   </a>
   to help us figure out where to host future classes.
  </p>
  <p>
   If you have other questions, please
   <a href="/contact/">
    get in touch
   </a>
   , or drop into our
   <a href="https://www.revsys.com/blog/2010/nov/02/open-office-hours/">
    office hours
   </a>
   tomorrow afternoon.
  </p>
 </div>
</div>
]]>/></item><item><title>Open "Office Hours"</title><link>http://www.revsys.com/tidbits/open-office-hours/</link><description>REVSYS held an open office hours meeting. </description><pubDate>Tue, 02 Nov 2010 16:55:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/open-office-hours/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We've been wanting to try this for awhile, but have been too busy to actually try it out until now. Jacob Kaplan-Moss and I will be holding open "office hours" this Friday November 5th between 1-3 PM CDT in IRC. Come join us in #revsys on freenode and pick our brains. Or just come to listen and learn.
  </p>
  <p>
   The concept is pretty simple, Jacob and I (and likely many other knowledgeable Djangonauts) will be in the channel and attempt to answer your questions related to:
  </p>
  <ul>
   <li>
    Django/Python
   </li>
   <li>
    Deployment
   </li>
   <li>
    Scaling/Performance
   </li>
   <li>
    PostgreSQL
   </li>
   <li>
    Anything else we happen to know...
   </li>
  </ul>
  <p>
   We're going to log the discussion and will provide a transcript online afterwards.
  </p>
  <p>
   <strong>
    Update:
   </strong>
   <a href="https://www.revsys.com/officehours/2010/nov/05/">
    The transcript is now available.
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>Centralized logging for fun and profit!</title><link>http://www.revsys.com/tidbits/centralized-logging-fun-and-profit/</link><description/><pubDate>Fri, 27 Aug 2010 00:12:23 +0000</pubDate><guid>http://www.revsys.com/tidbits/centralized-logging-fun-and-profit/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Setting up a centralized log server using syslog isn't as hard as many may believe. Whether it's logs from Apache, nginx, email services, or even from your own Python applications having a central log server gives you many benefits:
  </p>
  <h3>
   Benefits to a centralized logs
  </h3>
  <ul>
   <li>
    Reduces disk space usage and disk I/O on core servers that should be busy doing something else. This is especially true if you want to log all queries to your database. Doing this on the same disk as your actual database creates a write for every read and an extra write for every write.
   </li>
   <li>
    Removes logs from the server in the event of an intrusion or system failure. By having the logs elsewhere you at least have a chance of finding something useful about what happened.
   </li>
   <li>
    All of your logs are in one place, duh! This makes things like grepping through say Apache error logs across multiple webservers easier than bouncing around between boxes. Any log processing and log rotation can also be centralized which may delay your sysadmin from finally snapping and killing everyone.
   </li>
  </ul>
  <h3>
   Syslog Review
  </h3>
  <p>
   In case you aren't terribly familiar with how syslog works, here's a quick primer. Syslog separates out various logs using two items.
   <em>
    Facilities
   </em>
   and
   <em>
    Levels
   </em>
   . Here are the standard facilities:
  </p>
  <ul>
   <li>
    0 kernel messages
   </li>
   <li>
    1 user-level messages
   </li>
   <li>
    2 mail system
   </li>
   <li>
    3 system daemons
   </li>
   <li>
    4 security/authorization messages
   </li>
   <li>
    5 messages generated internally by syslogd
   </li>
   <li>
    6 line printer subsystem
   </li>
   <li>
    7 network news subsystem
   </li>
   <li>
    8 UUCP subsystem
   </li>
   <li>
    9 clock daemon
   </li>
   <li>
    10 security/authorization messages
   </li>
   <li>
    11 FTP daemon
   </li>
   <li>
    12 NTP subsystem
   </li>
   <li>
    13 log audit
   </li>
   <li>
    14 log alert
   </li>
   <li>
    15 clock daemon
   </li>
   <li>
    16 local use 0 (local0)
   </li>
   <li>
    17 local use 1 (local1)
   </li>
   <li>
    18 local use 2 (local2)
   </li>
   <li>
    19 local use 3 (local3)
   </li>
   <li>
    20 local use 4 (local4)
   </li>
   <li>
    21 local use 5 (local5)
   </li>
   <li>
    22 local use 6 (local6)
   </li>
   <li>
    23 local use 7 (local7)
   </li>
  </ul>
  <p>
   For each facility logs are sent using a particular level, the levels are:
  </p>
  <ul>
   <li>
    0 Emergency: system is unusable
   </li>
   <li>
    1 Alert: action must be taken immediately
   </li>
   <li>
    2 Critical: critical conditions
   </li>
   <li>
    3 Error: error conditions
   </li>
   <li>
    4 Warning: warning conditions
   </li>
   <li>
    5 Notice: normal but significant condition
   </li>
   <li>
    6 Informational: informational messages
   </li>
   <li>
    7 Debug: debug-level messages
   </li>
  </ul>
  <p>
   So for any given log message you set these two options to give a hint as to where the logs should be directed. For example, if an email server receives a new message it would likely be sent as
   <strong>
    mail.info
   </strong>
   and a kernel panic would be sent using
   <strong>
    kern.emerg
   </strong>
  </p>
  <p>
   The receiving syslog server then can be configured to direct log messages of a certain facility and/or log level to various files. For example, a default
   <a href="http://ubuntu.com">
    Ubuntu
   </a>
   system has some settings like this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">daemon</span><span class="o">.*</span><span class="w">        </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">daemon</span><span class="o">.</span><span class="n">log</span>
<span class="n">kern</span><span class="o">.*</span><span class="w">          </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">kern</span><span class="o">.</span><span class="n">log</span>
<span class="n">mail</span><span class="o">.*</span><span class="w">          </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">mail</span><span class="o">.</span><span class="n">log</span>
</code></pre>
  </div>
  <p>
   But you can also do more granular separation for example you might want to log
   <strong>
    mail.err
   </strong>
   into a separate file from the main mail logs to make it easier to spot new errors with this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">mail</span><span class="o">.*</span><span class="w">        </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">mail</span><span class="o">.</span><span class="n">log</span>
<span class="n">mail</span><span class="o">.</span><span class="n">err</span><span class="w">      </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">mail</span><span class="o">-</span><span class="n">errors</span><span class="o">.</span><span class="n">log</span>
</code></pre>
  </div>
  <h3>
   Setting up your central server
  </h3>
  <p>
   Configuring the master log server is pretty easy. On Ubuntu the default syslog server is
   <a href="http://www.rsyslog.com/">
    rsyslog
   </a>
   and that's what I'll be using as an example here. You'll need to edit
   <strong>
    /etc/rsyslog.conf
   </strong>
   and uncomment the UDP module. You could also use the TCP module, but that one binds to all of your interfaces so you will need to restrict access to it with iptables (or some other mechanism) in order to not allow hackers to fill up your disks remotely. So your configuration should now contain these uncommented lines, where 'x' is an internal protected IP address:
  </p>
  <div class="codehilite">
   <pre><span></span><code>$ModLoad imudp
$UDPServerAddress x.x.x.x
$UDPServerRun 514
</code></pre>
  </div>
  <p>
   And then restart rsyslogd. See that wasn't so hard...
  </p>
  <h3>
   Setting up the remote log sending servers
  </h3>
  <p>
   Setting up your remote servers is even easier. If you want to send ALL of your logs to the central server it's just a matter of adding one line to the top of
   <strong>
    /etc/rsyslog.d/50-default.conf
   </strong>
   . That line is:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">*</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="nv">@x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="nl">x</span><span class="p">:</span><span class="mi">514</span>
</code></pre>
  </div>
  <p>
   This will send all logs of any facility and any level to the server. Note that the local syslog will, as configured by default, still log locally. So if you don't want that be sure to remove all of the other configuration in this file.
  </p>
  <p>
   You can also get fancy here and keep some logs on the local server and only send some things remotely. For most of your custom apps and logs you'll want to be using the
   <em>
    LOCAL[0-9]
   </em>
   facilities. Let's say we're going to want to centrally log our Python logs and Apache error logs. We'll be using
   <em>
    LOCAL0
   </em>
   and
   <em>
    LOCAL1
   </em>
   for them respectively. That config would look like:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">local0</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="nv">@x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="nl">x</span><span class="p">:</span><span class="mi">514</span>
<span class="n">local1</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="nv">@x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="n">x</span><span class="p">.</span><span class="nl">x</span><span class="p">:</span><span class="mi">514</span>
</code></pre>
  </div>
  <p>
   Keep in mind however that most systems have
   <em>
    <em>
     .info_ , _
    </em>
    .debug
   </em>
   , etc. configurations setup so you might be duplicating your data. If you poke around this file you'll see lots of configurations ending in
   <em>
    .none
   </em>
   , this instructs rsyslog to not include those facilities in this particular file. So for example, you'd want to edit your
   <strong>
    /var/log/syslog
   </strong>
   to resemble this:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="o">*.*</span><span class="p">;</span><span class="n">auth</span><span class="p">,</span><span class="n">authpriv</span><span class="p">,</span><span class="n">local0</span><span class="p">,</span><span class="n">local1</span><span class="o">.</span><span class="n">none</span><span class="w">        </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">syslog</span>
</code></pre>
  </div>
  <h3>
   Additional help and features
  </h3>
  <p>
   While most applications are easy to setup for use with syslog, here are some pointers for more info on the subject:
  </p>
  <ul>
   <li>
    Apache support sending error logs to syslog via the
    <strong>
     ErrorLog syslog:local1
    </strong>
    configuration option. However, it does not support sending access logs directly. To do that you'll need a
    <a href="http://wiki.rsyslog.com/index.php/Working_Apache_and_Rsyslog_configuration">
     small script
    </a>
    and pipe your access logs through it.
   </li>
   <li>
    For more information on setting up your own Python code to use syslog, check out the
    <a href="http://docs.python.org/library/logging.html#sysloghandler">
     logging.handlers.SysLogHandler
    </a>
    handler for the logging module.
   </li>
  </ul>
  <p>
   We've only really scratched the surface of the features of rsyslog with this setup. You can configure it to do some fairly advanced separation of logs based on the sending host, application name, and other various aspects of the message itself. Refer to the
   <a href="http://www.rsyslog.com/doc/manual.html">
    rsyslog documentation
   </a>
   for more information on that.
  </p>
  <p>
   Happy Logging!
  </p>
 </div>
</div>
]]>/></item><item><title>Investing in Yourself - A review of Django 1.1 Testing and Debugging by Karen M. Tracey</title><link>http://www.revsys.com/tidbits/investing-yourself-review-django-11-testing-and-debugging-karen-m-tracey/</link><description/><pubDate>Tue, 06 Jul 2010 18:20:05 +0000</pubDate><guid>http://www.revsys.com/tidbits/investing-yourself-review-django-11-testing-and-debugging-karen-m-tracey/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Packt Publishing recently asked me to read and review
   <a href="https://www.packtpub.com/django-1-1-testing-and-debugging/book">
    Django 1.1 Testing and Debugging
   </a>
   and I have to admit I really enjoyed reading this book. Often I find myself debating whether or not to purchase a new development book. I'm usually thinking "If I spend $XX.XX on this book, will I really learn anything worth that much?". Especially considering most answers are a few Google searches away. I can happily attest this book is definitely worth the cost.
  </p>
  <p>
   The book starts off with the usual introduction to testing, discussing both Doctests and Unittest, which is obviously required for a book on this subject. However, this book differs greatly from many in that it walks you through testing and debugging your application as you would when building a
   <em>
    real
   </em>
   application. Many tech books strive to be a great tutorial, but often fall short of the mark and end up just being some verbiage around a rehashing of the available documentation. They end up being more reference than tutorial.
   <a href="https://www.packtpub.com/django-1-1-testing-and-debugging/book">
    Django 1.1 Testing and Debugging
   </a>
   however does a great job of walking you through real world scenarios. For example, it covers topics (and in the proper order in my opinion) like:
  </p>
  <ul>
   <li>
    Test coverage
   </li>
   <li>
    Everything you would ever want to know about the default Django debug page
   </li>
   <li>
    How to debug urls.py issues
   </li>
   <li>
    How debugging differs between the development server and what to do in production
   </li>
   <li>
    Getting ahold of what database queries were actually generated on a given page
   </li>
   <li>
    Using the wonderful
    <a href="http://github.com/robhudson/django-debug-toolbar">
     Django Debug Toolbar
    </a>
   </li>
   <li>
    Using logging to help debug your code
   </li>
   <li>
    Effectively using the Python Debugger
   </li>
   <li>
    How to report Django bugs
   </li>
  </ul>
  <p>
   All of these tools and techniques should be in all of our development arsenals, but this is the first book I've seen that puts them all together in a way that is accessible to Django developers of nearly any skill level. Beginners should read the book, even if they don't understand everything right away, just to know what options they do have at their disposal. However, more advanced developers will likely find a few golden nuggets of wisdom that will pay productivity dividends for years to come.
  </p>
  <p>
   Hence the title of this post
   <em>
    Investing in Yourself
   </em>
   . While Packt was nice enough to give me a free copy of the book to review, had I paid full list price the time savings from the few little nuggets of wisdom I either didn't know about or for whatever reason never clicked with me before would have paid for the book many times over in just the first few weeks.
  </p>
  <p>
   I highly encourage you to pick this up and with it improve your code going forward. You can check out a
   <a href="https://www.packtpub.com/sites/default/files/7566_Django%201.1%20Testing%20and%20Debugging_SampleChapter_1.pdf">
    sample chapter
   </a>
   of the book to whet your appetite a bit.
  </p>
 </div>
</div>
]]>/></item><item><title>Early registration for our Advanced Django class ends soon</title><link>http://www.revsys.com/tidbits/early-registration-advanced-django-ends-soon/</link><description/><pubDate>Tue, 16 Feb 2010 22:32:44 +0000</pubDate><guid>http://www.revsys.com/tidbits/early-registration-advanced-django-ends-soon/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Early registration ends Friday for the March
   <a href="http://www.revsys.com/training/django/march-2010/">
    Advanced Django Class
   </a>
   I'm teaching, so if you're planning on coming, you should
   <a href="http://revys-training-march-2010.eventbrite.com/">
    sign up
   </a>
   soon!
  </p>
  <p>
   I'm really excited about this class: we'll get to dive really deep into the good parts of Django. We'll cover all sorts of advanced usage of Django's APIs, spend a bunch of time playing with all the cool stuff out there in the Django ecosystem, and actually spend a whole day setting up and configuring a real-world deployment stack.
  </p>
  <p>
   Many more details are over
   <a href="http://www.revsys.com/training/django/march-2010/">
    on the class description page
   </a>
   , along with
   <a href="http://www.revsys.com/training/django/march-2010/#questions">
    contact info
   </a>
   if you've got any questions.
  </p>
 </div>
</div>
]]>/></item><item><title>Django Training in 2010</title><link>http://www.revsys.com/tidbits/django-training-2010/</link><description>Django Training in 2010</description><pubDate>Mon, 18 Jan 2010 23:28:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-training-2010/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   The new year's shaping up to be a great one for Django: Django 1.2 is on track to ship this March, and there's no doubt in my mind that it'll be the best release ever.
  </p>
  <p>
   Because of this, we expect to see a lot of new people wanting to learn Django next year, so we're stepping up our
   <a href="https://revsys.com/training/django/">
    training offerings
   </a>
   in the new year.
  </p>
  <p>
   This Friday, I'll be holding
   <a href="https://hwebdjmc01.eventbrite.com/">
    a one-day Django workshop in New York City
   </a>
   . The class is mostly full, but
   <a href="https://hwebdjmc01.eventbrite.com/">
    a few spots are still available
   </a>
   . This is the second in a series of workshops we've been holding in conjunction with
   <a href="https://holdenweb.com/">
    HoldenWeb
   </a>
   , and we plan to offer more (see below)
  </p>
  <p>
   In March, I'll be teaching
   <a href="https://www.revsys.com/training/django/march-2010/">
    a week-long advanced Django class
   </a>
   in Kansas City. This class is perfect for folks who know Django but want to
   <em>
    really
   </em>
   learn all the ins and outs. We'll look at a whole bunch of advanced uses of Django, learn how to use optional add-ons like GeoDjango, Piston, Haystack, and even dig into the internals of Django a bit. We'll also spend a whole day setting up a real-world Django production environment and tuning it for performance.
  </p>
  <p>
   I'm really looking forward to this class: there's some really fantastic bits buried deep in Django that I don't get much call to talk about.
  </p>
  <p>
   Finally, we're currently scouting locations and topics for future classes and
   <a href="https://spreadsheets.google.com/a/jacobian.org/viewform?hl=en&amp;formkey=cmJSYWNvZEZqN085QjBCaUtKRTh4bUE6MA..">
    would like your input
   </a>
   ! If you'd be interested in attending a future Django class there's
   <a href="https://spreadsheets.google.com/a/jacobian.org/viewform?hl=en&amp;formkey=cmJSYWNvZEZqN085QjBCaUtKRTh4bUE6MA..">
    a quick questionnaire you can fill out
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Django 1.0 Template Development Review</title><link>http://www.revsys.com/tidbits/django-10-template-development-review/</link><description/><pubDate>Thu, 17 Sep 2009 17:52:52 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-10-template-development-review/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <a href="http://www.packtpub.com/django-1.0-template-design-practical-guide/mid/210909irxdzd?utm_source=revsys.com&amp;utm_medium=bookrev&amp;utm_content=other&amp;utm_campaign=mdb_000707">
    Django 1.0 Template Development
   </a>
   by Scott Newman actually surprised me in it's depth of covering the topic.
  </p>
  <p>
   I assumed it would be written with the absolute Django Template beginner in mind. While it is definitely an appropriate book for beginners, it also covers more advanced topics such as:
  </p>
  <ul>
   <li>
    Writing custom template tags and filters, Chapter 7
   </li>
   <li>
    Covers pagination quite well in Chapter 8
   </li>
   <li>
    Customizing the Django admin's look and feel in Chapter 9
   </li>
   <li>
    And gives a good designer intro to caching in Chapter 10
   </li>
  </ul>
  <p>
   This book should definitely be required reading for web designers that are looking to use Django. As someone who has read and re-read the wonderful
   <a href="http://www.djangoproject.com/en/dev/">
    Django Documentation
   </a>
   many times this book does a great job of distilling all of that reference knowledge into book form.
  </p>
 </div>
</div>
]]>/></item><item><title>Django Training</title><link>http://www.revsys.com/tidbits/django-training/</link><description>Django Training 2009 by JKM</description><pubDate>Thu, 16 Jul 2009 18:14:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-training/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I'm pleased to announce that this September, Revolution Systems will be offering a
   <a href="https://www.revsys.com/training/django/sept-2009/">
    four-day Django training course
   </a>
   here in Kansas City, taught by Jacob Kaplan-Moss. Jacob has taught a number of similar courses privately in the past, but this is the first time we're opening this up to the public.
  </p>
  <p>
   If you've been wanting to really learn Django, this is a great chance. The class is intended for relative beginners to Django, but by the time you leave you'll know enough to create some pretty awesome websites.
  </p>
  <p>
   The class will be Monday, September 21 - Friday, September 25, 2009 in Kansas City, MO.
  </p>
  <p>
   For more, or to sign up,
   <a href="https://www.revsys.com/training/django/sept-2009/">
    check out the full details
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Some quick updates</title><link>http://www.revsys.com/tidbits/some-quick-updates/</link><description/><pubDate>Mon, 30 Mar 2009 15:15:46 +0000</pubDate><guid>http://www.revsys.com/tidbits/some-quick-updates/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It's been a busy and exciting week for us.  Jacob has been at
   <a href="http://us.pycon.org/2009/about/" title="US Python Conference">
    PyCon
   </a>
   in Chicago where he is participating in a number of panel discussions and giving quite a
   <a href="http://jacobian.org/speaking/" title="talks by Jacob Kaplan-Moss">
    few talks
   </a>
   as well.   Right now I imagine he's neck deep in code in the
   <a href="http://us.pycon.org/2009/sprints/projects/django/" title="django code sprint">
    Django sprint
   </a>
   helping to finish up the upcoming
   <a href="http://www.djangoproject.com/weblog/2009/mar/23/11-beta-1/">
    1.1 release
   </a>
   . If you're running a production site built with Django you should absolutely check out the talk he is giving with
   <a href="http://b-list.org">
    James Bennett
   </a>
   on
   <a href="http://jacobian.org/speaking/2009/real-world-django/">
    Real World Django
   </a>
   .
  </p>
  <p>
   While my week has been busy hacking away on several client projects and moving my main work machine to a shiny new
   <a href="http://www.amazon.com/gp/product/B000VR3P9K?ie=UTF8&amp;tag=revosystblog-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000VR3P9K">
    MacBook Pro
   </a>
   (can't recommend these highly enough),
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=as2&amp;o=1&amp;a=B000VR3P9K"/>
   I was interviewed by Daniel Dern of Business Trends Quarterly in his post about scaling and performance titled
   <a href="http://www.btquarterly.com/?mc=brain-brawn&amp;page=btp-viewarticle">
    For Scaling, Brains May Beat Brawn
   </a>
   . We talk about how just throwing more money and hardware at a problem is not always the best solution.  Often there are architectural, design, and/or configuration changes that can bring significant cost savings to your project.  Both in terms of the hardware necessary to keep everything flowing, but also in on going system maintenance labor costs.  I'm not talking about pre-optimization evils or complicating things for your admin, often these changes are transparent to day to day operations, but certainly not to your bottom line. For example, just using the proper RAID levels and physical disk configurations for your particular PostgreSQL database can be a huge win in performance.
  </p>
  <p>
   I also added a tidbit of wisdom in an advice post to budding entrepreneurs called
   <a href="http://www.toiletpaperentrepreneur.com/blog/163-ways-how-to-become-an-entrepreneur">
    163 Ways How To Become An Entrepreneur
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Welcome Jacob Kaplan-Moss</title><link>http://www.revsys.com/tidbits/welcome-jacob-kaplan-moss/</link><description>Jacob Kaplan-Moss joins REVSYS as a partner.</description><pubDate>Thu, 05 Mar 2009 13:02:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/welcome-jacob-kaplan-moss/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I'm very pleased to announce that
   <a href="/about/bio/jacob-kaplan-moss" title="Short biography for Jacob Kaplan-Moss">
    Jacob Kaplan-Moss
   </a>
   has joined Revolution Systems to head up a new line of services around the ever growing
   <a href="https://www.djangoproject.com">
    Django
   </a>
   web development framework. First up are
   <a href="/services/django/" title="Django Support Plans and Packages">
    commercial Django Support Plans
   </a>
   , but look for more Django related offerings in the near future.
  </p>
  <p>
   Jacob has been a good friend of mine since before Django was even released. It was a pleasure to work with him at our previous day jobs and I'm very excited for the future ahead. Not only is he
   <em>
    obviously
   </em>
   an authority on Django, he 's an amazing developer and generally an expert on all things tech. Jacob and Adrian are both great examples of how to lead an Open Source project and grow a real community around it.
  </p>
  <p>
   By offering
   <a href="/services/django/">
    Django Support
   </a>
   packages we hope to help adoption of Django in the business world, which helps grow the community at large.
  </p>
 </div>
</div>
]]>/></item><item><title>ORD Camp a Huge Success</title><link>http://www.revsys.com/tidbits/ord-camp-a-huge-success/</link><description/><pubDate>Tue, 03 Feb 2009 07:30:39 +0000</pubDate><guid>http://www.revsys.com/tidbits/ord-camp-a-huge-success/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was luck enough to be invited to attend
   <a href="http://www.ordcamp.com">
    ORD Camp
   </a>
   this last weekend in blisteringly cold Chicago. ORD Camp is an invite only, FooCamp style unconference targeted at geeks living in the Midwest. Having never attended a FooCamp style event I wasn't sure what to expect. I can now say if you ever have the opportunity to attend an event like this it is well worth your time.
  </p>
  <p>
   As you can see from the
   <a href="http://www.ordcamp.com/whos-coming">
    attendee list
   </a>
   it was a very diverse group of people, not just the usual crowd of notable Open Source geeks. The amount of brain power in that room was simply amazing and I can't remember when I had as much fun. Some sessions were presentations, others were just focused discussions. Everything from how words work, brewing beer, life hacking, to what not to do as a startup.
  </p>
  <p>
   While I loved the sessions the most fun was getting into random conversations ( some ended up being NSFW after midnight and many beers ) others were more typical. Spent some time talking with people about PostgreSQL's advantages over MySQL, alternative business models, how a certain entrepreneur might improve the performance of their servers, etc.
  </p>
  <p>
   It is difficult to determine how important this conference will be to my business in the future, but I can easily say that it has increased my drive, ambition, and overall excitement level. Passionate people, doing amazing things will do that to you! I can't wait to attend next year.
  </p>
 </div>
</div>
]]>/></item><item><title>ResumeBucket.com Launches</title><link>http://www.revsys.com/tidbits/resumebucketcom-launches/</link><description/><pubDate>Mon, 29 Dec 2008 18:19:16 +0000</pubDate><guid>http://www.revsys.com/tidbits/resumebucketcom-launches/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I hope everyone had a great holiday this year.  For the past few months I've been working on
   <a href="http://www.resumebucket.com" title="ResumeBucket -- Online Resume Service">
    an online resume site ResumeBucket.com
   </a>
   and I need your help taking it for a test drive.  Our goal with this site is to create a site where you can upload your current resume in Word form, build a new resume using our online resume creation tool, or even just type in what you want using using our online text editor.
  </p>
  <p>
   The site gives you a unique URL you can give out to friends and prospective employers so they can instantly access an up to date copy of your resume.  There are options for them to also download a copy of your resume in Word or PDF format.  For an example of how your resume can look see our
   <a href="http://www.resumebucket.com/joshstomel">
    CEO's resume
   </a>
   page.
  </p>
  <p>
   Unlike the other resume services out there, employers are able to search the database without paying any huge fees which will drive more qualified employment leads to your INBOX.
  </p>
  <p>
   Please take a few minutes to kick the tires.  You can leave feedback here in the comments or E-mail me at
   <a href="mailto:frank@revsys.com">
    frank@revsys.com
   </a>
   . Thanks!
  </p>
 </div>
</div>
]]>/></item><item><title>Why isn't PostgreSQL using my index?</title><link>http://www.revsys.com/tidbits/why-isnt-postgresql-using-my-index/</link><description>Reasons Postgres might not be using the INDEX you created. </description><pubDate>Tue, 16 Sep 2008 15:16:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/why-isnt-postgresql-using-my-index/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   This is a common question I see from PostgreSQL users.  In fact, someone was just in IRC asking it and it prompted this post.  The exchange usually goes:
  </p>
  <p>
   Steve: I have this column foo and I have an index on it, but when I do SELECT * FROM table WHERE foo = X, EXPLAIN doesn't use the index.  What am I doing wrong?
  </p>
  <p>
   90% of the time the answer is unfortunately "Because the query optimizer is smarter than you are". Or maybe it's
   <em>
    fortunately
   </em>
   depending on how you think about it!
  </p>
  <p>
   In this user's particular case he was mocking up a database schema and had only one row in the table he was querying against.  People who are more familiar with PostgreSQL will probably roll their eyes at the question, but if you put yourself in the user's shoes I can see how people would be confused by this.  They are thinking,
   <em>
    " I put an index on there on purpose, why the hell isn't it working?"
   </em>
   .
  </p>
  <p>
   PostgreSQL's query optimizer is
   <strong>
    smart,
    <em>
     really smart
    </em>
   </strong>
   and unless you have evidence otherwise you should trust what it is doing.   In this particular case, the optimizer realizes that if a table has only a few rows that using the index is actually
   <em>
    slower
   </em>
   than just spinning through the entire table.   Just because PostgreSQL isn't using your index today with a small number of rows, does not mean it won't choose to use it later when you have more data or the query changes. Because he was just mocking up a design he didn't have real world data, which is almost always a bad way to performance tune your system unless you are very familiar with how PostgreSQL behaves.
  </p>
  <p>
   Now there are other reasons why it might not be using the index.  If you have lots of data and the query you're running appears that it would benefit from the index, it might be a simple matter of forgetting to run an ANALYZE on the table or not having autovacuum turned on.  Until PostgreSQL re-analyzes your table it doesn't really "know" about that index to take it into account when building the query plan.
  </p>
  <p>
   While performance tuning PostgreSQL is much easier and better documented than in days gone by, it can still be very confusing and time consuming for the inexperienced.  If your business needs help tuning their system you might consider my
   <a href="/services/postgresql/">
    PostgreSQL Tuning Service
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Fret Free -- Introduction to Django and the Django Software Foundation</title><link>http://www.revsys.com/tidbits/fret-free-introduction-to-django-and-the-django-software-foundation/</link><description/><pubDate>Wed, 20 Aug 2008 17:07:40 +0000</pubDate><guid>http://www.revsys.com/tidbits/fret-free-introduction-to-django-and-the-django-software-foundation/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <a href="http://www.linuxpromagazine.com">
    LinuxPro Magazine
   </a>
   just released my latest article, an introduction to
   <a href="http://www.djangoproject.com">
    Django
   </a>
   and some discussion about the newly created
   <a href="http://www.djangoproject.com/foundation/">
    Django Software Foundation
   </a>
   . Being a life long Perl user, I didn't think I would enjoy Django at all. I have to admit that it is a VERY polished system.  It has great PostgreSQL support, in fact the core developers smartly prefer it over MySQL for their own systems.
  </p>
  <p>
   You can download a PDF copy of the article at,
   <a href="http://www.linuxpromagazine.com/issues/2008/95/fret_free">
    Fret Free -- Django and the Django Software Foundation
   </a>
   .  The print issue will hit the stands in October.  Hope you enjoy it!
  </p>
 </div>
</div>
]]>/></item><item><title>Installing Apache2::Request on a 64-bit system</title><link>http://www.revsys.com/tidbits/installing-apache2request-on-a-64-bit-system/</link><description/><pubDate>Tue, 17 Jun 2008 07:44:02 +0000</pubDate><guid>http://www.revsys.com/tidbits/installing-apache2request-on-a-64-bit-system/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I usually shy away from using 64-bit systems unless there is a clear need for it.  But with more and more hosting companies installing 64-but Linux distrobutions by default I sometimes have to suffer through.
  </p>
  <p>
   Today I ran into a very small bug in installing Apache2::Request modules via the CPAN shell.  This simply installs the default libapreq2 library with the Perl bindings for you. When attempting to start Apache I received this error:
  </p>
  <blockquote>
   <p>
    Can't load '/usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/auto/APR/Request/Request.so' for module APR::Request: libapreq2.so.2: cannot open shared object file: No such file or directory at /usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/DynaLoader.pm line 230. at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/APR/Request/Param.pm line 27 Compilation failed in require at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/APR/Request/Param.pm line 27. BEGIN failed--compilation aborted at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/APR/Request/Param.pm line 27. Compilation failed in require at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/Apache2/Request.pm line 2. BEGIN failed--compilation aborted at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/Apache2/Request.pm line 2.
   </p>
  </blockquote>
  <p>
   Basically it is saying it can't load libapreq2.so.2.  Digging around in the system I found it had installed them in /usr/lib instead of the proper /usr/lib64.  The actual shared library was compiled for 64-bit, but was just installed in the wrong location.  A quick symlink like this fixes the isuse:
  </p>
  <blockquote>
   <p>
    ln -s /usr/lib/libapreq2.so.2 /usr/lib64/libapreq2.so.2
   </p>
  </blockquote>
  <p>
   Hope this helps you get over this small issue.
  </p>
 </div>
</div>
]]>/></item><item><title>Django Software Foundation</title><link>http://www.revsys.com/tidbits/django-software-foundation/</link><description/><pubDate>Tue, 17 Jun 2008 06:38:38 +0000</pubDate><guid>http://www.revsys.com/tidbits/django-software-foundation/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   The
   <a href="http://www.djangoproject.com">
    Django
   </a>
   guys have scored another milestone today with the creation of the
   <a href="http://www.djangoproject.com/foundation/">
    Django Software Foundation
   </a>
   . Being a huge Open Source advocate I love hearing great news like this.  You can read more about the foundation at:
  </p>
  <ul>
   <li>
    <a href="http://www.djangoproject.com/weblog/2008/jun/17/foundation/">
     Announcing the Django Software Foundation
    </a>
   </li>
   <li>
    <a href="http://www2.ljworld.com/news/2008/jun/17/new_foundation_django/">
     New Foundation for Django
    </a>
   </li>
  </ul>
  <p>
   On an unrelated note, an article I wrote for
   <a href="http://www.linux-magazine.com">
    LinuxPro Magazine
   </a>
   last November about using
   <a href="http://www.danga.com/perlbal">
    Perlbal
   </a>
   was recently put online.  You can read it at
   <a href="http://w3.linux-magazine.com/issue/84/Perlbal.pdf">
    The Juggler -- Let the nimble Perlbal webserver keep your traffic in balance
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Test Driven Development and Getting Things Done</title><link>http://www.revsys.com/tidbits/test-driven-development-and-getting-things-done/</link><description/><pubDate>Fri, 16 May 2008 15:54:26 +0000</pubDate><guid>http://www.revsys.com/tidbits/test-driven-development-and-getting-things-done/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I'm sure someone, somewhere, has already determined this.  It's probably been blogged about before, but I just realized something today.  Test driven development dovetails nicely with
   <a href="http://www.davidco.com/">
    David Allen's
   </a>
   time management book
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FGetting-Things-Done-Stress-Free-Productivity%2Fdp%2F0142000280%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1210952702%26sr%3D1-1&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    Getting Things Done
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   .  By creating your tests first, with or without even marking them as TODO tests, builds you a TODO list of sorts for your project.
  </p>
  <p>
   Other than just being a
   <em>
    different
   </em>
   way of doing things, I never really took to test driven development for small or personal projects.   Most of my projects are web applications and "unit testing" modules never seemed worthwhile because you weren't easily able to test the database and web interactions.  Well I can easily say that
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FPerl-Testing-Developers-Notebook%2Fdp%2F0596100922%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1210952970%26sr%3D1-1&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    Perl Testing: A Developer's Notebook
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   by Ian Langworth and chromatic has shown me the way.
  </p>
  <p>
   I had always focused too much on ensuring that 'make test' would be able to be run from anywhere and simply did not think about the fact that I could automate most of the work of building a test environment and if I wasn't able, to bail on those tests so they would be skipped.  For example, you could automate creating your PostgreSQL database, but what if PostgreSQL isn't installed on the server or what about when your user isn't allowed to create databases?  You just bail out of those tests.
  </p>
  <p>
   I was definitely too concerned with getting 100% of the tests to pass 100% of the time, in all possible scenarios now that I think about it.
  </p>
 </div>
</div>
]]>/></item><item><title>Want to be a better manager?</title><link>http://www.revsys.com/tidbits/want-to-be-a-better-manager/</link><description/><pubDate>Tue, 11 Mar 2008 06:24:03 +0000</pubDate><guid>http://www.revsys.com/tidbits/want-to-be-a-better-manager/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I've been reading a great book recently titled
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FFirst-Break-All-Rules-Differently%2Fdp%2F0684852861%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1205258712%26sr%3D1-1&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    First, Break All the Rules: What the World's Greatest Managers Do Differently
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   . I highly recommend it for anyone who manages employees, but it makes two great points early on that are especially appropriate for technology managers:
  </p>
  <p>
   <strong>
    Treat your Employees Differently
   </strong>
  </p>
  <p>
   You
   <strong>
    should
   </strong>
   treat your employees differently.   Each has unique strengths, weaknesses, and differ in the way they learn and you should capitalize on that, not try to homogenize everyone. Sally might be an awesome systems architect, so you don't want to weigh her down pounding out mundane features.  Bob might be the fastest developer and giving him those same mundane features makes sense.  Steve on the other hand might be your more efficient debugger, etc.
  </p>
  <p>
   They learn in different ways, some by reading, others by doing, and some need a more traditional classroom environment. Sending a do-er to a lecture class is a waste of everyone's time.
  </p>
  <p>
   <strong>
    Don't Let Human Resources Get in the Way of Your Hiring
   </strong>
  </p>
  <p>
   I've touched on this topic before, but you can't leave the resume collection and filtering process to your HR department.  Even in our more tech savvy age, I still hear horror stories from programmers how they lost out on being interviewed for a job because the HR person didn't know J2EE or J2ME were 'Java', that Ubuntu is a Linux distribution, or that having 6 years of mod_perl experience probably means they know Perl pretty well.
  </p>
  <p>
   A great technology manager has to hire a person based on several things, one of which is experience with a particular technology or technologies.  But that's only part of it.  Troubleshooting skills, breadth of experience in many technologies, personality and how this new hire will fit into the overall team structure are just as important.  The worst possible case is when the manager is barely or not at all involved in the hiring process.
  </p>
  <p>
   Now there are exceptions to this.  The best example is my friend Josh Stomel who runs
   <a href="http://www.neohire.com">
    NeoHire
   </a>
   . Josh is one of those rare recruiters that actually understands technology and more importantly knows when he doesn't know and has a huge network of geeks like me to ask questions. You can just hand NeoHire a list of qualifications and they will bring you back a group of top notch talent.
  </p>
  <p>
   So it is possible to have HR deeply involved in the hiring process, it just has to be the
   <em>
    right
   </em>
   people.
  </p>
 </div>
</div>
]]>/></item><item><title>Building mongrel on Solaris</title><link>http://www.revsys.com/tidbits/building-mongrel-on-solaris/</link><description/><pubDate>Fri, 29 Feb 2008 17:28:08 +0000</pubDate><guid>http://www.revsys.com/tidbits/building-mongrel-on-solaris/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I keep running into this same situation, so I figured I would write a post for everyone's benefit.  When trying to build recent mongrels or some other gem dependencies such as fastthread on a Solaris system the automatically generated Makefile is all sorts of messed up.
  </p>
  <p>
   This testing was done on two different Joyent Accelerators, your milelage may vary on other Solaris installations.
  </p>
  <p>
   The steps are pretty easy:
   <br/>
   1. Attempt to install the gem via the normal 'gem install mongrel'
   <br/>
   2. This will fail, but will download the gem for you.
   <br/>
   3. Move into your gems directory.  On these sytems that is /opt/csw/lib/ruby/gems/1.8/gems/
   <br/>
   4. Move into the particular gem you are building that is causing you trouble. In this case mongrel-1.1.3
   <br/>
   5. Move into the ext/ directory and the subdirectory that can't be built.  You'll see it in the error messages from step 1.
   <br/>
   6. Edit the generated Makefile as such:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">Make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">CC</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">pointing</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">your</span><span class="w"> </span><span class="n">C</span><span class="w"> </span><span class="n">compiler</span><span class="p">,</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="n">had</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">edit</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">reference</span><span class="w"> </span><span class="n">gcc</span><span class="w"> </span><span class="n">rather</span><span class="w"> </span><span class="n">than</span><span class="w"> </span><span class="n">cc</span><span class="o">.</span><span class="w">   </span>
<span class="n">Make</span><span class="w"> </span><span class="n">sure</span><span class="w"> </span><span class="n">LD</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">pointing</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">real</span><span class="w"> </span><span class="n">ld</span><span class="w"> </span><span class="k">tool</span><span class="o">.</span><span class="w">  </span><span class="n">On</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="n">meant</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="n">changed</span><span class="w"> </span><span class="n">ld</span><span class="w"> </span><span class="o">-</span><span class="n">G</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">gld</span><span class="w"> </span><span class="o">-</span><span class="n">G</span><span class="w">   </span>
<span class="n">Edit</span><span class="w"> </span><span class="n">CFLAGS</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">CFLAGS</span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mi">03</span><span class="w"> </span><span class="o">-</span><span class="n">I</span><span class="o">/</span><span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">libs</span><span class="w"> </span><span class="n">Essentially</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">just</span><span class="w"> </span><span class="n">need</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">remove</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="o">-</span><span class="n">KPIC</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">all</span><span class="w">     </span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="n">es</span><span class="w"> </span><span class="n">from</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">generated</span><span class="w"> </span><span class="n">file</span><span class="o">.</span>
</code></pre>
  </div>
  <p>
   7. Run make install
   <br/>
   8. Copy the gemspec file to /opt/csw/lib/ruby/gems/1.8/specifications so it will be registered with the gem tools.
  </p>
  <p>
   Hope this helps.
  </p>
 </div>
</div>
]]>/></item><item><title>PostgreSQL version 8.3 Released</title><link>http://www.revsys.com/tidbits/postgresql-version-83-released/</link><description/><pubDate>Mon, 04 Feb 2008 17:39:06 +0000</pubDate><guid>http://www.revsys.com/tidbits/postgresql-version-83-released/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I just got word that version 8.3 of PostgreSQL has been released.  Along with the usual amount of improvements there are some new features in 8.3 that should be of interest to PostgreSQL admins and developers such as:
  </p>
  <ul>
   <li>
    Integrated TSearch
   </li>
   <li>
    ENUM and UUID data types
   </li>
   <li>
    Faster sorting technique used for LIMIT operations
   </li>
   <li>
    Faster LIKE and ILIKE operations
   </li>
   <li>
    Lazy XID assignment which will make many read only operations much faster
   </li>
  </ul>
  <p>
   Check out the
   <a href="http://www.postgresql.org/about/press/features83.html">
    full list of features
   </a>
   at the PostgreSQL site or download it from
   <a href="http://http://www.postgresql.org/download/">
    the download section
   </a>
   of their site.
  </p>
 </div>
</div>
]]>/></item><item><title>EveryBlock.com is now launched</title><link>http://www.revsys.com/tidbits/everyblockcom-is-now-launched/</link><description/><pubDate>Thu, 24 Jan 2008 17:50:11 +0000</pubDate><guid>http://www.revsys.com/tidbits/everyblockcom-is-now-launched/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   My friend and former co-worker
   <a href="http://www.holovaty.com">
    Adrian Holovaty
   </a>
   and his team just launched their new project
   <a href="http://www.everyblock.com">
    EveryBlock.com
   </a>
   . EveryBlock takes the term
   <em>
    hyperlocal
   </em>
   to a whole new level.   They aggregate tons of public data sources by geo location so you can for example find all of the recent crime around a particular address, neighborhood, zip code, etc.  Or maybe you might be interested in the building code violations of where you live or work?
  </p>
  <p>
   Right now they have San Francisco, Chicago, and New York up and running, but will be adding more cities as time goes on.  Adrian asked me to help performance tune their PostgreSQL database a couple of months ago and so far things seem to be humming along nicely.
  </p>
  <p>
   Here are some links to other blogs talking about EveryBlock.com:
  </p>
  <ul>
   <li>
    <a href="http://www.opendoorteam.com/news-kids-on-everyblock/">
     News Kids on EveryBlock
    </a>
   </li>
   <li>
    <a href="http://www.poynter.org/column.asp?id=2&amp;aid=136307">
     Launching EveryBlock.com
    </a>
   </li>
   <li>
    <a href="http://venturebeat.com/2008/01/23/everyblock-an-especially-slick-service-for-discovering-local-information/">
     EveryBlock, an especially slick interface for discovering local information
    </a>
   </li>
   <li>
    <a href="http://www.poynter.org/column.asp?id=2&amp;aid=136337">
     A Review of EveryBlock: How it helps the public
    </a>
   </li>
  </ul>
  <p>
   Congrats and the best of luck to EveryBlock! I'm sure we'll see even more new and interesting things from this team in the future!
  </p>
 </div>
</div>
]]>/></item><item><title>Some interesting links</title><link>http://www.revsys.com/tidbits/some-interesting-links/</link><description/><pubDate>Fri, 18 Jan 2008 09:31:13 +0000</pubDate><guid>http://www.revsys.com/tidbits/some-interesting-links/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Hope all of my readers have recovered from the holiday season.  Here are a couple links I've come across recently, but neglected to write about during the holidays.
  </p>
  <p>
   <a href="http://www.zedshaw.com/index.html">
    Zed Shaw
   </a>
   , author of
   <a href="http://mongrel.rubyforge.org/">
    mongrel
   </a>
   which is used by many Ruby on Rails applications, posted an
   <a href="http://www.zedshaw.com/rants/rails_is_a_ghetto.html">
    interesting rant
   </a>
   about the state of Rails development and the personalities of some of the major players.
  </p>
  <p>
   I've never been a huge fan of Rails because I've never been shown a compelling reason to switch away from the more mature Perl equivalents.  Lately I've been working with mongrel and Rails apps more and more as many of my clients have switched to that as their platform of choice.  The more I work with it the less fond of it I am, it really is just immature compared to other platforms out there.  Toward the end he mentions the Rails creator himself has an application that had to be restarted 400 times per day... with some fixes it only has to be restarted 10 times a day.  400 is just insane, but 10 is still
   <strong>
    very
   </strong>
   troubling.   If the creator can't make things run better than that it really doesn't speak well for the platform.  Normally I wouldn't link to such a huge rant, but when it comes from a major Rails contributor there has to be at least some truth to it.
  </p>
  <p>
   My friend
   <a href="http://www.stason.org">
    Stas Bekman
   </a>
   has created a new site where you can make it known how you want a better version of something.  It's called
   <a href="http://i-want-a-better.com/">
    i-want-a-better.com
   </a>
   .  I think it's a great way for people to vent their frustrations with products, but I think the really winning idea is that entrepreneurs can peruse the site for new products and untaped markets. Hop on the site and let us all know what's frustrating you!
  </p>
 </div>
</div>
]]>/></item><item><title>Making the software written in any language more readable</title><link>http://www.revsys.com/tidbits/making-the-software-written-in-any-language-more-readable/</link><description/><pubDate>Thu, 17 Jan 2008 08:47:50 +0000</pubDate><guid>http://www.revsys.com/tidbits/making-the-software-written-in-any-language-more-readable/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   There are two
   <em>
    <strong>
     very
    </strong>
   </em>
   simple ways to improve the readability and maintenance of the software you write. They are so simple they are often ignored in favor of more complicated tools and the various programming methodologies people blather on about.   This comes from our human nature to think our
   <em>
    own
   </em>
   problems are more special and complicated than they really are and from not following the
   <a href="http://en.wikipedia.org/wiki/KISS_principle">
    KISS principle
   </a>
   .
  </p>
  <p>
   <strong>
    So how do you improve your software?
   </strong>
  </p>
  <p>
   By using better names and being consistent. It really is that simple, which is probably why it is overlooked.  A development manager might score some points with his boss by switching to Agile programming, having some scrums, doubling the amount of developer documentation or maybe even switching to a whole new platform like Ruby on Rails.  But who scores any points by saying, "We're going to do better about naming things appropriately?"
  </p>
  <p>
   <strong>
    Appropriate Naming
   </strong>
  </p>
  <p>
   Obviously having a variable named
   <em>
    temp_user
   </em>
   isn't all that descriptive, but it is an improvement over just
   <em>
    temp
   </em>
   or simply
   <em>
    t.
   </em>
   Most, if not all, programmers realize this.  However, you will often see variables named
   <em>
    clt
   </em>
   when they should really be named
   <em>
    client,
   </em>
   as if those three extra characters were single handedly going to cause carpal tunnel.  Your variable names should be as descriptive as possible without being absurd about it.  A variable named
   <em>
    the_current_user_object_we_are_working_with
   </em>
   is obviously overkill. But perhaps
   <em>
    current_user_object
   </em>
   or even
   <em>
    current_user
   </em>
   is appropriate.
  </p>
  <p>
   Naming your functions and methods should also be given the same amount of care. Naming a function
   <em>
    fix_client
   </em>
   doesn't tell us anything useful, we can only speculate something is wrong with the client or the data and this function does something about it.
   <em>
    normalize_client_name
   </em>
   is a much better name when you read that all the function does is properly set the case of the letters in the client's name.
  </p>
  <p>
   The names you choose for your libraries are also very important.  This is one of the reasons programmers find Perl's
   <a href="http://www.cpan.org">
    CPAN
   </a>
   much more useful than other programming languages' library collections. Things are named and categorized (for the most part) sanely.  Need something related to E-mail, check the libraries in the
   <em>
    Mail
   </em>
   category.   Can you guess what
   <em>
    Net::SSH
   </em>
   ,
   <em>
    IO::File::CompressOnClose
   </em>
   , and
   <em>
    WWW::Google::News
   </em>
   do? Yeah I thought you could.
  </p>
  <p>
   If I stumble upon your use of a library called
   <em>
    Util
   </em>
   , it doesn't tell me anything I still have to go look at the library to see how it fits in with this code.  If it had been named something like
   <em>
    DB::Util
   </em>
   or
   <em>
    Client::Utils
   </em>
   I would at least know the library is probably related to the database or client.
  </p>
  <p>
   <strong>
    Consistency
   </strong>
  </p>
  <p>
   Consistency is another area where you can improve your code base without much effort.  If all of your classes contain an initialization method, they should all be named the same.  Not
   <em>
    initialize
   </em>
   in some classes and
   <em>
    init
   </em>
   in others.   Things that should be consistent throughout your code base, not only consistent within a single application. If possible, the source in your department should be consistent with other departments and business units.
  </p>
  <p>
   Consistency through conventions is one of the main reasons people like web frameworks like Ruby on Rails. I'm not advocating Ruby on Rails necessarily, you can accomplish these same things in any language.
  </p>
  <p>
   Things that should be consistent:
  </p>
  <ul>
   <li>
    variable, function, and method naming conventions ( underscores or CamelCase, but not both )
   </li>
   <li>
    frequency and layout of comments
   </li>
   <li>
    documentation
   </li>
   <li>
    configuration file syntax
   </li>
   <li>
    on disk layout of source code, binaries, configuration files, etc.
   </li>
   <li>
    installation, upgrades and package management
   </li>
   <li>
    language(s) used for development
   </li>
  </ul>
  <p>
   The point being the
   <em>
    <strong>
     more
    </strong>
   </em>
   consistent you are in
   <em>
    <strong>
     how
    </strong>
   </em>
   you build an application the easier it is to get down to the task at hand.   Be it new development or bug fixing.  Got a configuration file format you like and have already written libraries to parse it? Then use it
   <strong>
    EVERYWHERE
   </strong>
   , in every single darn application you build. No one has to learn the new format to start developing, no time is spent discussing which format is better, and another developer in your organization can jump in to fix bugs or add a new feature.
  </p>
  <p>
   The last development shop I ran we focused on being consistent. 95% of the time we were building web applications that were either used internally by fellow employees or externally by our customers.  If you saw the source to one of our applications, then all of our other applications would seem very familiar. One might be a ticket tracking system and the other an accounting package, but the layout, coding style, and use of common libraries let the developers dive right in and not have to worry so much about how this particular app is written differently than the others.
  </p>
  <p>
   Consistency in your applications also makes refactoring easier.  If all of your applications use a particular technique, library, etc. in the same way replacing it with a new tool you have fallen in love with is much easier.  If everyone is doing everything even slightly differently, you have to start worrying more and more how the change might impact your code.
  </p>
  <p>
   I'm definitely a "right tool for the right job" sort of fellow, but mixing and matching tools and techniques for every single application you build is a recipe for disaster. One shop I know (name withheld to protect the guilty) has two developers.  One who works entirely in Perl, the other entirely in Ruby.  Another application I saw was written mostly in Java, but with a smattering of C# and Python around the edges for kicks. None of these three were chosen from a "right tool for the job" perspective, but simply because those were the favorite languages of the specific developers tasked with those sub-systems. These are obviously worst case examples.
  </p>
  <p>
   These ideas aren't new, I'm positive I am not the first to use them or even write about them.  But I see these two simple rules violated so often by programmers of all experience levels that I felt the need to reiterate them.
  </p>
 </div>
</div>
]]>/></item><item><title>Log Buffer #65: a Carnival of the Vanities for DBAs</title><link>http://www.revsys.com/tidbits/log-buffer-65-a-carnival-of-the-vanities-for-dbas/</link><description/><pubDate>Fri, 05 Oct 2007 16:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/log-buffer-65-a-carnival-of-the-vanities-for-dbas/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Welcome to the 65th edition of
   <a href="http://www.pythian.com/blogs/about-log-buffer/">
    Log Buffer
   </a>
   , the weekly survey of database related blogs.
  </p>
  <p>
   First let's start with some miscellaneous entries that could be of interest to any DBA.
   <a href="http://crazydba.blogspot.com/2007/09/pass-musings.html">
    Crazy DBA
   </a>
   has an interesting post about how attending conferences helped to grow his professional network, which in turn has made him a better DBA.  And Thomas Kyte has a great post about
   <a href="http://tkyte.blogspot.com/2007/10/it-all-about-data.html">
    why it's the data
   </a>
   , not the application itself, that matters.
   <a href="http://krow.livejournal.com/553812.html">
    Brian Aker
   </a>
   gives us a great link to a
   <a href="http://www.allthingsdistributed.com/2007/10/amazons_dynamo.html">
    Werner Vogels' entry on Dynamo
   </a>
   , one of the key technologies used behind the scenes at Amazon.
  </p>
  <p>
   Oracle users will certainly find these two links of interest.  First off, Frederik Visser shows you
   <a href="http://startoracle.com/2007/09/30/so-you-want-to-play-with-oracle-11gs-rac-heres-how/">
    how to play with Oracle 11g RAC in VMWare
   </a>
   . And Alex Gorbachev has a
   <a href="http://www.pythian.com/blogs/626/the-first-miracle-open-world-gala-diner">
    nice write up
   </a>
   about Miracle Open World.
  </p>
  <p>
   SQL Server DBAs might enjoy the following posts.  If you're thinking about using or upgrading to Idera SQLsafe v4.5, you'll want to check out Sean McCown's
   <a href="http://weblog.infoworld.com/dbunderground/archives/2007/10/its_not_me_this.html">
    post about some of that product's issues
   </a>
   . Steve Jones has some
   <a href="http://blogs.sqlservercentral.com/blogs/steve_jones/archive/2007/09/28/2994.aspx">
    thoughts on monitoring and alerting with your SQL Server
   </a>
   , but are valid for any database. Need to know when your SQL Server instance was started? Check out Joe Webb's
   <a href="http://weblogs.sqlteam.com/joew/archive/2007/10/03/60351.aspx">
    tip
   </a>
   on how to find out.  And Mladen Prajdi&cacute; has some advice on
   <a href="http://weblogs.sqlteam.com/mladenp/archive/2007/10/01/SQL-Server-Notify-client-of-progress-in-a-long-running.aspx">
    how to notify a client in a long running process
   </a>
   with SQL Server.
  </p>
  <p>
   MySQL users will find
   <a href="http://www.xaprb.com/blog/2007/09/30/how-to-measure-mysql-slave-lag-accurately/">
    this post on accurately measuring how far behind your slave is lagging
   </a>
   . Over at the
   <a href="http://www.mysqlperformanceblog.com">
    MySQL Performance Blog
   </a>
   there is an
   <a href="http://www.mysqlperformanceblog.com/2007/09/28/heikki-tuuri-to-answer-your-in-depth-innodb-questions">
    opportunity to ask questions
   </a>
   of Heikki Tuuri, the creator of InnoDB, and Peter has some thoughts on a few
   <a href="http://www.mysqlperformanceblog.com/2007/10/04/mysql-quality-of-old-and-new-features/">
    serious bugs in the MySQL 5.0 release
   </a>
   .  Kaj Arn&ouml; has an interesting post on how MySQL GmbH and MySQL AB help
   <a href="http://www.planetmysql.org/kaj/?p=129">
    birds of a feather to flock together, quite literally
   </a>
   and about how they have
   <a href="http://www.planetmysql.org/kaj/?p=130">
    opened up the call for papers
   </a>
   for the 2008 MySQL Users Conference.
  </p>
  <p>
   Kevin Burton
   <a href="http://feedblog.org/2007/09/29/using-o_direct-on-linux-and-innodb-to-fix-swap-insanity/">
    talks about how to avoid swapping insanity
   </a>
   with InnoDB. Want a free MySQL Magazine? Lewis Cunningham has
   <a href="http://mysqldbnews.blogspot.com/2007/09/free-mysql-magazine.html">
    found one
   </a>
   for us all. Jan Kneschke introduces us to the
   <a href="http://jan.kneschke.de/2007/10/1/wormhole-storage-engine">
    Wormhole storage engine
   </a>
   for MySQL.  Not really sure how useful it is, but it is definitely interesting.
  </p>
  <p>
   Hubert Lubaczewski has written a great
   <a href="http://www.depesz.com/index.php/2007/09/30/finding-optimum-tables-placement-in-2-tablespace-situation/">
    tool to help you determine the optimal layout of tables
   </a>
   , indexes, etc. on your various tablespaces for PostgreSQL.  Robert Treat follows up with
   <a href="http://people.planetpostgresql.org/xzilla/index.php?/archives/322-tablespace-management-variables.html">
    some additional thoughts to consider
   </a>
   .
  </p>
  <p>
   Joshua Drake has announced the
   <a href="http://www.commandprompt.com/blogs/joshua_drake/2007/10/the_easy_but_harder_than_i_thought_road_to_postgresql_conference_fall_2007/">
    speakers and topics
   </a>
   for the PostgreSQL Conference Fall 2007, which is October 20th 2007 at Portland State University. Greg Sabino Mullane has a nice explanation of why you can't used prepared queries when using
   <a href="http://people.planetpostgresql.org/greg/index.php?/archives/110-Using-DBDPg-with-pg_bouncer.html">
    DBD::Pg and pg_bouncer
   </a>
   . And to finish our this week's  links, Francisco Figueiredo Jr mentions that
   <a href="http://fxjr.blogspot.com/2007/10/uuid-datatype-and-copy-inout-support.html">
    PostgreSQL will have a UUID data type
   </a>
   in version 8.3.
  </p>
  <p>
   Enjoy!
  </p>
 </div>
</div>
]]>/></item><item><title>Safely making changes to remote systems</title><link>http://www.revsys.com/tidbits/safely-making-changes-to-remote-systems/</link><description/><pubDate>Thu, 20 Sep 2007 17:17:50 +0000</pubDate><guid>http://www.revsys.com/tidbits/safely-making-changes-to-remote-systems/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I stumbled across this post about a great
   <a href="http://www.askbjoernhansen.com/2007/09/18/safely_change_firewall_rules_remotely.html">
    technique for safely making configuration changes to remote systems
   </a>
   .  Everyone has done it before, the "Oops I just shutdown sshd" or turned off the wrong network interface.
   <a href="http://www.askbjoernhansen.com/">
    Ask Bj&oslash;rn Hansen
   </a>
   shows you how you can use atd to give yourself a safety net when doing these types of operations.
  </p>
 </div>
</div>
]]>/></item><item><title>Under-used CPAN Modules</title><link>http://www.revsys.com/tidbits/under-used-cpan-modules/</link><description/><pubDate>Tue, 11 Sep 2007 16:20:40 +0000</pubDate><guid>http://www.revsys.com/tidbits/under-used-cpan-modules/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Perl often suffers from its history.  While it is great that there are so many online tutorials and code samples out there for new Perl developers to learn from, they sometimes miss out on all of the useful new code that has been created since the article was written.  Here are a few CPAN modules that are, in my opinion, under-used by new developers.
  </p>
  <p>
   These modules are great for several reasons.  The most compelling ones are there is lesss code for you to write/debug than not using these modules, the resuling code is often clearer to your fellow developers, and in many cases the module will out perform your hand hacked code.
  </p>
  <p>
   <strong>
    List manipulations
   </strong>
  </p>
  <p>
   There are two list related modules you should familiarize yourself with.  They are
   <a href="http://search.cpan.org/~gbarr/Scalar-List-Utils-1.19/lib/List/Util.pm">
    List::Util
   </a>
   and it's cousin
   <a href="http://search.cpan.org/~vparseval/List-MoreUtils-0.22/lib/List/MoreUtils.pm">
    List::MoreUtils
   </a>
   .
  </p>
  <p>
   Need to shuffle the values in an array? Don't reinvent the wheel, use List::Util's shuffle function.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="nn">strict</span><span class="p">;</span><span class="w">   </span>
<span class="k">use</span><span class="w"> </span><span class="nn">warnings</span><span class="p">;</span>

<span class="k">use</span><span class="w"> </span><span class="nn">List::Util</span><span class="w"> </span><span class="sx">qw( shuffle )</span><span class="p">;</span>

<span class="k">my</span><span class="w"> </span><span class="nv">@array</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sx">qw( 1 2 3 4 5 6 7 8 9 )</span><span class="p">;</span><span class="w">   </span>
<span class="k">my</span><span class="w"> </span><span class="nv">@shuffled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">shuffle</span><span class="p">(</span><span class="w"> </span><span class="nv">@array</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   Need to make a list only contain unique values? You can use the method found in the
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FPerl-Cookbook-Second-Tom-Christiansen%2Fdp%2F0596003137%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1189525849%26sr%3D8-1&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    Perl Cookbook
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   , or you can use the simpler
   <em>
    uniq()
   </em>
   function from List::MoreUtils like so:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="nn">strict</span><span class="p">;</span><span class="w">   </span>
<span class="w"> </span><span class="k">use</span><span class="w"> </span><span class="nn">warnings</span><span class="p">;</span>

<span class="k">use</span><span class="w"> </span><span class="nn">List::MoreUtils</span><span class="w"> </span><span class="sx">qw( uniq )</span><span class="p">;</span>

<span class="k">my</span><span class="w"> </span><span class="nv">@array</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sx">qw( 1 1 2 2 3 3 4 4 5 5 )</span><span class="p">;</span><span class="w">   </span>
<span class="k">my</span><span class="w"> </span><span class="nv">@unique_values</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">uniq</span><span class="p">(</span><span class="w"> </span><span class="nv">@array</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   Not to mention List::MoreUtils' implementation is done in C, so it is much faster than a Pure Perl implementation.  I once saw this improve the performance of a large web application by 5% because the developers were uniquing several lists for each page view.
  </p>
  <p>
   Be sure to also check out the other functions in these two modules such as any(), all(), first(), max(), etc. While you will have to install List::MoreUtils from CPAN, List::Util is part of Perl core.
  </p>
  <p>
   <strong>
    Merging Hashes
   </strong>
  </p>
  <p>
   While you can use the simple:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">my</span><span class="w"> </span><span class="nv">%merged_hash</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="nv">%hash_one</span><span class="p">,</span><span class="w"> </span><span class="nv">%hash_two</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   Someitmes you need a bit more power, enter
   <a href="http://search.cpan.org/~dmuey/Hash-Merge-0.10/Merge.pm">
    Hash::Merge
   </a>
   .  It gives you several options on how to handle conflicting keys, based on a left/right order, or by storage method. Or you can even control whether or not your data is cloned on not.  I find this very useful for merging in command line arguments against a configuration file.
  </p>
  <p>
   <strong>
    Speaking of configuration options and files....
   </strong>
  </p>
  <p>
   Repeat after me.
   <em>
    I will not write my own configuration parsing code.   I will not invent my own configuration file format. I will not parse my own command line options without a damn good reason.
    <br/>
   </em>
  </p>
  <p>
   Why waste your time on the most boring part of your code? Use one of the already existing modules and configuration file formats.  This saves you time, debugging headaches, and makes the configuration file syntax familiar to your users.
  </p>
  <p>
   I strongly suggest looking into
   <a href="http://search.cpan.org/~jv/Getopt-Long-2.36/lib/Getopt/Long.pm">
    Getopt::Long
   </a>
   for handling command line arguments. It might take you a bit of time to get used to this module, but once you're over the initial learning curve you'll wonder why you ever bothered doing this by hand in the first place.
  </p>
  <p>
   If you like Apache style configuration files (who doesn't?), start using
   <a href="http://search.cpan.org/~tlinden/Config-General-2.33/General.pm">
    Config::General
   </a>
   and cut your configuration processing code into a use statement and a couple of lines of code. Or if you happen to prefer .INI style configuration files take a look at
   <a href="http://search.cpan.org/~adamk/Config-Tiny-2.10/lib/Config/Tiny.pm">
    Config::Tiny
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Technique for improving code over time</title><link>http://www.revsys.com/tidbits/technique-for-improving-code-over-time/</link><description/><pubDate>Thu, 30 Aug 2007 17:47:04 +0000</pubDate><guid>http://www.revsys.com/tidbits/technique-for-improving-code-over-time/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was talking with a friend of mine recently about an all too common problem in software development.  That problem is finding the time and resources to revisit working, but not perfect code.  For whatever your definition of
   <em>
    " perfect"
   </em>
   may be.
  </p>
  <p>
   The developers feel this particular bit of code is just plain nasty. Maybe it is written in a confusing manner, poorly documented, or the landscape has changed so much that the entire architecture could use a rethink. The programmers may have thought up a better way, changed their style/best practices, or perhaps have just improved their skills since that code was written. But it
   <em>
    works
   </em>
   . It accomplishes the business goals.
  </p>
  <p>
   Because it works, the management/marketing/sales folks are not very interested in making it better.   They would rather the programmers focus their efforts on new features, because they don't feel they can sell code quality. "
   <em>
    Maybe next month/year/release we can do that, but right now go knock out some of these TODO list items...
   </em>
   ", is what you typically hear.
  </p>
  <p>
   Both sides of this struggle need to realize that they both are correct and both incorrect. The developers are right in saying the code isn't perfect and could be improved.  They are also wrong, because sometimes what you have
   <em>
    <strong>
     is
    </strong>
   </em>
   "good enough".
  </p>
  <p>
   The biz folks are correct in their thinking that they can't directly sell the results of the programmer's effort. They can't put it on brochure and it certainly won't get them talked about in FastCompany. But they are also wrong, because they are missing the long term impact the code quality/design will have on their future business. It is difficult for them to understand how changing, for example to a messaging oriented architecture, helps their modularity and scalability. How it might drastically reduce future development time, improve testing, etc, etc, etc.
  </p>
  <p>
   Arguing this out could take forever. Not to mention you will have to argue each architecture change as you find/want them.
  </p>
  <p>
   A technique I have used with great success in the past is what I have so cleverly named "Friday Afternoons".  Friday afternoons are probably the least productive part of an employee's week.  They are watching the clock in anticipation of the weekend. It happens to everyone.  Experienced sales people will tell you that it is basically impossible for them to get any real sales on Monday mornings and Friday afternoons.
  </p>
  <p>
   As a compromise,  I suggest management give the developers free reign to make architecture, design, and code clean up changes on Friday afternoons. You may need to implement rules around this, such as the changes can't impact any deadlines, they can't create any new work for others, can't impact the product in adverse ways from the customer's perspective, all work must be done in a cleanup branch, etc.
  </p>
  <p>
   Some of you managers out there are probably thinking, but "No way, that's 10% of our development time".  If you feel this way, you're really deluding yourself. No one is 100% productive, and I would estimate no one is even 25% productive on Friday afternoons, yourself included.
  </p>
  <p>
   So why do this? There are several reasons:
  </p>
  <ul>
   <li>
    Developers can cleanup the code, effectively making their workplace ( aka the code ) more enjoyable. And everyone knows that
    <em>
     Great Code comes from Happy Coders
    </em>
    .
   </li>
   <li>
    The programmers feel their concerns are being heard. That management cares about about the quality and not just the dollar signs.
   </li>
   <li>
    You eliminate the time drain of the meetings arguing about these issues.
   </li>
   <li>
    The coders are self-motivated in their Friday afternoon sessions. You weren't going to get much out of them anyway, but at least there is a chance that you can reap future benefits from this work.
   </li>
  </ul>
  <p>
   Maybe one of those architecture changes means that a requested new feature takes 2 days to implement rather than 2 weeks.  Or perhaps the code will run 5% faster or scale 2x as well.  Even if you just end up with better documentation and a slighly improved employee morale this is win.
  </p>
  <p>
   If you end up using this technique for awhile, please send me an E-mail or leave a comment here.  I would love to see how this idea plays out in different environments.
  </p>
 </div>
</div>
]]>/></item><item><title>I'm in the top 10....</title><link>http://www.revsys.com/tidbits/im-in-the-top-10/</link><description/><pubDate>Sun, 26 Aug 2007 09:03:20 +0000</pubDate><guid>http://www.revsys.com/tidbits/im-in-the-top-10/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Based on this
   <a href="http://perlbuzz.com/2007/08/top-10-perl-blogs.html">
    this article at PerlBuzz.com
   </a>
   , my humble little blog is in the Top 10 of all Perl related blogs.  While the author admits to having used a very simple/naive ranking system, it is still an honor to be listed.
  </p>
  <p>
   For anyone who wants to subscribe to only my Perl related posts you'll want to use
   <a href="http://feeds.feedburner.com/revsys/JkwX">
    this RSS link
   </a>
   . You can use the RSS feed link on the right to receive all of my posts.
  </p>
  <p>
   I guess I better start writing more about Perl!
  </p>
 </div>
</div>
]]>/></item><item><title>Followup to "A Guide to Hiring Programmers"</title><link>http://www.revsys.com/tidbits/followup-to-a-guide-to-hiring-programmers/</link><description>A follow up blog post to Frank's populate guide to how to hire programmers. </description><pubDate>Wed, 08 Aug 2007 07:11:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/followup-to-a-guide-to-hiring-programmers/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Please excuse my laziness, but I simply don't have time to respond to each and every person who has E-mailed or left comments on digg, reddit, or the
   <a href="/tidbits/a-guide-to-hiring-programmers-the-high-cost-of-low-quality/">
    original post itself
   </a>
   .  I would like to respond to a few of the larger themes I've seen in the questions/responses:
  </p>
  <h4>
   <strong>
    This applies to more than just programming
   </strong>
  </h4>
  <p>
   I definitely agree that this can be applied to nearly any type of job, not just programming.   A great designer is worth much more than an average one.  And I honestly wasn't trying to single out sales and customer service people.  I do agree that a great sales person or customer service rep is worth more than the average, and should be paid accordingly. And yes every employee is important to the company.
  </p>
  <p>
   Using customer service as an example, I've worked with the worst where they would literally scream at the customer on the phone to the best. The problem with customer service is that the metrics are against them in that even the best customer service person can only take a few more calls/tickets than the worst.  Just because of the nature of the interactions. It is also very difficult to measure if this person has pleased/retained more customers than another. With programming it is often easier to see how an individuals contributions impact the whole project.
  </p>
  <p>
   Sales is a different monster, but hopefully this has cleared up what I was trying to express.
  </p>
  <h4>
   <strong>
    Only experts?
   </strong>
  </h4>
  <p>
   Do I think it's reasonable to hire only experts?   Yes, in many situations a company can and should staff themselves with the vast majority being experts. Is it possible for larger companies with larger products? Probably not.  If the problem simply demands 50 developers, it would be difficult to staff that entirely with experts. However, I do believe they would see a boost if they were able to have at least 10-15 of those developers be experts. Instead most companies have 1-3 experts that lead the team of the masses.
  </p>
  <p>
   If you can't find experts, you should attempt to hire staff that could become experts over time as they gain experience.
  </p>
  <h4>
   <strong>
    How do you become an expert?
   </strong>
  </h4>
  <p>
   Everyone is correct in saying that experts started out as novices, I was certainly a novice.   In many ways I still am.  Being personally interested in martial arts I remember a story of someone, after years of training, finally receiving their black belt in Aikido and being told,
   <em>
    " Now you are ready to learn."
   </em>
   I believe this is true of programming, technology, and most professions.  The learning doesn't and shouldn't stop.
  </p>
  <p>
   So how can you become an expert? I think the best advice I can give is to read up as much as possible on your field.  You don't become an expert simply repeating what you did yesterday for many years until, poof, you're an expert. You need to be learning new idioms, patterns, and tips from your peers.
  </p>
  <p>
   Too many developers sit in their cubes and pound out code and never look up. You need to be reading up on your profession as much as possible, exploring new languages/tools to determine if you could be doing something easier or better.
  </p>
  <p>
   An example of what I see far too often happened again recently at OSCON. A professional Python developer did not know
   <a href="https://www.djangoproject.com">
    Django
   </a>
   was the predominant web framework for that language. I'm not a Python user, but even I know this. Maybe it's because I'm friends with the core Django team, but even if that had not been the case I would at least be aware of it and in general what it was from my day to day  tech reading.
  </p>
  <p>
   The other advice I would give is to read and become involved in an Open Source project.  This improves your code quality and allows you to see how other, presumably senior, developers work. Even if you aren't able to contribute to the project directly, get on the mailing lists and examine how those developers work.
  </p>
  <h4>
   <strong>
    How do you find and hire experts?
   </strong>
  </h4>
  <p>
   I think the biggest mistake managers make is leaving this up to HR.   I've always made sure I received every resume that came in for a position I was hiring for.  HR will often reject a candidate because their resume states they have "Years of J2EE experience", but since it's a Java programming position it goes in the trash.  Perhaps it is time we start hiring "HR Engineers" like we have "Sales Engineers."
  </p>
  <p>
   The first place I look when hiring programmers is the Open Source community. If they are involved in an Open Source project you can easily see how they work with others on the mailing lists, see examples of their code, etc.
  </p>
  <p>
   They also tend to be of higher quality because Open Source is a meritocracy. Not to mention the simple act of being involved in a project, for no monetary gain, shows a strong love of their craft.
  </p>
  <p>
   I think multiple choice tests are a very poor indicator of programming prowess. Too often they have a couple of esoteric or even trick questions that really compare the test writers ability to confuse with the test takers' ability to decipher.  It is much more important for your new hire to know how to find the answer than it is for him to actually have it tucked away in a brain cell. Ability to effectively use Google to search for the answer is much more important than many realize.
  </p>
  <p>
   If you happen to be one of the people who are looking for an expert Perl programmer I suggest you get in touch with my new friend
   <a href="https://www.sysarch.com">
    Uri Guttman
   </a>
   , The Perl Hunter, at
   <a href="mailto:uri@perloncall.com">
    uri@perloncall.com
   </a>
   .  He specializes in finding execellent Perl programmers for companies. Being an accomplished developer himself he easily separates the wheat from the chaff and can find someone who will be a good overall fit for your organization.
  </p>
  <h4>
   <strong>
    Many problems are marketing and management's fault...
   </strong>
  </h4>
  <p>
   This is also very true. Bad management will bring down any team or project, no matter how many experts they have on staff. This isn't even restricted to technology management.
  </p>
  <p>
   Marketing often over promises what can be delivered and demands it in an unreasonable time frame.   Unfortunately most of the time we blame the developers, because long after the sale all that we see is the code and not the brochure.
  </p>
  <p>
   My advice to marketing and management is that you bring a problem to your developers and then base your plans on when they believe they can deliver the solution.  Far too often management has already determined time lines and set things in motion before the development team has even been told about the project.  This is backwards.  You don't schedule your building contractors before you have the proper permits or before even speaking with the architect about the project.
  </p>
  <p>
   Even Microsoft gets this right. They realized it was much better to delay Vista until it is ready than to ship it too early just because they originally said a certain date.
  </p>
  <p>
   Obviously you can't always just wait around for something to be perfect. There are always restrictions and requirements that are outside of your control.  No one could move January 1st, 2000 out a few more weeks just because their Y2K cleanup wasn't done. But often I see companies attempt to move mountains to hit some arbitrary date when one of the largest consequences of delaying would be everyone had to update their Outlook calendars.
  </p>
  <h4>
   <strong>
    My language bias
   </strong>
  </h4>
  <p>
   I received a bunch of comments on my use of  "Perl vs Java" in the example, that simply was what we were talking about at dinner that night. I probably should have used "agile language X vs cumbersome language Y" to keep the flames down to a minimum.
  </p>
  <p>
   You
   <em>
    <strong>
     can
    </strong>
   </em>
   write efficient, readable, and maintainable Perl.   I've even had some notable Python programmers say that about code I was in charge of and honestly the code in question wasn't what I would consider the best of the best. I think Python is a great language, but for me personally I haven't been shown any compelling reason to switch.
  </p>
  <p>
   You can write crappy unreadable code in any language. You can make most any language/framework/toolset scale and perform to your needs.  For every "large app/website/etc" that uses language X I'm sure I can find you a comparable app/website using language Y.  Any performance differences between language X and Y can usually be solved with $100 worth of extra CPU.  What really matters is programmer efficiency.  That is where you save money and reap benefits. I simply don't see how having to write, read, troubleshoot, and maintain 10x the number of lines of code is an efficient use of the programmer's time.
  </p>
  <p>
   However, I do agree that you should use the right tool for the right job. Java/C++/C# are definitely the right tools in many situations.  I just feel that because everyone has seen a horribly written Perl CGI ( or written one themselves ) they think this is somehow ingrained in the language and because of this Perl simply isn't an option for anything "real."
  </p>
  <p>
   Perl is a language where the developer must use some self-control rather than having it imposed on him by his tool. Which is why Perl (or many of the more agile languages such as PHP/Python/etc) written by novice programmers is so awful. The knowledge and self-control comes with experience.
  </p>
  <p>
   The largest problem with any language is the use of poor variable, function, class, and method names.  Using adequately long and descriptive names is probably the single best way to improve code quality and no language out there enforces this. Some enforce a certain style, others force certain methodologies, but this is really only picking at less important aspects of the problem.
  </p>
  <h4>
   <strong>
    Company bias
   </strong>
  </h4>
  <p>
   By comparing Apple vs Microsoft I wasn't really singling out their development staffs.   I'm sure their management, design, and marketing departments are as much to blame for any successes or failures these companies have.
  </p>
  <p>
   What I was trying to get across was the "It simply works."  I would say the second most common comment I hear from Mac users, after how pretty/well designed they are, is that it "just works."  I don't hear that very often from Microsoft users.
  </p>
 </div>
</div>
]]>/></item><item><title>A Guide to Hiring Programmers: The High Cost of Low Quality</title><link>http://www.revsys.com/tidbits/a-guide-to-hiring-programmers-the-high-cost-of-low-quality/</link><description/><pubDate>Sun, 05 Aug 2007 11:11:57 +0000</pubDate><guid>http://www.revsys.com/tidbits/a-guide-to-hiring-programmers-the-high-cost-of-low-quality/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was invited to a wonderful dinner party (
   <em>
    I swear it wasn't too spicy Sarah!
   </em>
   ) with some St. Louis Perl peoples this week while I'm here on business.  At one point we were talking about hiring programmers, specifically Perl programmers.
  </p>
  <p>
   We agreed on the following:
  </p>
  <ul>
   <li>
    Finding good programmers is hard in any language.  And that a good programmer can be as effective as 5-10 average programmers.
   </li>
   <li>
    Average pay rates between equivalent programmers are out of sync and are based more on the language used than the skill of the programmer.
   </li>
   <li>
    You don't need to hire an expert in language X, you can and should look for expert programmers that are willing to learn language X. An expert can easily cross over from being a novice in any language in a matter of a few weeks.
   </li>
   <li>
    You should seriously consider allowing your expert developers to telecommute full-time. Restricting your search to programmers who live in your area or are willing to move limits the talent you can acquire. Arguments regarding "face time", productivity, etc. can easily be nullified when you look at how some of the largest and most successful Open Source projects such as Linux, Apache, and Firefox are developed by individuals rarely living in the same time zone or even country.
   </li>
   <li>
    We love Perl and think it's a great language that you graduate to after you have been forced to use less agile languages such as Java, C/C++/C#, etc. Not necessarily a first language you get your feet wet with and then move onto a
    <em>
     cough
    </em>
    "real" language.
   </li>
  </ul>
  <p>
   Many people in the Perl community have been writing on this topic lately and wanted to share my opinions on the subject, as it is one I have put many hours of thought into. Doing my best to keep this language agnostic as I believe these tips can be applied to any programming language. I will however, use Perl in some examples as it is my preferred language.
  </p>
  <h4>
   <strong>
    Why is it so hard to find good programmers?
   </strong>
  </h4>
  <p>
   The simplest reason is when a company finds a good developer they do more to make sure that person is happy which leads to longer tenures. Better salary, more flexible working conditions, good tools, interesting projects, and better perks can often keep a programmer working for you longer.
  </p>
  <p>
   Another obvious reason is that experts in any field are small in number, so your possible talent pool is limited. This leads managers and HR departments to settle for average or even below average developers.   I believe this is the single biggest mistake a technology oriented company can make, regarding developers, just short of not using a good
   <a href="http://subversion.tigris.org">
    version control system
   </a>
   .
  </p>
  <p>
   We're not talking about customer service representatives or sales people here. Just having a body to fill the seat is not, I repeat not, always a win for the company. Sub-standard programmers drag down the efficiency of your other developers with beginner questions, poor comments/documentation, and bad code that someone else will later be forced to spend time fixing.
  </p>
  <p>
   Companies need to stop thinking about their developers as cogs in the machine. They are more akin to artists, authors, designers, architects, scientists, or CEOs.  Would your HR department rush to find the first person who would be willing to take on the role of Chief Scientist, Art Director, or CEO in your company? Of course not, they would spend the time to do a thorough talent search for just the right candidate, court them, and then compensate them appropriately. They realize that having the wrong person in that seat is much worse than having the seat empty. It is absolutely the same with programming.
  </p>
  <p>
   Anyone who has been a developer or managed developers can tell you that an expert can accomplish as much as 10 average developers.  However, companies typically pay only a 10-20% premium for an expert over the average programmer. Whether or not their title is Lead, Architect, Development Manager, Guru or whatever nomenclature the company uses. I am not saying that if your average developer is paid $50k/year that you should pony up $500k/year for an expert. The employer/employee relationship never works like that, but what employers don't seem to realize is that in the end paying more saves them more.
  </p>
  <p>
   Let's look at an example.  One common argument from HR departments is that they "can't find any Perl programmers, but they can't swing a cat without hitting a Java developer".  While this is fairly accurate, they are approaching the problem from the wrong direction.  If you fill your shop with 15 average Java developers, paying an average of $60k per developer you have an approximate labor cost of $900k/year for your development staff.  Not considering any non-salary benefits.
  </p>
  <p>
   Suppose you instead took the time to find 5 experts, or at least above average, Perl developers at $120k each per year. Here is a partial list of the pros and cons of such a scenario:
  </p>
  <p>
   <strong>
    Cons:
   </strong>
  </p>
  <ul>
   <li>
    You must spend extra time finding, evaluating, and courting these more sought after developers.
   </li>
   <li>
    Your company and what the developer may be asked to build may simply not be attractive to this class of developer.  Very few people want to work for a spammer or a small web design firm that caters solely to freelance accountants for example. Smart people find boring things even more boring than the masses.
   </li>
   <li>
    When one of them leaves the company, there is the feeling that your company's business objectives are more at risk due to having only 4/5ths of your normal resources. Or that a larger chunk of your corporate knowledge just walked out the door. This is more of a perceived problem than an actual one as good developers are better at writing readable/maintainable code, commenting their work, and writing effective documentation.
   </li>
  </ul>
  <p>
   <strong>
    Pros:
   </strong>
  </p>
  <ul>
   <li>
    Each developer will be more content with their job, due in part to the higher than average salary, but also because his or her co-workers are of a much higher quality which improves anyone's job satisfaction.
   </li>
   <li>
    Development would require less overall communication as there are less people to communicate with.  This obviously improves efficiency as anyone who has been on a 20+ person conference call can attest to. Or read the
    <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FMythical-Man-Month-Software-Engineering-Anniversary%2Fdp%2F0201835959%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1186355118%26sr%3D8-1&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
     Mythical Man Month
    </a>
    <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
    if you want a more in-depth analysis of this phenomenon.
   </li>
   <li>
    Experts travel in the same social circles.  Having one expert on staff makes it much easier to find other experts in the same field, no matter what field that may be.
   </li>
   <li>
    You would save 2/3rds on infrastructure costs.  Things like cubicles, computers, cell phones, free lunches, training costs, travel, office space, air conditioning, electricity, etc, etc. The list is essentially endless.
   </li>
   <li>
    Your HR department would have 1/3rd the number of developers that it would need to take care of. Less paper work, less questions, less everything, and less turn over because of the lower number of employees.
   </li>
   <li>
    Oh and you'd save $300k/year on your labor costs.  Not to mention non-salary benefits such as stock options, retirement matches, health insurance premiums, perks, etc. You could spend as much as $100k/year on your talent searches and still be $200k/year ahead.  Hell, you could dedicate an entire HR person just to this task.
   </li>
  </ul>
  <h4>
   <strong>
    What is an expert programmer?
   </strong>
  </h4>
  <p>
   Experience is key, but not necessarily in ways you might imagine.   Time in the saddle, with a particular language is not as important as diversity of experience.  Someone who has worked in several disparate industries, a generalist, is often a much better developer than one who has spent years in the same industry.  There are exceptions to this, but in general I have found this to be the case.  Bonus points if your developer was a systems administrator in a former life.
  </p>
  <p>
   Some of the best developers I know were originally trained as journalists, mathmaticians, linguists, and other professions not normally associated with software development.
  </p>
  <p>
   Experts use better tools and care deeply about their craft.  They aren't assembling bits on an assembly line, they are crafting a unique product to solve a unique problem.  Experts are lazy, they work smarter rather than harder.  Experts prefer the easiest solution that gets the job done. Experts aren't interested in creating complex solutions simply to have the complexity, that misguided egoism is the territory of more junior developers. They often get it right the first try and almost always on the second one.
  </p>
  <p>
   Simply put, experts write readable code.  They comment and document it appropriately based on the complexity and criticality of that particular piece of code.
  </p>
  <p>
   All of this pays
   <em>
    huge dividends
   </em>
   when the next developer has to pick up where they left off. Especially if the next person isn't an expert.
  </p>
  <h4>
   <strong>
    More reasons you want an expert programmer
   </strong>
  </h4>
  <p>
   Is your business technology oriented?   Perhaps the software you create is even your main product. If nothing else I'm sure we can agree that if the software your developers create is to some degree critical to your business.
  </p>
  <p>
   I've worked in many different environments, with people of every skill level, and it's very easy to tell whether or not a company has expert developers. Do you often find that the software is down? That it has as many bugs or even just idiosyncrasies that make no sense to the user as it does features?  Do the users find it difficult to use?  Is the problem at hand relatively simple compared to the training or documentation necessary to begin using the software?
  </p>
  <p>
   If you answered yes to any of those questions you more than likely have average or below average developers.
  </p>
  <p>
   When you work in an environment with experts things simply work.  They are easier to use and require less initial training. The software is easier to modify.  Requested changes happen more frequently and easily.  Things just flow.  It is the difference between Apple and Microsoft.  It's the difference between the
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FApple-Video-Player-Black-Generation%2Fdp%2FB000EPHR0C%3Fie%3DUTF8%26s%3Delectronics%26qid%3D1186355223%26sr%3D8-2&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    iPod
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   and a
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FSony-CDPCX455-Disc-MegaStorage-Changer%2Fdp%2FB000069JWX%3Fie%3DUTF8%26s%3Delectronics%26qid%3D1186355301%26sr%3D1-2&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    400 disc CD changer
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   with 50+ buttons.
  </p>
  <p>
   As with many things in life, sometimes you get what you pay for. I'd love to hear your comments and opinions on the subject.
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   I've written a response to some of the questions and comments I've received on this article in a follow up post
   <a href="https://www.revsys.com/tidbits/followup-to-a-guide-to-hiring-programmers/">
    A follow up to "A Guide for Hiring Programmers"
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>Which PostgreSQL backend am I using?</title><link>http://www.revsys.com/tidbits/which-postgresql-backend-am-i-using/</link><description/><pubDate>Mon, 30 Jul 2007 09:25:38 +0000</pubDate><guid>http://www.revsys.com/tidbits/which-postgresql-backend-am-i-using/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Someone asked me how to determine which PostgreSQL backend a particular client was connected to.  Everyone's first thought is to do a
   <em>
    ps aux | grep postgres
   </em>
   which will show you the IP and user, but if you have different processes connecting from the same IP with the same usernames, how do you know which is which?
  </p>
  <p>
   One way to tell would be to see which queries are being executed by which backend and match that up to your client side.  But you can quickly get confused, especially if the various connections are all executing the same SQL statements, a web application for example.
  </p>
  <p>
   The simplest way was suggested by
   <a href="http://www.jacobian.org">
    Jacob Kaplan-Moss
   </a>
   , which is to use the pg_backend_pid() function like:
  </p>
  <p>
   SELECT pg_backend_pid();
  </p>
  <p>
   I love it when the solution is something really simple!
  </p>
 </div>
</div>
]]>/></item><item><title>Real PostgreSQL Benchmark</title><link>http://www.revsys.com/tidbits/real-postgresql-benchmark/</link><description/><pubDate>Mon, 09 Jul 2007 15:50:35 +0000</pubDate><guid>http://www.revsys.com/tidbits/real-postgresql-benchmark/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Josh Berkus and
   <a href="http://www.sun.com">
    Sun
   </a>
   have put together the first
   <em>
    real
   </em>
   <a href="http://blogs.ittoolbox.com/database/soup/archives/postgresql-publishes-first-real-benchmark-17470?ref=http%3A%2F%2Fwww%2Egoogle%2Ecom%2Freader%2Fview%2F">
    PostgreSQL performance benchmark
   </a>
   .  I run into people often who are still working under dubious performance comparisons done years ago against against competition like MySQL and Oracle. Hopefully this in depth comparison will put some of these arguments to rest.
  </p>
  <p>
   If you're just interested in the conclusions, PostgreSQL is as fast or faster than MySQL and nearly as fast as Oracle.  I know the performance improvements over the last few years have been nothing short of phenomenal, glad to see there is now a report to back up my gut feelings.
  </p>
 </div>
</div>
]]>/></item><item><title>SCALE Talk Slides and Audio</title><link>http://www.revsys.com/tidbits/scale-talk-slides-and-audio/</link><description>Slides and Audio from Frank's 2007 SCALE 5X talk. </description><pubDate>Mon, 09 Jul 2007 09:10:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/scale-talk-slides-and-audio/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I just noticed that the
   <a href="https://www.socallinuxexpo.org/scale5x/conference+info/speakers/Frank+Wiles/">
    Southern California Linux Expo
   </a>
   (SCALE) has my slides and a MP3 of the audio of the talk up on their site.  The audio isn't actually too bad, especially considering I forgot I was being taped and moved away from the microphone during the Q&amp;A section.
  </p>
  <p>
   You can also find
   <a href="https://www.revsys.com/talks/scale5x/mod_perl/">
    OpenOffice and PDF
   </a>
   versions of the slides on the Revolution Systems site. Enjoy!
  </p>
 </div>
</div>
]]>/></item><item><title>Gantry Article</title><link>http://www.revsys.com/tidbits/gantry-article/</link><description/><pubDate>Fri, 22 Jun 2007 14:01:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/gantry-article/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Phil Crow, one of the
   <a href="http://www.usegantry.org">
    Gantry
   </a>
   core developers and the creator of
   <a href="http://www.usegantry.org/pod/bigtop">
    Bigtop
   </a>
   has a great
   <a href="http://www.onlamp.com/pub/a/onlamp/2007/06/14/a-holiday-gantry-web-application.html">
    article on how easy it is to build and modify Gantry applications using Bigtop
   </a>
   .
  </p>
  <p>
   I highly recommend checking it out, it shows off some of Gantry and Bigtop's best features.
  </p>
 </div>
</div>
]]>/></item><item><title>Sprint Customer Service Experience</title><link>http://www.revsys.com/tidbits/sprint-customer-service-experience/</link><description/><pubDate>Thu, 21 Jun 2007 09:26:19 +0000</pubDate><guid>http://www.revsys.com/tidbits/sprint-customer-service-experience/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I've been a
   <a href="http://www.sprint.com">
    Sprint
   </a>
   customer for years.  While I don't think any of the current cell phone providers have what I would call good customer service or a good customer experience, but Sprint has finally gotten around to really pissing me off.
  </p>
  <p>
   It all started a few weeks ago.  My cell rings and a guy announces that he's with Sprint and that my contract is about to expire and if it would be a good time to discuss my plan, phone, etc.  It was approximately 12:15.  Right in the middle of lunch.  I told him it wasn't a good time and that I was planning on getting a new
   <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FPalm-One-Treo-Cellular-Unlocked%2Fdp%2FB000Q9M2FQ%3Fie%3DUTF8%26qid%3D1182458116%26sr%3D8-4&amp;tag=revosystblog-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
    Treo
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=ur2&amp;o=1"/>
   phone, with PalmOS, in a few months as mine is getting older and I'd review my plan at that point.  Sprint Guy thanked me.  So far not
   <em>
    too
   </em>
   bad.
  </p>
  <p>
   About a week later I received essentially the same call and gave the same response hoping this Sprint Girl would mark me off as Contacted, Handled or whatever they needed to do so I wouldn't be bothered again.
  </p>
  <p>
   I guess Sprint isn't interested in the fact that I'm disinterested in them, as they have responded by increasing the frequency of their calls.  It started with a couple of calls the next week, but this week has been ridiculous.  They have called me with
   <strong>
    THE TWO EXACT SAME OFFERS
   </strong>
   5 times since last Saturday.   Each time telling them I wasn't interested at this time and secret hoping this would be the last call I had to endure.
  </p>
  <p>
   When I was called again earlier today I asked to be put on their internal "do not call list" after I explained the issue.  Sprint Girl apologized and then said she would put me on the list, but that I should know it can take
   <em>
    <strong>
     several weeks
    </strong>
   </em>
   before that request would filter through the proper channels.
  </p>
  <p>
   So why does this piss me off? It isn't that they called in the first place or that the person on the other ends sounds so beaten down and bored reading their little scripts. It pisses me off because it proves that they
   <em>
    do not care about me as a customer
   </em>
   . Why do I know they don't care? Let me count the ways...
  </p>
  <ol>
   <li>
    Calling during lunch.  Sorry if it hurts your calls per hour numbers, but you shouldn't bug your customers first thing in the morning, during their lunch, during dinner, or late at night. Please remember that you are calling AN EXISTING customer and the last thing you want to do is actively annoy them.
   </li>
   <li>
    The first call did not stop the subsequent calls.  This is just plain laziness on Sprint's part. It's extra sad when you consider that they are wasting their own money re-calling people with the same offer they weren't interested in last week ( or yesterday ).
   </li>
   <li>
    They have no process to ensure they aren't calling their customers too much. I don't mean from the same list/telemarketing plan ( which should have been covered if they did #2 ), but company wide.  How hard it to put together a near real-time database of who has been called and stop any and all subsequent calls for a time period?  Well I'm a software developer and I can tell you it isn't hard at all.
   </li>
   <li>
    The fact that it takes several weeks for my request to not be called to be processed. What are they doing chiseling it into a granite wall at the call center? Do they need to get signed approval in triplicate from some paper pushing manager?  You're a technology company, start to act like it.
   </li>
  </ol>
  <p>
   Sprint always seems to be having money troubles and laying off staff.  What they probably don't realize is that it's marketing/customer service mistakes like this that are going to kill them in the end.
  </p>
  <p>
   Cell phone providers are all basically the same.  Coverage seems about the same from what I've read, same with dropped call numbers, and all of the usual issues. You can't really do much with stats like "We have 1.7% more coverage area" as no one cares. Every competing product works basically like yours. So what does a cell phone provider need to do, they need to make
   <strong>
    EVERYTHING
   </strong>
   about the customer experience as easy and painless as possible.
  </p>
  <p>
   And you don't do that by having the
   <em>
    only
   </em>
   real contact I've had with your business in years be ridiculously annoying. Hopefully some Sprint folks will run across this post and change their ways before customers like myself who care more about the ease of use and customer experience than they do about brands, phones, features, or even price.
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   Looks like the amazing
   <a href="http://sethgodin.typepad.com/seths_blog/2007/07/the-first-thing.html">
    Seth Godin agrees with me
   </a>
   that Sprint needs to start caring more about their customers before someone else does
  </p>
 </div>
</div>
]]>/></item><item><title>Plat_Forms Results Announced</title><link>http://www.revsys.com/tidbits/plat_forms-results-announced/</link><description/><pubDate>Thu, 21 Jun 2007 06:37:20 +0000</pubDate><guid>http://www.revsys.com/tidbits/plat_forms-results-announced/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   The results from the
   <a href="http://www.plat-forms.org/2007">
    Plat_forms
   </a>
   contest have finally been announced.  You can find a brief overview and the full results
   <a href="http://www.plat-forms.org/2007/results/">
    here
   </a>
   .
  </p>
  <p>
   While the Revolution Systems team did not come out the winner, overall I'm happy with how the contest turned out.  They seemed to have focused in on some areas that I didn't expect, but they definitely show everyone in a fair light.
  </p>
  <p>
   As I believe I mentioned in a previous blog post, our two biggest mistakes were strategic and not really code related.  We should have opted to completely ignore the SOAP aspects of the application.  We knew that SOAP support in Perl is pretty poor going in and Phil even took some time to code up some libraries to make it a bit easier for us.
  </p>
  <p>
   The other mistake we made was to do "big bang" integration at the end.  While many developers loath big bang integration, in our day-to-day environment it rarely if ever causes us any trouble.  But leaving the integration until the end of a 30 hour work session when we were exhausted was a mistake.  I know of at least a couple of requirements we missed simply because we forgot to link them into the site properly.  Code was all there, we were just tired!
  </p>
 </div>
</div>
]]>/></item><item><title>Email, Templates, and Perl</title><link>http://www.revsys.com/tidbits/email-templates-and-perl/</link><description>Templating emails when using Perl</description><pubDate>Thu, 31 May 2007 06:54:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/email-templates-and-perl/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I have been meaning to talk about one of my new favorite Perl modules,
   <a href="https://search.cpan.org/~chunzi/MIME-Lite-TT-HTML-0.03/lib/MIME/Lite/TT/HTML.pm">
    MIME::Lite::TT::HTML
   </a>
   , for quite a while now.  As I mentioned in a
   <a href="https://blog.revsys.com/2006/01/sending_email_f.html">
    previous post
   </a>
   , there are a bazillion different ways to send an Email message from Perl.  This one is just my new favorite.
  </p>
  <p>
   Here is a short list as to why:
  </p>
  <ul>
   <li>
    Can be used for complex multi-part messages and handles attachments easily
   </li>
   <li>
    Built upon the equally great MIME::Lite module
   </li>
   <li>
    Allows you to easily template your messages using the familiar Template Toolkit package
   </li>
  </ul>
  <p>
   The templating part is, in my opinion, the important part.  How many times have you had to go edit some source code just to change the text or subject of a message?  Isn't that just terribly annoying. We use configuration files, MVC with HTML templates, etc, etc. to
   <strong>
    not
   </strong>
   hard code things into our apps, but for some reason many people ( myself included for years ) have neglected Email.
  </p>
  <p>
   Not any longer, I've switched to using this module as my standard way of sending Email these days.  If you are interested in learning more about MIME::Lite::TT::HTML, check out my short howto
   <a href="https://www.revsys.com/writings/perl/sending-email-with-perl.html">
    Sending Email with Perl Best Practice
   </a>
   on the subject.
  </p>
 </div>
</div>
]]>/></item><item><title>PostgreSQL error messages confusing to new users</title><link>http://www.revsys.com/tidbits/postgresql-error-messages-confusing-to-new-users/</link><description>PostgreSQL errors can be confusing</description><pubDate>Wed, 23 May 2007 08:37:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/postgresql-error-messages-confusing-to-new-users/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   In the spirit of my
   <a href="/writings/postgresql/errors.html">
    blog post
   </a>
   last week, I've created a new page that shows a couple of the more
   <a href="/writings/postgresql/errors.html">
    common error messages
   </a>
   that confuse newer PostgreSQL users. It is my intention to expand this over time as I see people having trouble.
  </p>
  <p>
   If you have any error messages you feel should be included or you find any technical inaccuracies please post a comment and I'll include it on the page.
  </p>
 </div>
</div>
]]>/></item><item><title>Dumb Server Policies</title><link>http://www.revsys.com/tidbits/dumb-server-policies/</link><description/><pubDate>Sat, 19 May 2007 17:37:50 +0000</pubDate><guid>http://www.revsys.com/tidbits/dumb-server-policies/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was chatting with someone recently about what may truly be the dumbest server policy I've ever heard of. He indicated that the company required that:
  </p>
  <blockquote>
   <p>
    <em>
     " All company servers were required to be rebooted each day at noon and midnight".
    </em>
   </p>
  </blockquote>
  <p>
   At first you might be thinking this is an old vestige of a Windows shop policy from days long gone, but no this included their
   <em>
    BSD and Linux servers
   </em>
   <em>
    AND
   </em>
   * all of their desktop PCs.   He also mentioned a couple of choice quotes from the policy:
  </p>
  <blockquote>
   <p>
    <em>
     " The policy has been considered to save the company thousands of dollars in fewer crashes. And synergizes with our risk management initiatives".
    </em>
   </p>
  </blockquote>
  <p>
   This just screams of a policy created by someone who doesn't understand the real underlying problem. While their heart is in the right place, I seriously doubt this saves the company money.  In fact, I'm quite sure it costs them much more in early hardware failures and lost productivity when systems are offline.
  </p>
  <p>
   The policy is almost as bad as someone instituting a mandate that requires everyone to change their password twice a day at noon and midnight in an effort to "strengthen our security". ( For the record, all that does is weaken your security as EVERYONE just has to write it down ).
  </p>
  <p>
   The moral of the story is to handle the problem at hand with the proper solution. Restarting might be good for Windows based systems, but it is completely unnecessary in a Linux/Unix/*BSD system.
  </p>
 </div>
</div>
]]>/></item><item><title>Common PostgreSQL problem</title><link>http://www.revsys.com/tidbits/common-postgresql-problem/</link><description/><pubDate>Wed, 16 May 2007 07:18:16 +0000</pubDate><guid>http://www.revsys.com/tidbits/common-postgresql-problem/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I see this problem pop up in the #postgresql IRC channel so often I felt it was necessary to blog about it. This problem trips up so many new users it might even be worth changing the default error message to indicate what is going on. The error message happens when the user tries to run psql for the first time:
  </p>
  <p>
   <strong>
    psql: FATAL: database "root" does not exist
   </strong>
  </p>
  <p>
   Where
   <em>
    " root"
   </em>
   is the current Unix username of the operator.  By default PostgreSQL attempts to log you into a database that is the same as your username.  However, it does not setup this database for you because it would be silly to setup 500 databases for all of the Unix users on your system, if only two of them are going to be using PostgreSQL.
  </p>
  <p>
   When setting up PostgreSQL for the first time you need to do the following:
  </p>
  <ol>
   <li>
    su ( or otherwise ) become your root user
   </li>
   <li>
    su ( or otherwise ) become your PostgreSQL user, typically 'postgres'
   </li>
   <li>
    Create your first database
   </li>
  </ol>
  <p>
   The ultimate goal here is to become your PostgreSQL user, typically this involves becoming root and then switching to user postgres.  Upon setup this is the only user that is allowed to create users and databases.
  </p>
  <p>
   Your "first" database can be created in one of two ways:
  </p>
  <ol>
   <li>
    Run the command 'psql template1' followed by a 'CREATE DATABASE' SQL call
   </li>
   <li>
    Run the command 'createdb
    <dbname>
     '
    </dbname>
   </li>
  </ol>
  <p>
   While you're still the postgres user it is probably best to also create a user with 'createuser
   <username>
    ' or a 'CREATE USER' SQL call. See this section of the PostgreSQL documentation for more information on
    <a href="http://www.postgresql.org/docs/8.2/static/user-manag.html">
     creating users and roles
    </a>
    . You'll also want to read up on
    <a href="http://www.postgresql.org/docs/8.2/static/managing-databases.html">
     managing databases
    </a>
    .
   </username>
  </p>
  <p>
   <strong>
    NOTE:
   </strong>
   The programs createdb and createuser may not be, by default, in your PATH so it may be necessary to use locate or type in the full path to your PostgreSQL bin/ directory.
  </p>
  <p>
   Hope this helps!
  </p>
 </div>
</div>
]]>/></item><item><title>Gantry book released to the world</title><link>http://www.revsys.com/tidbits/gantry-book-released-to-the-world/</link><description/><pubDate>Wed, 11 Apr 2007 16:12:12 +0000</pubDate><guid>http://www.revsys.com/tidbits/gantry-book-released-to-the-world/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Phil Crow has released a new book on using Gantry and Bigtop.  It's titled
   <a href="http://www.lulu.com/content/764823">
    Building Web Applications with Gantry and Bigtop
   </a>
   I encourage you to check it out!
  </p>
 </div>
</div>
]]>/></item><item><title>Installing Mail::Cclient on RHEL</title><link>http://www.revsys.com/tidbits/installing-mailcclient-on-rhel/</link><description/><pubDate>Thu, 22 Mar 2007 15:23:32 +0000</pubDate><guid>http://www.revsys.com/tidbits/installing-mailcclient-on-rhel/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Ran into a small build problem when trying to install the CPAN module Mail::Cclient on a Red Hat Enterprise Linux system.  Figured I would go ahead and document the process in full for others ( and for myself later in life ).
  </p>
  <p>
   FIrst off you will most likely need to install two RPMs
  </p>
  <ul>
   <li>
    libc-client
   </li>
   <li>
    libc-client-devel
   </li>
  </ul>
  <p>
   Then you will need to download a distribution of Mail::Cclient, don't even bother trying to install this from the CPAN shell as it won't work.
  </p>
  <p>
   Unpack your distribution with the normal tar -xvzf Mail-Cclient-x.xx.tar.gz and cd into the directory. You will then, unfortunately, have to edit the Makefile.PL by hand.  Specifically you will have to change INC argument to WriteMakefile() to be:
  </p>
  <div class="codehilite">
   <pre><span></span><code>**" INC"    =&gt;   "-I$CCLIENT_DIR -I/usr/include/imap",**
</code></pre>
  </div>
  <p>
   This instructs the build process to look for the shared library in $CCLIENT_DIR and the headers in /usr/include/imap.
  </p>
  <p>
   Then it is just a matter of calling:
  </p>
  <p>
   <strong>
    perl Makefile.PL CCLIENT_DIR=/usr/lib
    <br/>
    make
    <br/>
    make install
   </strong>
  </p>
  <p>
   Hope this helps someone else who gets bit by this annoyance.
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   <br/>
   Turns out that I was grabbing an older version of Mail::Cclient ( version 1.1 specifically ) if you use Mail::Cclient 1.12 then the install process requires a few other RPMs:
  </p>
  <ul>
   <li>
    openssl-devel
   </li>
   <li>
    pam-devl e
   </li>
  </ul>
  <p>
   And then you install it with:
  </p>
  <p>
   <strong>
    perl Makefile.PL --cclient_dir=/usr/lib --with-pam --with-cclient-includes=/usr/include/imap/ --with-shared_cclient
   </strong>
  </p>
 </div>
</div>
]]>/></item><item><title>DateTime: Common Calcuations</title><link>http://www.revsys.com/tidbits/datetime-common-calcuations/</link><description/><pubDate>Mon, 19 Mar 2007 08:33:13 +0000</pubDate><guid>http://www.revsys.com/tidbits/datetime-common-calcuations/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Over the course of the last year or so I've fallen in love with
   <a href="http://search.cpan.org/~drolsky/DateTime-0.36/lib/DateTime.pm">
    DateTime
   </a>
   set of modules.  However, I think their docs are a bit lacking for some simple calculations.  Often I need to know how many hours, minutes, or seconds ( or some combo of them ) exist between two DateTime objects.
  </p>
 </div>
</div>
]]>/></item><item><title>SMTP Connections: How to handle large loads</title><link>http://www.revsys.com/tidbits/smtp-connections-how-to-handle-large-loads/</link><description/><pubDate>Mon, 12 Mar 2007 17:35:08 +0000</pubDate><guid>http://www.revsys.com/tidbits/smtp-connections-how-to-handle-large-loads/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I recently came across several blog posts about the declining state of E-mail due to spam.  Specifically these:
  </p>
  <ul>
   <li>
    <a href="http://taint.org/2007/03/06/141708a.html">
     Spam Volumes at accidental-DOS Levels
    </a>
   </li>
   <li>
    <a href="http://radar.oreilly.com/archives/2007/03/another_war_wer.html">
     Another War We Aren't Winning: Us vs Spam
    </a>
   </li>
   <li>
    <a href="http://jeremy.zawodny.com/blog/archives/008681.html">
     E-mail as we know it is doomed
    </a>
   </li>
  </ul>
  <p>
   I've been running E-mail servers for myself and others for over 10 years now and I have to agree that with the current version of SMTP we all use, there isn't much that can be done about spam that isn't already being done.  If you've got some RBLs, SPF, anti-virus, and a decent spam filter setup, there isn't much more you can do.  Sure you can get +/- another percentage point, but you won't really find a solution that is 100% effective 100% of the time.  It just isn't possible with the current standards.
  </p>
  <p>
   However, these articles also discuss another issue that is overlooked by everyone who doesn't run a
   <strong>
    large
   </strong>
   E-mail system.   By large I'll say over 1,000 users. Around that point you start to run into problems being able to handle the shear number of incoming SMTP connections.  Note that you
   <em>
    may
   </em>
   hit this point long before 1,000 users depending on hardware and your personal traffic levels. But I digress.
  </p>
  <p>
   I haven't found any Open Source solution to this problem, but I've come pretty close.
   <a href="http://www.mailchannels.com/connectionmanagement.html">
    TrafficControl
   </a>
   is a commercial product from
   <a href="http://www.mailchannels.com">
    MailChannels
   </a>
   that is built upon the good Open Source base of Apache and
   <a href="http://perl.apache.org">
    mod_perl
   </a>
   . TrafficControl does two things for you:
  </p>
  <ul>
   <li>
    It uses async I/O to proxy incoming SMTP connections.  This has allowed me to handle 10x the number of connections on the
    <em>
     <strong>
      exact same hardware
     </strong>
    </em>
   </li>
   <li>
    Allows you to configure throttling rules based on RBLs, Operating System, etc. and choke off the bandwidth to suspicious sending servers.  This reduces a great deal of spam as most bots simply move on if they are not getting go flow.  After a configurable time period it will remove the choke hold and allow the message to continue on normally.  Servers that follow the SMTP RFC have no problem with this as they hang on during the process.
   </li>
  </ul>
  <p>
   I've been using it for about a year now and couldn't imagine trying to run a large E-mail system without it now.  I encourage you to check it out if you see you are having a similar problem.
  </p>
 </div>
</div>
]]>/></item><item><title>Early bird registration for www.read-it-later.com begins</title><link>http://www.revsys.com/tidbits/early-bird-registration-for-wwwread-it-latercom-begins/</link><description/><pubDate>Sat, 03 Mar 2007 13:14:18 +0000</pubDate><guid>http://www.revsys.com/tidbits/early-bird-registration-for-wwwread-it-latercom-begins/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Revolution Systems will soon be offering a new web service called
   <a href="http://www.read-it-later.com">
    www.read-it-later.com
   </a>
   .
  </p>
  <p>
   <strong>
    What is read-it-later.com?
   </strong>
  </p>
  <p>
   read-it-later.com is a browser based application to help you better keep up with your online reading material.  It was built because I often found myself running across a long blog post, HOWTO, PDF, or otherwise large piece of text I
   <em>
    wanted
   </em>
   or even
   <em>
    needed
   </em>
   to read, but didn't have time at that particular moment.
  </p>
  <p>
   Like many of you I suspect, I had a "TO READ" Firefox bookmark folder where I would plop these links with every intention of going back to read them.  Unfortunately, links went in, but they rarely if ever came out.
  </p>
  <p>
   Enter read-it-later.com.  read-it-later.com allows you to bookmark these links into categories of your choosing and then receive a certain amount of links, your
   <em>
    Reading List
   </em>
   , via E-mail, RSS, or ATOM feeds on a schedule of your choosing.
  </p>
  <p>
   For example, I currently have my read-it-later.com account setup like this:
  </p>
  <ul>
   <li>
    Send me 2 links via E-mail from my 'Work' and 'Perl' categories every morning, Monday through Friday
   </li>
   <li>
    Send me 3 links via E-mail from my 'Personal' category and 2 links from my 'Business/Marketing' category every Tuesday and Thursday evenings
   </li>
   <li>
    Send me 3 links via RSS from my 'Technology to Check Out' category every morning.
   </li>
  </ul>
  <p>
   I have a few more categories and Reading Lists setup, but this gives you an idea of the options available to you.  With this, I was able to trim down my "TO READ" folder in just a few short weeks.
  </p>
  <p>
   <strong>
    I already use del.icio.us or ma.gnolia.com, will this work for me?
   </strong>
  </p>
  <p>
   If you are a
   <a href="http://del.icio.us">
    del.icio.us
   </a>
   or
   <a href="http://ma.gnolia.com">
    ma.gnolia.com
   </a>
   user you can continue to use those services to keep your bookmarks and read-it-later.com will just augment them by giving you the ability to populate your Reading Lists from tags you setup for that purpose in these services.
  </p>
  <p>
   It works like this, you setup a specific tag in your current bookmark service, say 'rl-work' and then setup a read-it-later category that is associated with that tag and your login from the service you use.  The preceeding 'rl-' is just a convention we've been using, your tag can be anything you like.
  </p>
  <p>
   Once this link between your bookmark service's tag and your read-it-later category is established, read-it-later will check that tag hourly to see if there are any new links you have added.  If it finds any, they will be added to a future reading list for you automatically.
  </p>
  <p>
   <strong>
    Other features available at launch:
   </strong>
  </p>
  <ul>
   <li>
    Set a specific date you must read a link by and it will appear in your read lists prior to that date.
   </li>
   <li>
    Links are tracked by when they are actually clicked through. While we can't guarantee that you'll
    <strong>
     actually
    </strong>
    read the material, this does remove any worry about missing the material if you neglect a particular E-mail or feed entry.
   </li>
   <li>
    If you find you have some extra time to do some reading you can browse your un-read links in whole or by category and get some extra reading out of the way.  Links read in this manner will not be re-sent to you in another Reading List.
   </li>
   <li>
    You can also have the system generate an extra, one time, Reading List you have previously defined and it will be sent to you immediately.
   </li>
  </ul>
  <p>
   <strong>
    Will there be an API?
   </strong>
  </p>
  <p>
   We plan on making available a REST style API to all aspects of the service in the coming months. We want to ensure we have the overall structure of the application baked in a bit more before we make the API public so we don't end up having to release a 2.0 version of the API too quickly and run into compatibility problems.
  </p>
  <p>
   <strong>
    Pricing?
   </strong>
  </p>
  <p>
   read-it-later.com will be priced at $3/month or $30/year.  All users will receive a 30 day free trial to make sure read-it-later is something they find useful.  No credit card or unnecessary personal information will be required to register and try out the service.
  </p>
  <p>
   As it stands right now, your E-mail address and a password are all that are required to register and we hope to keep it that way.  We hate filling out 97 different pieces of information as much as you do.
  </p>
  <p>
   In addition, early beta testers who sign up now will receive 90 days of free service for being early adopters and to help us find any remaining bugs or user interface confusion as soon as possible. You can sign up
   <a href="http://www.read-it-later.com">
    at the site right now
   </a>
   . The early beta registration site isn't very visually pleasing, we've focused all of our efforts on the actual site, so please bear with us.
  </p>
  <p>
   <strong>
    Questions or Comments?
   </strong>
  </p>
  <p>
   Got an question about the service or an idea on how we might make it better for you feel free to leave a comment on this blog post or send us an E-mail at
   <a href="mailto:info@read-it-later.com">
    info@read-it-later.com
   </a>
   . We look forward to hearing from you!
  </p>
 </div>
</div>
]]>/></item><item><title>Next actions</title><link>http://www.revsys.com/tidbits/next-actions/</link><description/><pubDate>Sun, 25 Feb 2007 10:00:50 +0000</pubDate><guid>http://www.revsys.com/tidbits/next-actions/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   We've all been to websites that break long articles up into multiple pages. It tends to be used mostly on news sites these days. Long ago this technique was primarily used to reduce the amount of time it took for a page to load, but now it is an attempt at increasing the sites page view count.
  </p>
  <p>
   If a user is on say Page 1 of 7, there is a high probability that they will want to read the rest of the article. I'll go so far to say that the two highest probability actions for a user to take is to move on to the next page or leave the site entirely.  The second most likely option the web designer has no real control over, but
   <strong>
    why or why
   </strong>
   do they make the most likely option so damn hard?
  </p>
  <p>
   Here is an example of a site that sorta gets it right:
  </p>
  <p>
   <a href="http://revsys.typepad.com/photos/uncategorized/example1_1.jpg">
    <img alt="Example1_1" src="http://revsys.typepad.com/photos/uncategorized/example1_1.jpg"/>
   </a>
  </p>
  <p>
   They have small navigation in case I want to jump ahead several pages and the 'Next Page' link is at least fairly prominent.
  </p>
  <p>
   Here is another example where a site is trying, but still gets it wrong:
  </p>
  <p>
   <a href="http://revsys.typepad.com/photos/uncategorized/example2_1.jpg">
    <img alt="Example2_1" src="http://revsys.typepad.com/photos/uncategorized/example2_1.jpg"/>
   </a>
  </p>
  <p>
   I have to admit I've been using this particular site for a long time and did not notice the "CONTINUED:' link at all until I was cropping the example image above.  We have all been conditioned to look for the "Next" or page numbers, so my eye is always immediately drawn to the small '2' link.  Which, it is sad to say, is smaller than the Digg icon.  No to mention the entire 'Digg this' link's size.
  </p>
  <p>
   The fragment of the title, with the trailing ellipse, serves only to confuse me.  I have to think "Oh, is that a fragment of the title of the article I'm currently reading" or it is a link off to some other article on a similar subject.  What
   <em>
    <strong>
     <em>
      is
     </em>
    </strong>
   </em>
   the title of the article I'm currently reading?
  </p>
  <p>
   Not to harp on this site too much, but why are the Print, del.icio.us, and Digg links better visually designed than the most probable next action of reading the next page?
  </p>
  <p>
   In my opinion, it should look more like this:
  </p>
  <p>
   <a href="http://revsys.typepad.com/photos/uncategorized/example3.jpg">
    <img alt="Example3" src="http://revsys.typepad.com/photos/uncategorized/example3.jpg"/>
   </a>
  </p>
  <p>
   You still have the option of jumping ahead to a certain page, but your most likely action is big, bold, and visible.  I think the key here is to not make the user think. The font size of the "Next" link needs to be considerably larger than the article's text, not just the same size and bold.
  </p>
  <p>
   Speaking of which, if you haven't read Steve Krug's book on the subject I encourage to pick up a copy of
   <a href="http://www.amazon.com/gp/product/0321344758?ie=UTF8&amp;tag=revosystblog-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0321344758">
    Don't Make Me Think: A Common Sense Approach to Web Usability (2nd Edition)
   </a>
   <img alt="" src="http://www.assoc-amazon.com/e/ir?t=revosystblog-20&amp;l=as2&amp;o=1&amp;a=0321344758"/>
  </p>
 </div>
</div>
]]>/></item><item><title>Speaking at SCALE in Los Angeles</title><link>http://www.revsys.com/tidbits/speaking-at-scale-in-los-angeles/</link><description>Frank Wiles spoke at SCALE 5X in 2007</description><pubDate>Tue, 06 Feb 2007 14:50:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/speaking-at-scale-in-los-angeles/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I nearly forgot to blog about this, but next Sunday (February 11th, 2007) I'll be giving a short introduction on using
   <a href="https://perl.apache.org">
    mod_perl 2.0
   </a>
   at the 5th Annual
   <a href="https://www.socallinuxexpo.org/scale5x/">
    Southern California Linux Expo
   </a>
   . I hope to see you there!
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   The slides for this talk can be found
   <a href="https://www.revsys.com/talks/scale5x/mod_perl/">
    here.
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>Plat_Forms Recap</title><link>http://www.revsys.com/tidbits/plat_forms-recap/</link><description/><pubDate>Thu, 01 Feb 2007 17:25:42 +0000</pubDate><guid>http://www.revsys.com/tidbits/plat_forms-recap/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   So we survived
   <a href="http://www.plat-forms.org/2007">
    Plat_Forms
   </a>
   . Overall I think the contest was fun, interesting, and very well run.  But the real test of this is going to be how the results come out.
  </p>
  <p>
   I think we fared well, compared to the other Perl teams, but we certainly did make some mistakes. We didn't prepare our VMWare server image as well as we should have.  We also took the approach of building major pieces of the functionality and then linking them all up towards the end.  In retrospect, we should have taken a more (ugh I hate saying this)
   <em>
    Agile Programming
   </em>
   approach and done more small iterations.   Normally this isn't at all an issue, but with the entire development happening in 30 hours it would have been a better approach.
  </p>
  <p>
   What I really underestimated was how much of a role fatigue played in this contest. I guess I'm not as young as I used to be! We had a few big mistakes that can only by explained by being way too tired to be coding. If the contest had been three 10 hour sessions or even 2 15 hour sessions we would have done at least 50% better than we ultimately did.
  </p>
 </div>
</div>
]]>/></item><item><title>Progress at  Plat_forms</title><link>http://www.revsys.com/tidbits/progress-at-plat_forms/</link><description/><pubDate>Fri, 26 Jan 2007 18:32:17 +0000</pubDate><guid>http://www.revsys.com/tidbits/progress-at-plat_forms/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Well the contest began roughly 21 hours ago and I think we are making pretty good progress.  Just got back from a quick 4 hour nap in the hotel, my team makes are more hard core and only took 3 hour naps.
  </p>
  <p>
   It's difficult to tell how we are stacking up to the other teams as there is very little interaction between the teams as we are all too busy building our applications.  Check out the
   <a href="http://www.plat-forms.org/2007/blog/">
    contest blog
   </a>
   for more regular updates from yesterday and today, including some photo's of all of the various team members. Luckily those photos were taken around 11pm last night so we don't look
   <em>
    <strong>
     too
    </strong>
   </em>
   tired!
  </p>
 </div>
</div>
]]>/></item><item><title>Arrival in Germany</title><link>http://www.revsys.com/tidbits/arrival-in-germany/</link><description/><pubDate>Wed, 24 Jan 2007 08:39:02 +0000</pubDate><guid>http://www.revsys.com/tidbits/arrival-in-germany/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   After an interesting flight where two people got sick over the Atlantic ( luckily there were 2 doctors and 4 nurses traveling on the flight ) and a ground crew who added
   <strong>
    too
   </strong>
   much oil to the engines we finally made it into Germany yesterday morning.
  </p>
  <p>
   One of the cultural differences I forgot about when traveling in Europe is the lack of free wireless Internet access.  Turns out our hotel only really has wifi from 6pm to 9am for a reasonable price. We just finished registering at the
   <em>
    Open Source Meets Business
   </em>
   conference so I'm taking advantage of their free wifi to download the 3 metric tons of E-mail that is waiting for me.
  </p>
 </div>
</div>
]]>/></item><item><title>And some people say programmers are boring</title><link>http://www.revsys.com/tidbits/and-some-people-say-programmers-are-boring/</link><description/><pubDate>Thu, 18 Jan 2007 17:23:13 +0000</pubDate><guid>http://www.revsys.com/tidbits/and-some-people-say-programmers-are-boring/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It is a common stereotype that computer programmers tend to be on the boring side.  Not Perl programmers it turns out.  We're a
   <em>
    wacky
   </em>
   bunch. Take for example our benevolent dictator
   <a href="http://www.wall.org/~larry/">
    Larry Wall
   </a>
   , he's anything but boring.
  </p>
  <p>
   Not to mention
   <a href="http://www.theperlreview.com/Shirt/">
    Schwern's Shirt
   </a>
   .
  </p>
  <p>
   And what other programming language has an elder like
   <a href="http://use.perl.org/~brian_d_foy/journal/32173?from=rss">
    brian d foy
   </a>
   who has violated the Posse Commitatus Act?
  </p>
  <p>
   This is just a small sampling of Perl's fun and wacky side.  Personally, I think it's one of the reasons our community is so strong.
  </p>
 </div>
</div>
]]>/></item><item><title>Netgear only a part time peripheral</title><link>http://www.revsys.com/tidbits/netgear-only-a-part-time-peripheral/</link><description/><pubDate>Wed, 17 Jan 2007 15:27:40 +0000</pubDate><guid>http://www.revsys.com/tidbits/netgear-only-a-part-time-peripheral/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Looks like
   <a href="http://www.askbjornhansen.com">
    Ask Bj&oslash;rn Hansen
   </a>
   has been bitten by the
   <a href="http://www.askbjoernhansen.com/2007/01/16/netgear_equipment_not_made_to_be_on_all_day.html">
    bad customer service at Netgear.
   </a>
   While I do somewhat understand they feel they are building a product for home or SOHO users, but seriously who turns off their switch or router at home?  That's right
   <em>
    <strong>
     only when it hangs
    </strong>
   </em>
   .
  </p>
  <p>
   I've never been a big fan of Netgear products, but based on this wonderful word of mouth marketing I'm sure to stay far away from them. They could really use some help from someone like
   <a href="http://sethgodin.typepad.com">
    Seth Godin
   </a>
   , specifically I think they need to read
   <a href="http://sethgodin.typepad.com/seths_blog/2006/11/why_bother.html">
    this post.
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>Plat_forms Web Framework Contest</title><link>http://www.revsys.com/tidbits/plat_forms-web-framework-contest/</link><description/><pubDate>Tue, 16 Jan 2007 07:02:26 +0000</pubDate><guid>http://www.revsys.com/tidbits/plat_forms-web-framework-contest/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   As some of you may have heard, along with some help from the
   <a href="http://www.perl-workshop.de/">
    German Perl Workshop
   </a>
   Revolution Systems is sponsoring a team for the upcoming
   <a href="http://www.plat-forms.org/">
    Plat_forms Web Development Platform Comparison
   </a>
   .
  </p>
  <p>
   I will be blogging about the trip and contest here and my team member
   <a href="http://use.perl.org/~philcrow/journal/">
    Phil Crow
   </a>
   will be also over at
   <a href="http://use.perl.org">
    use.perl.org
   </a>
   .
  </p>
  <p>
   Personally, I think the results of this contest are going to be facinating. We'll finally have some in depth analysis of some of the more popular technologies in as close to a real world scenario as you can create artificially.
  </p>
  <p>
   My only worry is that the results will be dismissed by the community at large due to the lack of Python, Ruby, and .NET being involved in the contest. I'm not sure, but I bet the fact that it is in Germany ( and in the middle of winter no less ) is part of it!
  </p>
  <p>
   We're obviously a Perl team, but here is a list of the technologies we'll be using during the contest:
  </p>
  <ul>
   <li>
    <a href="http://perl.apache.org">
     mod_perl
    </a>
   </li>
   <li>
    <a href="http://www.usegantry.org">
     Gantry
    </a>
   </li>
   <li>
    <a href="http://www.postgresql.org">
     PostgreSQL
    </a>
   </li>
  </ul>
 </div>
</div>
]]>/></item><item><title>Articles claims Open Source databases to have lower TCO</title><link>http://www.revsys.com/tidbits/articles-claims-open-source-databases-to-have-lower-tco/</link><description/><pubDate>Wed, 22 Nov 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/articles-claims-open-source-databases-to-have-lower-tco/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Ran across
   <a href="http://www.vnunet.com/vnunet/news/2168971/open-source-databases-slice">
    this article
   </a>
   shows some Forrester Research data that Open Source databases such as
   <a href="http://www.postgresql.org">
    PostgreSQL
   </a>
   are
   <strong>
    <em>
     60%
    </em>
   </strong>
   cheaper than the commercial alternatives.
  </p>
  <p>
   I think most geeks are already aware of this. What I found interesting is the quote:
  </p>
  <blockquote>
   <p>
    "Eighty per cent of the applications typically use only 30 per cent of the features found in commercial databases," Yuhanna told vnunet.com. "The open source databases deliver those features today."
   </p>
  </blockquote>
  <p>
   In my experience working with clients it is more like 95% of applications use only 10% of the features found in commercial databases. I can't even count the number of times a company
   <em>
    absolutely needed
   </em>
   Oracle for a 100MB, 3 table, simple CRUD database.
  </p>
  <p>
   One of my favorite themes that comes up is when a company again
   <em>
    needs Oracle/DB2/whatever
   </em>
   because it has all of the mission critical features they need such as clustering, fail over, etc.   Then when it comes to implementation time the tune changes to "Oh we don't really need a cluster.  And now that I think about it, we can handle downtime easily, so fail over isn't required either."
  </p>
  <p>
   I think it comes from the fear that they
   <em>
    might
   </em>
   need those features and so they play a safe bet.   Much like when you buy something like a car and get the luggage rack on the roof, "just in case", but then realize years later that you've never used it.
  </p>
  <p>
   Moral of the story? As with any type of project, even non-technical ones, you should worry the most about what you
   <strong>
    know
   </strong>
   you need today and let tomorrow take care of itself. The tomorrow you are worried about may never come.
  </p>
 </div>
</div>
]]>/></item><item><title>More distros should do this...</title><link>http://www.revsys.com/tidbits/more-distros-should-do-this/</link><description/><pubDate>Tue, 14 Nov 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/more-distros-should-do-this/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I was reading
   <a href="http://community.linux.com/community/06/11/13/2112259.shtml?tid=53&amp;tid=96">
    this article
   </a>
   on the Ubuntu Developer Summit and found this quote:
  </p>
  <blockquote>
   <p>
    However, Shuttleworth says that "Feisty will actually warn you that you're running proprietary drivers and point you to alternative hardware ... so you're better educated for your next hardware decision. I don't expect this to be very popular with Nvidia or ATI, or with manufacturers of proprietary-only Wi-Fi cards, but of course the easiest solution for them is to open source their drivers."
   </p>
  </blockquote>
  <p>
   While I'm not certain of how well this will work, shaming hardware companies in general is a great new tactic in helping the situation.
  </p>
  <p>
   So far most peoples experience with closed-source binary drivers is typically one of these:
  </p>
  <ul>
   <li>
    <p>
     "Oh that's a shame Linux doesn't support my card." A common misperception about the whole issue.
    </p>
   </li>
   <li>
    <p>
     Attempting ( and often failing ) to getting the binary drivers to work.
    </p>
   </li>
   <li>
    <p>
     Holding true to your Open Source ideals and not using the binary driver.
    </p>
   </li>
  </ul>
  <p>
   Maybe as the user base of Linux on the desktop increases and more users become aware of this situation the hardware companies will come around.
  </p>
 </div>
</div>
]]>/></item><item><title>Introduction to Gantry, Bigtop, and Tentmaker screencasts</title><link>http://www.revsys.com/tidbits/introduction-to-gantry-bigtop-and-tentmaker-screencasts/</link><description/><pubDate>Thu, 05 Oct 2006 12:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/introduction-to-gantry-bigtop-and-tentmaker-screencasts/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Thought I would mention that there are now two screencasts that show off some of the main features of the
   <a href="http://www.usegantry.org">
    Gantry
   </a>
   framework that I play a minor role in.
  </p>
  <p>
   You can find the screencasts here under
   <a href="http://www.usegantry.org/movies/">
    movies
   </a>
  </p>
 </div>
</div>
]]>/></item><item><title>Automatically updating a timestamp column in PostgreSQL</title><link>http://www.revsys.com/tidbits/automatically-updating-a-timestamp-column-in-postgresql/</link><description/><pubDate>Fri, 04 Aug 2006 12:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/automatically-updating-a-timestamp-column-in-postgresql/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   One of the great things about modern databases is you can let your database automate some of what used to only happen in application logic.  The example I love to show people is automatically updating a "last modified time" timestamp column in a table.
  </p>
  <p>
   This is easily accomplished if you always use the same name for those types of columns.  I like to use 'created' for the creation timestamp and 'modified' for the last modified time.  First we create a simple function:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nv">CREATE</span><span class="w"> </span><span class="nv">OR</span><span class="w"> </span><span class="nv">REPLACE</span><span class="w"> </span><span class="nv">FUNCTION</span><span class="w"> </span><span class="nf">update_modified_column</span><span class="p">()</span><span class="w"> </span>
<span class="nv">RETURNS</span><span class="w"> </span><span class="nv">TRIGGER</span><span class="w"> </span><span class="nv">AS</span><span class="w"> </span><span class="p">$$</span>
<span class="nv">BEGIN</span>
<span class="w">    </span><span class="nv">NEW</span><span class="o">.</span><span class="nv">modified</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">now</span><span class="p">();</span>
<span class="w">    </span><span class="nv">RETURN</span><span class="w"> </span><span class="nv">NEW</span><span class="p">;</span><span class="w"> </span>
<span class="nv">END</span><span class="p">;</span>
<span class="p">$$</span><span class="w"> </span><span class="nv">language</span><span class="w"> </span><span class="o">'</span><span class="nv">plpgsql</span><span class="o">'</span><span class="p">;</span>
</code></pre>
  </div>
  <p>
   This function simply sets any column named 'modified' to the current timestamp for each row passed to it by the trigger. If you use the same column name consitently you only have to do this step once.  Now, you just have to create your trigger like so:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="n">CREATE</span> <span class="n">TRIGGER</span> <span class="n">update_customer_modtime</span> <span class="n">BEFORE</span> <span class="n">UPDATE</span> <span class="n">ON</span> <span class="n">customer</span> <span class="kr">FOR</span> <span class="mh">EACH</span> <span class="n">ROW</span> <span class="n">EXECUTE</span> <span class="kr">PROCEDURE</span>  <span class="n">update_modified_column</span><span class="p">();</span>
</code></pre>
  </div>
  <p>
   `
  </p>
  <p>
   This technique is very useful when you don't want to have to rely on your application developers to always remember to update the time stamps.  You can just let PostgreSQL handle it for you.
  </p>
  <p>
   You should note that you will have to create a separate trigger for each table, which isn't a big deal.  Also, the BEFORE UPDATE is very important.  If you attempt to use AFTER UPDATE you put yourself into an infinite loop!
  </p>
 </div>
</div>
]]>/></item><item><title>Doing a LEFT OUTER join with DBIx::Class</title><link>http://www.revsys.com/tidbits/doing-a-left-outer-join-with-dbixclass/</link><description/><pubDate>Tue, 25 Jul 2006 12:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/doing-a-left-outer-join-with-dbixclass/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I have recently been using
   <a href="ttp://search.cpan.org/~mstrout/DBIx-Class-0.07000/lib/DBIx/Class.pm">
    DBIx::Class
   </a>
   instead of the more popular Class::DBI. It has many advantages over Class::DBI that I won't go into here, but if you haven't used it yet you should definitely check it out.
  </p>
  <p>
   One thing I found the other day is how to setup a special LEFT OUTER join query. If you have a situation where you need to do a LEFT OUTER join on your data, but only say in one particular script.  Or maybe a one off report that you won't be keeping around. You could go ahead and put in this relationship in your main model class, but for a
   <em>
    one off
   </em>
   that is a bit of overkill.
  </p>
  <p>
   What I hadn't thought about, was you can define those relationships from outside the MyModelClass.pm file itself.  Take for example a simple Artist -&gt; CD relationship, where you want all artists even if they don't have any CDs:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="nn">ExampleSchema</span><span class="p">;</span>

<span class="nn">ExampleSchema::Artist</span><span class="o">-&gt;</span><span class="n">has_many</span><span class="p">(</span><span class="s">'left_outer_albums'</span><span class="w"> </span><span class="o">=&gt;</span><span class="w">  </span>
<span class="w">                       </span><span class="s">'ExampleSchema::Cd'</span><span class="p">,</span><span class="w"> </span><span class="s">'artist_id'</span><span class="p">,</span><span class="w">  </span>
<span class="w">                       </span><span class="p">{</span><span class="w"> </span><span class="n">join_type</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">'LEFT_OUTER'</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">);</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$schema</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nn">ExampleSchema</span><span class="o">-&gt;</span><span class="nb">connect</span><span class="p">(</span><span class="s">'dbi:Pg:dbname=outer'</span><span class="p">,</span><span class="w"> </span><span class="s">''</span><span class="p">,</span><span class="w"> </span><span class="s">''</span><span class="p">);</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$rs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$schema</span><span class="o">-&gt;</span><span class="n">resultset</span><span class="p">(</span><span class="s">'Artist'</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">search</span><span class="p">(</span>

<span class="w">    </span><span class="nb">undef</span><span class="p">,</span>

<span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="w"> </span><span class="k">my</span><span class="w"> </span><span class="nv">$artist</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$rs</span><span class="o">-&gt;</span><span class="k">next</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">print</span><span class="w"> </span><span class="s">"Name: "</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="nv">$artist</span><span class="o">-&gt;</span><span class="n">name</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="s">"\n"</span><span class="p">;</span>
<span class="w">    </span><span class="k">print</span><span class="w"> </span><span class="s">"Albums: \n"</span><span class="p">;</span>
<span class="w">    </span><span class="k">foreach</span><span class="w"> </span><span class="k">my</span><span class="w"> </span><span class="nv">$album</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="nv">$artist</span><span class="o">-&gt;</span><span class="n">left_outer_albums</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">print</span><span class="w"> </span><span class="s">"\t"</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="nv">$album</span><span class="o">-&gt;</span><span class="n">title</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="s">"\n"</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre>
  </div>
  <p>
   The nice thing about this is that this special
   <em>
    left_outer_artists
   </em>
   is defined and used in the one off and doesn't have to polute your main ExampleSchema::Artist relationships that might confuse someone. It may not be the best practice, but it is something to consider.
  </p>
 </div>
</div>
]]>/></item><item><title>Learn something new about Perl every day</title><link>http://www.revsys.com/tidbits/learn-something-new-about-perl-every-day/</link><description/><pubDate>Wed, 26 Apr 2006 12:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/learn-something-new-about-perl-every-day/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Just when you think you know everything about Perl, something silly   rises up and shows you have ignorant you really are.
  </p>
  <p>
   How many times have you written code similar to this?
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="nv">$filename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"/path/to/file.txt"</span><span class="p">;</span><span class="w">  </span>
<span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="nv">@dir_parts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">split</span><span class="p">(</span><span class="s">'/'</span><span class="p">,</span><span class="w"> </span><span class="nv">$filename</span><span class="p">);</span>

<span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="nv">$file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">pop</span><span class="p">(</span><span class="w"> </span><span class="nv">@dir_parts</span><span class="w"> </span><span class="p">);</span><span class="w">  </span>
<span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="nv">$path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">join</span><span class="p">(</span><span class="s">'/'</span><span class="p">,</span><span class="w"> </span><span class="nv">@dir_parts</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   or
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="p">(</span><span class="nv">$name</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$filename</span><span class="w"> </span><span class="o">=~</span><span class="w"> </span><span class="n">s</span><span class="sr">/\/(.*?)$/o</span><span class="p">;</span>
</code></pre>
  </div>
  <p>
   While I knew about the existance of File::Basename, the last time I looked at it I don't believe it was part of Perl Core.  I should have suspected, but now it is a standard Perl module that makes this trivial:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">      </span><span class="k">use</span><span class="w"> </span><span class="nn">File::Basename</span><span class="p">;</span>

<span class="w">  </span><span class="c1"># Retrieve just the filename</span>

<span class="w">  </span><span class="k">my</span><span class="w"> </span><span class="nv">$filename_only</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">basename</span><span class="p">(</span><span class="nv">$filename</span><span class="p">);</span>

<span class="w">  </span><span class="c1"># Get just the path in this filename</span>

<span class="w">  </span><span class="k">my</span><span class="w"> </span><span class="nv">$path_only</span><span class="w">     </span><span class="o">=</span><span class="w"> </span><span class="n">dirname</span><span class="p">(</span><span class="nv">$filename</span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   You can get even fancier with the fileparse() function provided in
   <br/>
   this module.
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="p">(</span><span class="nv">$base</span><span class="p">,</span><span class="w"> </span><span class="nv">$path</span><span class="p">,</span><span class="w"> </span><span class="nv">$suffix</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fileparse</span><span class="p">(</span><span class="w"> </span><span class="nv">$filename</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   Would yield the filename only in $base, the path in $path, and
   <br/>
   nothing in $suffix.  This is because we did not provide a regular
   <br/>
   expression to match on.
  </p>
  <p>
   If we instead used:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">      </span><span class="k">my</span><span class="w"> </span><span class="p">(</span><span class="nv">$base</span><span class="p">,</span><span class="w"> </span><span class="nv">$path</span><span class="p">,</span><span class="w"> </span><span class="nv">$suffix</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fileparse</span><span class="p">(</span><span class="w"> </span><span class="nv">$filename</span><span class="p">,</span><span class="w"> </span><span class="sx">qr{\.txt}</span><span class="w"> </span><span class="p">);</span>
</code></pre>
  </div>
  <p>
   And we ran it against $filename = '/home/frank/test.txt' and
   <br/>
   $filename2 = '/home/frank/test.doc' it would give us:
  </p>
  <div class="codehilite">
   <pre><span></span><code>      Base: test
      Path: /home/frank
      Type: .txt


  and






      Base: test.doc
      Path: /home/frank
      Type:
</code></pre>
  </div>
  <p>
   If the filename give to fileparse() does not match, it is not
   <br/>
   stripped from the basename.
  </p>
  <p>
   It just goes to show that no matter how long you've been using Perl, or how much you
   <em>
    think
   </em>
   you know, there is always something out there you could be learning.
  </p>
 </div>
</div>
]]>/></item><item><title>Been far too long...</title><link>http://www.revsys.com/tidbits/been-far-too-long/</link><description/><pubDate>Thu, 20 Apr 2006 15:01:34 +0000</pubDate><guid>http://www.revsys.com/tidbits/been-far-too-long/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   It's been far too long since I've had time to post on this blog.  Time passes at a frightening pace doesn't it?  Here two tidbits of info you might be interested in:
  </p>
  <ol>
   <li>
    Released a new version of
    <a href="http://search.cpan.org/~fwiles/Apache-DB-0.13/DB.pm">
     Apache::DB
    </a>
    ,
    <a href="http://search.cpan.org/~fwiles/Apache-DB-0.13/lib/Apache/DProf.pm">
     Apache::DProf
    </a>
    , and
    <a href="http://search.cpan.org/~fwiles/Apache-DB-0.13/lib/Apache/SmallProf.pm">
     Apache::SmallProf
    </a>
    . The new release fixes a bug where you couldn't use Apache::DProf under taint mode and allows you to specify anywhere on the file system for your dprof info to be dumped.  Previously it had to be relative to ServerRoot.
   </li>
   <li>
    <a href="http://www.usegantry.org">
     Gantry
    </a>
    , yet another web framework, that I have been helping out with was finally released publically. I helped mostly with
    <a href="http://www.usegantry.org/cgi-bin/gantry/main/Conf::Tutorial">
     Gantry::Conf
    </a>
    a configuration abstraction interface.  It is essentially DBI for configuration files.  Right now it doesn't support everything I would like it to, but the hooks are all there and I plan on fleshing the rest out in the near future.
   </li>
  </ol>
 </div>
</div>
]]>/></item><item><title>Seriously funny</title><link>http://www.revsys.com/tidbits/seriously-funny/</link><description/><pubDate>Fri, 10 Mar 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/seriously-funny/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I only have two things to say about
   <a href="http://java.sys-con.com/read/193146.htm">
    this quote from James Gosling
   </a>
   regarding how Java is under no serious threat from PHP, Ruby, or C#.
  </p>
  <ol>
   <li>
    HA HA HA HA HA HA HA HA HA HA
   </li>
   <li>
    Seriously James, do you live in a secret Java cave somewhere?
   </li>
  </ol>
 </div>
</div>
]]>/></item><item><title>New product marketing trend</title><link>http://www.revsys.com/tidbits/new-product-marketing-trend/</link><description/><pubDate>Thu, 09 Mar 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/new-product-marketing-trend/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I've noticed a trend with software businesses that I think is wonderful. It may not be very new, but I sure haven't seen it before. The trend is to donate money to a charity or Open Source project for each product purchased.
  </p>
  <p>
   For example, there is a company out there with a product that retails for around $500 for I believe a 25 user license.  They setup a promotion only linked to by blogs that allowed users to download a 5 user copy for $5.  And they donate the $5 to the Red Cross. I unfortunately can't seem to find the link in my book marks.
  </p>
  <p>
   Now there is another company
   <a href="http://www.amsoftwaredesign.com/">
    AM Software Design
   </a>
   who is offering to donate a dollar to the
   <a href="http://www.postgresql.org">
    PostgreSQL
   </a>
   project provided they sell 1,000 copies of their product. The product is a GUI PostgreSQL administration application.
  </p>
  <p>
   My question is why we don't see more of this in the FOSS world? Why aren't there more companies who will donate a small share of their profits to help out an Open Source project that is directly related to their product? Why doesn't every vendor who sells a Perl application not donating one, five, ten, or more dollars to the
   <a href="http://www.perlfoundation.org">
    Perl Foundation
   </a>
   ?
  </p>
  <p>
   If I was debating between two vendors products or whether or not to purchase a particular product at all, a donation to a good cause would help persuade me. It obviously would not make up for a shoddy product or one that doesn't fit my needs, but it would make a big difference when choosing between two like products.
  </p>
  <p>
   I encourage more vendors to adopt this approach in their marketing promotions. I wager that you will be pleasantly surprised at the results.
  </p>
 </div>
</div>
]]>/></item><item><title>The need for a 12 step program for software developers...</title><link>http://www.revsys.com/tidbits/the-need-for-a-12-step-program-for-software-developers/</link><description/><pubDate>Fri, 24 Feb 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/the-need-for-a-12-step-program-for-software-developers/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I think the wise kilt wearing
   <a href="http://www.benhammersley.com/FCE47259-78BA-4B5E-ABF2-F39B93520C85/Blog/8A127A89-AAD2-4243-8E11-0BDE85AB5394.html">
    Ben Hammersley
   </a>
   has it right that developers spend too much time worrying about details that simply don't matter to non-geeks.
  </p>
  <p>
   Is my site better because it
   <a href="http://validator.w3.org/check?uri=http%3A%2F%2Fwww.revsys.com%2F&amp;charset=%28detect+automatically%29&amp;doctype=Inline">
    validates
   </a>
   ? Do you seriously come back here because the blog software I wrote is built upon a MVC framework and uses a nice, coherent, and flexible database schema? Would you like me more if I had used some REST interfaces? If you do... you really need to get a life.
  </p>
  <p>
   If you don't like my content, topics, writing style, or me personally would it help if my permanent URLs were /blog/archive/some_story_frank_wrote instead of /blog/archive/37 ???? Would you become my reader if I rewrote all of this in Ruby On Rails instead of using my beloved mod_perl? I really doubt it.
  </p>
  <p>
   I think this syndrome, I'm going to call it CSD for
   <em>
    Compulsive Standards Disorder
   </em>
   , is a cousin of the common ED syndrome. No not erectile dysfunction, I'm talking about Efficiency Disorder. The disorder that causes developers to prematurely optimize their software.
  </p>
  <p>
   I'm as bad about this as the next developer, but sometimes stuff just needs to ship. Holding it back until it is
   <em>
    perfect
   </em>
   is essentially putting it on hold forever. Just face it, it is
   <strong>
    <em>
     NEVER
    </em>
   </strong>
   going to be perfect. There is always going to be a slightly better way, a newer standard, a new best practice, etc. What you should be asking yourself is this "Is this
   <em>
    perfect enough
   </em>
   for the user?". If the answer is even close to yes, ship it.
  </p>
  <p>
   The first step is admitting you have a problem.
  </p>
 </div>
</div>
]]>/></item><item><title>Random musing...</title><link>http://www.revsys.com/tidbits/random-musing/</link><description/><pubDate>Thu, 16 Feb 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/random-musing/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I remember back when I first got into computers reading
   <a href="http://www.pcmag.com/category2/0,1874,3574,00.asp">
    John C. Dvorak
   </a>
   's columns in PC Magazine. But seriously, has this guy lost his mind or what? In a recent
   <a href="http://www.pcmag.com/article2/0,1895,1923151,00.asp">
    opinion piece
   </a>
   he posits that Apple has been secretly moving towards adopting Windows in favor of OS X.  Maybe I keep missing it, but I'm still waiting for this guy to be right about something.
  </p>
  <p>
   He goes on to bring Linux on the desktop into the mix saying " Linux on the desktop never caught on because too many devices don't run on that OS.".  First off, I think it's safe to say that Linux on the Desktop is far from "done", so it's.... I dunno...
   <em>
    several years
   </em>
   too early to say it  "never caught" on.  Hasn't caught on yet, sure.
  </p>
  <p>
   No one disagrees that, especially in the early days, getting random hardware to run on Linux was difficult at best.  But those days are gone.  It used to be "I need a X that runs on Linux" and now it's closer to "Are there any Xs that don't run on Linux?".  And just in case Mr. Dvorak ends up reading this, "John, all three of my different DVD burners work just fine on my Linux desktops, just plugged them in and started burning. Thanks for your concern!".
  </p>
 </div>
</div>
]]>/></item><item><title>Application Configuration Best Practices</title><link>http://www.revsys.com/tidbits/application-configuration-best-practices/</link><description>Advice we had on some configuration best practices related to ModPerl back in the day. </description><pubDate>Sun, 12 Feb 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/application-configuration-best-practices/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I signed myself up to write application configuration management into the soon to be released $SUPER_SECRET_OSS_PROJECT. The goal is to have a single default configuration file located on the system (with conf.d/* style includes ) that would define each
   <em>
    instance
   </em>
   of a particular application.
  </p>
  <p>
   An
   <em>
    instance
   </em>
   is simply a name and an easy way for the programmers and admins to talk about each install of a particular application. By simply knowing the instance's name the web application, whether it is a standard Perl CGI or uses FastCGI, mod_perl 1.x, or mod_perl 2.x, can essentially automatically configure itself based on the configuration method preferred by the admin. That's right
   <strong>
    the admin
   </strong>
   .
  </p>
  <p>
   Often there exists a complete disconnect between the programmers and the system administrators responsible for actually making the app run. Programmers want a configuration setup and style that is easy to parse , while sysadmins want a system that is flexible and easy to use.
  </p>
  <p>
   The configuration infrastructure that I'm building into $SUPER_SECRET_OSS_PROJECT should give everyone the best of several worlds. Here is the idea in a nutshell:
  </p>
  <ul>
   <li>
    You have a central config file let's call it /etc/myconf.conf that will contain a entry for each
    <em>
     instance
    </em>
    of each application.
   </li>
   <li>
    The instance information will contain either the entire configuration or enough configuration information to bootstrap the config engine enough to gather the rest of the config.
   </li>
   <li>
    The application and any programs or associated cron jobs only need to be given ( via environment variables, command line options, or
    <em>
     eek
    </em>
    hard coding ) the name of the instance they are a part of to configure themselves.
   </li>
  </ul>
  <p>
   This is probably best explain with a short example, here is a mock up of the central config file /etc/myconf.conf:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="nt">&lt;instance</span><span class="w"> </span><span class="err">foo</span><span class="nt">&gt;</span>
ConfigViaFlatFile<span class="w"> </span>Config::Tiny<span class="w"> </span>/etc/apps/foo/foo.cfg<span class="w"> </span>
<span class="nt">&lt;/instance&gt;</span>


<span class="w"> </span><span class="nt">&lt;instance</span><span class="w"> </span><span class="err">bar</span><span class="nt">&gt;</span>

ConfigViaParamBuilder<span class="w"> </span>Apps::Foo::Params

<span class="nt">&lt;/instance&gt;</span>
</code></pre>
  </div>
  <p>
   The first instance
   <em>
    foo
   </em>
   wants to configure itself using the Config::Tiny module and the config information is in /etc/apps/foo/food.cfg
  </p>
  <p>
   The second instance
   <em>
    bar
   </em>
   is saying that it wants to configure itself using
   <a href="/writings/modperl/ModPerl-ParamBuilder/">
    ModPerl::ParamBuilder
   </a>
   <br/>
   and to use the Apps::Foo::Params module to do so.
  </p>
  <p>
   So far this is all pretty boring. The real exciting piece is that these two
   <em>
    instances
   </em>
   could very well be the same code base. One where the admin wants to configure it via custom Apache directives in the httpd.conf and the other using a simple flat text file for configuration. It doesn't matter to the application. You might be asking yourself how that could be. Time for another example:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="nn">SSOP::Conf</span><span class="p">;</span><span class="w">   </span><span class="c1"># For Super Secret OSS Project</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$conf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nn">SSOP::Conf</span><span class="o">-&gt;</span><span class="n">retrieve</span><span class="p">(</span><span class="w"> </span><span class="nv">$instance</span><span class="w"> </span><span class="p">);</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$template</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$conf</span><span class="o">-&gt;</span><span class="n">template</span><span class="p">;</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$dbuser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$conf</span><span class="o">-&gt;</span><span class="n">dbuser</span><span class="p">;</span>




<span class="w"> </span><span class="c1"># Or by using a hash reference</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$conf_ref</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nn">SSOP::Conf</span><span class="o">-&gt;</span><span class="n">retrieve_hashref</span><span class="p">(</span><span class="w"> </span><span class="nv">$instance</span><span class="w"> </span><span class="p">);</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$template</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$$conf_ref</span><span class="p">{</span><span class="n">template</span><span class="p">};</span>

<span class="k">my</span><span class="w"> </span><span class="nv">$dbuser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$$conf_ref</span><span class="p">{</span><span class="n">dbuser</span><span class="p">};</span>
</code></pre>
  </div>
  <p>
   That is all the application needs to know about, the "instance" name,
   <br/>
   to configure itself via several methods. Right now the plan is support Config::General, Config::Tiny, PerlSetVars in mod_perl 1 and 2, and ModPerl::ParamBuilder in mod_perl 2.x.
  </p>
  <p>
   The infrastructure is written so that it doesn't matter to the application how the configuration is gathered and parsed, just so long as it has it. This frees the admin to use whatever configuration method makes the most sense for not only each particular application, but each particular
   <strong>
    <em>
     instance
    </em>
   </strong>
   of that application.
  </p>
  <p>
   I think the most exciting aspect is that, provided you use a separately included httpd.conf for each of your instances, your behind the scenes programs, scripts, and cron jobs can share their configuration information. If you like PerlSetVars or ModPerl::ParamBuilder and want all of your app's config to live in your httpd.conf, you don't have to have a separate configuration scheme for your cron jobs.
  </p>
  <p>
   You're probably saying to yourself, "Why the hell is he telling us this when we can't see the code yet." Well the whole purpose of this entry was to ask all of my readers which configuration file syntaxes they prefer to use, Apache style, .INI style, etc. so we know what to support out of the box. There are plans for a generic SQL backend and LDAP support. The system is built so you can expand on it yourself if there is something we don't support.
  </p>
  <p>
   Comments, suggestions, rants, death threats, etc. are most welcome. Send them to
   <a href="mailto:blog@revsys.com">
    blog@revsys.com
   </a>
   . We want this system to be as useful to as many developers and admins as possible.
  </p>
 </div>
</div>
]]>/></item><item><title>Apache debugging and performance tuning article</title><link>http://www.revsys.com/tidbits/apache-debugging-and-performance-tuning-article/</link><description/><pubDate>Thu, 09 Feb 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/apache-debugging-and-performance-tuning-article/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Looks like my article on
   <a href="http://www.perl.com/pub/a/2006/02/09/debug_mod_perl.html">
    Debugging and performance profiling mod_perl applications
   </a>
   is up on
   <a href="http://www.perl.com">
    www.perl.com
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Commerical DB vendors "opening up"</title><link>http://www.revsys.com/tidbits/commerical-db-vendors-opening-up/</link><description/><pubDate>Sun, 05 Feb 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/commerical-db-vendors-opening-up/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <a href="http://www.newsforge.com">
    NewsForge
   </a>
   recently published an article discussing
   <a href="http://software.newsforge.com/software/06/02/02/1942230.shtml?tid=72">
    the new trend of
    <em>
     opening up
    </em>
    commercial database
   </a>
   products. PostgreSQL's own
   <a href="http://blogs.ittoolbox.com/database/soup/authorbio.asp">
    Josh Berkus
   </a>
   is quoted in the article.
  </p>
  <p>
   I agree with Robert Treat's blog
   <a href="http://people.planetpostgresql.org/xzilla/index.php?/archives/142-What-the-free-non-free-databases-signal.html">
    What the free non-free databases signal
   </a>
   that in the end the commercial database vendors are going to loose out in the same manner web server vendors have lost out to Apache, lighttpd, tux, etc.
  </p>
  <p>
   But I think there is another aspect of this that helps the Open Source database community more than anything. Providing these free installs tranishes all of the mystique of their products.
  </p>
  <p>
   I remember when I was starting out as a programmer and had my first opportunity to work with a big name commercial database product. I felt I had finally arrived. I was one of the
   <em>
    Big Dogs &trade;
   </em>
   now. Able to leap tall data structures in a single bound!
  </p>
  <p>
   Little did I know that I would be quickly disappointed and spend the rest of my tenure at that company lobbying to replace the commercial package with PostgreSQL. Not only to reduce costs and improve performance, but to simply make my life/job easier.
  </p>
  <p>
   If I had not been previously exposed to a product like PostgreSQL, I would have probably thought that
   <em>
    insert commercial vendor here
   </em>
   was the best thing available, based on advertising and hype. I probably would not even have investigated any further.
  </p>
  <p>
   Having these free/non-free installations of the propular commercial databases will allow the next generation of geeks to evaluate both the commercial and truly Open on equal footing. And in this fair fight, we are sure to win!
  </p>
 </div>
</div>
]]>/></item><item><title>E-Trade VP Talks about Open Source</title><link>http://www.revsys.com/tidbits/e-trade-vp-talks-about-open-source/</link><description/><pubDate>Mon, 30 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/e-trade-vp-talks-about-open-source/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <a href="http://www.eweek.com">
    eWeek.com
   </a>
   has an article where they
   <a href="http://www.eweek.com/article2/0,1895,1916587,00.asp">
    interview the VP of Architecture, Lee Thompson, of E-Trade
   </a>
   . It's a great overview of how much time, money, and headaches you can solve by moving to Open Source software. The most interesting comment Mr. Thompson made however is on
   <a href="http://www.eweek.com/article2/0,1895,1916595,00.asp">
    page four
   </a>
   , where he discusses the rate of change in Open Source:
  </p>
  <blockquote>
   <p>
    OK, so you know the phenomenon the phenomena is, the amount of change that you are sustaining on a Gentoo system is orders of magnitude larger than the amount of change that a typical proprietary operating system from anybody Solaris, HP-UX, mainframes, whatever [would go through].
   </p>
   <p>
    Whatever operating system, the rate of patches coming out of the vendor is much lower than what you enjoy on, you know, my Gentoo laptop or your Gentoo machine.
   </p>
   <p>
    And then I started looking, kind of watching this, obviously, from a technology management perspective. If you can sustain change faster than somebody else, you're going to survive, and the person who can't sustain the change is not going to evolve, and they're going to die off. This is almost more important a realization than the direct cost savings, which is still phenomenal.
   </p>
  </blockquote>
  <p>
   I'm not a big fan of the Gentoo distribution personally ( not to knock Gentoo, I just had enough of compiling my whole system back when I ran FreeBSD ), but I think Mr. Thompson has stumbled onto yet another reason why Open Source is succeeding so well.
  </p>
  <p>
   Many theories on business and management, not to mention pundits and journalists, talk about a business' ability to react to change. You will often hear
   <em>
    "Company X failed because they were unable to react to change Y in the market"
   </em>
   or
   <em>
    "When Company Z entered the market it changed everything!"
   </em>
   . With technology being so pervasive in companies these days, I believe a company's ability to change is directly related to how fast they can change their technology.
  </p>
  <p>
   But changing their own technology is only part of the equation. The technology they either purchase commercially or Open Source software they use needs to be flexible in handling the rate of change the business needs require.
  </p>
  <p>
   I had never thought of it before today, but the ability of Open Source software to handle the rapid changes being thrown at it is one of it's greatest strengths. And considering most Open Source developers are working to either fix a problem they are having or to make life better for someone else, I think this quote from Ghandi is appropriate:
  </p>
  <blockquote>
   <p>
    "You must be the change you wish to see in the world" Ghandi
   </p>
  </blockquote>
 </div>
</div>
]]>/></item><item><title>Apache::DB, Apache::DProf, and Apache::SmallProf updates</title><link>http://www.revsys.com/tidbits/apachedb-apachedprof-and-apachesmallprof-updates/</link><description/><pubDate>Tue, 24 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/apachedb-apachedprof-and-apachesmallprof-updates/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Recently it came to my attention that there were some fairly serious bugs in some of the
   <a href="http://search.cpan.org/~fwiles/">
    CPAN modules I maintain
   </a>
   .
   <a href="http://www.stason.org">
    Stas Bekman
   </a>
   was trying to use Apache::DProf to profile the performance of one of his projects and could not get it to work.
  </p>
  <p>
   The problem essentially boiled down to the generally accepted
   <em>
    best practice
   </em>
   at
   <a href="http://perl.apache.org/docs/2.0/user/porting/porting.html#Porting_a_Module_to_Run_under_both_mod_perl_2_0_and_mod_perl_1_0">
    how to determine if your code is running in mod_perl 1.x or 2.x
   </a>
   doesn't work in all cases in all environments.  This is because of when the $mod_perl or $mod_perl2 modules are loaded in relation to the Apache lifecycle.
  </p>
  <p>
   Instead of using:
  </p>
  <p>
   use constant MP2 = &gt; eval { require mod_perl; $mod_perl::VERSION &gt; 1.99 };
  </p>
  <p>
   We're going to use:
  </p>
  <p>
   use constant MP2 =&gt; ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} &gt;= 2 );
  </p>
  <p>
   Because this environment variable is guaranteed to always be available and accurate.
  </p>
  <p>
   I've uploaded a new distribution of these modules to CPAN just now and  it should make it to your favorite mirror in the next 24/48 hours. If you currently use these modules or the above technique to make your mod_perl code work in both an 1.x and 2.x environment you are encouraged to make these changes as well.
  </p>
 </div>
</div>
]]>/></item><item><title>greet_pause -- A new anti-spam feature in Sendmail 8.13.x</title><link>http://www.revsys.com/tidbits/greet_pause-a-new-anti-spam-feature-in-sendmail-813x/</link><description/><pubDate>Thu, 19 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/greet_pause-a-new-anti-spam-feature-in-sendmail-813x/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   With the release of
   <a href="http://www.sendmail.org">
    Sendmail
   </a>
   8.13.x there is a new anti-spam feature that you should seriously consider taking advantage of. The new feature is called
   <em>
    greet_pause
   </em>
   .
  </p>
  <p>
   During an SMTP session, after the sender connects to port 25 on the recipient's MX, it should wait to receive the Sendmail banner prior to sending any further data.   In the interest of spewing as much spam as possible, many spammers disregard this and simply force send all of the SMTP commands at once and move on.  The receiving MTA typically takes this spew and delivers it.
  </p>
  <p>
   <em>
    greet_pause
   </em>
   tells Sendmail to wait for a specified amount of time before sending the banner and if the sender spews commands early the message is rejected.   This can drastically cut down on the amount of spam you receive on a Sendmail system.  You implement this by adding the following to your sendmail.mc:
  </p>
  <p>
   FEATURE(`greet_pause',5000)
  </p>
  <p>
   This tells sendmail to wait 5 seconds before displaying the banner.  This should be plenty of time for a spammer to begin spewing, but short enough to not cause any timeout problems for legitimate E-mail.  You can set it to whatever you think is best, I would suggest something in the 2-10 second range.
  </p>
  <p>
   Some legit MTAs might still have problems with this, so you can
   <em>
    whitelist
   </em>
   them by adding the following to your
   <em>
    acess
   </em>
   file and rebuilding the database:
  </p>
  <p>
   GreetPause:localhost     0
   <br/>
   GreetPause:24.124.0.1   0
  </p>
  <p>
   This would not delay the banner for localhost and the IP 24.124.0.1. I recommend at least putting in localhost as there is no need to delay yourself.
  </p>
 </div>
</div>
]]>/></item><item><title>Article showing PostgreSQL materialized views</title><link>http://www.revsys.com/tidbits/article-showing-postgresql-materialized-views/</link><description/><pubDate>Tue, 17 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/article-showing-postgresql-materialized-views/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I recently came across this article
   <a href="http://jonathangardner.net/PostgreSQL/materialized_views/matviews.html">
    Materialized Views In PostgreSQL
   </a>
   which shows different ways of using this technique.
  </p>
  <p>
   For example it shows techniques much like the one I used in my
   <a href="/blog/2006/jan/03/when-to-use-a-materialized-view-in-postgresql/">
    previous blog entry
   </a>
   on the subject along with going into depth on how techniques for tables whose data changes frequently.
  </p>
 </div>
</div>
]]>/></item><item><title>ModPerl::ParamBuilder released</title><link>http://www.revsys.com/tidbits/modperlparambuilder-released/</link><description>Noting the release of ModPerl::ParamBuilder</description><pubDate>Thu, 12 Jan 2006 09:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/modperlparambuilder-released/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   I just released a new module to CPAN
   <a href="https://search.cpan.org/~fwiles/ModPerl-ParamBuilder-0.08/lib/ModPerl/ParamBuilder.pm">
    ModPerl::ParamBuilder
   </a>
   which makes it much easier to build
   <a href="https://perl.apache.org/docs/2.0/user/config/custom.html">
    custom Apache directives
   </a>
   for your mod_perl 2.0 applications.
  </p>
  <p>
   You might be asking why you would want to do such a thing. The main reasons are:
  </p>
  <ul>
   <li>
    <p>
     Custom directives are more efficient than using PerlSetEnv or PerlSetVar. They are evaluated only on server startup and
     <strong>
      not
     </strong>
     for each request like PerlSetVars
    </p>
   </li>
   <li>
    <p>
     It gives your application a more polished and professional        look and makes your configuration more intuitive for end users
    </p>
   </li>
   <li>
    <p>
     It's just plain cool
    </p>
   </li>
  </ul>
  <p>
   Assuming you're building an application called
   <em>
    MyApp
   </em>
   that will need to be passed various parameters such as database user, password, database name, database server address, etc. Here is how
   <a href="https://search.cpan.org/~fwiles/ModPerl-ParamBuilder-0.08/lib/ModPerl/ParamBuilder.pm">
    ModPerl::ParamBuilder
   </a>
   fits in.
  </p>
  <p>
   First you create a separate module that will hold your custom directives. We'll call that
   <em>
    MyApp::Params
   </em>
   and would look like:
  </p>
  <p>
   package MyApp::Params;
   <br/>
   use ModPerl::ParamBuilder;
   <br/>
   use base qw( ModPerl::ParamBuilder );
  </p>
  <p>
   my $builder = ModPerl::ParamBuilder-&gt;new(
   <strong>
    PACKAGE
   </strong>
   );
  </p>
  <p>
   $builder-&gt;param('DBUser');
  </p>
  <p>
   $builder-&gt;param('DBPass');
  </p>
  <p>
   $builder-&gt;param('DBName');
  </p>
  <p>
   $builder-&gt;param('DBServer');
  </p>
  <p>
   $builder-&gt;on_off('AutoCommit');
  </p>
  <p>
   $builder-&gt;load;
  </p>
  <p>
   1;
  </p>
  <p>
   Putting these directives to use in your Apache's httpd.conf is easy,
   <br/>
   you just need to load your
   <em>
    MyApp::Params
   </em>
   module.
  </p>
  <p>
   PerlLoadModule MyApp::Params
  </p>
  <p>
   <location myapp="">
   </location>
  </p>
  <p>
   SetHandler perl-script
  </p>
  <p>
   DBUser apache
  </p>
  <p>
   DBPass secret
  </p>
  <p>
   DBServer 127.0.0.1
  </p>
  <p>
   DBName myapp
  </p>
  <p>
   AutoCommit On
  </p>
  <p>
   PerlResponseHandler MyApp::Main
  </p>
  <p>
  </p>
  <p>
   <strong>
    NOTE:
   </strong>
   You must use
   <em>
    PerlLoadModule
   </em>
   and not
   <br/>
   the more common
   <em>
    PerlModule
   </em>
   Apache directive for your parameter module.   This is because Apache needs to load this module very early in the server startup so that it can read it's own configuration files.
  </p>
  <p>
   To retrieve and use these directives from your application you add
   <br/>
   the following to
   <em>
    MyApp::Main
   </em>
   :
  </p>
  <p>
   use MyApp::Params;
  </p>
  <p>
   my $params = MyApp::Params-&gt;new;
  </p>
  <p>
   my $config = $params-&gt;get_config;
  </p>
  <p>
   my $dbuser = $$config{'DBUser'};
  </p>
  <h1>
   etc
  </h1>
  <p>
   Hopefully everyone will find this module as useful as I have. Personally, I think being able to build custom Apache directives easily is of the neatest features of mod_perl 2.0.
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   I've also put up a short
   <a href="/writings/modperl/ModPerl-ParamBuilder/">
    short tutorial on how to use ModPerl::ParamBuilder
   </a>
   .
  </p>
  <p>
   Feel free to
   <a href="mailto:info@revsys.com">
    contact me
   </a>
   if you have any questions or have a problem using it.
  </p>
 </div>
</div>
]]>/></item><item><title>Why vulnerability counts are stupid...</title><link>http://www.revsys.com/tidbits/why-vulnerability-counts-are-stupid/</link><description/><pubDate>Fri, 06 Jan 2006 18:39:10 +0000</pubDate><guid>http://www.revsys.com/tidbits/why-vulnerability-counts-are-stupid/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Every year
   <a href="http://www.us-cert.gov">
    US-CERT
   </a>
   releases a
   <a href="http://www.us-cert.gov/cas/bulletins/SB2005.html" title="CERT">
    list of vulnerabilities for the previous year
   </a>
   . And every year the press and media misrepresent these security vulnerabilities as being a big deal and that Microsoft is clearly winning in this effort against all other operating systems. This just simply isn't true and if one of these lame reporters would just call anyone they know who understands Open Source Software or computers in general they would get set straight. There are some great articles already discussing this such as:
  </p>
  <ul>
   <li>
    <h3>
     <a href="http://trends.newsforge.com/article.pl?sid=06/01/05/1627242">
      US-CERT's FUD
     </a>
    </h3>
   </li>
   <li>
    <h3>
     <a href="http://www.groklaw.net/article.php?story=20051231142317870">
      On CERT's 2005 Software Vulnerabilities List
     </a>
    </h3>
   </li>
   <li>
    <h3>
     <a href="http://news.zdnet.co.uk/software/linuxunix/0,39020390,39245889,00.htm">
      Red Hat disputes CERT vulnerability figures
     </a>
    </h3>
   </li>
  </ul>
  <p>
   I see this happen all of the time with various media outlets. They report on some technology story and get the entire concept wrong. Don't get me wrong, I do not fault the reporter for not knowing everything about technology. That isn't their job. But what large media outlet on the planet doesn't have an IT department or other department filled with resident computer geeks? Why don't these reporters take advantage of the resources they have in house? On a story like this it would only take 10 minutes to explain why their conclusions are just plain wrong and save themselves the embarassement of having a million geeks E-mail them corrections. Not to mention that some of the corrections are going to be, well... unfriendly in nature :)
  </p>
  <p>
   If any reporter out there has a quick technology question they need fact checked, please send it to a geek you know. If you don't know any geeks feel free to send it to me. I can't guarantee a response time, but the lack of your own personal embarassement should be well...
   <em>
    priceless!
   </em>
  </p>
 </div>
</div>
]]>/></item><item><title>Perl DB2 Article</title><link>http://www.revsys.com/tidbits/perl-db2-article/</link><description/><pubDate>Fri, 06 Jan 2006 18:38:58 +0000</pubDate><guid>http://www.revsys.com/tidbits/perl-db2-article/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Marina Greenstein has written a
   <a href="http://www-128.ibm.com/developerworks/db2/library/techarticle/dm-0512greenstein/?ca=dgr-lnxw03Perl4DB2" title="Using DB2 with Perl">
    short introduction to using DB2 with Perl
   </a>
   .  While it is geared specifically toward DB2 the concepts apply to pretty much all DBI work.  I still prefer
   <a href="http://www.postgresql.org" title="PostgreSQL">
    my favorite database
   </a>
   for all of my work, but if you're looking for a DBI tutorial this is definitely worth checking out.
  </p>
 </div>
</div>
]]>/></item><item><title>Why you should date geeks</title><link>http://www.revsys.com/tidbits/why-you-should-date-geeks/</link><description/><pubDate>Fri, 06 Jan 2006 16:31:53 +0000</pubDate><guid>http://www.revsys.com/tidbits/why-you-should-date-geeks/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Maryam Ghaemmaghami Scoble, the wife of Robert Scoble, has put up a Top 10 list of reasons to date a geek, titled
   <a href="http://spaces.msn.com/members/maryamie/Blog/cns!1pJf1AP0KsxqptNL0A6dlsgA!848.entry" title="Top 10 Reasons to date a Geek">
    In Defense of Geeks or Ten Reasons Why You Should Date a Geek
   </a>
   . While I think this is hillarious, now I'm worried that I will have to start lying about my
   <em>
    geekness
   </em>
   otherwise I run the risk of being trambled to death by hordes of technologically challenged women!
  </p>
  <p>
   Ok... maybe that wouldn't be
   <strong>
    <em>
     so
    </em>
   </strong>
   bad...
  </p>
 </div>
</div>
]]>/></item><item><title>Tuning your PostgreSQL Database</title><link>http://www.revsys.com/tidbits/tuning-your-postgresql-database/</link><description>PostgreSQL Tuning Service </description><pubDate>Fri, 06 Jan 2006 16:31:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/tuning-your-postgresql-database/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Several months ago I wrote an article on
   <a href="/writings/postgresql-performance.html" title="Performance Tune your PostgreSQL server">
    tuning your PostgreSQL database for performance
   </a>
   that has gained a lot of attention. While I think the article covers most of the basic to intermediate level options you can use to better tune your database server, it is by no means
   <strong>
    <em>
     all
    </em>
   </strong>
   you're ever going to need to know.   If you use
   <a href="https://www.postgresql.org" title="Postgres Homepage">
    PostgreSQL
   </a>
   often I strongly suggest you at least scan the posts on the
   <a href="https://www.postgresql.org/community/lists/">
    postgresql-performance
   </a>
   mailing list.
  </p>
  <p>
   What surprised me most is how many companies and individual developers that are in need of a consultant to help them get the most out of their PostgreSQL setup. Because of this we've launched a new
   <a href="/services/postgresql/tuning/" title="PostgreSQL Performance Tuning Service">
    PostgreSQL Performance Tuning Service
   </a>
   designed to help organizations receive better performance out of their systems and reduce the need to upgrade their server hardware. We often find that a few well placed configuration, query, or stored procedure changes can dramatically impact the speed of your application or website.
  </p>
  <p>
   The problem with online tuning guides and the standard documentation is that every company's database is designed and/or used just differently enough from everyone else that a customized tuning is the best option.
   <a href="/contact/">
    Contact REVSYS
   </a>
   to find out more and schedule a performance analysis.
  </p>
 </div>
</div>
]]>/></item><item><title>Sending E-mail from Perl</title><link>http://www.revsys.com/tidbits/sending-e-mail-from-perl/</link><description>One way to send templated emails with Perl</description><pubDate>Thu, 05 Jan 2006 17:22:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/sending-e-mail-from-perl/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <strong>
    UPDATE:
   </strong>
   I've recently found an even better way of sending Email messages than using any of the information listed here.   Check out my
   <a href="/tidbits/email-templates-and-perl/">
    new post on the subject
   </a>
   for the details
  </p>
  <p>
   I'm always amazed at how many people have trouble doing something as simple as sending E-mail from Perl or mod_perl.  I think this is because new Perl programmers are either unaware of
   <a href="https://www.cpan.org" title="Comprehensive Perl Archive Network">
    CPAN
   </a>
   or afraid to use it.  Trust me CPAN is your friend. :)  If you install the
   <a href="https://search.cpan.org/~gbarr/libnet-1.19/Net/SMTP.pm" title="Net::SMTP">
    Net::SMTP
   </a>
   module, which is part of the libnet CPAN package, on your system it is trivial to send plain text E-mail messages.
  </p>
  <p>
   Here is a brief example:
  </p>
  <h1>
   Create an instance of the module
  </h1>
  <p>
   my $smtp = Net::SMTP-&gt;new( 'smtp.example.com') or die "Cannot connect to host: $!";
  </p>
  <p>
   $smtp-&gt;to( 'recipient@example.com' );
  </p>
  <p>
   $smtp-&gt;data();
   <br/>
   $smtp-&gt;datasend("To: recipient\@example.com\n");
   <br/>
   $smtp-&gt;datasend("From: sender\@example.com\n");
   <br/>
   $smtp-&gt;datasend("Subject: Test Subject\n");
   <br/>
   $smtp-&gt;datasend("\n");
   <br/>
   $smtp-&gt;datasend("This is where the body of your message goes!\n");
   <br/>
   $smtp-&gt;quit();
  </p>
  <p>
   This module assumes you know a little bit about the SMTP protocol, but there are tons of modules on CPAN that are even easier to use.  This one just happens to be the one I use the most.  Some others are:
  </p>
  <ul>
   <li>
    <p>
     <a href="https://search.cpan.org/~mivkovic/Mail-Sendmail-0.79/Sendmail.pm" title="Mail::Sendmail CPAN module">
      Mail::Sendmail
     </a>
    </p>
   </li>
   <li>
    <p>
     <a href="https://search.cpan.org/~gmpassos/Mail-SendEasy-1.2/lib/Mail/SendEasy.pm">
      Mail::SendEasy
     </a>
    </p>
   </li>
   <li>
    <p>
     <a href="https://search.cpan.org/~yves/MIME-Lite-3.01/lib/MIME/Lite.pm">
      MIME::Lite
     </a>
    </p>
   </li>
   <li>
    <p>
     <a href="https://search.cpan.org/~mthurn/Email-Send-SMTP-Auth-1.007/Auth.pm">
      Email::Sender::SMTP::Auth
     </a>
    </p>
   </li>
   <li>
    <p>
     <a href="https://search.cpan.org/~cwest/Email-Send-2.00/lib/Email/Send.pm" title="Email::Send">
      Email::Send
     </a>
    </p>
   </li>
  </ul>
  <p>
   While I haven't personally used all of these modules, they all seem to have clean interfaces for sending E-mail from your Perl programs.  MIME::Lite especially is useful when you want to send attachments along with your message.  Hopefully this information helps you in your future projects!
  </p>
 </div>
</div>
]]>/></item><item><title>Using SSH ControlMaster to speed up your connect times</title><link>http://www.revsys.com/tidbits/using-ssh-controlmaster-to-speed-up-your-connect-times/</link><description>Using the ControlMaster option with OpenSSH for faster connections. </description><pubDate>Thu, 05 Jan 2006 14:48:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/using-ssh-controlmaster-to-speed-up-your-connect-times/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Awhile back I wrote a short article on
   <a href="/writings/quicktips/ssh-faster-connections.html">
    how to speed up subsequent connections to the same host via SSH
   </a>
   originally I had an issue using the ControlMaster technique with
   <a href="http://subversion.tigris.org/" title="Subversion">
    subversion
   </a>
   when using
   <em>
    svn+ssh
   </em>
   . Rob Holland from
   <a href="https://www.inversepath.com/">
    Inverse Path
   </a>
   sent me a messge showing how to fix the issue.  The
   <a href="/writings/quicktips/ssh-faster-connections.html">
    article
   </a>
   has been updated with the fix. Thanks Rob for figuring this out for all of us.
  </p>
 </div>
</div>
]]>/></item><item><title>DNS &amp; BIND Security</title><link>http://www.revsys.com/tidbits/dns-bind-security/</link><description>DNS is baffling for many people.  Especially the security aspects. </description><pubDate>Thu, 05 Jan 2006 07:28:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/dns-bind-security/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   Understanding DNS is hard.
   <strong>
    REALLY HARD
   </strong>
   . It takes most people several years of running a DNS server on the public Internet before they really become comfortable with all of the various nuances. I've been doing it for many years and I even make silly mistakes sometimes.
  </p>
  <p>
   <a href="https://news.zdnet.co.uk/0,39020330,39233366,00.htm" title="DNS Security Study">
    A recent study of DNS server configurations
   </a>
   found that
   <strong>
    20% had security holes
   </strong>
   and
   <em>
    <strong>
     nearly 70%
    </strong>
   </em>
   were improperly configured in some aspect or another. I shouldn't be surprised by this, but deep down part of me is surprised these numbers are quite so high. I really recommend, if you can, have someone else run your DNS for you. If you aren't comfortable with it there is no reason to put yourself through the trouble of getting everything setup properly. However, if you do need or want to run your own DNS server I highly recommend the DNS  &amp; BIND book from O'Reilly. It covers everything you will need to know and then some.
  </p>
  <p>
   If you do run your own DNS, I suggest you have someone who is very familiar with DNS to
   <a href="/services/support/dns-bind/" title="DNS Configuration Service">
    verify your DNS configuration
   </a>
   is accurate and secure to help ensure nothing bad happens to you.
  </p>
 </div>
</div>
]]>/></item><item><title>When to use a materialized view in PostgreSQL</title><link>http://www.revsys.com/tidbits/when-to-use-a-materialized-view-in-postgresql/</link><description>Situations where it makes sense to use a materialized view in PostgreSQL. </description><pubDate>Tue, 03 Jan 2006 13:49:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/when-to-use-a-materialized-view-in-postgresql/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   A
   <em>
    materialized view
   </em>
   is defined as a table which is actually physically stored on disk, but is really just a
   <em>
    view
   </em>
   of other database tables. In
   <a href="https://www.postgresql.org" title="www.postgresql.org">
    PostgreSQL
   </a>
   , like many database systems, when data is retrieved from a traditional
   <em>
    view
   </em>
   it is really executing the underlying query or queries that build that view. This is great for better representation of data for users, but does not do anything to help performance.
  </p>
  <p>
   Materialized views are different in that they are an actual physical table that is built from the data in other tables. To use another example from my
   <a href="https://www.revsys.com/newscloud/" title="NewsCloud">
    NewsCloud
   </a>
   application in order to achieve the performance I needed, I used a materialized view for representing the tag cloud.
  </p>
  <p>
   In this particular application the data used to build the tag cloud changes very infrequently, but to generate the actual tag cloud the ORDER BY needed to rank the results was terribly slow. They query in question is:
  </p>
  <div class="codehilite">
   <pre><span></span><code>SELECT k.id, k.keyword, c.count FROM news_keywords AS k, news_keyword_total_count AS c WHERE k.id = c.keyword ORDER BY c.count DESC;
</code></pre>
  </div>
  <p>
   This query was taking an average of 2 seconds to complete which would mean, when you figured in all of the other time aspects such as mod_perl, Apache, transporting the HTML back to the browser, etc. this could easily mean the user would see a 3-4 second page load time. However, by creating a new table with:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">test</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="n">k</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">.</span><span class="n">keyword</span><span class="p">,</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">count</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">news_keywords</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="n">news_keyword_total_count</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">k</span><span class="p">.</span><span class="n">id</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">keyword</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">count</span><span class="w"> </span><span class="k">DESC</span><span class="p">;</span>
</code></pre>
  </div>
  <p>
   And then I dropped my old view table ( named count_mview ) and renamed the test table to the old name. A quick vacuum analyze afterwards and everything is happy. With this simple change I can then directly query the count_mview data and it is returned in the order I need, but this query takes just
   <strong>
    slightly less than 1 millisecond!
   </strong>
  </p>
  <p>
   If the data in your underlying tables changes more frequently you will be better served by using triggers on those tables that fire when INSERTs, UPDATEs, and/or DELETEs are performed on them and update the materialized view table according. For a good introduction to this check out the PostgreSQL manual section on
   <a href="https://www.postgresql.org/docs/8.1/static/triggers.html" title="PostgreSQL triggers">
    triggers
   </a>
   and
   <a href="https://www.postgresql.org/docs/8.1/static/plpgsql-trigger.html" title="PL/pgSQL Triggers in PostgreSQL">
    PL/pgSQL Trigger Procedures
   </a>
   .
  </p>
  <p>
   Hopefully you can use this technique in the future to speed up some of your slower performing queries.
  </p>
  <p>
   <strong>
    UPDATE:
   </strong>
   If you're interested in this topic you might also want to check out my
   <a href="https://www.djangocon.org">
    DjangoCon
   </a>
   talk on this subject
   <a href="/talks/djangocon/2009/making-your-postgresql-database-sing/">
    Making your PostgreSQL database sing
   </a>
   .
  </p>
 </div>
</div>
]]>/></item><item><title>Perl in 2005</title><link>http://www.revsys.com/tidbits/perl-in-2005/</link><description/><pubDate>Tue, 03 Jan 2006 10:59:06 +0000</pubDate><guid>http://www.revsys.com/tidbits/perl-in-2005/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   No one can argue that 2005 has been an exciting year for the Perl community.
   <a href="http://www.oreillynet.com/pub/au/176" title="chromatic">
    Chromatic
   </a>
   has written up a nice
   <a href="http://www.oreillynet.com/pub/wlg/8894" title="Perl in 2005">
    year in review
   </a>
   of what happened in the Perl community over the last year.  He also touches on a few things that are going to happen in 2006.
  </p>
  <p>
   With the up coming release of Perl 6 coming closer and closer, it is only a matter of time before we can begin to reap all of the benefits this new version has to offer.  Check out these sites for more detailed information on
   <a href="http://www.parrotcode.org/" title="Parrot">
    Parrot
   </a>
   and/or
   <a href="http://dev.perl.org/perl6/" title="Perl 6">
    Perl 6
   </a>
   if you aren't familiar with them.
  </p>
 </div>
</div>
]]>/></item><item><title>Using Apache's mod_deflate for speed</title><link>http://www.revsys.com/tidbits/using-apaches-mod_deflate-for-speed/</link><description>How to add mod_deflate to compress your Apache content. </description><pubDate>Mon, 02 Jan 2006 14:29:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/using-apaches-mod_deflate-for-speed/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   If you have a site that sends large textual content ( XHTML, XML, CSS, plain text, etc. ) to a browser via Apache you should investigate using
   <a href="https://httpd.apache.org/docs/2.0/mod/mod_deflate.html" title="Official mod_deflate homepage">
    mod_deflate
   </a>
   . This Apache module compresses your content at the server, before sending to the browser. This can dramatically speed up the time it takes for your pages to load. Especially for users on slower Internet connections. Many Linux distributions already have mod_deflate installed, but if yours does not you will need to install it by hand.
  </p>
  <p>
   For example, an application I wrote called
   <a href="https://www.revsys.com/newscloud/" title="NewsCloud">
    NewsCloud
   </a>
   has some pages that are over
   <strong>
    350K
   </strong>
   of XHTML. This can take awhile even on the fastest of home Internet connections. But by having mod_deflate compress the content before sending it to the browser 350K of XHTML becomes a much more managable 40K. Obviously your results will vary based upon your content. Because
   <a href="https://www.revsys.com/newscloud/" title="NewsCloud">
    NewsCloud
   </a>
   is a mod_perl application this is what I added to my config:
  </p>
  <div class="codehilite">
   <pre><span></span><code><span class="w">         </span><span class="nt">&lt;Location</span><span class="w"> </span><span class="err">/newscloud</span><span class="nt">&gt;</span><span class="w">   </span>
<span class="w">   </span>SetOutputFilter<span class="w"> </span>DEFLATE<span class="w">  </span>
<span class="w">   </span>ExpiresByType<span class="w"> </span>text/html<span class="w"> </span>"access<span class="w"> </span>plus<span class="w"> </span>2<span class="w"> </span>hours"<span class="w">  </span>
<span class="w">  </span><span class="nt">&lt;/Location&gt;</span>
</code></pre>
  </div>
  <p>
   The
   <em>
    ExpiresByType
   </em>
   instructs the browser to keep cached any content from this particular
   <em>
    Location
   </em>
   for 2 hours after it was last accessed. This also is useful to ensure a user's experience is good, but obviously varies depending on the nature of the application you are building. It wouldn't be too smart in a shopping cart for instance...
   <em>
    " Hey?!?!?! Where the $@#$# did my stuff go?"
   </em>
  </p>
  <p>
   Tp learn more about mod_deflate you should check out this article, "
   <a href="https://www.ffnn.nl/pages/articles/linux/apache-2-mod_deflate-benchmark.php" title="mod_deflate">
    Apache 2
    <em>
     mod_deflate
    </em>
    Benchmark
   </a>
   "
  </p>
 </div>
</div>
]]>/></item><item><title>Why you should make your PostgreSQL tables without OIDs</title><link>http://www.revsys.com/tidbits/why-you-should-make-your-postgresql-tables-without-oids/</link><description/><pubDate>Mon, 02 Jan 2006 14:16:45 +0000</pubDate><guid>http://www.revsys.com/tidbits/why-you-should-make-your-postgresql-tables-without-oids/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <strong>
    NOTE:
   </strong>
   This advice only applies to really old versions of PostgreSQL. User created tables stopped having OIDs added to them in the early 8.x versions.
  </p>
  <p>
   Something most people don't know about the
   <a href="http://www.postgresql.org">
    PostgreSQL
   </a>
   RDBMS is that when you create tables each row is given a unique object identifier ( aka OID ) whether or not you define any primary keys for that table. This is useful if you need to be able to delete an individual row from the database that doesn't have any
   <em>
    unique
   </em>
   way of referring to it. You can retrieve the OID for any row by just adding it to the items you with to SELECT:
  </p>
  <div class="codehilite">
   <pre><span></span><code>SELECT oid, id FROM table1;
</code></pre>
  </div>
  <p>
   If you try this on one of your existing tables you will see the various OIDs for your database rows. While these OIDs can be useful in certain circumstances, if you aren't aware of them then you probably aren't using them! Instructing PostgreSQL to not create OIDs for you can save some space on your filesystem and speed up your queries. However, don't expect to get a noticble amount of difference in either speed or disk space as this is just on extra integer per row. It really depends on how your schema is setup. To create a table without OIDs you simply need to do the following:
  </p>
  <div class="codehilite">
   <pre><span></span><code>  CREATE TABLE test (
    .....
) WITHOUT OIDS;
</code></pre>
  </div>
  <p>
   If your tables were build with OIDs ( the default ) then you can remove them by issuing the following:
  </p>
  <div class="codehilite">
   <pre><span></span><code>ALTER TABLE test SET WITHOUT OIDS;
</code></pre>
  </div>
  <p>
   However, this will only keep new rows and rows that are updated from having OIDs. It isn't optimal, but certainly a step in the right direction.
  </p>
  <p>
   As I don't ever use OIDs in my queries I now explicitly define all of my tables without OIDs. Perhaps you should do the same?
  </p>
 </div>
</div>
]]>/></item><item><title>Sendmail Virtual User Trick</title><link>http://www.revsys.com/tidbits/sendmail-virtual-user-trick/</link><description/><pubDate>Mon, 02 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/sendmail-virtual-user-trick/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   One of the things people find difficult about Sendmail is virtual users.  These are defined in the
   <em>
    virtusertable
   </em>
   file ( usually in /etc/mail/virtusertable ). This file instructs Sendmail to
   <strong>
    translate
   </strong>
   a  "virtual" user into a real user or alias. The reason I mention aliases here is because, with Sendmail, you can have a virtual user that translates into a alias for multiple local and/or remote E-mail accounts.
  </p>
  <p>
   A situation some people run into is that they want all usernames at
   <strong>
    domain2.com
   </strong>
   to be delivered to the same username at
   <strong>
    domain1.com
   </strong>
   , except for a few users.... If you really wanted all users to map to the other domain it would be as simple as adding the domain into the
   <em>
    local-host-names
   </em>
   .   It is the need to have
   <strong>
    <em>
     most
    </em>
   </strong>
   users map to the other domain where the
   <em>
    virtusertable
   </em>
   file comes in handy.   For this example, let's assume that we want all users names
   <strong>
    @domain1.com
   </strong>
   except for postmaster, webmaster, and support to map to the same username
   <strong>
    @domain2.com
   </strong>
   this would be accomplished by adding domain2.com to the
   <em>
    local-host-names
   </em>
   file to tell Sendmail that we wish to receive mail for that domain and then adding the following to the
   <em>
    virtusertable
   </em>
   file:
  </p>
  <p>
   postmaster@domain2.com: mailadmin@example.com
   <br/>
   webmaster@domain2.com: webmsater@example.com
   <br/>
   support@domain2.com: support@example.com
   <br/>
   @domain2.com: %1@domain1.com
  </p>
  <p>
   The first three lines tells Sendmail to send messages sent to those three usernames at domain2.com to the appropriate remote E-mail addresses. The last line instructs sendmail to send
   <strong>
    <em>
     any
    </em>
   </strong>
   other usernames sent to domain2.com to the same username at domain1.com. Note that this will also include any fake usernames that a spammer might send to. The E-mail server at domain1.com will still be responsible for determining what is or is not a valid username. After you've added those entries to the
   <em>
    virtusertable
   </em>
   file all you need to do is rebuild it and it becomes active.
  </p>
 </div>
</div>
]]>/></item><item><title>Happy New Year!</title><link>http://www.revsys.com/tidbits/happy-new-year/</link><description/><pubDate>Sun, 01 Jan 2006 14:00:00 +0000</pubDate><guid>http://www.revsys.com/tidbits/happy-new-year/</guid><content:encoded<![CDATA[<div class="block-markdown">
 <div class="prose prose-lg max-w-none">
  <p>
   <strong>
    Happy New Year from Revolution Systems!
   </strong>
  </p>
  <p>
   We felt the best way to start off the New Year was to launch a new service for our customers and the general public. This blog will be used to discuss various technology, business/marketing trends, and on topic news we think you will be interested in. We hope you will check back often to see what we have to say.
  </p>
  <p>
   Frank Wiles
  </p>
  <p>
   President and Founder
  </p>
 </div>
</div>
]]>/></item></channel></rss>