<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://meshy.co.uk//feed.xml" rel="self" type="application/atom+xml" /><link href="https://meshy.co.uk//" rel="alternate" type="text/html" /><updated>2026-04-07T13:31:40+00:00</updated><id>https://meshy.co.uk//feed.xml</id><title type="html">Charlie Denton</title><subtitle>The sporadic ramblings of a software developer in the UK.</subtitle><entry><title type="html">Use ‘The Notes of GHC’</title><link href="https://meshy.co.uk//posts/use-the-notes-of-ghc" rel="alternate" type="text/html" title="Use ‘The Notes of GHC’" /><published>2022-12-09T00:00:00+00:00</published><updated>2022-12-09T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/use-the-notes-of-ghc</id><content type="html" xml:base="https://meshy.co.uk//posts/use-the-notes-of-ghc"><![CDATA[<p>I like to write code comments in the style of <a href="https://www.stackbuilders.com/blog/the-notes-of-ghc/">The Notes of GHC</a>.</p>

<p>The basic idea is that you write the large (or repeated) comment once in a sensible place,
and reference it in the myriad other places where you may want the extra context.</p>

<p>Here’s an example of one:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Note [CharField length 191]
# ===========================
#
# MySQL VARCHAR fields with an index or constraint and the utf8mb4 encoding
# are limited to 191 characters in length.
# This is because MySQL assumes that each char may be 4 bytes long,
# and the maximum length of an InnoDB index is 767 bytes.
#
# 767 / 4 = 191.75
#
# See https://www.grouparoo.com/blog/varchar-191
</span></code></pre></div></div>

<p>Now when you write code, you can “link” to the note without massive comments breaking up your reading of the code.
This is especially useful if you might be tempted to repeat comments in lots of places.</p>

<p>For example, the code below “links” to the note we defined above next to each <code class="language-plaintext highlighter-rouge">CharField</code>.
The note doesn’t even have to be in the same file, as you can search for the heading to find it anywhere.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MyModel</span><span class="p">(</span><span class="n">models</span><span class="p">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="c1"># Note [CharField length 191]
</span>    <span class="n">value</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">191</span><span class="p">,</span> <span class="p">...)</span>


<span class="k">class</span> <span class="nc">MyOtherModel</span><span class="p">(</span><span class="n">models</span><span class="p">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="c1"># Note [CharField length 191]
</span>    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">191</span><span class="p">,</span> <span class="p">...)</span>
</code></pre></div></div>

<p>Thanks to <a href="https://jml.io/">jml</a> for bringing this style to my attention. I hope it’s useful!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[A nice way to avoid cluttering code with repeated comments]]></summary></entry><entry><title type="html">Three Laws of Environment Variables</title><link href="https://meshy.co.uk//posts/three-laws-of-environment-variables" rel="alternate" type="text/html" title="Three Laws of Environment Variables" /><published>2020-06-03T00:00:00+00:00</published><updated>2020-06-03T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/three-laws-of-environment-variables</id><content type="html" xml:base="https://meshy.co.uk//posts/three-laws-of-environment-variables"><![CDATA[<p>My guidelines for default env var values are:</p>

<ol>
  <li>A default must not injure production or, through omission, allow production to come to harm.</li>
  <li>A default must ease local development except where such a default would conflict with the First Law.</li>
  <li>A default must reflect the live environment as long as such reflection does not conflict with the First or Second Laws.</li>
</ol>

<p>This format is inspired by <a href="https://en.wikipedia.org/wiki/Three_Laws_of_Robotics">Isaac Asimov’s Three Laws of Robotics</a>.</p>

<p>Like Asimov’s Laws, I expect there are some odd cases when they’re pushed to their limits.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[An idea for how to prioritise defaults.]]></summary></entry><entry><title type="html">UFW for dynamic domains</title><link href="https://meshy.co.uk//posts/ufw-for-dynamic-domains" rel="alternate" type="text/html" title="UFW for dynamic domains" /><published>2019-04-24T00:00:00+00:00</published><updated>2019-04-24T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/ufw-for-dynamic-domains</id><content type="html" xml:base="https://meshy.co.uk//posts/ufw-for-dynamic-domains"><![CDATA[<p>I have a server online that I only ever access from home, and I use UFW to
firewall it. It’s a little tricky to firewall it though, because my IP changes
fairly regularly. I’ve set up dynamic DNS at home, but it’s not possible to set
a firewall rule based on the domain, only based on an IP address.</p>

<p>This script runs on a regular cron-job on my server, and solves the issue
nicely:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># fix_firewall.py
import subprocess
import sys


def run_command(command):
    command = command.split()
    return subprocess.run(command, check=True, stdout=subprocess.PIPE).stdout


def get_remote_ip_address(domain):
    stdout = run_command(f'host {domain}')
    return stdout.strip().rsplit(maxsplit=1)[-1].decode()


def get_firewalled_ip_and_rule_number(port):
    stdout = run_command('ufw status numbered')
    for line in stdout.decode().splitlines():
        if f'{port}/tcp' in line:
            number, *_, ip = line.rsplit(maxsplit=4)
            return number.strip('[] '), ip


def remove_old_rule(rule_number):
    run_command(f'ufw --force delete {rule_number}')


def add_new_rule(ip, port):
    run_command(f'ufw allow from {ip} proto tcp to any port {port}')


if __name__ == '__main__':
    _, domain, port = sys.argv
    remote_ip = get_remote_ip_address(domain)
    rule_number, firewalled_ip = get_firewalled_ip_and_rule_number(port)
    if remote_ip != firewalled_ip:
        print(f'Changing IP from {firewalled_ip} to {remote_ip}.')
        remove_old_rule(rule_number)
        add_new_rule(remote_ip, port)
</code></pre></div></div>

<p>This script has to be run as <code class="language-plaintext highlighter-rouge">root</code> so that it can modify the firewall rules.</p>

<p>It’s run like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 fix_firewall.py my-domain.example.com 12345
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[How to open a port when your domain changes IP]]></summary></entry><entry><title type="html">Ordering Django applications</title><link href="https://meshy.co.uk//posts/ordering-django-applications" rel="alternate" type="text/html" title="Ordering Django applications" /><published>2018-10-30T00:00:00+00:00</published><updated>2018-10-30T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/ordering-django-applications</id><content type="html" xml:base="https://meshy.co.uk//posts/ordering-django-applications"><![CDATA[<p>When you create a Django project, the <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code> setting will be
generated for you like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Somewhere in settings.py...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
</code></pre></div></div>

<p>Then, when you create an app, or install a library, you’re faced with a
choice: do you add the new item <em>above</em> or <em>below</em> Django’s apps?</p>

<p>Well, it’s good practice to <strong>add new apps <em>above</em> Django’s</strong>, unless you
have a specific reason not to. I’ve seen this done the other way around a lot,
so it might not be what you expect.</p>

<p>This is because when Django is looking for something, it will go through the
<code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code> list, from the top, one app at a time, until it has found what
it’s looking for. <strong>Putting your code first means it takes priority.</strong></p>

<p>For example, if you want to override a template in Django’s admin, you can do
so by placing a modified version of the original into the <code class="language-plaintext highlighter-rouge">templates/</code>
directory of one of your apps.</p>

<p>This applies to:</p>

<ul>
  <li>management commands,</li>
  <li>templates,</li>
  <li>template-tags,</li>
  <li>translations,</li>
  <li>probably a lot more.</li>
</ul>

<p>It’s also handy to know that this is the order in which apps will be
registered, and admin integration discovered.</p>

<p>Finally: don’t add the core app’s <code class="language-plaintext highlighter-rouge">templates/</code> directory into
<code class="language-plaintext highlighter-rouge">TEMPLATES[...]['DIRS']</code> (or <code class="language-plaintext highlighter-rouge">TEMPLATE_DIRS</code>)! Instead, simply add
each app containing <code class="language-plaintext highlighter-rouge">templates/</code> to <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code>.</p>

<h2 id="tldr">TLDR</h2>

<p>In a Django projects, expect to see <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code> structured like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INSTALLED_APPS = [
    # The "core" (AKA "project" or "main") app.
    'core',

    # Other apps in the project.
    'foo',
    'bar',

    # Libraries.
    'orderable',
    'pgcrypto',
    'webpack_loader',

    # Django.
    'django.contrib.admin',
    'django.contrib.auth',
    ...  # Etc.
]
</code></pre></div></div>

<p>This ensures:</p>

<ul>
  <li>The ‘core’ app can override everything.</li>
  <li>Your secondary apps can extend the libraries and Django.</li>
  <li>Libraries can be used to extend Django.</li>
  <li>As usual, Django provides a solid default.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Why the order of INSTALLED_APPS matters in Django]]></summary></entry><entry><title type="html">Ubuntu on a ReadyNAS Pro 6</title><link href="https://meshy.co.uk//posts/ubuntu-on-readynas-pro-6" rel="alternate" type="text/html" title="Ubuntu on a ReadyNAS Pro 6" /><published>2018-03-18T00:00:00+00:00</published><updated>2018-03-18T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/ubuntu-on-readynas-pro-6</id><content type="html" xml:base="https://meshy.co.uk//posts/ubuntu-on-readynas-pro-6"><![CDATA[<p>A little while ago I was looking to build a home server, and was very grateful
to receive a <a href="https://www.netgear.com/support/product/RNDP6000-200_(ReadyNAS_Pro_6).aspx">Netgear ReadyNAS Pro 6</a> as a gift. The software on
it was rather out of date, so rather than update it to the latest (but
unsupported on this hardware) Netgear OS 6, I decided to install Ubuntu Server.</p>

<p>I’ve installed Ubuntu on desktops and servers loads of times, so I’m pretty
familiar with using it in a normal setting, but this presented a couple of
unusual challenges.</p>

<p>Firstly, the box doesn’t have anywhere to plug in a monitor. Now, it’s not
needed once the NAS is up and running, but it <em>is</em> important for the
installation. It does have a suspiciously-VGA-port-shaped plate on the back,
however…</p>

<p><a href="https://warwick.ac.uk/fac/sci/csc/people/computingstaff/jaroslaw_zachwieja/readynaspro-jailfix/">Jaroslaw Zachwieja’s related blog post</a> very helpfully
explains that there are some Nvidia graphics card breakout cables that can be
installed to get this working. He lists them as “XFX part: MA-BK01-LP1K”. It
proved very hard to find that item in stock anywhere. I finally found a listing
on eBay under the name “<a href="https://www.ebay.co.uk/itm//261646911396">VGA SVGA Video Graphics Adapter Card Slot Bracket
Header Cable 15 Pin 16 Hole</a>”. Catchy. I snapped one up.
I’ve just checked: now they’re sold out too.</p>

<p>If you’re trying to do this, and are unable to get hold of (or make) the
required cable, then you might have luck <a href="https://nerdyness2012.wordpress.com/2015/03/31/installing-ubuntu-14-10-server-on-a-netgear-readynas-ultra-duo-v2/">using the serial port on the back
instead</a>.</p>

<p>With the VGA cable installed I was able to plug in a monitor, watch the default
OS boot, and do a little victory jig.</p>

<p>After that, a had some false starts. I discovered that:</p>

<ul>
  <li>To boot to the live USB, one must hold down the “Backup” button while turning
on the machine.</li>
  <li>The NAS is picky about USB sticks. A Kingston DataTraveller (100 G3) 8GB did
not work, but my Sandisk Ultra Fit 128GB did.</li>
  <li>On boot, Ubuntu showed an error and a prompt:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  graphics initialization failed
  Error setting up gfxboot
  boot:
</code></pre></div>    </div>
    <p>The advice online was to <a href="https://askubuntu.com/a/417966/30904">type <code class="language-plaintext highlighter-rouge">help</code> at this prompt</a>, but that
  failed for me on Ubuntu 16.04. It turns out that <a href="https://bugzilla.suse.com/show_bug.cgi?id=980570"><code class="language-plaintext highlighter-rouge">gfxboot</code> has improved
  since 16.04 came out</a>, so 17.10 got me beyond this point,
  despite displaying the same error.</p>
  </li>
  <li>One of the USB ports was faulty.</li>
</ul>

<p>I had intended to install from one USB stick to another, using the internal
128MB drive as a boot partition. The faulty USB port meant I could only plug in
one USB stick and a keyboard.</p>

<p>I found out that I can <a href="https://askubuntu.com/a/855298/30904">install to the USB I booted from</a>
if I boot with the <code class="language-plaintext highlighter-rouge">toram</code> boot option. The way to do this was to type <code class="language-plaintext highlighter-rouge">install
toram</code> at the <code class="language-plaintext highlighter-rouge">boot:</code> prompt <em>instead</em> of typing <code class="language-plaintext highlighter-rouge">help</code>.</p>

<p>The standard Ubuntu Server image failed to format the USB it was running from
with this method, but it worked a treat on the <a href="https://www.ubuntu.com/download/alternative-downloads">Ubuntu 17.10 Network
installer</a>. (Confusingly, the file was called
<code class="language-plaintext highlighter-rouge">mini.iso</code>, unlike the other Ubuntu downloads, which, on the whole, are named
descriptively.)</p>

<p>The installation went swimmingly from there on. I thought things might have
gone wrong a couple of times, but nothing turned out to be an issue. Notably:</p>

<ul>
  <li>The installer seemed stuck at 20% of “Creating swap file”, but it did
eventually get moving again.</li>
  <li>My concern that <a href="https://bugs.launchpad.net/ubuntu/+source/unattended-upgrades/+bug/1357093">automatic updates would fill up the <code class="language-plaintext highlighter-rouge">/boot/</code> partition with
old kernels</a>, was based on out of date information, so I
was able to turn them on.</li>
  <li>I was asked what software I wished to install. I selected “OpenSSH Server”
(which is required to SSH into the machine over the LAN) and “Basic Ubuntu
Server” (which, in retrospect, <a href="https://askubuntu.com/a/153292/30904">I probably didn’t need</a>).</li>
  <li>Installing Grub to the Master Boot Record didn’t seem to cause the chaos I
thought it might.</li>
  <li>Now that it’s installed, nothing after the BIOS appears on the monitor. I
just get a blank screen. I suspect that there is an incorrect video setting
somewhere. That doesn’t cause me any issues, however. I’m able to SSH in, and
I do not plan on having a monitor plugged in.</li>
</ul>

<p>So now I have Ubuntu installed! <a href="https://media0.giphy.com/media/6GxdekV8GmwhO/giphy.webp">Yatta!</a></p>

<p>I have a small list of things to do next:</p>

<ul>
  <li>This thing is <em>loud</em>. I’d like to <a href="https://community.netgear.com/t5/ReadyNAS-Hardware-Compatibility/ReadyNAS-Pro-6-Power-Supply-FAN-replacement/td-p/1072881">pop some new fans in it</a>.</li>
  <li>I’ll be installing <a href="https://pi-hole.net/">Pi Hole</a> and <a href="https://nextcloud.com/">NextCloud</a>.</li>
  <li>There’s a tiny screen on the front. I’d like to see if can get it to display
anything.</li>
  <li>It’s a NAS… Maybe I should put some hard drives in it…</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Steps and mis-steps taken to install Linux on my NAS.]]></summary></entry><entry><title type="html">Python package licences</title><link href="https://meshy.co.uk//posts/python-package-licences" rel="alternate" type="text/html" title="Python package licences" /><published>2017-06-25T00:00:00+00:00</published><updated>2017-06-25T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/python-package-licences</id><content type="html" xml:base="https://meshy.co.uk//posts/python-package-licences"><![CDATA[<p>Sometimes it’s handy to know the licences of your project’s dependancies. You
could check each entry in <code class="language-plaintext highlighter-rouge">requirements.txt</code> one by one, but it’d be too easy
to miss out a nested dependancy. Also, if a project has a lot of dependancies,
it quickly becomes laborious. Don’t worry though, it’s easy to script, and I’ve
already done it for you:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">print_function</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">defaultdict</span>
<span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">join</span>

<span class="c1"># The `pkg_resources` module isn't in the standard library,
# but it's frequently available anywhere the `setuptools`
# module is (it typically comes with `pip`).
</span><span class="kn">import</span> <span class="nn">pkg_resources</span>


<span class="n">licences</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>

<span class="c1"># We go over each installed package:
</span><span class="k">for</span> <span class="n">package</span> <span class="ow">in</span> <span class="n">pkg_resources</span><span class="p">.</span><span class="n">working_set</span><span class="p">:</span>
    <span class="c1"># Find the directory where the metadata is stored.
</span>    <span class="n">info_dir</span> <span class="o">=</span> <span class="n">package</span><span class="p">.</span><span class="n">_provider</span><span class="p">.</span><span class="n">egg_info</span>
    <span class="k">if</span> <span class="n">info_dir</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">'No data for {}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">package</span><span class="p">))</span>
        <span class="k">continue</span>
    <span class="c1"># Determine if the package is a wheel...
</span>    <span class="n">is_wheel</span> <span class="o">=</span> <span class="n">info_dir</span><span class="p">.</span><span class="n">endswith</span><span class="p">(</span><span class="s">'.dist-info'</span><span class="p">)</span>
    <span class="c1"># ...and chose correct file.
</span>    <span class="n">filename</span> <span class="o">=</span> <span class="s">'METADATA'</span> <span class="k">if</span> <span class="n">is_wheel</span> <span class="k">else</span> <span class="s">'PKG-INFO'</span>
    <span class="n">filepath</span> <span class="o">=</span> <span class="n">join</span><span class="p">(</span><span class="n">info_dir</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>

    <span class="c1"># We then loop over the lines in the file...
</span>    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">lines</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">lines</span><span class="p">):</span>
            <span class="c1"># ...find the line that stores the license...
</span>            <span class="k">if</span> <span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">'Classifier: License :: '</span><span class="p">)</span> <span class="ow">or</span>
                    <span class="n">line</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">'License: '</span><span class="p">)):</span>
                <span class="c1"># ... and add the package to the list for it.
</span>                <span class="n">license</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">': '</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">strip</span><span class="p">()</span>
                <span class="n">licences</span><span class="p">[</span><span class="n">license</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">package</span><span class="p">)</span>
                <span class="k">break</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="s">'No data for {}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">package</span><span class="p">))</span>

<span class="c1"># Then all we have to do is print it out:
</span><span class="k">for</span> <span class="n">licence</span><span class="p">,</span> <span class="n">packages</span> <span class="ow">in</span> <span class="n">licences</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="n">licence</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">package</span> <span class="ow">in</span> <span class="n">packages</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">'</span><span class="se">\t</span><span class="s">'</span><span class="p">,</span> <span class="n">package</span><span class="p">)</span>
</code></pre></div></div>

<p>Note that this only works for packages where the correct information has been
<a href="https://packaging.python.org/distributing/#license">added to the dependancy’s
<code class="language-plaintext highlighter-rouge">setup.py</code></a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How to determine the licence of your pip installed packages]]></summary></entry><entry><title type="html">Bullet journal tips</title><link href="https://meshy.co.uk//posts/bullet-journal-tips" rel="alternate" type="text/html" title="Bullet journal tips" /><published>2017-06-23T00:00:00+00:00</published><updated>2017-06-23T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/bullet-journal-tips</id><content type="html" xml:base="https://meshy.co.uk//posts/bullet-journal-tips"><![CDATA[<p>I’ve tried keeping a diary, and I’ve tried all sorts of life management apps,
but I’ve never managed to really stick with it. Then a colleague introduced me
to <a href="https://bulletjournal.com/">Bullet Journal</a>. It’s turned out to be invaluable. I’m a
fan, even if I’m not using it as prolifically now as I was when I started a
little over a year ago.</p>

<p>A few days ago, I was asked if I knew a good introduction guide. I don’t
really. Rather than disappoint, I typed up some quick tips, and I was
encouraged to <a href="https://www.drmaciver.com/2014/07/you-should-write-more/">write them down publicly</a>. The particulars of the
process are already covered very well elsewhere, so, in lieu of anything
coherent, here’re a few disjointed thoughts on the subject.</p>

<p>To start with, you can’t go wrong with the “<a href="https://www.youtube.com/watch?v=fm15cmYU0IM">How to Bullet Journal</a>”
video (~4 mins) from the project’s main home page. It’s a short video
explaining how the system works, and why you might want to give it a go.</p>

<p>If you pick it up, after a while, you might start looking for further
inspiration. There are a lot of great ideas on the <a href="https://bulletjournal.com/blog/">Bullet Journal blog</a>.
Scanning through the pictures will reveal a lot of (dauntingly artistic)
alternative layouts for the main “modules”. Don’t worry if you’re not
super-artistic. There’s a chance that might come with time, but I recommend
focussing on the routine and utility. You can always add flair later.</p>

<p>I like to keep a lot of lists in the book, and transfer them to the new book
once I’ve filled one up. I have lists for “movies/tv to watch”, “books to
read”, “games to play”, “birthdays”, and others. Recently, as I’ve made more of
an effort to cook, I’ve been recording <a href="https://www.bbcgoodfood.com/recipes/1167651/chicken-and-chorizo-jambalaya">recipes I like</a> too.</p>

<p>As for the book you use, I highly recommend dotted grid-paper. I use the
large Moleskine hard-cover books, but the Leuchtturm 1917 books look pretty
nice too. The Leuchtturm books have slightly broader pages. They both ought to
be good for fountain-pen use, if that’s your thing. It’s my thing.</p>

<p>Finally, don’t forget that it’s your book. By this I mean: it doesn’t matter if
you stray from the guidelines, tear out a few pages, and scribble all over the
others. Go nuts. Adapting, inventing you own way of doing things, and skipping
the things that get in your way is greatly encouraged.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[or: How I Learned to Stop Worrying and Love the Book]]></summary></entry><entry><title type="html">Debugging assertNumQueries tests</title><link href="https://meshy.co.uk//posts/debugging-assertnumqueries-tests" rel="alternate" type="text/html" title="Debugging assertNumQueries tests" /><published>2013-11-06T00:00:00+00:00</published><updated>2013-11-06T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/debugging-assertnumqueries-tests</id><content type="html" xml:base="https://meshy.co.uk//posts/debugging-assertnumqueries-tests"><![CDATA[<div class="alert alert-info">
    <strong>Update:</strong>
    Thanks to <a class="alert-link" href="https://twitter.com/dominicrodger/status/412713091877437440">Dominic Rodger</a> this behaviour is now in Django 1.7+.
</div>

<p>Tests with <code class="language-plaintext highlighter-rouge">assertNumQueries</code> failing? Want a way to inspect your SQL queries in your tests? I have an answer.</p>

<p>I expect you have a test that looks something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class GoodTestCase(django.test.TestCase):
    def test_fantastic_stuff(self):
        with self.assertNumQueries(1):
            thing.do_fantastic_stuff()  # Queries happen here.
</code></pre></div></div>

<p>..and unexpectedly you’re getting an error that looks a bit like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FAIL: test_fantastic_stuff (greatproject.tests.GoodTestCase)
------------------------------------------------------
Traceback (most recent call last):
  File "/greatproject/tests.py", line 130, in test_fantastic_stuff
    thing.do_fantastic_stuff()
  File "/path/to/django/test/testcases.py", line 105, in __exit__
    executed, self.num

AssertionError: 2 queries executed, 1 expected
</code></pre></div></div>

<p>Okay, so we know something’s wrong, but we don’t know what. How do we find out what that extra query is? Well, Django can show you the queries run if you just ask it nicely. Lets throw some debug code in the test to print them out…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from django.db import connection

class GoodTestCase(django.test.TestCase):
    def test_fantastic_stuff(self):
        with self.assertNumQueries(1):
            # Get num queries run before.
            so_far = len(connection.queries)
            thing.do_fantastic_stuff()  # Queries happen here.
            for query in connection.queries[so_far:]:
                print(query['sql'])  # Print our queries
</code></pre></div></div>

<p>When you run this, you should get the executed queries printed in your test output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT (1) AS "a" FROM "great_model" WHERE "great_model"."id" = 15  LIMIT 1
UPDATE "great_model" SET "field" = 1 WHERE "great_model"."id" = 15
</code></pre></div></div>

<p>Nice, eh? That should be enough to set you on the path of debugging the issue, but I feel like going a little further (too far?) and making it pretty. Wouldn’t it have been nicer to run the following? (Note the extra context manager.)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class GoodTestCase(django.test.TestCase):
    def test_fantastic_stuff(self):
        with self.assertNumQueries(1), PrintQueries():
            thing.do_fantastic_stuff()  # Queries happen here.
</code></pre></div></div>

<p>I think so too, so here’s my definition of <code class="language-plaintext highlighter-rouge">PrintQueries</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from django.db import connection

class PrintQueries(object):
    def __enter__(self):
        # Get num queries run before.
        self.so_far = len(connection.queries)

    def __exit__(self, type, value, traceback):
        for query in connection.queries[self.so_far:]:
            print(query['sql'])  # Print our queries.
</code></pre></div></div>

<p>I hope you find it useful! <code class="language-plaintext highlighter-rouge">:)</code></p>]]></content><author><name></name></author><summary type="html"><![CDATA[How to see exactly what queries have run in a Django test]]></summary></entry><entry><title type="html">PostgreSQL without passwords</title><link href="https://meshy.co.uk//posts/postgresql-without-passwords" rel="alternate" type="text/html" title="PostgreSQL without passwords" /><published>2013-10-19T00:00:00+00:00</published><updated>2013-10-19T00:00:00+00:00</updated><id>https://meshy.co.uk//posts/postgresql-without-passwords</id><content type="html" xml:base="https://meshy.co.uk//posts/postgresql-without-passwords"><![CDATA[<p>Wouldn’t it be nice if you didn’t need to type out that nasty
<code class="language-plaintext highlighter-rouge">sudo -u postgres pqsl</code> <em>and</em> then enter a password every time you wanted to
use a local development database? Well, if you don’t mind every Tom, Dick and
Harry having a peek at your data, you can get all that “security” balderdash
out from under your feet by granting superuser access to your normal login
user. <code class="language-plaintext highlighter-rouge">:D</code></p>

<ol>
  <li>
    <p>Add the following to your <code class="language-plaintext highlighter-rouge">/etc/postgresql/9.1/main/pg_hba.conf</code> just below
where is says <code class="language-plaintext highlighter-rouge"># Put your actual configuration here</code>.</p>

    <p><strong>Replace <code class="language-plaintext highlighter-rouge">meshy</code> with your login name</strong>. (If you don’t know your
 username you can get it by running <code class="language-plaintext highlighter-rouge">whoami</code> in your terminal.)</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> host    all     meshy       127.0.0.1/32            trust
 local   all     meshy                               trust
 local   all     all                                 ident
</code></pre></div>    </div>
  </li>
  <li>
    <p>Restart postgres</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo -u postgres service postgresql restart
</code></pre></div>    </div>
  </li>
  <li>
    <p>Set up postgres.</p>

    <p>Run postgres:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo -u postgres psql
</code></pre></div>    </div>

    <p>Create superuser and database.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> create user meshy;
 alter role meshy with superuser;
 alter role meshy login;
 create database meshy;
</code></pre></div>    </div>
  </li>
  <li>
    <p>This step is not required, and should <strong>NEVER be used in production</strong>.
 If you are fond of the data in <em>any</em> of your databases using this install
 of postgres <strong>Do Not Use This</strong>.</p>

    <p>That said, if the data doesn’t matter, and your only concern is speed, this
 should sharply increase database creation time and speed up your tests.</p>

    <p>Final warning: <strong><a href="http://managing-geeks.blogspot.co.uk/2009/07/what-happens-when-you-turn-fsync-off-on.html">Do NOT use on machines with important data!</a></strong>
 You have been warned.</p>

    <p>Edit <code class="language-plaintext highlighter-rouge">/etc/postgresql/9.1/main/postgresql.conf</code> with:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # WARNING! This is dangerous!
 # These settings are likely to cause corruption across all databases.
 fsync = off
 synchronous_commit = off
 full_page_writes = off
</code></pre></div>    </div>
  </li>
  <li>
    <p>Profit.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> psql
</code></pre></div>    </div>
  </li>
</ol>

<p>I’m using this to run Django tests on Ubuntu 12.04 and 13.04, but I’m sure this is fit for many other purposes. Have fun, and happy hacking!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How to set up PostgreSQL 9.1 for local user access/tests on Ubuntu Linux]]></summary></entry></feed>