<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alex Tomkins</title><link href="https://www.alextomkins.com/" rel="alternate"></link><link href="https://www.alextomkins.com/feeds/all.atom.xml" rel="self"></link><id>https://www.alextomkins.com/</id><updated>2018-07-21T13:14:00+01:00</updated><entry><title>Avoiding cached datetimes with Django querysets</title><link href="https://www.alextomkins.com/2018/07/avoiding-cached-datetimes-with-django-querysets/" rel="alternate"></link><published>2018-07-21T13:14:00+01:00</published><updated>2018-07-21T13:14:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2018-07-21:/2018/07/avoiding-cached-datetimes-with-django-querysets/</id><summary type="html">&lt;p&gt;In Django we often need to filter out objects from a queryset which shouldn't be visible to public
users, a typical example of this would be a news post in a blog. A staff user could edit a news
post to have a publish date in the future, allowing it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In Django we often need to filter out objects from a queryset which shouldn't be visible to public
users, a typical example of this would be a news post in a blog. A staff user could edit a news
post to have a publish date in the future, allowing it to be automatically published by the site
without having to log back in and publish it.&lt;/p&gt;
&lt;p&gt;A simple model for such a news post could look like:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ordering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-published_at'&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;In this example, we're using a typical &lt;tt class="docutils literal"&gt;ListView&lt;/tt&gt;, filtering out any posts which haven't yet been
published:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Note - we could use an &lt;tt class="docutils literal"&gt;ArchiveIndexView&lt;/tt&gt; instead, which by default excludes objects from the
future. However for this example, we're sticking with &lt;tt class="docutils literal"&gt;ListView&lt;/tt&gt; to show a simplified version of
the problem for other use cases.&lt;/p&gt;
&lt;p&gt;When we first load the page, looking through the SQL queries generated for the request, we can see
the posts being filtered by their publish date:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'2018-07-21T11:02:12.998079+00:00'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;At first this all seems okay, however at some point later on you'll realise that new posts aren't
being shown. Looking at the SQL queries generated for the following requests, we can see that the
timestamp doesn't change between requests:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'2018-07-21T11:02:12.998079+00:00'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Why? The queryset gets evaluated when the Django server starts, as the queryset is an attribute of
the generic view.&lt;/p&gt;
&lt;p&gt;One solution for this is to move the queryset into the &lt;tt class="docutils literal"&gt;get_queryset&lt;/tt&gt; method for the generic
view:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;By using a method we're creating a new queryset for every request - using the current timestamp
when the request is generated. Problem solved!&lt;/p&gt;
&lt;p&gt;However, since Django 1.9 there's a better way - let the database figure out the current time
stamp.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;tt class="docutils literal"&gt;timezone.now()&lt;/tt&gt;, we can switch the view code to the &lt;tt class="docutils literal"&gt;Now()&lt;/tt&gt; database
function:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Now&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Looking at the SQL queries generated for the request, we can see that &lt;tt class="docutils literal"&gt;STATEMENT_TIMESTAMP()&lt;/tt&gt; is
being used by Postgres to filter out any news posts&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATEMENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;news_post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;published_at&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The same SQL query will be used for every request, which will now work as the current timestamp
gets evaluated by the database - and we don't need to create a &lt;tt class="docutils literal"&gt;get_queryset&lt;/tt&gt; method for every
generic view!&lt;/p&gt;
</content><category term="django"></category><category term="python"></category></entry><entry><title>RUNLEVEL=1 apt-get install package alternative</title><link href="https://www.alextomkins.com/2018/03/runlevel-apt-get-install-package-alternative/" rel="alternate"></link><published>2018-03-31T17:25:00+01:00</published><updated>2018-03-31T17:25:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2018-03-31:/2018/03/runlevel-apt-get-install-package-alternative/</id><summary type="html">&lt;p&gt;An old documented way of preventing services from starting immediately after installation in
Debian/Ubuntu is using the &lt;tt class="docutils literal"&gt;RUNLEVEL&lt;/tt&gt; environment variable to trick the runlevel helper into
returning a response that the system isn't fully running, such as:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;RUNLEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; apt-get install nginx
&lt;/pre&gt;
&lt;p&gt;Sadly this doesn't work in newer versions …&lt;/p&gt;</summary><content type="html">&lt;p&gt;An old documented way of preventing services from starting immediately after installation in
Debian/Ubuntu is using the &lt;tt class="docutils literal"&gt;RUNLEVEL&lt;/tt&gt; environment variable to trick the runlevel helper into
returning a response that the system isn't fully running, such as:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;RUNLEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; apt-get install nginx
&lt;/pre&gt;
&lt;p&gt;Sadly this doesn't work in newer versions of Debian/Ubuntu, the official way is to use the policy
helper script &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/sbin/policy-rc.d&lt;/span&gt;&lt;/tt&gt; and return a 101 exit code. This is a a bit more
inconvenient - having to temporarily create this file only to remove it after installing the
package.&lt;/p&gt;
&lt;p&gt;Fortunately there is an alternative - &lt;cite&gt;policyrcd-script-zg2&lt;/cite&gt;. Install the package:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install policyrcd-script-zg2
&lt;/pre&gt;
&lt;p&gt;Create a new script which returns a 101 exit code, I've created it as
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/local/sbin/policy-donotstart&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh
&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;101&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Then make it executable:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; sudo chmod &lt;span class="m"&gt;755&lt;/span&gt; /usr/local/sbin/policy-donotstart
&lt;/pre&gt;
&lt;p&gt;When installing packages where you don't want the service to immediately start, use the
&lt;tt class="docutils literal"&gt;POLICYRCD&lt;/tt&gt; environment variable:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;POLICYRCD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin/policy-donotstart apt-get install nginx
&lt;/pre&gt;
&lt;p&gt;The service will install, but you'll a message similar to:&lt;/p&gt;
&lt;pre class="code text literal-block"&gt;
invoke-rc.d: policy-rc.d denied execution of start.
&lt;/pre&gt;
&lt;p&gt;For Ansible, you can add environment variables to any task:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;Install nsd&lt;/span&gt;
  &lt;span class="l-Scalar-Plain"&gt;apt&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;pkg=nsd install_recommends=no&lt;/span&gt;
  &lt;span class="l-Scalar-Plain"&gt;environment&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;POLICYRCD&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;/usr/local/sbin/policy-donotstart&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now you can safely install a package, configure it, and then start it once you've got all the
correct files in place - all with a convenient environment variable.&lt;/p&gt;
</content><category term="ansible"></category><category term="debian"></category><category term="ubuntu"></category></entry><entry><title>Fixing GDAL and GEOS for Django on macOS</title><link href="https://www.alextomkins.com/2017/08/fixing-gdal-geos-django-macos/" rel="alternate"></link><published>2017-08-06T15:32:00+01:00</published><updated>2017-08-06T15:32:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2017-08-06:/2017/08/fixing-gdal-geos-django-macos/</id><summary type="html">&lt;p&gt;As a user of &lt;a class="reference external" href="https://www.macports.org/"&gt;MacPorts&lt;/a&gt; for all the additional packages needed when working with Django on macOS, a
recent upgrade to &lt;a class="reference external" href="https://trac.osgeo.org/geos/"&gt;GEOS&lt;/a&gt; managed to break all my projects which used GeoDjango:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./manage.py &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;span class="go"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="go"&gt;  File &amp;quot;./manage.py&amp;quot;, line 10, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;    execute_from_command_line(sys.argv)&lt;/span&gt;

&lt;span class="go"&gt;  ...&lt;/span&gt;

&lt;span class="go"&gt;  File …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;As a user of &lt;a class="reference external" href="https://www.macports.org/"&gt;MacPorts&lt;/a&gt; for all the additional packages needed when working with Django on macOS, a
recent upgrade to &lt;a class="reference external" href="https://trac.osgeo.org/geos/"&gt;GEOS&lt;/a&gt; managed to break all my projects which used GeoDjango:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./manage.py &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;span class="go"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="go"&gt;  File &amp;quot;./manage.py&amp;quot;, line 10, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;    execute_from_command_line(sys.argv)&lt;/span&gt;

&lt;span class="go"&gt;  ...&lt;/span&gt;

&lt;span class="go"&gt;  File &amp;quot;/Users/tomkins/.virtualenvs/greendale/lib/python3.5/site-packages/django/contrib/gis/geos/libgeos.py&amp;quot;, line 147, in geos_version_info&lt;/span&gt;
&lt;span class="go"&gt;    raise GEOSException(&amp;#39;Could not parse version info string &amp;quot;%s&amp;quot;&amp;#39; % ver)&lt;/span&gt;
&lt;span class="go"&gt;django.contrib.gis.geos.error.GEOSException: Could not parse version info string &amp;quot;3.6.2-CAPI-1.10.2 4d2925d6&amp;quot;&lt;/span&gt;

&lt;span class="gp"&gt;$&lt;/span&gt; port installed geos
&lt;span class="go"&gt;The following ports are currently installed:&lt;/span&gt;
&lt;span class="go"&gt;  geos @3.6.2_0 (active)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Highly annoying!&lt;/p&gt;
&lt;p&gt;This will be fixed in &lt;a class="reference external" href="https://github.com/django/django/pull/8817"&gt;PR #8817&lt;/a&gt; for Django master, which will be released in Django 2.0 later
this year, and &lt;a class="reference external" href="https://github.com/django/django/pull/8841"&gt;PR #8841&lt;/a&gt; for the upcoming Django 1.11.5 release. However the fix won't be
backported to older versions of Django, such as the Django 1.8 LTS branch. So to continue using
GeoDjango on macOS, we need to use an older working version of GEOS.&lt;/p&gt;
&lt;div class="section" id="kyngchaos-packages"&gt;
&lt;h2&gt;KyngChaos packages&lt;/h2&gt;
&lt;p&gt;Fortunately &lt;a class="reference external" href="http://www.kyngchaos.com/"&gt;KyngChaos&lt;/a&gt; has a variety of &lt;a class="reference external" href="http://www.kyngchaos.com/software/frameworks"&gt;Unix Compatibility Frameworks&lt;/a&gt; available for download,
including GDAL and GEOS. Fortunately it's an older version of GEOS (3.6.1) which will still work
with older versions of Django. Also the older version of GDAL (1.11) works with Django 1.8, as
newer versions of GDAL also cause problems with Django 1.8.&lt;/p&gt;
&lt;p&gt;Download and install:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;GDAL 1.11 Complete&lt;/li&gt;
&lt;li&gt;GDAL 2.1 Complete&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although we won't be using GDAL 2.1 in this example, you can easily switch to it if you're only
running Django 1.11.&lt;/p&gt;
&lt;p&gt;Add the following to your &lt;tt class="docutils literal"&gt;.bash_profile&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;GDAL_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Library/Frameworks/GDAL.framework/Versions/1.11/GDAL&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;GEOS_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Library/Frameworks/GEOS.framework/Versions/3/GEOS&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then add the following to your Django settings file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# GeoDjango fixes&lt;/span&gt;
&lt;span class="n"&gt;GDAL_LIBRARY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GDAL_LIBRARY_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GEOS_LIBRARY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GEOS_LIBRARY_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On the server you're deploying these environment variables won't be set, so the setting will
default to &lt;tt class="docutils literal"&gt;None&lt;/tt&gt; - in which case Django will automatically find the installed versions of GDAL
and GEOS.&lt;/p&gt;
&lt;p&gt;Now you should have a fully functioning GeoDjango project on macOS!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"></category><category term="python"></category></entry><entry><title>The cost of Dirty Fields</title><link href="https://www.alextomkins.com/2016/12/the-cost-of-dirtyfields/" rel="alternate"></link><published>2016-12-04T16:13:00+00:00</published><updated>2016-12-04T16:13:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-12-04:/2016/12/the-cost-of-dirtyfields/</id><summary type="html">&lt;p&gt;After installing &lt;a class="reference external" href="https://github.com/romgar/django-dirtyfields"&gt;Django Dirty Fields&lt;/a&gt; on projects a few
months ago and seeing a dramatic reduction in the number of writes to our main
Postgres database - everything seemed fine. However on a brand new project,
something wasn't quite right performance wise:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; siege --concurrent&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --reps&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://127.0.0 …&lt;/span&gt;&lt;/pre&gt;</summary><content type="html">&lt;p&gt;After installing &lt;a class="reference external" href="https://github.com/romgar/django-dirtyfields"&gt;Django Dirty Fields&lt;/a&gt; on projects a few
months ago and seeing a dramatic reduction in the number of writes to our main
Postgres database - everything seemed fine. However on a brand new project,
something wasn't quite right performance wise:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; siege --concurrent&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --reps&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:8000/map/?lat=51.4995&amp;amp;lng=0.1248&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;...

Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                  11.85 secs
Data transferred:               2.27 MB
Response time:                  0.88 secs
Transaction rate:               0.84 trans/sec
Throughput:                     0.19 MB/sec
Concurrency:                    0.75
Successful transactions:          10
Failed transactions:               0
Longest transaction:            0.96
Shortest transaction:           0.83&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Painfully slow! Although it wasn't the most optimised code possible, an average
of 880ms per request wasn't acceptable.&lt;/p&gt;
&lt;div class="section" id="investigating-the-cause"&gt;
&lt;h2&gt;Investigating the cause&lt;/h2&gt;
&lt;p&gt;As the view for this request generates a JSON response, using
&lt;a class="reference external" href="https://github.com/jazzband/django-debug-toolbar"&gt;Django Debug Toolbar&lt;/a&gt; wasn't a viable option - as all the debugging output
gets attached to HTML responses only. So instead I decided to run through the
code with &lt;cite&gt;shell_plus&lt;/cite&gt; from &lt;a class="reference external" href="https://github.com/django-extensions/django-extensions"&gt;Django Extensions&lt;/a&gt; and &lt;a class="reference external" href="https://ipython.org/"&gt;IPython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After going through parts of the code, one of the querysets seemed slower than
expected:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="go"&gt;10 loops, best of 3: 101 ms per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Over 100ms to go through a fairly small queryset? This is far too slow. Just to
see if it's a database problem, we'll change it to use &lt;cite&gt;values_list&lt;/cite&gt; instead,
which just returns a list of tuples:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'location'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;10000 loops, best of 3: 80.3 µs per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And testing another model from another app as a quick sanity check to ensure
there's no problems with other models:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;permission_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="go"&gt;100 loops, best of 3: 2.29 ms per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;So something is obviously wrong with the queryset/model.&lt;/p&gt;
&lt;p&gt;After seeing that this model had &lt;tt class="docutils literal"&gt;DirtyFieldsMixin&lt;/tt&gt;, which was one obvious
difference between this model and all the others, the next test was to remove it
and see if that made any difference:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="go"&gt;100 loops, best of 3: 8.04 ms per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;From 101ms to 8ms.&lt;/p&gt;
&lt;p&gt;After removing all instances of &lt;tt class="docutils literal"&gt;DirtyFieldsMixin&lt;/tt&gt;, another performance test
showed an improvement:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="go"&gt;$ siege --concurrent=1 --reps=10 &amp;quot;http://127.0.0.1:8000/map/?lat=51.4995&amp;amp;lng=0.1248&amp;quot;
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;
Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                   7.98 secs
Data transferred:               2.27 MB
Response time:                  0.40 secs
Transaction rate:               1.25 trans/sec
Throughput:                     0.28 MB/sec
Concurrency:                    0.50
Successful transactions:          10
Failed transactions:               0
Longest transaction:            0.44
Shortest transaction:           0.36&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;From 880ms to 400ms - a big difference.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-django-model-utils"&gt;
&lt;h2&gt;Testing django-model-utils&lt;/h2&gt;
&lt;p&gt;As Django Dirty Fields wasn't great for performance, an alternative which seems
to offer similar functionality is the tracker field from &lt;a class="reference external" href="https://github.com/carljm/django-model-utils"&gt;django-model-utils&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's test by adding a &lt;tt class="docutils literal"&gt;FieldTracker&lt;/tt&gt; field to a model:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="go"&gt;10 loops, best of 3: 21.5 ms per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Much faster than Django Dirty Fields!&lt;/p&gt;
&lt;p&gt;Some of the code when saving/updating objects needs updating for
django-model-utils, as it doesn't have the same convenience methods:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;changed_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;changed_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;changed_fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;One subtle difference between the two packages is that you'll need to ensure
the data you enter is the same type. It's possible to give an &lt;tt class="docutils literal"&gt;IntegerField&lt;/tt&gt;
a string value, and Django will still save it.&lt;/p&gt;
&lt;p&gt;With Django Dirty Fields:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14132&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt;
&lt;span class="go"&gt;442478
&lt;/span&gt;&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'442478'&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dirty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;False
&lt;/span&gt;&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt;
&lt;span class="go"&gt;'442478'
&lt;/span&gt;&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123'&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dirty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;True
&lt;/span&gt;&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt;
&lt;span class="go"&gt;'123'
&lt;/span&gt;&lt;span class="n"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt;
&lt;span class="go"&gt;123&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;With django-model-utils:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14132&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt;
&lt;span class="mi"&gt;442478&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_ref_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'442478'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'grid_ref_x'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;442478&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Which would result in a lot of additional saves for data, even though the saved
data will end up being the same.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-proxy-models"&gt;
&lt;h2&gt;Using proxy models&lt;/h2&gt;
&lt;p&gt;Given that none of the code in the Django views used the dirty fields methods,
and won't use the tracker field either - this seems like an ideal case for
proxy models in Django. Instead of adding the tracker to the &lt;tt class="docutils literal"&gt;Venue&lt;/tt&gt; model,
we'll create a proxy model instead:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VenueTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FieldTracker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now we've got two versions of the same model:&lt;/p&gt;
&lt;pre class="code pycon literal-block"&gt;
&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Venue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="go"&gt;100 loops, best of 3: 8.85 ms per loop
&lt;/span&gt;&lt;span class="o"&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;venue_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VenueTracker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="go"&gt;10 loops, best of 3: 21.2 ms per loop&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;So by default we'll use the standard &lt;tt class="docutils literal"&gt;Venue&lt;/tt&gt; model in views for speed, however
if we need a version with tracking for our update scripts, we simply change the
imports to point to the model with a tracker:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VenueTracker&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Venue&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now we can have fast views as normal, but with the option to switch to the
tracked version if needed.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"></category><category term="python"></category></entry><entry><title>Modern Django with Ubuntu Trusty</title><link href="https://www.alextomkins.com/2016/10/modern-django-with-ubuntu-trusty/" rel="alternate"></link><published>2016-10-02T22:57:00+01:00</published><updated>2016-10-02T22:57:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-10-02:/2016/10/modern-django-with-ubuntu-trusty/</id><summary type="html">&lt;p&gt;At this point Ubuntu 14.04 Trusty Tahr is nearly 2.5 years old, with another 2.5
years support left until it reaches end of life for support. However for those
of us still working with Trusty, it's often desirable to try and get a few
backported modern packages …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At this point Ubuntu 14.04 Trusty Tahr is nearly 2.5 years old, with another 2.5
years support left until it reaches end of life for support. However for those
of us still working with Trusty, it's often desirable to try and get a few
backported modern packages to make development and hosting of Django apps a bit
easier to work with.&lt;/p&gt;
&lt;p&gt;These days you could use containers to deploy bleeding edge applications with
all the new stuff bundled inside, maybe with either &lt;a class="reference external" href="https://www.docker.com/"&gt;Docker&lt;/a&gt; or &lt;a class="reference external" href="https://coreos.com/rkt/"&gt;rkt&lt;/a&gt;. However in
this post we'll be going through a couple of available APT repositories to help
modernise a slightly older distribution.&lt;/p&gt;
&lt;div class="section" id="python"&gt;
&lt;h2&gt;Python&lt;/h2&gt;
&lt;p&gt;As of October 2016, Python 3.5 is the latest stable version of Python. It's
supported in Django 1.8 to 1.10, and for anyone wanting to develop an app for
the long term - going for Python 2.7 at this point in time is a dead end. Ubuntu
Trusty only has Python 2.7 and 3.4, although 3.4 is well supported - we want to
try and go for the latest possible Python version.&lt;/p&gt;
&lt;div class="section" id="deadsnakes"&gt;
&lt;h3&gt;Deadsnakes&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://launchpad.net/~fkrull/+archive/ubuntu/deadsnakes"&gt;Old and New Python Versions&lt;/a&gt; repository (or deadsnakes)
provides multiple Python versions which aren't included in a particular version
of Ubuntu. Consider the support of this repository carefully, as it isn't an
official repo (stick with Python 3.4 if this bothers you).&lt;/p&gt;
&lt;p&gt;Installation is quick and easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo add-apt-repository ppa:fkrull/deadsnakes
&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get update
&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install python3.5
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As deadsnakes provides even more versions - we could also install Python 3.3 as
well, which could help in testing code under multiple Python versions with &lt;a class="reference external" href="http://tox.testrun.org/"&gt;tox&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="postgresql"&gt;
&lt;h2&gt;PostgreSQL&lt;/h2&gt;
&lt;p&gt;Ubuntu Trusty comes with PostgreSQL 9.3, which is missing JSONB support. So if
we want the JSONField which is new since Django 1.9 - we'll need a more modern
version of Postgres. As of October 2016 the latest release of Postgres is 9.6 -
which is what we want to aim for.&lt;/p&gt;
&lt;div class="section" id="postgresql-apt-repository"&gt;
&lt;h3&gt;PostgreSQL Apt Repository&lt;/h3&gt;
&lt;p&gt;Fortunately postgresql.org provides official packages of all the supported
Postgres versions for quite a few supported distributions. As this is provided
by the official Postgres site, packages are updated with every new release - so
support is good.&lt;/p&gt;
&lt;p&gt;To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo add-apt-repository &lt;span class="s2"&gt;&amp;quot;deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main&amp;quot;&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; curl -sL https://www.postgresql.org/media/keys/ACCC4CF8.asc &lt;span class="p"&gt;|&lt;/span&gt; sudo apt-key add -
&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get update
&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install postgresql-9.6
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can use JSONField thanks to the updated Postgres.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Just because you're stuck on an older release of Ubuntu doesn't mean you're
stuck with all the old tools - go upgrade!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"></category><category term="python"></category><category term="ubuntu"></category></entry><entry><title>Easier PostgreSQL Extension Installations</title><link href="https://www.alextomkins.com/2016/09/easier-postgresql-extension-installations/" rel="alternate"></link><published>2016-09-24T20:32:00+01:00</published><updated>2016-09-24T20:32:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-09-24:/2016/09/easier-postgresql-extension-installations/</id><summary type="html">&lt;p&gt;Postgres has a number of extensions which require superuser access for a user to
install them, &lt;a class="reference external" href="http://postgis.net/"&gt;PostGIS&lt;/a&gt; being one of them. If you're wanting to install a
&lt;a class="reference external" href="https://docs.djangoproject.com/en/1.8/ref/contrib/gis/"&gt;GeoDjango&lt;/a&gt; app, you'll probably encounter an error such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./manage.py migrate
&lt;span class="go"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="go"&gt;  ..&lt;/span&gt;
&lt;span class="go"&gt;django.db.utils.ProgrammingError: permission denied …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Postgres has a number of extensions which require superuser access for a user to
install them, &lt;a class="reference external" href="http://postgis.net/"&gt;PostGIS&lt;/a&gt; being one of them. If you're wanting to install a
&lt;a class="reference external" href="https://docs.djangoproject.com/en/1.8/ref/contrib/gis/"&gt;GeoDjango&lt;/a&gt; app, you'll probably encounter an error such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./manage.py migrate
&lt;span class="go"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="go"&gt;  ..&lt;/span&gt;
&lt;span class="go"&gt;django.db.utils.ProgrammingError: permission denied to create extension &amp;quot;postgis&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;HINT:  Must be superuser to create this extension.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For security reasons you don't want to give the user superuser access, and for
convenience you don't want to go and manually go into each database just to add
PostGIS support.&lt;/p&gt;
&lt;div class="section" id="postgresql-extension-whitelisting"&gt;
&lt;h2&gt;PostgreSQL Extension Whitelisting&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/dimitri/pgextwlist"&gt;pgextwlist&lt;/a&gt; extension can allow regular users to use the &lt;cite&gt;CREATE EXTENSION&lt;/cite&gt;
command, allowing them to install the extension without getting the permission
denied error.&lt;/p&gt;
&lt;p&gt;To install on Ubuntu 16.04 (Xenial Xerus):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install postgresql-9.5-pgextwlist
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To install on Debian 8 (Jessie):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install postgresql-9.4-pgextwlist
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After installing, edit the relevant &lt;tt class="docutils literal"&gt;postgresql.conf&lt;/tt&gt; file for your Postgres
instance and add the following lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# Allow PostGIS to be loaded easily
local_preload_libraries = &amp;#39;pgextwlist&amp;#39;
extwlist.extensions = &amp;#39;postgis&amp;#39;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo service postgresql restart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now you should be able to use a GeoDjango app/database without any errors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./manage.py migrate
&lt;span class="go"&gt;Operations to perform:&lt;/span&gt;
&lt;span class="go"&gt;  Apply all migrations: admin, auth, contenttypes, sessions&lt;/span&gt;
&lt;span class="go"&gt;Running migrations:&lt;/span&gt;
&lt;span class="go"&gt;  ..&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="postgresql"></category><category term="django"></category></entry><entry><title>Testing cloud-init with Vagrant</title><link href="https://www.alextomkins.com/2016/09/testing-cloud-init-with-vagrant/" rel="alternate"></link><published>2016-09-18T21:22:00+01:00</published><updated>2016-09-18T21:22:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-09-18:/2016/09/testing-cloud-init-with-vagrant/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://cloudinit.readthedocs.io/"&gt;cloud-init&lt;/a&gt; is a standard tool in most cloud computing environments as it's an
easy way to provide configuration for provisioning server instances. You provide
a YAML config file and it'll use the data to configure the server as needed.&lt;/p&gt;
&lt;p&gt;Testing cloud-init in a public can be a bit problematic and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://cloudinit.readthedocs.io/"&gt;cloud-init&lt;/a&gt; is a standard tool in most cloud computing environments as it's an
easy way to provide configuration for provisioning server instances. You provide
a YAML config file and it'll use the data to configure the server as needed.&lt;/p&gt;
&lt;p&gt;Testing cloud-init in a public can be a bit problematic and time consuming,
having to relaunch servers to test the config from a pristine state. It's
possible to reset the cloud-init config on the server - but if you want to test
that your config file works as expected, it's best to test from a freshly booted
server.&lt;/p&gt;
&lt;p&gt;Thanks to the &lt;a class="reference external" href="https://atlas.hashicorp.com/ubuntu"&gt;Ubuntu Vagrant boxes&lt;/a&gt; which are built with cloud-init support,
we can test our cloud-init configuration with &lt;a class="reference external" href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; easily. We'll be using
the Ubuntu 16.04 Xenial Xerus box to test with - as it's the latest Ubuntu LTS
release.&lt;/p&gt;
&lt;div class="section" id="creating-a-cloud-init-iso"&gt;
&lt;h2&gt;Creating a cloud-init ISO&lt;/h2&gt;
&lt;p&gt;cloud-init can support multiple data sources for configuration, one of them
being an CD image. We'll be using this as an easy way to provide our config
files. To generate this we can use genisoimage/mkisofs.&lt;/p&gt;
&lt;p&gt;To install on Debian/Ubuntu:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install genisoimage
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To install on OS X Macports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo port install cdrtools
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next up we'll need our &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;user-data&lt;/span&gt;&lt;/tt&gt; file, the main cloud-init config file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;#cloud-config&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;chpasswd&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;list&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;ubuntu:RANDOM&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;expire&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;False&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;manage_etc_hosts&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;localhost&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ssh_authorized_keys&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;
  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This can be customised with the configuration you need to test, however in this
example we're adding the default Vagrant insecure public SSH key. Don't worry -
Vagrant will replace it shortly after rebooting.&lt;/p&gt;
&lt;p&gt;Now we need a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;meta-data&lt;/span&gt;&lt;/tt&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;instance-id&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;iid-0123456789abcdef&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;local-hostname&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;ubuntu-xenial&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is just the bare minimum - cloud providers usually provide more data. If
you depend on other data provided as metadata then just add it as needed.&lt;/p&gt;
&lt;p&gt;To create the ISO image we'll create a &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;nocloud.iso&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;-&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;-&lt;span class="n"&gt;data&lt;/span&gt;
    mkisofs &lt;span class="se"&gt;\&lt;/span&gt;
        -joliet -rock &lt;span class="se"&gt;\&lt;/span&gt;
        -volid &lt;span class="s2"&gt;&amp;quot;cidata&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        -output nocloud.iso meta-data user-data
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Don't forget - a Makefile must be indented with tabs and not spaces!&lt;/p&gt;
&lt;p&gt;Now to create the ISO file, all we have to do is run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And you'll have a brand new &lt;tt class="docutils literal"&gt;nocloud.iso&lt;/tt&gt; ISO image.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cloud-init-vagrantfile"&gt;
&lt;h2&gt;cloud-init Vagrantfile&lt;/h2&gt;
&lt;p&gt;Now we can create a Vagrantfile to test our newly generated ISO image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vagrant init ubuntu/xenial64
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will create a sample Vagrantfile, which we'll need to edit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;


&lt;span class="no"&gt;CLOUD_CONFIG_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nocloud.iso&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_version&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;gt;= 1.8.0&amp;quot;&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ubuntu/xenial64&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Disable SSH password for 16.04 - we&amp;#39;ll add the insecure Vagrant key&lt;/span&gt;
    &lt;span class="c1"&gt;# (don&amp;#39;t worry, it&amp;#39;s just an example and gets replaced anyway)&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;

    &lt;span class="c1"&gt;# Disable shared folders&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/vagrant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="c1"&gt;# Tweak virtualbox&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="ss"&gt;:virtualbox&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="c1"&gt;# Attach nocloud.iso to the virtual machine&lt;/span&gt;
        &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customize&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;storageattach&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;--storagectl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;SCSI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;--port&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;--type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dvddrive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;--medium&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CLOUD_CONFIG_PATH&lt;/span&gt;
        &lt;span class="o"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Speed up machine startup by using linked clones&lt;/span&gt;
        &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linked_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates a new virtual machine based on the xenial64 Vagrant box, but
replaces the CD/DVD port with our &lt;tt class="docutils literal"&gt;nocloud.iso&lt;/tt&gt; file.&lt;/p&gt;
&lt;p&gt;All you have to do at this point is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vagrant up
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Vagrant will start up a virtual machine, customised with your cloud-init config
file. If anything goes wrong you can easily &lt;tt class="docutils literal"&gt;vagrant destroy&lt;/tt&gt; the machine and
try again - without the cost of starting a new server in a public cloud.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-your-own-ssh-keys"&gt;
&lt;h2&gt;Using your own SSH keys&lt;/h2&gt;
&lt;p&gt;If you're developing a user-data file and want to use your own SSH keys instead
of Vagrant replacing it on every boot, edit the Vagrantfile and add these lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.ssh/id_rsa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Don't forget to edit the user-data file and add your own public key to
&lt;tt class="docutils literal"&gt;ssh_authorized_keys&lt;/tt&gt;, and run &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; to generate a new ISO image.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Testing cloud-init with Vagrant is quick and easy once you've got everything
setup.&lt;/p&gt;
&lt;p&gt;Source code for this is available in the &lt;a class="reference external" href="https://github.com/tomkins/cloud-init-vagrant"&gt;cloud-init-vagrant&lt;/a&gt; repo on GitHub.&lt;/p&gt;
&lt;/div&gt;
</content><category term="vagrant"></category><category term="cloud-init"></category></entry><entry><title>Automating Ansible groups with Vagrant</title><link href="https://www.alextomkins.com/2016/09/automating-ansible-groups-with-vagrant/" rel="alternate"></link><published>2016-09-10T21:57:00+01:00</published><updated>2016-09-10T21:57:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-09-10:/2016/09/automating-ansible-groups-with-vagrant/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; is a great tool for creating development environments and testing
deployment scripts, especially with &lt;a class="reference external" href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt;. Testing an Ansible playbook is
can be as simple as &lt;tt class="docutils literal"&gt;vagrant up&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Ansible uses an inventory file to define the SSH details for each server, what
groups a server belongs to - which …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; is a great tool for creating development environments and testing
deployment scripts, especially with &lt;a class="reference external" href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt;. Testing an Ansible playbook is
can be as simple as &lt;tt class="docutils literal"&gt;vagrant up&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Ansible uses an inventory file to define the SSH details for each server, what
groups a server belongs to - which then allows you to run certain
roles/playbooks on specific servers. However the two options with Vagrant in
dealing with the inventory file are rather limited.&lt;/p&gt;
&lt;p&gt;The first option is the auto generated inventory, Vagrant will create a fresh
inventory file based on the servers listed in the Vagrantfile which are
currently up and running. This inventory file gets populated with the correct
SSH details, however it means it starts with an empty inventory file which is
only populated by setting &lt;tt class="docutils literal"&gt;ansible.groups&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The second option is the static inventory, which Vagrant will use instead of
creating a dynamic inventory file. The downside with this would be duplicating
the existing hosts file just to edit the SSH details, as well as hardcoding the
IP addresses of all your Vagrant virtual machines.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;We can use the &lt;a class="reference external" href="https://github.com/aisrael/ansibler"&gt;ansibler&lt;/a&gt; Ruby gem to read the hosts file to automatically
populate &lt;tt class="docutils literal"&gt;ansible.groups&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Installation is easy, as Vagrant maintains plugins separately from other Ruby
libraries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vagrant plugin install ansibler
&lt;span class="go"&gt;Installing the &amp;#39;ansibler&amp;#39; plugin. This can take a few minutes...&lt;/span&gt;
&lt;span class="go"&gt;Installed the plugin &amp;#39;ansibler (0.2.2)&amp;#39;!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we need to update the Vagrantfile. First of all we're going to read the
existing hosts file, and populate the &lt;tt class="docutils literal"&gt;ANSIBLE_GROUPS&lt;/tt&gt; variable with a hash of
all the groups:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ansible&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hosts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;ANSIBLE_GROUPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;ANSIBLE_GROUPS&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next up is updating the Ansible provisioner config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ansible&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ANSIBLE_GROUPS&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's it!&lt;/p&gt;
&lt;p&gt;Now that &lt;tt class="docutils literal"&gt;ansible.groups&lt;/tt&gt; contains an exact copy of all the group config from
your hosts file, every time Vagrant creates a new dynamic inventory file it'll
be populated with all the group configuration from your main hosts file.&lt;/p&gt;
&lt;/div&gt;
</content><category term="vagrant"></category><category term="ansible"></category></entry><entry><title>Upgrading packages in all Python virtual environments</title><link href="https://www.alextomkins.com/2016/09/upgrading-packages-in-all-python-virtual-environments/" rel="alternate"></link><published>2016-09-03T14:32:00+01:00</published><updated>2016-09-03T14:32:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-09-03:/2016/09/upgrading-packages-in-all-python-virtual-environments/</id><summary type="html">&lt;p&gt;If you're working with a lot of Python virtual environments in a wide variety of
projects, over time you'll end up with outdated packages in some of the older
ones - potentially missing out on new features.&lt;/p&gt;
&lt;p&gt;For most of the packages you'll want to keep them at a specific version …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you're working with a lot of Python virtual environments in a wide variety of
projects, over time you'll end up with outdated packages in some of the older
ones - potentially missing out on new features.&lt;/p&gt;
&lt;p&gt;For most of the packages you'll want to keep them at a specific version for each
virtual environment, however for packages such as &lt;a class="reference external" href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt;, you may want to upgrade
it to the latest version in all your local virtual environments. If you're
running &lt;a class="reference external" href="http://virtualenvwrapper.readthedocs.io/"&gt;virtualenvwrapper&lt;/a&gt;, all you have to do is use the &lt;tt class="docutils literal"&gt;allvirtualenv&lt;/tt&gt;
command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; allvirtualenv pip install --upgrade pip setuptools wheel
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And inside every virtual environment you'll have the latest version of pip and
the other essential package installation tools updated in your virtual
environment.&lt;/p&gt;
</content><category term="python"></category><category term="virtualenv"></category></entry><entry><title>Speed up Django static files</title><link href="https://www.alextomkins.com/2016/08/speed-up-django-static-files/" rel="alternate"></link><published>2016-08-28T21:16:00+01:00</published><updated>2016-08-28T21:16:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-08-28:/2016/08/speed-up-django-static-files/</id><summary type="html">&lt;p&gt;For a fairly easy performance win, and for something which makes dealing with
old cached CSS something a thing of the past - enable
&lt;a class="reference external" href="https://docs.djangoproject.com/en/1.8/ref/contrib/staticfiles/#manifeststaticfilesstorage"&gt;ManifestStaticFilesStorage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First of all you'll need to edit &lt;em&gt;settings.py&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Use manifest static storage&lt;/span&gt;
&lt;span class="n"&gt;STATICFILES_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles.storage.ManifestStaticFilesStorage&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then you'll need to edit any templates …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a fairly easy performance win, and for something which makes dealing with
old cached CSS something a thing of the past - enable
&lt;a class="reference external" href="https://docs.djangoproject.com/en/1.8/ref/contrib/staticfiles/#manifeststaticfilesstorage"&gt;ManifestStaticFilesStorage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First of all you'll need to edit &lt;em&gt;settings.py&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Use manifest static storage&lt;/span&gt;
&lt;span class="n"&gt;STATICFILES_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles.storage.ManifestStaticFilesStorage&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then you'll need to edit any templates which aren't using the static template
tag. Instead of using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;img src=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;STATIC_URL&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;images/hello.jpg&amp;quot; %}&amp;quot; alt=&amp;quot;Hello&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll need to use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;static&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;staticfiles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;img src=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;images/hello.jpg&amp;#39;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; alt=&amp;quot;Hello&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now when you run &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-admin&lt;/span&gt; collectstatic&lt;/tt&gt;, Django will include the MD5
hash of the file as part of the file name. Now when you use the &lt;tt class="docutils literal"&gt;{% static %}&lt;/tt&gt;
tag you'll see the file name with the hash.&lt;/p&gt;
&lt;p&gt;If you're running nginx, update your site configuration so that browsers will
cache static files for as long as possible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/static/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;max&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then reload nginx.&lt;/p&gt;
&lt;p&gt;Now you should be up and running with a good performance boost, and you won't
have to ask users to refresh a page to get updated static files.&lt;/p&gt;
</content><category term="django"></category><category term="python"></category></entry><entry><title>Be careful with Django's .create_or_update()!</title><link href="https://www.alextomkins.com/2016/08/be-careful-with-djangos-create-or-update/" rel="alternate"></link><published>2016-08-21T18:44:00+01:00</published><updated>2016-08-21T18:44:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-08-21:/2016/08/be-careful-with-djangos-create-or-update/</id><summary type="html">&lt;p&gt;Although using &lt;tt class="docutils literal"&gt;Model.objects.create_or_update()&lt;/tt&gt; with a Django model is
extremely convenient, sometimes you might want to consider using it carefully
with certain usage patterns.&lt;/p&gt;
&lt;div class="section" id="excessive-postgres-wal-files"&gt;
&lt;h2&gt;Excessive Postgres WAL files&lt;/h2&gt;
&lt;p&gt;To allow point-in-time recovery (PITR), I usually setup &lt;a class="reference external" href="http://www.pgbarman.org/"&gt;Barman&lt;/a&gt;. Set it up on a
remote server, get your Postgres instance to …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Although using &lt;tt class="docutils literal"&gt;Model.objects.create_or_update()&lt;/tt&gt; with a Django model is
extremely convenient, sometimes you might want to consider using it carefully
with certain usage patterns.&lt;/p&gt;
&lt;div class="section" id="excessive-postgres-wal-files"&gt;
&lt;h2&gt;Excessive Postgres WAL files&lt;/h2&gt;
&lt;p&gt;To allow point-in-time recovery (PITR), I usually setup &lt;a class="reference external" href="http://www.pgbarman.org/"&gt;Barman&lt;/a&gt;. Set it up on a
remote server, get your Postgres instance to rsync files over to the Barman
server - and you've got a set of backups which should allow you to recover your
database from an earlier point in time.&lt;/p&gt;
&lt;p&gt;For every UPDATE/INSERT, Postgres will write data to the WAL (Write-Ahead Log).
This isn't a problem if you're not doing anything else with the finished WAL
files, although you'll have increased disk I/O - it's probably just a minor
increase you won't notice too much.&lt;/p&gt;
&lt;p&gt;However if you're keeping the WAL and archiving it for backups, every update to
a table row will be backed up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; barman list-backup all
&lt;span class="go"&gt;golestandt 20160613T063007 - Mon Jun 13 06:36:06 2016 - Size: 4.5 GiB - WAL Size: 12.7 GiB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ouch.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-the-problem-databases"&gt;
&lt;h2&gt;Finding the problem databases&lt;/h2&gt;
&lt;p&gt;Assuming you've got statistics enabled, use &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; to show the statistics for
each database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;postgres=#&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tup_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tup_inserted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tup_deleted&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_database&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;tup_updated&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="go"&gt;                datname                | tup_updated | tup_inserted | tup_deleted&lt;/span&gt;
&lt;span class="go"&gt;---------------------------------------+-------------+--------------+-------------&lt;/span&gt;
&lt;span class="go"&gt; site1                                 |     1138475 |       191605 |      136569&lt;/span&gt;
&lt;span class="go"&gt; site2                                 |      153224 |        46650 |       12385&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we've got a list of databases which are excessively updating and inserting
new rows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-did-we-get-here"&gt;
&lt;h2&gt;How did we get here?&lt;/h2&gt;
&lt;p&gt;By being too lazy with &lt;tt class="docutils literal"&gt;.create_or_update()&lt;/tt&gt; in applications doing regular
syncs.&lt;/p&gt;
&lt;p&gt;If you've got Django code syncing with a third party service, it's easy to write
code similar to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tweet_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;Tweet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_or_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;screen_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you're only updating a few objects occasionally - this is fine and does the
job. However if you're updating hundreds of objects every hour - you could end
up with hundreds of thousands of rows being updated on a weekly basis, even
though most of the data is likely to stay the same.&lt;/p&gt;
&lt;p&gt;Every field for the object gets updated every time the object gets saved. If
you've got a model with more fields, this will add up very quickly to additional
WAL data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="avoiding-updates"&gt;
&lt;h2&gt;Avoiding updates&lt;/h2&gt;
&lt;p&gt;If only some of the synced data is actually being updated - just update the
fields which will receive any updates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tweet_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tweet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;screen_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Update counts, but try to avoid excessive updates&lt;/span&gt;
        &lt;span class="n"&gt;update_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retweet_count&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retweet_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example we're assuming tweet IDs or usernames won't ever change. This is
probably a safe assumption for IDs, and slightly less so for usernames.&lt;/p&gt;
&lt;p&gt;By switching to &lt;tt class="docutils literal"&gt;.get_or_create()&lt;/tt&gt;, and using &lt;tt class="docutils literal"&gt;.save(update_fields=list)&lt;/tt&gt; -
we've significantly reduced the number of updates. If the object already exists
and none of the fields have been changed - &lt;tt class="docutils literal"&gt;update_fields&lt;/tt&gt; will be an empty
list and we don't even bother with &lt;tt class="docutils literal"&gt;.save()&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The only downside using this method is that it can be tedious having to check
every single field for updates. We could probably improve this with a more
generic solution - but at this point it's easier to use a third party package
which solves the problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="django-dirty-fields"&gt;
&lt;h2&gt;Django Dirty Fields&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/romgar/django-dirtyfields"&gt;Django Dirty Fields&lt;/a&gt; is an easy alternative to writing
your own code to check all of the fields of an object. Just add a
&lt;tt class="docutils literal"&gt;DirtyFieldsMixin&lt;/tt&gt; to your model and you can simplify your code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tweet_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tweet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Tweet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;screen_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retweet_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;retweet_count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Only save if needed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dirty&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_dirty_fields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Considerably easier to deal with!&lt;/p&gt;
&lt;p&gt;The one huge advantage of this approach is that you can add as many fields as
you want, and Django Dirty Fields will do the job of figuring out which fields
to update for you. As before if none of the fields have been updated - the
object won't be saved.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-results"&gt;
&lt;h2&gt;The Results&lt;/h2&gt;
&lt;p&gt;After trying to fix several projects:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; barman list-backup all
&lt;span class="go"&gt;golestandt 20160704T063009 - Mon Jul  4 06:33:29 2016 - Size: 4.6 GiB - WAL Size: 822.0 MiB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There's still a few old projects which haven't been optimised - however we've
now managed to reduce the size of the weekly WAL to be smaller than the weekly
snapshot.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"></category><category term="python"></category><category term="postgresql"></category></entry><entry><title>Improving a Pelican blog with gulp</title><link href="https://www.alextomkins.com/2016/08/improving-pelican-blog-with-gulp/" rel="alternate"></link><published>2016-08-13T22:49:00+01:00</published><updated>2016-08-13T22:49:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2016-08-13:/2016/08/improving-pelican-blog-with-gulp/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; is a Python powered static site generator, which is useful for blogs or
other small static sites. Although a respectable number of &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins"&gt;Pelican plugins&lt;/a&gt;
are available - the Node.js/JavaScript ecosystem for build systems is currently
much stronger and updated far more frequently.&lt;/p&gt;
&lt;p&gt;In this example I'm going to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; is a Python powered static site generator, which is useful for blogs or
other small static sites. Although a respectable number of &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins"&gt;Pelican plugins&lt;/a&gt;
are available - the Node.js/JavaScript ecosystem for build systems is currently
much stronger and updated far more frequently.&lt;/p&gt;
&lt;p&gt;In this example I'm going to use &lt;a class="reference external" href="http://gulpjs.com/"&gt;gulp.js&lt;/a&gt; to take the output from Pelican,
process it and output something slightly better.&lt;/p&gt;
&lt;div class="section" id="installing-gulp"&gt;
&lt;h2&gt;Installing gulp&lt;/h2&gt;
&lt;p&gt;We'll quickly create a &lt;tt class="docutils literal"&gt;package.json&lt;/tt&gt; file to make things easier later:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; npm init
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we can install the packages we need. In this small example we'll use
&lt;a class="reference external" href="https://github.com/jonschlinkert/gulp-htmlmin"&gt;gulp-htmlmin&lt;/a&gt; to minify the HTML output from Pelican:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; npm install --save gulp gulp-htmlmin
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To make running gulp a bit easier, update the &lt;tt class="docutils literal"&gt;package.json&lt;/tt&gt; file and add an
entry into &lt;tt class="docutils literal"&gt;scripts&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;quot;gulp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gulp&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can run gulp with &lt;tt class="docutils literal"&gt;npm run gulp&lt;/tt&gt; - however we'll need to create a
gulpfile first.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="gulpfile-js"&gt;
&lt;h2&gt;gulpfile.js&lt;/h2&gt;
&lt;p&gt;Create a file named &lt;tt class="docutils literal"&gt;gulpfile.js&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;gulp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gulp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;htmlmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gulp-htmlmin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;copy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./output/**&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;!./output/**/**.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dist&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./output/**/*.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlmin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;collapseWhitespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dist&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;copy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This gulpfile has a &lt;tt class="docutils literal"&gt;copy&lt;/tt&gt; task, which will just copy all files which don't
have an HTML extension from &lt;tt class="docutils literal"&gt;output&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;dist&lt;/tt&gt;, so any images, CSS, or RSS
feed files will be copied across without any modifications.&lt;/p&gt;
&lt;p&gt;We've also got an &lt;tt class="docutils literal"&gt;html&lt;/tt&gt; task, which takes any files with an &lt;tt class="docutils literal"&gt;.html&lt;/tt&gt;
extension and runs it a minifier before being output to the &lt;tt class="docutils literal"&gt;dist&lt;/tt&gt; directory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="updating-the-makefile"&gt;
&lt;h2&gt;Updating the Makefile&lt;/h2&gt;
&lt;p&gt;A few tweaks are needed for publishing to&lt;/p&gt;
&lt;p&gt;Add a &lt;tt class="docutils literal"&gt;DISTDIR&lt;/tt&gt; variable at the top of your Makefile, this is where we're going
to output files from gulp:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;DISTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;BASEDIR&lt;span class="k"&gt;)&lt;/span&gt;/dist
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Update the &lt;tt class="docutils literal"&gt;publish&lt;/tt&gt; target to run gulp every time we regenerate the site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;$(&lt;/span&gt;PELICAN&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;INPUTDIR&lt;span class="k"&gt;)&lt;/span&gt; -o &lt;span class="k"&gt;$(&lt;/span&gt;OUTPUTDIR&lt;span class="k"&gt;)&lt;/span&gt; -s &lt;span class="k"&gt;$(&lt;/span&gt;PUBLISHCONF&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;PELICANOPTS&lt;span class="k"&gt;)&lt;/span&gt;
    npm run gulp
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Update the &lt;tt class="docutils literal"&gt;service_upload&lt;/tt&gt; target for the provider you upload your Pelican
site with, replace &lt;tt class="docutils literal"&gt;OUTPUTDIR&lt;/tt&gt; with &lt;tt class="docutils literal"&gt;DISTDIR&lt;/tt&gt;. In this example I've updated
the &lt;tt class="docutils literal"&gt;rsync_upload&lt;/tt&gt; target:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;rsync_upload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt;
    rsync -e &lt;span class="s2"&gt;&amp;quot;ssh -p &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SSH_PORT&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; -P -rvzc --delete &lt;span class="k"&gt;$(&lt;/span&gt;DISTDIR&lt;span class="k"&gt;)&lt;/span&gt;/ &lt;span class="k"&gt;$(&lt;/span&gt;SSH_USER&lt;span class="k"&gt;)&lt;/span&gt;@&lt;span class="k"&gt;$(&lt;/span&gt;SSH_HOST&lt;span class="k"&gt;)&lt;/span&gt;:&lt;span class="k"&gt;$(&lt;/span&gt;SSH_TARGET_DIR&lt;span class="k"&gt;)&lt;/span&gt; --cvs-exclude
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="publish"&gt;
&lt;h2&gt;Publish!&lt;/h2&gt;
&lt;p&gt;Now we've got gulp installed, created our gulpfile, and updated the Makefile,
updating the published content should be as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make publish
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pelican will run first, then gulp will process all the files into the &lt;tt class="docutils literal"&gt;dist&lt;/tt&gt;
directory. Any other targets will perform both of these tasks first before
uploading the content to your preferred provider.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-next"&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Improve your gulpfile and add more tasks to it! You might find some inspiration
from &lt;a class="reference external" href="https://github.com/vigetlabs/gulp-starter"&gt;Gulp Starter&lt;/a&gt;, or from hundreds of other gulp tutorials online.&lt;/p&gt;
&lt;p&gt;Alternatively you could use &lt;a class="reference external" href="http://gruntjs.com/"&gt;Grunt&lt;/a&gt;, or an entirely different build system which
isn't JavaScript powered. Thanks to the Makefile you can easily change the build
process for your site.&lt;/p&gt;
&lt;/div&gt;
</content><category term="pelican"></category><category term="javascript"></category><category term="gulp"></category></entry><entry><title>MySQL UNIX socket authentication</title><link href="https://www.alextomkins.com/2014/07/mysql-unix-socket-authentication/" rel="alternate"></link><published>2014-07-26T23:49:00+01:00</published><updated>2014-07-26T23:49:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2014-07-26:/2014/07/mysql-unix-socket-authentication/</id><summary type="html">&lt;p&gt;It's not currently a default feature, but since MySQL 5.5.10 you can easily
&lt;a class="reference external" href="https://dev.mysql.com/doc/refman/5.5/en/socket-authentication-plugin.html"&gt;enable UNIX socket authentication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Add the following to your MySQL config file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[mysqld]
plugin-load=auth_socket=auth_socket.so
&lt;/pre&gt;
&lt;p&gt;Restart MySQL, then you'll be able to use &lt;tt class="docutils literal"&gt;IDENTIFIED WITH auth_socket&lt;/tt&gt; as a
replacement for &lt;tt class="docutils literal"&gt;IDENTIFIED BY 'password' …&lt;/tt&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's not currently a default feature, but since MySQL 5.5.10 you can easily
&lt;a class="reference external" href="https://dev.mysql.com/doc/refman/5.5/en/socket-authentication-plugin.html"&gt;enable UNIX socket authentication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Add the following to your MySQL config file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[mysqld]
plugin-load=auth_socket=auth_socket.so
&lt;/pre&gt;
&lt;p&gt;Restart MySQL, then you'll be able to use &lt;tt class="docutils literal"&gt;IDENTIFIED WITH auth_socket&lt;/tt&gt; as a
replacement for &lt;tt class="docutils literal"&gt;IDENTIFIED BY 'password'&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Makes things easier for local installations for services running under a
specific account, or for limiting access to the root account to the local root
user.&lt;/p&gt;
</content><category term="mysql"></category></entry><entry><title>DigitalOcean Debian kernel</title><link href="https://www.alextomkins.com/2013/11/digitalocean-debian-kernel/" rel="alternate"></link><published>2013-11-16T16:38:00+00:00</published><updated>2013-11-16T16:38:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2013-11-16:/2013/11/digitalocean-debian-kernel/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.digitalocean.com/?refcode=1ad7cea15b72"&gt;DigitalOcean&lt;/a&gt; offers some fantastic KVM powered virtual machines for low prices,
however the technical decisions they've made in various places leaves something
to be desired.&lt;/p&gt;
&lt;p&gt;One of the problems which you may face at some point is that you're stuck with
whatever kernel they provide, you can't &lt;a class="reference external" href="https://digitalocean.uservoice.com/forums/136585-digital-ocean/suggestions/2814988-give-option-to-use-the-droplet-s-own-bootloader-"&gt;use your own …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.digitalocean.com/?refcode=1ad7cea15b72"&gt;DigitalOcean&lt;/a&gt; offers some fantastic KVM powered virtual machines for low prices,
however the technical decisions they've made in various places leaves something
to be desired.&lt;/p&gt;
&lt;p&gt;One of the problems which you may face at some point is that you're stuck with
whatever kernel they provide, you can't &lt;a class="reference external" href="https://digitalocean.uservoice.com/forums/136585-digital-ocean/suggestions/2814988-give-option-to-use-the-droplet-s-own-bootloader-"&gt;use your own bootloader&lt;/a&gt;. They've
attempted to solve this by providing kernels from every Linux distribution they
support - but sadly this isn't the solution which I'd expect a KVM provider to
use.&lt;/p&gt;
&lt;p&gt;To work around this, we can use kexec to change the kernel on boot. Install
kexec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo apt-get install kexec-tools
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Move &lt;tt class="docutils literal"&gt;/sbin/init&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;/sbin/init.distrib&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; dpkg-divert --add --local --rename /sbin/init &lt;span class="nv"&gt;creates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sbin/init.distrib
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And create a script to replace  &lt;tt class="docutils literal"&gt;/sbin/init&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; grep -qv &lt;span class="s2"&gt;&amp;quot; kexeced&lt;/span&gt;$&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; /proc/cmdline&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    /sbin/kexec --load /vmlinuz &lt;span class="se"&gt;\&lt;/span&gt;
        --reuse-cmdline &lt;span class="se"&gt;\&lt;/span&gt;
        --initrd&lt;span class="o"&gt;=&lt;/span&gt;/initrd.img &lt;span class="se"&gt;\&lt;/span&gt;
        --append&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;init=/sbin/init.distrib kexeced&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; /bin/mount -o ro,remount / &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; /sbin/kexec --exec
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt; /sbin/init.distrib &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Make it executable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chmod &lt;span class="m"&gt;755&lt;/span&gt; /sbin/init
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then you can reboot. Next time your server restarts, the script will run
before any other init scripts and will change the running kernel with kexec. You
can change the kernel parameters in this file if you need custom options, or
just leave it as is - and you'll be able to stick with your standard Debian
kernel.&lt;/p&gt;
&lt;p&gt;Credit to Joe Johnston for the &lt;a class="reference external" href="https://github.com/simple10/guides/blob/fa8261c17d3bade13da0644a38c3c38b24dc5b96/digitalocean.md#debian-selinux"&gt;original script&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="update"&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;p&gt;A couple of years later, DigitalOcean eventually created new images which use
the virtual machines bootloader - which means this hack is no longer needed for
newer Linux distributions.&lt;/p&gt;
&lt;/div&gt;
</content><category term="digitalocean"></category><category term="debian"></category></entry><entry><title>Hello Pelican!</title><link href="https://www.alextomkins.com/2013/09/hello-pelican/" rel="alternate"></link><published>2013-09-08T19:53:00+01:00</published><updated>2013-09-08T19:53:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2013-09-08:/2013/09/hello-pelican/</id><summary type="html">&lt;p&gt;After nearly 5 years of using &lt;a class="reference external" href="http://wordpress.org/"&gt;Wordpress&lt;/a&gt; to power this blog, I've finally switched to an alternative - &lt;a class="reference external" href="http://docs.getpelican.com/"&gt;Pelican&lt;/a&gt;, a &lt;a class="reference external" href="http://www.python.org/"&gt;Python&lt;/a&gt; powered static site generator.&lt;/p&gt;
&lt;p&gt;Why the switch? Maintaining a Wordpress site is annoying in the long term. Upgrades are needed on a regular basis for security patches, which is to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After nearly 5 years of using &lt;a class="reference external" href="http://wordpress.org/"&gt;Wordpress&lt;/a&gt; to power this blog, I've finally switched to an alternative - &lt;a class="reference external" href="http://docs.getpelican.com/"&gt;Pelican&lt;/a&gt;, a &lt;a class="reference external" href="http://www.python.org/"&gt;Python&lt;/a&gt; powered static site generator.&lt;/p&gt;
&lt;p&gt;Why the switch? Maintaining a Wordpress site is annoying in the long term. Upgrades are needed on a regular basis for security patches, which is to be expected from a fairly large web application. However having to deal with styling issues from using a custom theme, and dealing with plugin incompatibilites every time isn't something I want to deal with for a simple site.&lt;/p&gt;
&lt;p&gt;Pelican includes the features I want (themes, code highlighting, etc) without the hassle of dealing with a site which I need to upgrade every few months.&lt;/p&gt;
</content><category term="python"></category><category term="pelican"></category></entry><entry><title>Android fragmentation must be addressed</title><link href="https://www.alextomkins.com/2010/05/android-fragmentation-must-be-addressed/" rel="alternate"></link><published>2010-05-04T00:10:00+01:00</published><updated>2010-05-04T00:10:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2010-05-04:/2010/05/android-fragmentation-must-be-addressed/</id><summary type="html">&lt;p&gt;According to the &lt;a class="reference external" href="http://developer.android.com/resources/dashboard/platform-versions.html"&gt;Platform Version&lt;/a&gt; statistics at the Android developer site, 37% of devices are running 1.5, 29% are on 1.6, with 32% running 2.1 - the latest version. Android 2.1 has been available since January 2010, however the number of users on older phones is shockingly …&lt;/p&gt;</summary><content type="html">&lt;p&gt;According to the &lt;a class="reference external" href="http://developer.android.com/resources/dashboard/platform-versions.html"&gt;Platform Version&lt;/a&gt; statistics at the Android developer site, 37% of devices are running 1.5, 29% are on 1.6, with 32% running 2.1 - the latest version. Android 2.1 has been available since January 2010, however the number of users on older phones is shockingly worrying and is harmful for the future of Android.&lt;/p&gt;
&lt;p&gt;I'm not too bothered about the amount of different devices which use the Android operating system, which certain people seem to focus on. What does concern me are the slow handset manufacturers who aren't updating their phones to the latest version within a reasonable amount of time. The Motorola Droid was one of the more successful Android 2.0 devices which has been updated to 2.1, which probably explains why there are still a few users still on the 2.0 series (inexperienced users who don't know how to update), but could be much worse if this device wasn't updated.&lt;/p&gt;
&lt;p&gt;Manufacturers need to update all their recent phones within a reasonable amount of time, otherwise the future of the platform is going to be an awkward one. Users are often spending hundreds of pounds on phones which they consider to be smart phones, yet their manufacturer is treating them like the existing dumb phones - where you get updates if you're lucky. Customers are paying a premium for what they think is an alternative smart phone to the likes of the Apple iPhone. Smart phones deserve a respectable lifespan of updates after the release.&lt;/p&gt;
&lt;p&gt;At the moment my HTC Hero is due an update in June 2010, this is 5 months after the release of 2.1 on other platforms. I love Android, however I no longer like HTC. I'm sure there are others who love the operating system, but hate the way their manufacturer is treating them with updates.&lt;/p&gt;
&lt;p&gt;Just remember HTC - other phone manufacturers are releasing Android powered devices too.&lt;/p&gt;
</content></entry><entry><title>Sockets with JavaScript</title><link href="https://www.alextomkins.com/2009/03/sockets-with-javascript/" rel="alternate"></link><published>2009-03-10T23:14:00+00:00</published><updated>2009-03-10T23:14:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2009-03-10:/2009/03/sockets-with-javascript/</id><summary type="html">&lt;p&gt;We've seen a great push towards using JavaScript in modern websites, using AJAX and JSON for dynamic websites which start to feel more like applications instead of plain boring pages. One of the problems which more interactive sites face is the constant polling of the web server due to the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;We've seen a great push towards using JavaScript in modern websites, using AJAX and JSON for dynamic websites which start to feel more like applications instead of plain boring pages. One of the problems which more interactive sites face is the constant polling of the web server due to the way HTTP works. Various web apps these days tend to poll for data with the server keeping the connection alive without sending any data, once data has been sent then a new request is sent asking for more data.&lt;/p&gt;
&lt;p&gt;One particular web app which isn't that great is IRC web chat. Most IRC servers are keen on users connecting from their real connection, as it's hard to track abuse when connections are being proxied by a web server. Using AJAX or JSON with a web server isn't that ideal either, browsers have to constantly poll and refresh with the server to receive the data, this typically requires a light weight web server as Apache with PHP or Perl is hardly efficient for multiple users. The most common approaches over the years has been &lt;a class="reference external" href="http://cgiirc.org/"&gt;CGI:IRC&lt;/a&gt;, but this is just yet another inefficient Perl script which keeps the connection open. One of the oldest methods is using a hefty Java application, hardly friendly for users.&lt;/p&gt;
&lt;p&gt;So, why not use sockets with JavaScript? Obviously JavaScript can't do it directly, but with Adobe Flash and something like &lt;a class="reference external" href="http://matthaynes.net/blog/2008/07/17/socketbridge-flash-javascript-socket-bridge/"&gt;socketBridge&lt;/a&gt; (or &lt;a class="reference external" href="http://ionelmc.wordpress.com/2008/11/29/flash-socket-bridge-with-haxe/"&gt;this improved version&lt;/a&gt;) you could control a socket with mostly JavaScript. The only problem with Flash and sockets is that it would have to connect to a policy port before being authorised to connect to that particular port, however it would be a much lighter solution for the server compared to all the polling techniques.&lt;/p&gt;
&lt;p&gt;I'm tempted to try and create a web IRC client using JavaScript and the Flash socket bridge. Even if other IRC networks won't open a port for Flash policy requests, you could do something similar to the other web chat apps - use your server as a proxy.&lt;/p&gt;
</content></entry><entry><title>Forwarding all mail with Gmail</title><link href="https://www.alextomkins.com/2009/03/forwarding-all-mail-with-gmail/" rel="alternate"></link><published>2009-03-09T17:59:00+00:00</published><updated>2009-03-09T17:59:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2009-03-09:/2009/03/forwarding-all-mail-with-gmail/</id><summary type="html">&lt;p&gt;If you've got multiple Gmail accounts, such as different Google Apps Gmail accounts (for hosted domains) and gmail.com accounts, being able to forward them all to one account saves a lot of time and effort. Gmail does allow you to forward email under the &lt;em&gt;Forwarding and POP/IMAP&lt;/em&gt; section …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you've got multiple Gmail accounts, such as different Google Apps Gmail accounts (for hosted domains) and gmail.com accounts, being able to forward them all to one account saves a lot of time and effort. Gmail does allow you to forward email under the &lt;em&gt;Forwarding and POP/IMAP&lt;/em&gt; section of the control panel, however this only forwards non-spam emails. As most people know, sometimes the spam filters aren't quite perfect and suffer from false positives occasionally.&lt;/p&gt;
&lt;p&gt;To forward all mail to another account, simply remove your existing forward, create a new filter from * (matches any sender), then forward it to your main email address - making sure you've got &lt;em&gt;Never send to spam&lt;/em&gt; selected. You'll probably want to choose &lt;em&gt;Delete it&lt;/em&gt; as well, this means any incoming mail on that specific account will be forwarded and deleted on the original account - but you'll still have 30 days until it gets removed from trash.&lt;/p&gt;
&lt;p&gt;This way you'll get &lt;strong&gt;all&lt;/strong&gt; mail forwarded from Gmail - including spam. Now you can see all the spam from any other Gmail accounts incase there are any false positives.&lt;/p&gt;
</content></entry><entry><title>Expansions in MMORPGs</title><link href="https://www.alextomkins.com/2009/01/expansions-in-mmorpgs/" rel="alternate"></link><published>2009-01-02T11:57:00+00:00</published><updated>2009-01-02T11:57:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2009-01-02:/2009/01/expansions-in-mmorpgs/</id><summary type="html">&lt;p&gt;After resubscribing to Dark Age of Camelot, there is one thing which has been slightly bothering me whilst reminiscing about the game - the fact that expansions and updates in MMORPGs tend to destroy any use of old areas.&lt;/p&gt;
&lt;p&gt;Trials of Atlantis rendered a huge amount of armour and weapons entirely …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After resubscribing to Dark Age of Camelot, there is one thing which has been slightly bothering me whilst reminiscing about the game - the fact that expansions and updates in MMORPGs tend to destroy any use of old areas.&lt;/p&gt;
&lt;p&gt;Trials of Atlantis rendered a huge amount of armour and weapons entirely useless from the original version and the first expansion, thanks to their overpowered nature. Catacombs comes along and it had some of the best areas to level up characters in - until they increased the bonuses for other areas making it entirely pointless. The more recent Dragon's Revenge updates makes the older armour less useful, so everyone changes to the latest and greatest. Certain things tend to remain useful for quite a long time if there isn't any better alternative, however once something better has been added the players will flock to it.&lt;/p&gt;
&lt;p&gt;Why does it bother me? There is a lot of quality content in the game which people won't touch and will avoid as much as possible because newer things are better. Why would I bother doing the very old quests which give virtually no reward? Instead I can do the latest expansions quests and get 500 gold just for completing one of steps. Many hours must have been spent on the design, the coding, the graphics, the storyline and everything else involved in an expansions content - yet a few updates a year later will render that content pointless.&lt;/p&gt;
&lt;p&gt;I'd like to see more MMORPGs updating their older areas at the same time as working on their expansions. This is something Mythic has been doing, although it has only been a fairly recent and minor effort. Update the stats on drops for old items, though not retroactively as to encourage people to go back for the new bits. Add more quests and content to existing areas instead of putting the entire focus on new areas, maybe more of the old content could be updated to point to new areas within the storyline - instead of just having a portal to switch to new bits.&lt;/p&gt;
&lt;p&gt;Players like to reminisce about the old days, take advantage of your old content and update it slightly to make them think they're still playing the game they once loved.&lt;/p&gt;
</content></entry><entry><title>Adverts in RSS feeds</title><link href="https://www.alextomkins.com/2008/12/adverts-in-rss-feeds/" rel="alternate"></link><published>2008-12-29T20:06:00+00:00</published><updated>2008-12-29T20:06:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-12-29:/2008/12/adverts-in-rss-feeds/</id><summary type="html">&lt;p&gt;RSS feeds are great for those people who want to follow up on news sites without having to constantly revisit the same site multiple times a day. Google &lt;a class="reference external" href="http://www.feedburner.com/"&gt;FeedBurner&lt;/a&gt; offers nice statistics to any sites with RSS feeds, allowing them to see how many subscribers they've got by hosting the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;RSS feeds are great for those people who want to follow up on news sites without having to constantly revisit the same site multiple times a day. Google &lt;a class="reference external" href="http://www.feedburner.com/"&gt;FeedBurner&lt;/a&gt; offers nice statistics to any sites with RSS feeds, allowing them to see how many subscribers they've got by hosting the RSS feed. What I'm finding increasingly annoying is the other service which FeedBurner offers - their advertising programme.&lt;/p&gt;
&lt;p&gt;I don't mind if RSS feeds are either partial feeds to show users the first paragraph to attract users to visit the site, or full feeds to show users the entire article, however putting in adverts is highly annoying.&amp;nbsp;To me RSS feeds are there to entice me to go and visit the site itself. RSS feeds are mostly used by fairly technical users - putting in adverts where the people reading them are most likely to ignore them seems a bit pointless. Adding other little buttons/icons to enhance the feed is annoying as well, I just want the article and nothing more!&lt;/p&gt;
&lt;p&gt;I don't usually bother with advert blocking, but after seeing more feeds with adverts in I've finally installed Adblock Plus for Firefox. Good riddance to pointless adverts!&lt;/p&gt;
</content></entry><entry><title>Cables not included</title><link href="https://www.alextomkins.com/2008/12/cables-not-included/" rel="alternate"></link><published>2008-12-25T17:56:00+00:00</published><updated>2008-12-25T17:56:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-12-25:/2008/12/cables-not-included/</id><summary type="html">&lt;p&gt;To me this is one of the most annoying things manufacturers decide on when releasing their products – not including all the cables necessary to use the product!&lt;/p&gt;
&lt;p&gt;Various USB printers have done this for quite a while, you have to buy the cable separately. Whilst it's sometimes understandable with cheap …&lt;/p&gt;</summary><content type="html">&lt;p&gt;To me this is one of the most annoying things manufacturers decide on when releasing their products – not including all the cables necessary to use the product!&lt;/p&gt;
&lt;p&gt;Various USB printers have done this for quite a while, you have to buy the cable separately. Whilst it's sometimes understandable with cheap products, it's still slightly annoying that the product you buy isn't usable until you go and buy yet another cable. What I find really annoying is spending £300 on a console such as the PlayStation 3 and they're too cheap to include an HDMI cable. This is a console designed for high definition gaming, yet they don't even bother including a cable for it.&lt;/p&gt;
&lt;p&gt;It feels like the modern equivalent of &amp;quot;batteries not included&amp;quot;, certainly not something which should happen if you spend large amounts of money on a new device.&lt;/p&gt;
</content></entry><entry><title>Poor Windows Vista support in games</title><link href="https://www.alextomkins.com/2008/11/poor-windows-vista-support-in-games/" rel="alternate"></link><published>2008-11-19T16:39:00+00:00</published><updated>2008-11-19T16:39:00+00:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-11-19:/2008/11/poor-windows-vista-support-in-games/</id><summary type="html">&lt;p&gt;As much as people might dislike Vista, it's the operating system bundled with new machines and it's here to stay. It's been around 2 years since Vista was launched, whilst I can understand certain software developers might not offer support from the immediate release – it should be one of their …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As much as people might dislike Vista, it's the operating system bundled with new machines and it's here to stay. It's been around 2 years since Vista was launched, whilst I can understand certain software developers might not offer support from the immediate release – it should be one of their high priorities. MMORPG developers seem to be among the worst for poor Vista compatability, along with other game developers.&lt;/p&gt;
&lt;p&gt;Certain games decide that they need to run as Administrator from the moment the patcher starts up, even though the patcher might not need to download any file at all – therefore not needing to write any files to the Program Files directory. So the entire game runs under admin privileges, just incase the patcher might need to update files – Mythic Entertainment are guilty of this with Dark Age of Camelot.&lt;/p&gt;
&lt;p&gt;Other games are still living in the old days where they still write any configuration files to the game directory for all players, instead of the relevant application data directory for the user. This is just extremely lazy and makes things awkward for users, instead of a clean split between game data and user data – everything is just lumped together in one directory. If I wanted to make a backup of my computer, it would be far easier to just backup my user profile which would contain any important data – however keeping this data along with the game itself makes it difficult. If I wanted to restore my machine from a backup, I'd lose any settings from the game.&lt;/p&gt;
&lt;p&gt;There are other games which are complete failures, getting players to go with workarounds such as changing the permissions on the installation directory. After installing Lord of the Rings Online I didn't even get a shortcut to the game from my start menu.&lt;/p&gt;
&lt;p&gt;If game developers could just put a bit of extra effort into game installation it would make things so much easier. I know the aim of these developers is probably just to get the game running on these platforms, but a fully functional game which works properly would save users so many headaches.&lt;/p&gt;
</content></entry><entry><title>Server Name Indication being held back</title><link href="https://www.alextomkins.com/2008/10/server-name-indication-being-held-back/" rel="alternate"></link><published>2008-10-17T20:10:00+01:00</published><updated>2008-10-17T20:10:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-10-17:/2008/10/server-name-indication-being-held-back/</id><summary type="html">&lt;p&gt;Ever since we've had websites available over HTTPS (HTTP over SSL), there has always been the problem that the host always requires an IP per site as each site needs a new certificate. With SSL the server couldn't just switch certificate according to the site, as SSL is negotiated &lt;strong&gt;before …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ever since we've had websites available over HTTPS (HTTP over SSL), there has always been the problem that the host always requires an IP per site as each site needs a new certificate. With SSL the server couldn't just switch certificate according to the site, as SSL is negotiated &lt;strong&gt;before&lt;/strong&gt; any HTTP request is sent.&lt;/p&gt;
&lt;p&gt;Server Name Indication is a TLS extension which sends the hostname during the TLS negotiation, which means the server can switch to the appropriate certificate – allowing a web host to potentially have as many HTTPS sites as they want on a single IP address. It's a great solution, however the only problem is that any chance of it's currently being held back.&lt;/p&gt;
&lt;p&gt;Firefox 2.0, Opera 8.0 and even Google Chrome supports SNI, however Safari on OS X currently doesn't support it and Internet Explorer 7 only supports it on Windows Vista and not Windows XP. So SNI is being held back due to a lack of support from IE and Safari, these are two fairly significant browsers which many people use – admins can't ignore either of them.&lt;/p&gt;
&lt;p&gt;SNI is only just becoming available in Linux distributions with Apache 2.2.8 and the latest versions of OpenSSL, so it isn't available to everyone just yet. However being unable to use it for many years due to the fact that any Internet Explorer user on Windows XP won't be able to view any SNI sites is going to be a huge hinderance towards adopting it.&lt;/p&gt;
</content></entry><entry><title>3ware 8006-2LP RAID with CentOS 5</title><link href="https://www.alextomkins.com/2008/10/3ware-8006-2lp-raid-with-centos-5/" rel="alternate"></link><published>2008-10-14T12:50:00+01:00</published><updated>2008-10-14T12:50:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-10-14:/2008/10/3ware-8006-2lp-raid-with-centos-5/</id><summary type="html">&lt;p&gt;The Hetzner DS-8000 servers feature a 3ware RAID card (8006-2LP) for hardware RAID 1, a nice feature – although the default CentOS setup is slightly broken with SELinux. After several hours of wondering why smartd wouldn't work, /var/log/audit/audit.log reveals the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;type=AVC msg=audit(1223922183.059 …&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;The Hetzner DS-8000 servers feature a 3ware RAID card (8006-2LP) for hardware RAID 1, a nice feature – although the default CentOS setup is slightly broken with SELinux. After several hours of wondering why smartd wouldn't work, /var/log/audit/audit.log reveals the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;type=AVC msg=audit(1223922183.059:22): avc:  denied  { getattr } for  pid=2654 comm=&amp;quot;smartd&amp;quot; path=&amp;quot;/dev/twe0&amp;quot; dev=tmpfs ino=8940 scontext=user_u:system_r:fsdaemon_t:s0 tcontext=user_u:
object_r:device_t:s0 tclass=chr_file
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A good administrator will probably want to add some &lt;a class="reference external" href="http://www.centos.org/docs/5/html/5.2/Deployment_Guide/sec-sel-policy-customizing.html"&gt;custom SELinux policies&lt;/a&gt;, but the lazy admin might just want to disable SELinux. I went with the disabling of SELinux as the easier option! It's not a recommended solution though.&lt;/p&gt;
&lt;p&gt;Eventually I got smartd working again, so I've now got decent monitoring of the disks on the machine.&lt;/p&gt;
</content></entry><entry><title>Using Linux but never giving back</title><link href="https://www.alextomkins.com/2008/09/using-linux-but-never-giving-back/" rel="alternate"></link><published>2008-09-30T20:55:00+01:00</published><updated>2008-09-30T20:55:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-09-30:/2008/09/using-linux-but-never-giving-back/</id><summary type="html">&lt;p&gt;One great thing we see with Linux is that it's used for servers quite frequently, it's probably one of the strongest points about it. A strong and stable server platform which many companies rely on to run many parts of their infrastructure for an organisation.&lt;/p&gt;
&lt;p&gt;A common situation we'll see …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One great thing we see with Linux is that it's used for servers quite frequently, it's probably one of the strongest points about it. A strong and stable server platform which many companies rely on to run many parts of their infrastructure for an organisation.&lt;/p&gt;
&lt;p&gt;A common situation we'll see are MMORPGs, games with thousands of players mostly with Windows clients connecting to servers powered by Linux. Occasionally you'll see people trying to run their game client under WINE, but it isn't quite the same as running a native game. These companies use Linux to run their gaming servers, web servers and possibly many other parts of their infrastructure, but when it comes to building a Linux client for their game they'll rarely consider it or even dismiss it without even considering the feasibility of it.&lt;/p&gt;
&lt;p&gt;I don't think anyone expects companies who use Linux to start contributing hundreds of lines of source code back to various projects, but it would be nice if they considered a Linux client of their product where it's viable as a way of saying thank you. They're getting the benefits of many hours of effort being placed into a freely available operating system, but they're not helping this operating system by refusing to release their product for it.&lt;/p&gt;
&lt;p&gt;In an ideal world we'd see multi platform clients, maybe we'll see more if it one day.&lt;/p&gt;
</content></entry><entry><title>How not to launch an MMORPG</title><link href="https://www.alextomkins.com/2008/09/how-not-to-launch-an-mmorpg/" rel="alternate"></link><published>2008-09-29T10:20:00+01:00</published><updated>2008-09-29T10:20:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-09-29:/2008/09/how-not-to-launch-an-mmorpg/</id><summary type="html">&lt;p&gt;The past few weeks I've been enjoying one of the latest MMORPG games – &lt;a class="reference external" href="http://www.war-europe.com/"&gt;Warhammer Online&lt;/a&gt;. The main annoying thing about the European WAR experience is the fact that GOA are running the servers for all players over here. As expected they managed to make several mistakes which many players knew …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The past few weeks I've been enjoying one of the latest MMORPG games – &lt;a class="reference external" href="http://www.war-europe.com/"&gt;Warhammer Online&lt;/a&gt;. The main annoying thing about the European WAR experience is the fact that GOA are running the servers for all players over here. As expected they managed to make several mistakes which many players knew were bad decisions, yet GOA still went full steam ahead. Tim Jenks has listed some of them in his &lt;a class="reference external" href="http://www.jenkz.org/2008/09/09/how-not-to-launch-a-beta-for-a-wow-killer-mmo/"&gt;How not to launch a beta for a WoW-Killer MMO&lt;/a&gt;, I'm going to a list a few of the things which have annoyed me about the entire launch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't leave registration to the last minute.&lt;/strong&gt; GOA managed to utterly fail the open beta release as their system couldn't cope with the large numbers of players trying to register their open beta codes. Apparently it was an unforeseen problem and they did test their systems beforehand, however this wouldn't be so much of an issue if they spotted this a week earlier by letting players register in advance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't open your servers in the afternoon.&lt;/strong&gt; As with all MMORPG games you need to kill something in order to gain experience to advance your character, this is an absolute nightmare if you're fighting with hundreds of other players in the starting zone. If the servers were up at around 6AM then the hardcore players would've been able to rush through the starting zones allowing other players who get up at a more reasonable time to get started with a zone which isn't so busy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't launch with a limited number of servers.&lt;/strong&gt; Players with the collectors edition or standard edition who ordered from certain retailers were able to get head start codes which allowed them to play 3-4 days earlier than those who didn't pre-order. Certain servers became overpopulated rather quickly, as players who didn't pre-order wanted to join their friends in guilds on the servers which were open for head start, instead of the newer servers which were open on the official launch day.&lt;/p&gt;
&lt;p&gt;The entire point of all the different head start dates for collectors edition, standard edition and the official launch should've allowed GOA to spread out registrations and people joining servers – however if they leave everything to the last minute then disasters are bound to happen, and they did.&lt;/p&gt;
&lt;p&gt;Overall I'm enjoying the game quite a lot, there are various concerns I've got with various aspects of the game, but for now I'm having fun!&lt;/p&gt;
</content><category term="warhammer"></category></entry><entry><title>Ubuntu on the Aspire One</title><link href="https://www.alextomkins.com/2008/08/ubuntu-on-the-aspire-one/" rel="alternate"></link><published>2008-08-26T01:40:00+01:00</published><updated>2008-08-26T01:40:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-08-26:/2008/08/ubuntu-on-the-aspire-one/</id><summary type="html">&lt;p&gt;Okay, I didn't last long. After 24 hours of sticking with Linpus I eventually decided to install Ubuntu on the laptop, even though adjusting it for the Aspire One was going to involve some tweaking – sticking with Linpus would've ended up annoying me.&lt;/p&gt;
&lt;p&gt;The AspireOne page on the Ubuntu wiki …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Okay, I didn't last long. After 24 hours of sticking with Linpus I eventually decided to install Ubuntu on the laptop, even though adjusting it for the Aspire One was going to involve some tweaking – sticking with Linpus would've ended up annoying me.&lt;/p&gt;
&lt;p&gt;The AspireOne page on the Ubuntu wiki explains how to get you Ubuntu up and running. The Aspire One supports PXE booting, which I found far easier to setup than fiddling about with a USB stick just to get the install going.&lt;/p&gt;
&lt;p&gt;The Ubuntu Netbook remix includes some great packages which are useful for running GNOME on a small screen, I did try them all however I wasn't too happy with the UME launcher. I'd highly recommend Maximus, an application which maximises open windows and removes the top bar from running programs, window-picker-applet which is ideal with Maximus and doesn't hog too much space for switching between programs, and human-netbook-theme for a few visual tweaks.&lt;/p&gt;
&lt;p&gt;Overall I'm fairly happy, now I've got the power of Ubuntu for this laptop. Hopefully the weird quirks and bugs which the Aspire One has will be ironed out in future releases.&lt;/p&gt;
</content></entry><entry><title>Acer Aspire One</title><link href="https://www.alextomkins.com/2008/08/acer-aspire-one/" rel="alternate"></link><published>2008-08-23T00:24:00+01:00</published><updated>2008-08-23T00:24:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-08-23:/2008/08/acer-aspire-one/</id><summary type="html">&lt;p&gt;After resisting the EeePC for so long in the hope that better ultra portable laptops would appear with better specs, I finally decided to go for an Acer Aspire One. The 8GB flash storage and 512MB RAM are perfectly adequate for basic use in Linux, it's a great little device …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After resisting the EeePC for so long in the hope that better ultra portable laptops would appear with better specs, I finally decided to go for an Acer Aspire One. The 8GB flash storage and 512MB RAM are perfectly adequate for basic use in Linux, it's a great little device for browsing a few sites without having to take a huge laptop around with you.&lt;/p&gt;
&lt;p&gt;The Aspire One comes with Linpus Lite, a Fedora based distribution aimed at lightweight devices. After using it for a day it seems reasonably adequate for an average user, but for someone who understands Linux well might get a little bit annoyed at the quality of this particular distribution.&lt;/p&gt;
&lt;p&gt;This version of Linpus Lite is based on Fedora 8, slightly out of date as it was released back in November 2007. Firefox 2 is included, which is also a little bit old and support is ending shortly as Firefox 3 has been out for quite a while. I suppose this is sort of expected with the fast pace of change, but it's slightly annoying. The one thing I did try to do is run yum upgrade from the command line, however the dependencies are broken so it won't update – not exactly great.&lt;/p&gt;
&lt;p&gt;I'm keen on replacing Linpus with Ubuntu in the future, however the support for the Aspire One isn't quite complete at the moment with some functionality not quite working. I'll keep track of it for the future, however I want ease of use instead of having to deal with command line tweaks to fix broken functionality.&lt;/p&gt;
&lt;p&gt;Overall a thumbs up for the Acer Aspire One, but a bit of a thumbs down for Linpus Lite. I just wish they could've sorted out Ubuntu for this device.&lt;/p&gt;
</content></entry><entry><title>Hello world!</title><link href="https://www.alextomkins.com/2008/08/hello-world/" rel="alternate"></link><published>2008-08-10T21:04:00+01:00</published><updated>2008-08-10T21:04:00+01:00</updated><author><name>Alex Tomkins</name></author><id>tag:www.alextomkins.com,2008-08-10:/2008/08/hello-world/</id><summary type="html">&lt;p&gt;It's taken over a year for me to finally get my blog up and running, I registered the domain back in April 2007 but I've only recently managed to get the urge to finish it. Many thanks to &lt;a class="reference external" href="http://www.jonikorpi.com/"&gt;Joni&lt;/a&gt; for helping me finish the design off.&lt;/p&gt;
&lt;p&gt;Some of you may …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's taken over a year for me to finally get my blog up and running, I registered the domain back in April 2007 but I've only recently managed to get the urge to finish it. Many thanks to &lt;a class="reference external" href="http://www.jonikorpi.com/"&gt;Joni&lt;/a&gt; for helping me finish the design off.&lt;/p&gt;
&lt;p&gt;Some of you may recognise that the avatar at the top of the page is my current Mii avatar from the Wii. The only problem is that it's taken so long that it isn't entirely accurate – I've recently had my eyes lasered, so no more glasses for me.  Looks like I'll have to update it eventually, hopefully it won't take me another year to get that sorted though!&lt;/p&gt;
&lt;p&gt;I'll be using this place for my various thoughts and rants, so check back often for updates.&lt;/p&gt;
</content></entry></feed>