<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>tech.agilitynerd</title><link href="https://tech.agilitynerd.com/" rel="alternate"></link><link href="https://tech.agilitynerd.com/feeds/all-en.atom.xml" rel="self"></link><id>https://tech.agilitynerd.com/</id><updated>2023-01-01T16:00:00-06:00</updated><subtitle>scratching that itch</subtitle><entry><title>Running Django Management Commands Periodically in Docker</title><link href="https://tech.agilitynerd.com/running-django-management-commands-periodically-in-docker.html" rel="alternate"></link><published>2023-01-01T16:00:00-06:00</published><updated>2023-01-01T16:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2023-01-01:/running-django-management-commands-periodically-in-docker.html</id><summary type="html">&lt;p&gt;This past week I updated my &lt;a href="https://agilitycoursemaster.com/"&gt;Agility Course Master&lt;/a&gt; API to &lt;a href="https://www.djangoproject.com"&gt;Django&lt;/a&gt; version 4.1.4 and &lt;a href="https://www.python.org"&gt;Python&lt;/a&gt; version 3.11.
I noticed that I hadn't run some of the database cleanup management commands (i.e. &lt;a href="https://docs.djangoproject.com/en/4.1/ref/django-admin/#clearsessions"&gt;clearsessions&lt;/a&gt; ) that I should have been running periodically.
So I started investigating how to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This past week I updated my &lt;a href="https://agilitycoursemaster.com/"&gt;Agility Course Master&lt;/a&gt; API to &lt;a href="https://www.djangoproject.com"&gt;Django&lt;/a&gt; version 4.1.4 and &lt;a href="https://www.python.org"&gt;Python&lt;/a&gt; version 3.11.
I noticed that I hadn't run some of the database cleanup management commands (i.e. &lt;a href="https://docs.djangoproject.com/en/4.1/ref/django-admin/#clearsessions"&gt;clearsessions&lt;/a&gt; ) that I should have been running periodically.
So I started investigating how to run them automatically.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;First, I started investigating how to run &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;cron&lt;/a&gt;-like jobs in Django.
There are a &lt;a href="https://djangopackages.org/grids/g/cron/"&gt;number of django packages&lt;/a&gt; that support importing/running Django application code in &lt;code&gt;cron&lt;/code&gt; jobs.
In all my Django projects I used &lt;a href="https://django-extensions.readthedocs.io/en/latest/"&gt;django-extensions&lt;/a&gt; which contains a simple &lt;code&gt;Job&lt;/code&gt; model for defining tasks which are then run by its &lt;code&gt;runjobs&lt;/code&gt; management command.
So that was my choice.&lt;/p&gt;
&lt;p&gt;You don't strictly need a separate package, you could just invoke &lt;code&gt;python&lt;/code&gt; in your virtualenv with a script that &lt;code&gt;import&lt;/code&gt;s the necessary packages, connects to the db, etc.
But, these packages/apps take care of that boilerplate for you and you can use familiar Django idioms.&lt;/p&gt;
&lt;p&gt;But, none of these apps/packages can run simultaneously in the same &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; container that serves the Django webapp/API.
So you need to run some sort of &lt;code&gt;cron&lt;/code&gt; process to control running the scripts.&lt;/p&gt;
&lt;p&gt;After much searching, I figured out that I wanted to create and run a new container; based on my Django API image that runs &lt;code&gt;cron&lt;/code&gt; in the foreground.
That way, it has the same configuration and code as my API container and can run any Python code/Django management commands I want.&lt;/p&gt;
&lt;p&gt;Here are some articles I found helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.thesparktree.com/cron-in-docker"&gt;Running Cron in Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devtron.ai/blog/running-a-cronjob-inside-docker-container-in-5-steps/"&gt;Running A Cronjob Inside Docker Container In 5 Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/12534135/crontab-not-executing-a-python-script"&gt;Crontab not executing a python script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/a/69803971/457935"&gt;Sharing an image across multiple containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Details&lt;/h2&gt;
&lt;p&gt;Some of my &lt;code&gt;cron&lt;/code&gt; configuration is specific to the &lt;a href="https://github.com/docker-library/repo-info/blob/master/repos/debian/local/buster-slim.md"&gt;Debian &lt;code&gt;slim-buster&lt;/code&gt;&lt;/a&gt; image upon which I base my Django API container.
But, it should be similar enough to other Linux containers to get you started.&lt;/p&gt;
&lt;p&gt;1. Install &lt;code&gt;cron&lt;/code&gt; in your &lt;code&gt;Dockerfile&lt;/code&gt; if it isn't already part of your Docker image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;RUN apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get -y upgrade &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install --no-install-recommends -y &amp;lt;your packages here&amp;gt; cron
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;2. Install &lt;code&gt;django-extensions&lt;/code&gt; and follow it's instructions for creating your job file(s) that will be run by &lt;code&gt;cron&lt;/code&gt;.
Unlike, their examples of crontab files, Debian uses a different format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# run daily at 7:07 UTC&lt;/span&gt;
&lt;span class="c1"&gt;# has no environment and needs to redirect to stdout so docker can see results&lt;/span&gt;
&lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt; * * * root &lt;span class="nb"&gt;source&lt;/span&gt; /etc/environment&lt;span class="p"&gt;;&lt;/span&gt; /app/manage.py runjobs daily &amp;gt;/proc/1/fd/1 &lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/proc/1/fd/2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I'm running the &lt;code&gt;runjobs daily&lt;/code&gt; management command at 7:07 UTC everyday as &lt;code&gt;root&lt;/code&gt;. Create as many entries/crontab files as you need.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I put the above file named &lt;code&gt;djangocrondaily&lt;/code&gt; in the root of my repo in a &lt;code&gt;crontab&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I found out the hard way that &lt;code&gt;cron&lt;/code&gt; jobs don't inherit any environment. In my containers I don't hard code any values into the &lt;code&gt;settings.py&lt;/code&gt; file, they are all read from the container's environment variables. That's why I had to &lt;code&gt;source /etc/environment&lt;/code&gt; which is populated in the &lt;code&gt;entrypoint.sh&lt;/code&gt; below.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can test running your script with no environment within your Docker container by running &lt;code&gt;env -i /bin/sh scriptname&lt;/code&gt; or &lt;code&gt;env -i /bin/bash scriptname&lt;/code&gt; as appropriate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In order to get the output of the &lt;code&gt;cron&lt;/code&gt; jobs in &lt;code&gt;docker logs&lt;/code&gt; you need to redirect their output to &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3. &lt;code&gt;entrypoint.sh&lt;/code&gt; shared by my Django and &lt;code&gt;cron&lt;/code&gt; containers&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c1"&gt;# store environment variables in a file so cron can source them&lt;/span&gt;
env &amp;gt;&amp;gt; /etc/environment

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Waiting for &lt;/span&gt;&lt;span class="nv"&gt;$SQL_DATABASE&lt;/span&gt;&lt;span class="s2"&gt; database...&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; ! nc -z &lt;span class="nv"&gt;$SQL_HOST&lt;/span&gt; &lt;span class="nv"&gt;$SQL_PORT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  sleep &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$SQL_DATABASE&lt;/span&gt;&lt;span class="s2"&gt; ready!&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# production/staging environments&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gunicorn&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then&lt;/span&gt;
  mkdir -p staticfiles
  python manage.py collectstatic --noinput
  python manage.py migrate
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt; &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;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This writes the environment variables into &lt;code&gt;/etc/environment&lt;/code&gt; at runtime so that file can be &lt;code&gt;source&lt;/code&gt;'d by each crontab entry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The final &lt;code&gt;exec "$@"&lt;/code&gt; executes the &lt;code&gt;CMD&lt;/code&gt; for each of the &lt;code&gt;docker-compose.yml&lt;/code&gt; services.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4. Install &lt;code&gt;crontab&lt;/code&gt; used by &lt;code&gt;cron&lt;/code&gt; in the final build stage of the &lt;code&gt;Dockerfile&lt;/code&gt;&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;FROM python:3.11-slim-bullseye as base&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV PYTHONFAULTHANDLER=1 \&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;PYTHONHASHSEED=random \&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;PYTHONUNBUFFERED=1 \&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;PYTHONDONTWRITEBYTECODE=1&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV VIRTUAL_ENV=/venv&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN python3 -m venv $VIRTUAL_ENV&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV PATH=&amp;quot;$VIRTUAL_ENV/bin:$PATH&amp;quot;&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;WORKDIR /app&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;######################&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;FROM base as builder&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV VIRTUAL_ENV=/venv&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV PATH=&amp;quot;$VIRTUAL_ENV/bin:$PATH&amp;quot;&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENV PIP_DEFAULT_TIMEOUT=100 \&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;PIP_DISABLE_PIP_VERSION_CHECK=1 \&lt;/span&gt;
  &lt;span class="l l-Scalar l-Scalar-Plain"&gt;PIP_NO_CACHE_DIR=1&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y git curl netcat build-essential libpq-dev libjpeg-dev zlib1g-dev libffi-dev&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN curl -sSL https://install.python-poetry.org | python3 -&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;COPY pyproject.toml poetry.lock ./&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# Only non dev dependencies&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN $HOME/.local/bin/poetry install --no-dev&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;#####################&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;FROM base as final&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;COPY --from=builder /venv /venv&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;COPY --from=builder /root/.local /root/.local&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# install runtime dependencies&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get -y upgrade &amp;amp;&amp;amp; apt-get install --no-install-recommends -y libffi-dev libpq-dev libjpeg-dev memcached libmemcached-tools netcat cron&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# copy project&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;COPY . /app/&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# install crontab to run django_extensions runscript - it is only run in the cron container&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;COPY crontab/djangocrondaily /etc/cron.d/djangocrondaily&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RUN chmod 0644 /etc/cron.d/djangocrondaily&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EXPOSE 8000&lt;/span&gt;

&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# run entrypoint.sh&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ENTRYPOINT [&amp;quot;/app/entrypoint.sh&amp;quot;]&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# entrypoint.sh gets CMD as arguments&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;CMD [&amp;quot;gunicorn&amp;quot;, &amp;quot;api.wsgi:app&amp;quot;, &amp;quot;--bind&amp;quot;, &amp;quot;0.0.0.0:8000&amp;quot;, &amp;quot;--log-level=info&amp;quot;, &amp;quot;--access-logfile=-&amp;quot;, &amp;quot;--error-logfile=-&amp;quot;, &amp;quot;--workers=2&amp;quot;, &amp;quot;--threads=4&amp;quot;, &amp;quot;--worker-class=gthread&amp;quot;, &amp;quot;--worker-tmp-dir=/dev/shm&amp;quot;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the default &lt;code&gt;CMD&lt;/code&gt; starts &lt;code&gt;gunicorn&lt;/code&gt; for my production and staging environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;5. Simplified &lt;code&gt;docker-compose&lt;/code&gt; file that starts my &lt;code&gt;django&lt;/code&gt; and &lt;code&gt;cron&lt;/code&gt; docker services:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;3.7&amp;#39;&lt;/span&gt;

&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;django&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;registry.xxx.com/yyy/zzz/api-django&lt;/span&gt;
    &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;.&lt;/span&gt;
      &lt;span class="nt"&gt;dockerfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Dockerfile.prod&lt;/span&gt;
    &lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;api-django&lt;/span&gt;
    &lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;8000&lt;/span&gt;
    &lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;./env.prod&lt;/span&gt;
    &lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;db&lt;/span&gt;

  &lt;span class="nt"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;registry.xxx.com/yyy/zzz/api-django&lt;/span&gt;
    &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;.&lt;/span&gt;
      &lt;span class="nt"&gt;dockerfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Dockerfile.prod&lt;/span&gt;
    &lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;api-cron&lt;/span&gt;
    &lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;./env.prod&lt;/span&gt;
    &lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;cron -f -L 15&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;both services use the same &lt;code&gt;dockerfile&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;, and &lt;code&gt;env_files&lt;/code&gt; settings.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;cron&lt;/code&gt; service overrides the &lt;code&gt;CMD&lt;/code&gt;/&lt;code&gt;command&lt;/code&gt; to run &lt;code&gt;cron&lt;/code&gt; in the foreground instead of the default &lt;code&gt;gunicorn&lt;/code&gt; process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope you find this helpful if you need to run &lt;code&gt;cron&lt;/code&gt; to execute periodic Django tasks.&lt;/p&gt;</content><category term="devops"></category><category term="django"></category><category term="webdevelopment"></category><category term="docker"></category><category term="python"></category></entry><entry><title>Building/Uploading Source Maps and Triggering a Deploy in Angular 11 to Rollbar</title><link href="https://tech.agilitynerd.com/buildinguploading-source-maps-and-triggering-a-deploy-in-angular-11-to-rollbar.html" rel="alternate"></link><published>2021-02-07T13:00:00-06:00</published><updated>2021-02-07T13:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2021-02-07:/buildinguploading-source-maps-and-triggering-a-deploy-in-angular-11-to-rollbar.html</id><summary type="html">&lt;p&gt;I recently updated my &lt;a href="https://agilitycoursemaster.com/"&gt;Agility Course Master&lt;/a&gt; Ionic PWA to Angular 11 and wanted to fully automate generating and uploading of source map files to &lt;a href="https://rollbar.com"&gt;Rollbar&lt;/a&gt;.
I found an existing project that almost did everything I needed: &lt;a href="https://github.com/lucasklaassen/angular-rollbar-source-maps"&gt;angular-rollbar-source-maps&lt;/a&gt;.
It was missing triggering Rollbar's &lt;code&gt;deploy&lt;/code&gt; endpoint to indicate that a new …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently updated my &lt;a href="https://agilitycoursemaster.com/"&gt;Agility Course Master&lt;/a&gt; Ionic PWA to Angular 11 and wanted to fully automate generating and uploading of source map files to &lt;a href="https://rollbar.com"&gt;Rollbar&lt;/a&gt;.
I found an existing project that almost did everything I needed: &lt;a href="https://github.com/lucasklaassen/angular-rollbar-source-maps"&gt;angular-rollbar-source-maps&lt;/a&gt;.
It was missing triggering Rollbar's &lt;code&gt;deploy&lt;/code&gt; endpoint to indicate that a new release had been deployed.&lt;/p&gt;
&lt;p&gt;So I took a little time to add that feature and I also changed the scripts so only &lt;code&gt;node&lt;/code&gt; was needed to generate the Git SHA revision and to perform the upload/deploy HTTP POSTs to Rollbar.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;During the build the file &lt;code&gt;src/environments/version.ts&lt;/code&gt; is automatically generated containing the definition of the current Git revision:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;automatically&lt;/span&gt; &lt;span class="n"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;12f54f9&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Import that &lt;code&gt;version.ts&lt;/code&gt; file into the file where you create your &lt;code&gt;Rollbar.Configuration&lt;/code&gt; and use it to set your &lt;code&gt;code_version&lt;/code&gt; Rollbar parameter.&lt;/p&gt;
&lt;p&gt;The same version is used when uploading the source maps and informing Rollbar of the deploy.&lt;/p&gt;
&lt;p&gt;The entire process is initiated by a single command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run prod&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;code&gt;package.json&lt;/code&gt; and add the &lt;code&gt;prod&lt;/code&gt; target line to the &lt;code&gt;scripts&lt;/code&gt; section. This generates and uploads the source maps and then deletes them from the &lt;code&gt;www&lt;/code&gt; directory so they aren't uploaded to your site.&lt;/p&gt;
&lt;div class="gist"&gt;
    &lt;script src='https://gist.github.com/1debf7be659005d8226a0db78b88476c.js?file=package.json'&gt;&lt;/script&gt;
    &lt;noscript&gt;
        &lt;pre&gt;&lt;code&gt;{
  ... 
  "scripts": {
    "start": "ng serve",
    "build": "ng build",
    ... ADD:
    "prod": "node git-version.js &amp;&amp; ng build --prod --source-map &amp;&amp; node rollbar-deploy.js"
  },
...
}&lt;/code&gt;&lt;/pre&gt;
    &lt;/noscript&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file with values populated for your site (you probably want to add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file also):&lt;/p&gt;
&lt;div class="gist"&gt;
    &lt;script src='https://gist.github.com/1debf7be659005d8226a0db78b88476c.js?file=.env'&gt;&lt;/script&gt;
    &lt;noscript&gt;
        &lt;pre&gt;&lt;code&gt;ROLLBAR_WRITE_ACCESS_TOKEN=
MAIN_APP_URL='https://...'
ROLLBAR_USERNAME=
&lt;/code&gt;&lt;/pre&gt;
    &lt;/noscript&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;npm install dotenv&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy &lt;code&gt;git-version.js&lt;/code&gt; and &lt;code&gt;rollbar-deploy.js&lt;/code&gt; (below) into the same directory as your &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;div class="gist"&gt;
    &lt;script src='https://gist.github.com/1debf7be659005d8226a0db78b88476c.js?file=git-version.js'&gt;&lt;/script&gt;
    &lt;noscript&gt;
        &lt;pre&gt;&lt;code&gt;const fs = require('fs');

const commitHash = require('child_process')
  .execSync('git rev-parse --short HEAD')
  .toString()
  .trim();

const content =
  '// this file is automatically generated by git-version.js script\n' +
  `export const version = {revision: '${commitHash}'};`;
fs.writeFileSync('./src/environments/version.ts', content, {
  encoding: 'utf8',
});
&lt;/code&gt;&lt;/pre&gt;
    &lt;/noscript&gt;
&lt;/div&gt;
&lt;div class="gist"&gt;
    &lt;script src='https://gist.github.com/1debf7be659005d8226a0db78b88476c.js?file=rollbar-deploy.js'&gt;&lt;/script&gt;
    &lt;noscript&gt;
        &lt;pre&gt;&lt;code&gt;require('dotenv').config();

const childProcess = require('child_process');
const fs = require('fs');
const process = require('process');
const request = require('request');

const sendFile = function (file) {
  request(
    {
      url: 'https://api.rollbar.com/api/1/sourcemap',
      headers: {
        'content-type': 'multipart/form-data',
      },
      method: 'POST',
      multipart: [
        {
          'Content-Disposition': 'form-data; name="access_token"',
          body: process.env.ROLLBAR_WRITE_ACCESS_TOKEN,
        },
        {
          'Content-Disposition': 'form-data; name="version"',
          // The version number cannot contain any whitespace or it will break
          body: commitHash,
        },
        {
          'Content-Disposition': 'form-data; name="minified_url"',
          body: `${process.env.MAIN_APP_URL}/${file}`,
        },
        {
          'Content-Disposition': `form-data; name="source_map"; filename="${file}.map"`,
          body: fs.readFileSync(`./www/${file}.map`),
        },
      ],
    },
    function (err, response, body) {
      if (err) {
        console.log('Could not send file', err);
      } else {
        console.log(body);
      }
    }
  );
};

const cleanUpMapFile = function (file) {
  fs.unlink(`./www/${file}.map`, function (err) {
    if (err &amp;&amp; err.code == 'ENOENT') {
      console.info("File doesn't exist, won't remove it.");
    } else if (err) {
      console.error('Error occurred while trying to remove file');
    } else {
      console.info(`Removed ./www/${file}.map`);
    }
  });
};

const commitHash = childProcess
  .execSync('git rev-parse --short HEAD')
  .toString()
  .trim();

fs.readdir('./www/', function (err, files) {
  if (err) {
    console.error('Could not list the directory.', err);
    process.exit(1);
  }
  files.forEach(function (file, index) {
    if (file.endsWith('.js') &amp;&amp; fs.existsSync(`./www/${file}.map`)) {
      sendFile(file);
      cleanUpMapFile(file);
    }
  });
});

request(
  {
    url: 'https://api.rollbar.com/api/1/deploy',
    headers: {
      'X-Rollbar-Access-Token': process.env.ROLLBAR_WRITE_ACCESS_TOKEN,
    },
    method: 'POST',
    json: {
      environment: 'prod',
      revision: commitHash,
      rollbar_username: process.env.ROLLBAR_USERNAME,
    },
  },
  function (err, response, body) {
    if (err) {
      console.log('Could not deploy new version ', err);
    } else {
      console.log(body);
    }
  }
);
&lt;/code&gt;&lt;/pre&gt;
    &lt;/noscript&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;code&gt;rollbar.ts&lt;/code&gt; to import the &lt;code&gt;environment/version.ts&lt;/code&gt; file and set the &lt;code&gt;version&lt;/code&gt; in the Rollbar configuration.&lt;/p&gt;
&lt;div class="gist"&gt;
    &lt;script src='https://gist.github.com/1debf7be659005d8226a0db78b88476c.js?file=rollbar.ts'&gt;&lt;/script&gt;
    &lt;noscript&gt;
        &lt;pre&gt;&lt;code&gt;import * as Rollbar from 'rollbar';
import {
  Injectable,
  Inject,
  InjectionToken,
  ErrorHandler,
} from '@angular/core';
import { environment } from '../environments/environment';
import { version } from '../environments/version';  // &lt;== ADD

const rollbarConfig: Rollbar.Configuration = {
  accessToken: environment.rollbarAccessToken,
  captureUncaught: true,
  captureUnhandledRejections: true,
  nodeSourceMaps: false,
  inspectAnonymousErrors: true,
  ignoreDuplicateErrors: true,
  wrapGlobalEventHandlers: false,
  scrubRequestBody: true,
  exitOnUncaughtException: false,
  stackTraceLimit: 20,
  enabled: environment.env !== 'dev',
  environment: environment.env,
  },
  payload: {
    environment: environment.env,
    client: {
      javascript: {
        code_version: version,  // &lt;== ADD
        // Optionally have Rollbar guess which frames the error was thrown from
        // when the browser does not provide line and column numbers.
        guess_uncaught_frames: true,
      },
...&lt;/code&gt;&lt;/pre&gt;
    &lt;/noscript&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Building and Deploying&lt;/h3&gt;
&lt;p&gt;When you are ready to build/deploy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;npm run prod
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once the script is done the &lt;code&gt;www&lt;/code&gt; directory contains only the artifacts you need to deploy your site (no JS source map files).
In my case I deploy to firebase via:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;firebase deploy&lt;/code&gt;&lt;/p&gt;</content><category term="webdev"></category><category term="javascript"></category><category term="rollbar"></category><category term="angular"></category></entry><entry><title>Lightning Talk: StorybookJS Streamlines UI Component Development</title><link href="https://tech.agilitynerd.com/lightning-talk-storybookjs-streamlines-ui-component-development.html" rel="alternate"></link><published>2020-10-24T20:00:00-05:00</published><updated>2020-10-24T20:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2020-10-24:/lightning-talk-storybookjs-streamlines-ui-component-development.html</id><summary type="html">&lt;p&gt;I became interested in &lt;a href="https://storybook.js.org/"&gt;Storybook.js&lt;/a&gt; when I was creating a new UI component and wanted a way to quickly iterate on the design and be able to demo the different ways it could be used to my team members.&lt;/p&gt;
&lt;p&gt;In the past I would temporarily add a page to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I became interested in &lt;a href="https://storybook.js.org/"&gt;Storybook.js&lt;/a&gt; when I was creating a new UI component and wanted a way to quickly iterate on the design and be able to demo the different ways it could be used to my team members.&lt;/p&gt;
&lt;p&gt;In the past I would temporarily add a page to our application, add multiple versions of the component, configure them for each variation, and then demo it.
Then repeat as we refined the design.
The temporary page would never be committed to the product's main branch.&lt;/p&gt;
&lt;p&gt;Storybook had recently released version 6 so I thought I'd give it a try.
I was &lt;em&gt;really impressed&lt;/em&gt; with how easy it was to use for my scenario.&lt;/p&gt;
&lt;p&gt;You define your component as you normally would (in &lt;a href="https://vuejs.org/"&gt;VueJS&lt;/a&gt; in my case) and import it into a "story file".
A story file is just a JS (or Typescript file) where you define the properties for your component for any variations (use cases) in which you are interested.
Storybook then reads that file and renders pages containing the component for each story.&lt;/p&gt;
&lt;p&gt;I was able to quickly modify my component, interact with it, style it, create new stories for different property configurations, and Storybook re-displayed them on each save.
It made for a fast and easy development cycle!&lt;/p&gt;
&lt;p&gt;TL;DR: These are the key takeaways for using Storybook when developing web components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installs along side your components/project.&lt;/li&gt;
&lt;li&gt;Watches your component and story files for changes and automatically hot reloads.&lt;/li&gt;
&lt;li&gt;Story files define your components' expected use cases.&lt;/li&gt;
&lt;li&gt;The Storybook UI also allows for interactive modification of properties in the browser.&lt;/li&gt;
&lt;li&gt;The Storybook UI provides basic documentation for users of your components.&lt;/li&gt;
&lt;li&gt;Individual stories can be directly imported into spec files &lt;strong&gt;unit and visual regression tests&lt;/strong&gt;. This is a valuable Storybook feature that is often overlooked!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was so impressed by Storybook I put together a "lightning talk" on using it with VueJS for the &lt;a href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript meetup&lt;/a&gt; September 2020 virtual meeting.
Check it out to see how easy and powerful Storybook is to use.
Here's the video of my presentation (unfortunately, the first few minutes didn't get recorded):&lt;/p&gt;
&lt;div class="embed-video"&gt;
&lt;iframe src="https://www.youtube.com/embed/ku-62XlU8yk" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;If you'd like to see my &lt;a href="https://nwcjs-vue2-storybook.netlify.app/#1"&gt;slides they are online&lt;/a&gt;. Use arrow keys/spacebar to navigate them.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github.com/saschwarz/vue2-storybook"&gt;example repo is on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you develop web components/controls give Storybook.js a try to simplify component development and test, provide an interactive component browser, and component documentation.&lt;/p&gt;</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="vuejs"></category></entry><entry><title>Moving the AgilityNerd.com Blog to GatsbyJS</title><link href="https://tech.agilitynerd.com/moving-the-agilitynerdcom-blog-to-gatsbyjs.html" rel="alternate"></link><published>2019-11-28T08:00:00-06:00</published><updated>2019-11-28T08:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2019-11-28:/moving-the-agilitynerdcom-blog-to-gatsbyjs.html</id><summary type="html">&lt;p&gt;As a software developer, one of the ways I learn about new web technology is by attending the monthly &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript meetup&lt;/a&gt;.
It's a supportive and enthusiastic group of folks interested in continuing to learn more about software development in JavaScript.
Every month there are one or more informal …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As a software developer, one of the ways I learn about new web technology is by attending the monthly &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript meetup&lt;/a&gt;.
It's a supportive and enthusiastic group of folks interested in continuing to learn more about software development in JavaScript.
Every month there are one or more informal presentations by members of the group with questions and discussions.&lt;/p&gt;
&lt;p&gt;This past week I gave a talk titled: &amp;quot;Moving the AgilityNerd Blog to GatsbyJS&amp;quot;.
I started my dog agility blog &lt;a class="reference external" href="https://agilitynerd.com"&gt;AgilityNerd&lt;/a&gt; in 2004 using the now defunct, Perl-based, flat file CGI site generator, &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;blosxom&lt;/a&gt;.
Over the years I wrote, enhanced, and fixed a number of plugins to the Blosxom project.
Though it worked well, technology has long since moved passed Blosxom's humble beginnings.&lt;/p&gt;
&lt;p&gt;The costly hosting requirements for Blosxom at the AgilityNerd's site size (with over 800 source pages generating 1,500 pages dynamically), authoring in HTML, lack of preview, and enhancing a nearly 20 year old code base became so onerous that I decided to extract the content and move to a more modern static site generator.&lt;/p&gt;
&lt;p&gt;This site, tech.agilitynerd.com, uses the Python powered &lt;a class="reference external" href="https://blog.getpelican.com/"&gt;Pelican static generator&lt;/a&gt; and deploys to GitHub pages.
It is simple to author, deploy, and free to host.
But I was looking for a static site generator that would make it easy to integrate custom web components, provide image processing, dynamic image/content loading, and integrate 3rd party plugins like: &lt;a class="reference external" href="https://analytics.google.com/"&gt;Google Analytics&lt;/a&gt;, &lt;a class="reference external" href="https://rollbar.com/"&gt;Rollbar&lt;/a&gt; error tracking, and advanced search (&lt;a class="reference external" href="https://www.algolia.com/"&gt;Algolia&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;My search boiled down to React-based &lt;a class="reference external" href="https://www.gatsbyjs.org/"&gt;GatsbyJS&lt;/a&gt; and Vue-based &lt;a class="reference external" href="https://gridsome.org/"&gt;Gridsome&lt;/a&gt;.
I've been using Vue daily at work for almost two years, but Gridssome isn't as far along in its development as Gatsby.
It has been a while since I did any serious React development so I decided to go with the more popular GatsbyJS as a reason to get my skills on React more up to date.&lt;/p&gt;
&lt;p&gt;Gatsby's offering an easy &amp;quot;GitOps&amp;quot; continuous deployment model hosted (for free) on &lt;a class="reference external" href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; was just icing on the cake.&lt;/p&gt;
&lt;p&gt;Here's &lt;a class="reference external" href="https://nwcjs-gatsby.netlify.com/"&gt;a link to my slides&lt;/a&gt;.
Interestingly, the slides are written using &lt;a class="reference external" href="https://mdx-deck.jxnblk.com/"&gt;MDX Deck&lt;/a&gt; which is itself built on top of Gatsby.
Of course I used that functionality to deploy the slide deck to Netlify too.&lt;/p&gt;
&lt;p&gt;Here's the video of my presentation which goes into a lot more detail on my reasoning for choosing Gatsby, setting it up, how it works, how plugins work, and deploying a Gatsby site:&lt;/p&gt;
&lt;div class="embed-video"&gt;
&lt;iframe src="https://www.youtube.com/embed/bL_QNj_AdWE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;p&gt;Since this presentation, I've started using Gatsby Cloud's build service to get faster builds and then have it upload the assets to Netlify for hosting.&lt;/p&gt;
</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="sideprojects"></category><category term="gatsbyjs"></category></entry><entry><title>Disabling Rollbar and Enabling Logging During Vue.js Development</title><link href="https://tech.agilitynerd.com/disabling-rollbar-and-enabling-logging-during-vuejs-development.html" rel="alternate"></link><published>2019-03-30T08:00:00-05:00</published><updated>2019-03-30T08:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2019-03-30:/disabling-rollbar-and-enabling-logging-during-vuejs-development.html</id><summary type="html">&lt;p&gt;I was adding &lt;a class="reference external" href="https://rollbar.com"&gt;Rollbar&lt;/a&gt; support to a &lt;a class="reference external" href="https://vuejs.org"&gt;Vue.js&lt;/a&gt; application and ran into an issue that made it inconvenient for use in development environments:&lt;/p&gt;
&lt;p&gt;Captured exceptions and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;rollbar.error|warning|log|...&lt;/span&gt;&lt;/tt&gt; calls are always sent and logged in the Rollbar dashboard.&lt;/p&gt;
&lt;p&gt;You could configure different a Rollbar &lt;tt class="docutils literal"&gt;environment&lt;/tt&gt; for development …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was adding &lt;a class="reference external" href="https://rollbar.com"&gt;Rollbar&lt;/a&gt; support to a &lt;a class="reference external" href="https://vuejs.org"&gt;Vue.js&lt;/a&gt; application and ran into an issue that made it inconvenient for use in development environments:&lt;/p&gt;
&lt;p&gt;Captured exceptions and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;rollbar.error|warning|log|...&lt;/span&gt;&lt;/tt&gt; calls are always sent and logged in the Rollbar dashboard.&lt;/p&gt;
&lt;p&gt;You could configure different a Rollbar &lt;tt class="docutils literal"&gt;environment&lt;/tt&gt; for development and filter the dashboard; but I'd rather not even send &amp;quot;developer-induced&amp;quot; errors to Rollbar at all.
After speaking with support they don't yet have a &amp;quot;log locally and don't send to Rollbar&amp;quot; configuration.&lt;/p&gt;
&lt;p&gt;So I came up with this simple Vue plugin that is only installed during development builds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;development&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Rollbar&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// I only call $rollbar.error in the app&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
    &lt;span class="c1"&gt;// define other rollbar API mocks here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RollbarMockPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rollbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Rollbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$rollbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rollbar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RollbarMockPlugin&lt;/span&gt;&lt;span class="p"&gt;)&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="c1"&gt;// Your normal Rollbar configuration&lt;/span&gt;
  &lt;span class="c1"&gt;// I set some env vars in the build script&lt;/span&gt;
  &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Rollbar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VUE_APP_ROLLBAR_POST_CLIENT_ITEM_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;captureUncaught&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="nx"&gt;captureUnhandledRejections&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="nx"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// dashboard filtering&lt;/span&gt;
    &lt;span class="nx"&gt;verbose&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="c1"&gt;// also log to console&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;guess_uncaught_frames&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="nx"&gt;source_map_enabled&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="nx"&gt;code_version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VUE_APP_VERSION&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&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;So when I have code like this in the app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$rollbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifySaveFailed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The call to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;this.$rollbar.error(e)&lt;/span&gt;&lt;/tt&gt; now logs &lt;strong&gt;only&lt;/strong&gt; to the dev console when running in development.&lt;/p&gt;
&lt;p&gt;So I hope this little shim helps you keep your Rollbar dashboard free from development environment clutter.&lt;/p&gt;
</content><category term="javascript"></category><category term="javascript"></category><category term="vuejs"></category><category term="rollbar"></category></entry><entry><title>Build In Customer Support to Your Mobile/Web App/PWA</title><link href="https://tech.agilitynerd.com/build-in-customer-support-to-your-mobileweb-apppwa.html" rel="alternate"></link><published>2018-10-28T08:00:00-05:00</published><updated>2018-10-28T08:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2018-10-28:/build-in-customer-support-to-your-mobileweb-apppwa.html</id><summary type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1JgU1WHvVzlit0eM0a4D0Jy6dyfoOPwa5tUkPhfcIS78/edit?usp=sharing"&gt;slides on Build in Customer Support: Simplify support of your mobile/web applications&lt;/a&gt;
for a &amp;quot;lightning talk&amp;quot; that I gave at the October &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/BuildInCustomerSupport.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I've been a developer for a while and have worked in organizations who have been focused on the customer (really …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1JgU1WHvVzlit0eM0a4D0Jy6dyfoOPwa5tUkPhfcIS78/edit?usp=sharing"&gt;slides on Build in Customer Support: Simplify support of your mobile/web applications&lt;/a&gt;
for a &amp;quot;lightning talk&amp;quot; that I gave at the October &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/BuildInCustomerSupport.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I've been a developer for a while and have worked in organizations who have been focused on the customer (really!).
So I've always been interested in helping support folk and developers build and improve the product.
There is a lot of discussion on design to help products meet customer needs and improve usability/interaction.
But I hadn't found any discussions on how to build your product so you can provide your customers with better support.&lt;/p&gt;
&lt;p&gt;So I put together this talk on what I've learned.
I focus on tools/techniques for web/mobile apps and support for small groups/single developers.
Here's the outline:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Overview support options and choose channels that fit your customers.&lt;/li&gt;
&lt;li&gt;Demonstrate some tools for those channels that are helpful for startups/side projects.&lt;/li&gt;
&lt;li&gt;Can you avoid support?&lt;/li&gt;
&lt;li&gt;Find/fix bugs before they are detected by customers.&lt;/li&gt;
&lt;li&gt;Acting as the customer&lt;/li&gt;
&lt;li&gt;Reproducing bugs&lt;/li&gt;
&lt;li&gt;Acting on and Documenting issues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is also video of my talk:&lt;/p&gt;
&lt;div class="embed-video"&gt;
    &lt;iframe src="https://www.youtube.com/embed/kZkkUAQ7FSs" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="mobile"></category><category term="sideprojects"></category></entry><entry><title>Cypress E2E Testing Overview Presentation</title><link href="https://tech.agilitynerd.com/cypress-e2e-testing-overview-presentation.html" rel="alternate"></link><published>2018-06-21T08:00:00-05:00</published><updated>2018-06-21T08:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2018-06-21:/cypress-e2e-testing-overview-presentation.html</id><summary type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1wMR3n8WJAw7VeibSkosdXA2VRjtKNpDD4wRqOPKQElQ/edit?usp=sharing"&gt;slides on Cypress E2E Testing&lt;/a&gt;
for a talk that I gave at the June &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/CypressE2ETesting.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I first tried &lt;a class="reference external" href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt; when testing my &lt;a class="reference external" href="https://agilitycoursemaster.com"&gt;Agility Course Master&lt;/a&gt; hybrid mobile (&lt;a class="reference external" href="https://ionicframework.com/"&gt;Ionic&lt;/a&gt;) application/website.
I was really impressed with how easy it was to install and use.
The …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1wMR3n8WJAw7VeibSkosdXA2VRjtKNpDD4wRqOPKQElQ/edit?usp=sharing"&gt;slides on Cypress E2E Testing&lt;/a&gt;
for a talk that I gave at the June &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/CypressE2ETesting.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I first tried &lt;a class="reference external" href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt; when testing my &lt;a class="reference external" href="https://agilitycoursemaster.com"&gt;Agility Course Master&lt;/a&gt; hybrid mobile (&lt;a class="reference external" href="https://ionicframework.com/"&gt;Ionic&lt;/a&gt;) application/website.
I was really impressed with how easy it was to install and use.
The thorough and well written documentation together with their online support further &amp;quot;sold&amp;quot; me on it for testing.&lt;/p&gt;
&lt;p&gt;So I put together this talk on what I've learned. Here is video of my talk:&lt;/p&gt;
&lt;div class="embed-video"&gt;
   &lt;iframe src="https://www.youtube.com/embed/LFUm9qV_Gjo" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="mobile"></category><category term="sideprojects"></category><category term="testing"></category></entry><entry><title>PouchDB and Couchbase Replication for the Offline-First Web</title><link href="https://tech.agilitynerd.com/pouchdb-and-couchbase-replication-for-the-offline-first-web.html" rel="alternate"></link><published>2017-09-21T08:00:00-05:00</published><updated>2017-09-21T08:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2017-09-21:/pouchdb-and-couchbase-replication-for-the-offline-first-web.html</id><summary type="html">&lt;p&gt;I gave a short talk on 'PouchDB and Couchbase Replication for the Offline-First Web' at the September &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;p&gt;It builds on Peter Mbanugo's blog post:
&lt;a class="reference external" href="https://www.codementor.io/pmbanugo/using-pouchdb-and-couchbase-in-an-offline-first-application-5pw2sxs6o"&gt;Using PouchDB and Couchbase in an Offline-First Application&lt;/a&gt;
by using the &lt;a class="reference external" href="https://pouchdb.com/"&gt;PouchDB API&lt;/a&gt; in place of the &lt;a class="reference external" href="http://hood.ie/"&gt;Hoodie API&lt;/a&gt; Peter used.&lt;/p&gt;
&lt;p&gt;My …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I gave a short talk on 'PouchDB and Couchbase Replication for the Offline-First Web' at the September &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;p&gt;It builds on Peter Mbanugo's blog post:
&lt;a class="reference external" href="https://www.codementor.io/pmbanugo/using-pouchdb-and-couchbase-in-an-offline-first-application-5pw2sxs6o"&gt;Using PouchDB and Couchbase in an Offline-First Application&lt;/a&gt;
by using the &lt;a class="reference external" href="https://pouchdb.com/"&gt;PouchDB API&lt;/a&gt; in place of the &lt;a class="reference external" href="http://hood.ie/"&gt;Hoodie API&lt;/a&gt; Peter used.&lt;/p&gt;
&lt;p&gt;My source code is in this &lt;a class="reference external" href="https://github.com/saschwarz/couchbase-phonebook"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a video of my talk:&lt;/p&gt;
&lt;div class="embed-video"&gt;
    &lt;iframe src="https://www.youtube.com/embed/ZQ8YzXo0dWQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="mobile"></category><category term="pouchdb"></category><category term="nosql"></category><category term="couchbase"></category><category term="couchdb"></category><category term="offline"></category></entry><entry><title>Using the Ionic Framework for Mobile and Web Development</title><link href="https://tech.agilitynerd.com/using-the-ionic-framework-for-mobile-and-web-development.html" rel="alternate"></link><published>2017-07-20T08:00:00-05:00</published><updated>2017-07-20T08:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2017-07-20:/using-the-ionic-framework-for-mobile-and-web-development.html</id><summary type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1OmABc2JcqsPP3i6Q2EfYat-F6GKqGHjiUE2ywPcB09Q/edit?usp=sharing"&gt;slides on Using the Ionic Framework for Mobile and Web Development&lt;/a&gt;
for a talk that I gave at the July &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/UsingIonicMobileWeb.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I've been using &lt;a class="reference external" href="https://https://ionicframework.com"&gt;Ionic&lt;/a&gt; since early 2017.
I was drawn to it because it allowed a single code base to create native applications …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://docs.google.com/presentation/d/1OmABc2JcqsPP3i6Q2EfYat-F6GKqGHjiUE2ywPcB09Q/edit?usp=sharing"&gt;slides on Using the Ionic Framework for Mobile and Web Development&lt;/a&gt;
for a talk that I gave at the July &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/UsingIonicMobileWeb.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I've been using &lt;a class="reference external" href="https://https://ionicframework.com"&gt;Ionic&lt;/a&gt; since early 2017.
I was drawn to it because it allowed a single code base to create native applications for both iOS and Android (with &lt;a class="reference external" href="https://cordova.apache.org/"&gt;Cordova&lt;/a&gt; providing the abstraction on top of the mobile platforms).
Ionic provides a set of platform styled widgets, integration and tooling to make it work together easily.
Their use of Typescript/Angular also meant I could create a purely web version of the application also using the same code.&lt;/p&gt;
&lt;p&gt;Here is a video of my talk:&lt;/p&gt;
&lt;div class="embed-video"&gt;
    &lt;iframe src="https://www.youtube.com/embed/fT8S_WuqcnQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</content><category term="webdev"></category><category term="talks"></category><category term="javascript"></category><category term="video"></category><category term="mobile"></category><category term="sideprojects"></category><category term="ionic"></category><category term="typescript"></category><category term="angular"></category></entry><entry><title>Django Shrink The Web django-stw 1.0 Released</title><link href="https://tech.agilitynerd.com/django-shrink-the-web-10-released.html" rel="alternate"></link><published>2017-01-10T13:00:00-06:00</published><updated>2017-01-10T13:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2017-01-10:/django-shrink-the-web-10-released.html</id><summary type="html">&lt;p&gt;It has been several years since I've worked on my &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; driven website that uses &lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; (STW) screen shots.
During that time STW has simplified their feature set and their API.&lt;/p&gt;
&lt;p&gt;As I upgraded my site (&lt;a class="reference external" href="https://www.googility.com/"&gt;Googility.com&lt;/a&gt;) I took
some time to modernize the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-stw&lt;/span&gt;&lt;/tt&gt; template tag …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It has been several years since I've worked on my &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; driven website that uses &lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; (STW) screen shots.
During that time STW has simplified their feature set and their API.&lt;/p&gt;
&lt;p&gt;As I upgraded my site (&lt;a class="reference external" href="https://www.googility.com/"&gt;Googility.com&lt;/a&gt;) I took
some time to modernize the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-stw&lt;/span&gt;&lt;/tt&gt; template tag and support API changes for the latest Django and Python versions.&lt;/p&gt;
&lt;p&gt;I also tested the package when deployed using HTTP and HTTPS.&lt;/p&gt;
&lt;p&gt;Here are the changes (from the &lt;a class="reference external" href="https://github.com/saschwarz/django-stw/blob/master/CHANGELOG.txt"&gt;CHANGELOG.txt&lt;/a&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
1.0.1 Use https for all requests to shrinktheweb.com
- Stops browser warnings when used on https websites while not interferring with sites running on http.

1.0.0 Updates for Django 1.9/1.10 and Python 2.7/3.5
- Old versions of Django/Python are no longer supported.
- Removed ``shrinkthewebimage`` template tag since there is no longer an API distinction between free and Pro accounts.
&lt;/pre&gt;
&lt;p&gt;You can the template tag in action on this &lt;a class="reference external" href="https://www.googility.com/django-stw/"&gt;demo page&lt;/a&gt; in a nicer &lt;a class="reference external" href="http://getbootstrap.com/"&gt;&amp;quot;Bootstrap-ified&amp;quot;&lt;/a&gt; UI:&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/DjangoSTWDemoScreenshot.png" /&gt;
&lt;/div&gt;
&lt;p&gt;The latest package is &lt;a class="reference external" href="http://pypi.python.org/pypi?:action=display&amp;amp;name=django-stw"&gt;available on PyPi&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stw&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Or as a &lt;a class="reference external" href="https://github.com/saschwarz/django-stw/releases"&gt;source download on github&lt;/a&gt;, or via &lt;a class="reference external" href="https://github.com/saschwarz/django-stw"&gt;git clone&lt;/a&gt;.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="shrinktheweb"></category><category term="python"></category><category term="images"></category><category term="bootstrap"></category></entry><entry><title>Using Gestures in Angular 2 with TypeScript Lightning Talk</title><link href="https://tech.agilitynerd.com/using-gestures-in-angular-2-with-typescript-lightning-talk.html" rel="alternate"></link><published>2016-12-14T14:00:00-06:00</published><updated>2016-12-14T14:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-12-14:/using-gestures-in-angular-2-with-typescript-lightning-talk.html</id><summary type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://saschwarz.github.io/angular2-gestures-slides/#/"&gt;slides on Using Gestures in Angular 2 with TypeScript&lt;/a&gt;
for a lightning talk that I gave at the December &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/GesturesAngularTypeScriptSlides.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I converted the &lt;a class="reference external" href="http://lab.hakim.se/reveal-js/#/"&gt;Reveal.js&lt;/a&gt; slides checkout to use the
&lt;a class="reference external" href="https://cli.angular.io/"&gt;Angular 2 CLI&lt;/a&gt;. That let me include two small Angular 2 components within the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are my &lt;a class="reference external" href="https://saschwarz.github.io/angular2-gestures-slides/#/"&gt;slides on Using Gestures in Angular 2 with TypeScript&lt;/a&gt;
for a lightning talk that I gave at the December &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup.&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of first slide of my presentation" src="https://tech.agilitynerd.com/images/GesturesAngularTypeScriptSlides.png" /&gt;
&lt;/div&gt;
&lt;p&gt;I converted the &lt;a class="reference external" href="http://lab.hakim.se/reveal-js/#/"&gt;Reveal.js&lt;/a&gt; slides checkout to use the
&lt;a class="reference external" href="https://cli.angular.io/"&gt;Angular 2 CLI&lt;/a&gt;. That let me include two small Angular 2 components within the slides themselves.
Which also allowed me to demonstrate using the &lt;tt class="docutils literal"&gt;pan&lt;/tt&gt; gesture without leaving the presentation. The Angular CLI also made it easy to
generate and publish the presentation to the GitHub pages associated with the slides via &lt;tt class="docutils literal"&gt;ng &lt;span class="pre"&gt;github-pages:deploy&lt;/span&gt;&lt;/tt&gt;.
Here's the &lt;a class="reference external" href="https://github.com/saschwarz/angular2-gestures-slides"&gt;source code for the slides/components&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have more examples of using gestures in the larger demo app I showed at the beginning of the talk:
&lt;a class="reference external" href="https://github.com/saschwarz/angular2-image-crop"&gt;Angular 2 TypeScript Virtual Image Cropping repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is the video of my presentation:&lt;/p&gt;
&lt;div class="embed-video"&gt;
    &lt;iframe src="https://www.youtube.com/embed/Je2r1pawZ3M" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</content><category term="javascript"></category><category term="angular"></category><category term="typescript"></category><category term="talks"></category><category term="gestures"></category><category term="javascript"></category><category term="video"></category></entry><entry><title>Maximize and Minimize Code Blocks in Reveal.js Slide Shows</title><link href="https://tech.agilitynerd.com/maximize-and-minimize-code-blocks-in-revealjs-slide-shows.html" rel="alternate"></link><published>2016-12-04T12:00:00-06:00</published><updated>2016-12-04T12:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-12-04:/maximize-and-minimize-code-blocks-in-revealjs-slide-shows.html</id><summary type="html">&lt;p&gt;I was working on a slideshow about &lt;a class="reference external" href="https://saschwarz.github.io/angular2-gestures-slides/#/"&gt;Using Gestures in Angular 2 Components&lt;/a&gt;
for a lightning talk at the December &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup and
I found the code sections just weren't large enough. So I threw together a little JavaScript to add &amp;quot;+&amp;quot; and &amp;quot;-&amp;quot;
buttons next to each code section …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was working on a slideshow about &lt;a class="reference external" href="https://saschwarz.github.io/angular2-gestures-slides/#/"&gt;Using Gestures in Angular 2 Components&lt;/a&gt;
for a lightning talk at the December &lt;a class="reference external" href="https://www.meetup.com/Northwest-Chicago-JavaScript/"&gt;Northwest Chicago JavaScript&lt;/a&gt; meetup and
I found the code sections just weren't large enough. So I threw together a little JavaScript to add &amp;quot;+&amp;quot; and &amp;quot;-&amp;quot;
buttons next to each code section that maximizes/restores the code blocks for easier viewing during the presentation:&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of slide showing plus sign to right of code section" src="https://tech.agilitynerd.com/images/max-min-screenshot.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Just copy/paste the following into your &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt; file:&lt;/p&gt;
&lt;script src="https://gist.github.com/saschwarz/ee02786cd1a64c33611fafd70c0df900.js"&gt;&lt;/script&gt;</content><category term="webdev"></category><category term="webdev"></category><category term="CSS"></category><category term="JavaScript"></category></entry><entry><title>Pelican For Fast Site/Project Development</title><link href="https://tech.agilitynerd.com/pelican-for-fast-siteproject-development.html" rel="alternate"></link><published>2016-11-08T13:00:00-06:00</published><updated>2016-11-08T13:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-11-08:/pelican-for-fast-siteproject-development.html</id><summary type="html">&lt;p&gt;I was working on a simple single page website for &lt;a class="reference external" href="http://www.agilitynerd.com/jumpheights/"&gt;calculating dog agility jump heights&lt;/a&gt; and was really missing the tool chain I normally use in Flask and Django web sites for bundling, compressing, and versioning CSS and JS files and a mechanism for putting the bundled/versioned file names …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was working on a simple single page website for &lt;a class="reference external" href="http://www.agilitynerd.com/jumpheights/"&gt;calculating dog agility jump heights&lt;/a&gt; and was really missing the tool chain I normally use in Flask and Django web sites for bundling, compressing, and versioning CSS and JS files and a mechanism for putting the bundled/versioned file names in the HTML files. I was searching for what I needed and was about to write a little script to do it and then it occurred to me that &lt;a class="reference external" href="http://getpelican.com"&gt;Pelican&lt;/a&gt; already has just what I needed!&lt;/p&gt;
&lt;p&gt;Pelican is known for making static generation of blogs easy. But it also has a lot of powerful features that can be easily leveraged to create small web sites and web sites for projects:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://webassets.readthedocs.io/en/latest/"&gt;webassets&lt;/a&gt; integration to SASS/LESS, minify, bundle, and version CSS and JavaScript. It automatically inserts versioned bundled names in the HTML.&lt;/li&gt;
&lt;li&gt;Dozens of &lt;a class="reference external" href="https://github.com/getpelican/pelican-themes"&gt;themes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Themes are implemented using &lt;a class="reference external" href="http://jinja.pocoo.org/"&gt;Jinja2 templates&lt;/a&gt; and allow sharing page layouts across your project's pages. You can also have custom templates per page.&lt;/li&gt;
&lt;li&gt;Theme templates already contain useful integrations which can be used in you templates:&lt;ul&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Disqus&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dozens of &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins"&gt;Pelican plugins&lt;/a&gt; can be installed to add new features.&lt;/li&gt;
&lt;li&gt;During development regeneration of files is automatic when you save files.&lt;/li&gt;
&lt;li&gt;Many deployment options are also available:&lt;ul&gt;
&lt;li&gt;GitHub pages&lt;/li&gt;
&lt;li&gt;FTP/SSH to your own server&lt;/li&gt;
&lt;li&gt;Dropbox&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;Rackspace Cloud Files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="overview"&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;I've written a lot of detail on every step but it is actually very easy to use Pelican for non-blog web sites. Here's how it works:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create a virtualenv and install Pelican and webassets Python packages (I use the same venv for all my Pelican projects).&lt;/li&gt;
&lt;li&gt;Checkout pelican-plugins.&lt;/li&gt;
&lt;li&gt;Edit the Pelican theme's &lt;tt class="docutils literal"&gt;base.html&lt;/tt&gt; Jinja template to include the CSS and JavaScript files you need.&lt;/li&gt;
&lt;li&gt;Create your pages' body content in files in &lt;tt class="docutils literal"&gt;content/pages&lt;/tt&gt; in HTML, Markdown or reStructuredText.&lt;/li&gt;
&lt;li&gt;Run &lt;tt class="docutils literal"&gt;make devserver&lt;/tt&gt; and refresh your browser to see your changes or use this &lt;a class="reference external" href="https://tech.agilitynerd.com/livereload-with-pelican.html"&gt;LiveReload script&lt;/a&gt; to automatically reload your browser. Repeat steps 3 and 4 until you are done.&lt;/li&gt;
&lt;li&gt;Deploy to GitHub Pages: &lt;tt class="docutils literal"&gt;make github&lt;/tt&gt;. Or deploy to your own server.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="setup-the-environment"&gt;
&lt;h2&gt;Setup the Environment&lt;/h2&gt;
&lt;p&gt;Install and configure Pelican for creating non-blog web sites:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Create a virtualenv and install pelican, webassets, cssmin, and jsmin (or any other &lt;a class="reference external" href="http://webassets.readthedocs.io/en/latest/builtin_filters.html"&gt;CSS/JS filters supported by webassets&lt;/a&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virtualenv ~/virtualenvs/pelican
source ~/virtualenvs/pelican/bin/activate
pip install pelican webassets cssmin jsmin
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Check out the pelican plugins repository outside of your project (plugins are only used during the build process):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git checkout https://github.com/getpelican/pelican-plugins.git
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Create your project directory, run &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pelican-quickstart&lt;/span&gt;&lt;/tt&gt; and answer the questions just like for a blog site:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mkdir myproject &amp;amp;&amp;amp; cd $_
pelican-quickstart

Welcome to pelican-quickstart v3.6.3.

This script will help you create a new Pelican-based website.

Please answer the following questions so this script can generate the files
needed by Pelican.

&amp;gt; Where do you want to create your new web site? [.]
&amp;gt; What will be the title of this web site? Dog Agility Jump Height Calculator
&amp;gt; Who will be the author of this web site? Steve Schwarz
&amp;gt; What will be the default language of this web site? [en]
&amp;gt; Do you want to specify a URL prefix? e.g., http://example.com   (Y/n) y
&amp;gt; /agility-jump-heights           &amp;lt;-- enter the name of your GitHub repository
&amp;gt; Do you want to enable article pagination? (Y/n) n
&amp;gt; What is your time zone? [Europe/Paris] America/Chicago
&amp;gt; Do you want to generate a Fabfile/Makefile to automate generation and publishing? (Y/n) y
&amp;gt; Do you want an auto-reload &amp;amp; simpleHTTP script to assist with theme and site development? (Y/n) y
&amp;gt; Do you want to upload your website using FTP? (y/N) n
&amp;gt; Do you want to upload your website using SSH? (y/N) n
&amp;gt; Do you want to upload your website using Dropbox? (y/N) n
&amp;gt; Do you want to upload your website using S3? (y/N) n
&amp;gt; Do you want to upload your website using Rackspace Cloud Files? (y/N) n
&amp;gt; Do you want to upload your website using GitHub Pages? (y/N) y
&amp;gt; Is this your personal page (username.github.io)? (y/N) n
Done. Your new project is available at /home/dev/agility-jump-heights
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Paste the following at the end of your &lt;tt class="docutils literal"&gt;pelican.conf&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
STATIC_PATHS = ['images']  # put page specific assets here
PLUGIN_PATHS = ['../pelican-plugins']  # set this to the location of your plugins checkout
PLUGINS = ['assets']
THEME = './theme'          # All CSS/JS files go in directories under here
# I only want to generate Pages so I disable all &amp;quot;blog-like&amp;quot; pages see Note in:
# http://docs.getpelican.com/en/stable/settings.html?highlight=url_for#url-settings
TAGS_SAVE_AS = ''          # Don't generate Tags pages
TAG_SAVE_AS = ''
CATEGORY_SAVE_AS = ''      # Don't generate Category pages
AUTHOR_SAVE_AS = ''        # Don't generate Author pages
DIRECT_TEMPLATES = ['index']  # Don't generate tag, category, or author output for some themes
# In the generated output directory move files to the root and adjust their URLs to match:
PAGE_URL = '{slug}.html'
PAGE_SAVE_AS = '{slug}.html'
INDEX_SAVE_AS = &amp;quot;/ignore/index.html&amp;quot;  # don't create normal index.html which lists all articles and pages
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Copy any theme from &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pelican-themes&lt;/span&gt;&lt;/tt&gt; into &lt;tt class="docutils literal"&gt;.theme&lt;/tt&gt; or I just copy the &lt;tt class="docutils literal"&gt;notmyidea&lt;/tt&gt; theme installed with Pelican from the virtualenv:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cp -pR $VIRTUAL_ENV/lib/python*/site-packages/pelican/themes/notmyidea/ theme
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Have Git ignore the output directory:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
echo &amp;quot;/output&amp;quot; &amp;gt;&amp;gt; .gitignore
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="create-your-project-web-site"&gt;
&lt;h2&gt;Create Your Project Web Site&lt;/h2&gt;
&lt;div class="section" id="setup-templates"&gt;
&lt;h3&gt;Setup Templates&lt;/h3&gt;
&lt;p&gt;Edit &lt;tt class="docutils literal"&gt;./templates/base.html&lt;/tt&gt; and delete/add any sections, stylesheets and javascript you like. Your pages only need to define content that goes in the &lt;cite&gt;content&lt;/cite&gt; block of the Jinja templates. Of course you can define your own templates and use the full power of Jinja templating &lt;a class="reference external" href="http://docs.getpelican.com/en/stable/settings.html?highlight=url_for#template-pages"&gt;even for individual pages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For small projects it is easiest to serve the same JS/CSS on all pages so I put them in the &lt;tt class="docutils literal"&gt;base.html&lt;/tt&gt; file. Using Jinja template inheritance you can also create and serve separate bundles for individual pages.&lt;/p&gt;
&lt;p&gt;I use &lt;tt class="docutils literal"&gt;webassets&lt;/tt&gt; right in the template to define how to combine JS/CSS files into bundles, minify and version them. For CSS files in the &lt;tt class="docutils literal"&gt;head&lt;/tt&gt; of my &lt;tt class="docutils literal"&gt;base.html&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
 {% assets filters=&amp;quot;cssmin&amp;quot;, output=&amp;quot;css/style.%(version)s.min.css&amp;quot;, &amp;quot;css/normalize.css&amp;quot;, &amp;quot;css/skeleton.css&amp;quot;, &amp;quot;css/style.css&amp;quot; %}
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ SITEURL }}/{{ ASSET_URL }}&amp;quot;&amp;gt;
 {% endassets %}
&lt;/pre&gt;
&lt;p&gt;For JavaScript the bundled, versioned, compressed &lt;tt class="docutils literal"&gt;script&lt;/tt&gt; tag(s) is defined similarly just before the end of the HTML &lt;tt class="docutils literal"&gt;body&lt;/tt&gt; tag:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% assets filters=&amp;quot;jsmin&amp;quot;, output=&amp;quot;js/main.%(version)s.min.js&amp;quot;, &amp;quot;js/main.js&amp;quot; %}
&amp;lt;script src=&amp;quot;{{ SITEURL }}/{{ ASSET_URL }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
{% endassets %}
&lt;/pre&gt;
&lt;p&gt;For more options &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/blob/master/assets/Readme.rst"&gt;see the webassets README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Edit &lt;tt class="docutils literal"&gt;theme/templates/page.html&lt;/tt&gt; to suite your needs. I just put in a wrapper &lt;tt class="docutils literal"&gt;div&lt;/tt&gt; around the content:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% extends &amp;quot;base.html&amp;quot; %}
{% block title %}{{ page.title }}{% endblock %}

{% block content %}
&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
{{ page.content }}
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/pre&gt;
&lt;p&gt;You can also delete any CSS, JS, images, and unused Jinja templates from your copied theme.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="write-the-pages"&gt;
&lt;h3&gt;Write the Pages&lt;/h3&gt;
&lt;p&gt;Create the &lt;tt class="docutils literal"&gt;pages&lt;/tt&gt; directory:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mkdir content/pages
&lt;/pre&gt;
&lt;p&gt;Lastly put each page's body content in a file in the &lt;tt class="docutils literal"&gt;content/pages&lt;/tt&gt; directory. I like to write the body content in HTML. You put the Pelican metadata in &lt;tt class="docutils literal"&gt;meta&lt;/tt&gt; elements in the &lt;tt class="docutils literal"&gt;head&lt;/tt&gt; element as &lt;a class="reference external" href="http://docs.getpelican.com/en/stable/content.html#file-metadata"&gt;shown the Pelican docs&lt;/a&gt;. Here's &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt; and I recommend specifying the &lt;tt class="docutils literal"&gt;title&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;save_as&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;!-- By default used to create the URL slug --&amp;gt;
        &amp;lt;title&amp;gt;Dog Agility Jump Height Calculator&amp;lt;/title&amp;gt;
        &amp;lt;!-- Override the default URL made up of the slug; needed for the index.html --&amp;gt;
        &amp;lt;meta name=&amp;quot;save_as&amp;quot; content=&amp;quot;index.html&amp;quot;/&amp;gt;
        &amp;lt;!-- any other metadata attributes as meta tags; none normally needed --&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;!-- all  markup goes here. e.g. --&amp;gt;
        &amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;
&lt;p&gt;You can use any input syntax supported by Pelican e.g. ReStructuredText, Markdown, or even write a Reader class for your own custom input file format.&lt;/p&gt;
&lt;p&gt;Start up the Pelican development server to watch for file changes and regenerate changed files:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
make devserver
&lt;/pre&gt;
&lt;p&gt;Point your browser to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:8000/&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I recommend using this &lt;a class="reference external" href="https://tech.agilitynerd.com/livereload-with-pelican.html"&gt;LiveReload script&lt;/a&gt; as it also watches for changes to the &lt;tt class="docutils literal"&gt;themes&lt;/tt&gt; directory and automatically reloads your browser on &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:5500&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Once you are setup just edit your templates, JS, and CSS under the &lt;tt class="docutils literal"&gt;theme&lt;/tt&gt; directory and add/edit pages in your &lt;tt class="docutils literal"&gt;content/pages&lt;/tt&gt; directory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deploy"&gt;
&lt;h3&gt;Deploy&lt;/h3&gt;
&lt;p&gt;I like to deploy small projects to GitHub Pages and it's this easy:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
make github
&lt;/pre&gt;
&lt;p&gt;Then on GitHub enable GitHub Pages in your project's settings.&lt;/p&gt;
&lt;p&gt;To see this whole setup in action take a look at this &lt;a class="reference external" href="https://github.com/saschwarz/agility-jump-heights"&gt;single page calculator application with one JS and HTML file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next step to make this even easier would be to use &lt;a class="reference external" href="https://github.com/audreyr/cookiecutter"&gt;Cookiecutter&lt;/a&gt; to make setting this up via one command.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="python"></category><category term="webdev"></category><category term="webops"></category><category term="pelican"></category><category term="CSS"></category><category term="JavaScript"></category></entry><entry><title>LiveReload with Pelican</title><link href="https://tech.agilitynerd.com/livereload-with-pelican.html" rel="alternate"></link><published>2016-11-06T00:00:00-05:00</published><updated>2016-11-06T00:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-11-06:/livereload-with-pelican.html</id><summary type="html">&lt;p&gt;I was looking to use &lt;a class="reference external" href="http://livereload.com/"&gt;LiveReload&lt;/a&gt; while developing using  &lt;a class="reference external" href="http://getpelican.com"&gt;Pelican&lt;/a&gt; and I came across this &lt;a class="reference external" href="https://merlijn.vandeen.nl/2015/pelican-livereload.html"&gt;nice simple solution&lt;/a&gt; by &lt;a class="reference external" href="https://merlijn.vandeen.nl/"&gt;Merlijn van Deen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my use case I also wanted to watch the &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt; file and &lt;tt class="docutils literal"&gt;themes&lt;/tt&gt; directory for changes and then regenerate the output and reload the browser. Lastly …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was looking to use &lt;a class="reference external" href="http://livereload.com/"&gt;LiveReload&lt;/a&gt; while developing using  &lt;a class="reference external" href="http://getpelican.com"&gt;Pelican&lt;/a&gt; and I came across this &lt;a class="reference external" href="https://merlijn.vandeen.nl/2015/pelican-livereload.html"&gt;nice simple solution&lt;/a&gt; by &lt;a class="reference external" href="https://merlijn.vandeen.nl/"&gt;Merlijn van Deen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my use case I also wanted to watch the &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt; file and &lt;tt class="docutils literal"&gt;themes&lt;/tt&gt; directory for changes and then regenerate the output and reload the browser. Lastly I wanted to use the host/port defined in my &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt;. So I made some small edits to his script named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pelican-livereload.py&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;script src="https://gist.github.com/saschwarz/8eff30f5ea5d468f0b86bd0bb149a186.js"&gt;&lt;/script&gt;&lt;p&gt;Just copy it into your Pelican top level directory and execute it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python ./pelican-livereload.py
&lt;/pre&gt;
&lt;p&gt;The LiveReload server automatically injects the livereload JavaScript script tag into the HTML so you don't need to install the LiveReload browser extension.&lt;/p&gt;
&lt;p&gt;So all you need to do is visit the &lt;tt class="docutils literal"&gt;SITEURL&lt;/tt&gt; you've specified in your &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt; otherwise it defaults to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:5500&lt;/span&gt;&lt;/tt&gt;. Then any edit you make causes Pelican to regenerate the files and the browser immediately refreshes. The only downside to the regular Pelican watcher feature is all files are regenerated instead of just the modified file. But for me having the browser automatically reload is is worth the extra brief delay.&lt;/p&gt;
</content><category term="webdev"></category><category term="python"></category><category term="webdev"></category><category term="pelican"></category></entry><entry><title>HTTP/2: A Quick and Easy Website Speed Up</title><link href="https://tech.agilitynerd.com/http2-a-quick-and-easy-website-speed-up.html" rel="alternate"></link><published>2016-09-23T18:00:00-05:00</published><updated>2016-09-23T18:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-09-23:/http2-a-quick-and-easy-website-speed-up.html</id><summary type="html">&lt;p&gt;I always want my websites to be secure and fast. &lt;a class="reference external" href="https://http2.github.io/"&gt;HTTP/2&lt;/a&gt; is the latest enhancement to the HTTP protocol that can provide significant performance improvements:&lt;/p&gt;
&lt;blockquote class="epigraph"&gt;
&lt;p&gt;The primary goals for HTTP/2 are to reduce latency by enabling full request and response multiplexing, minimize protocol overhead via efficient compression of …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;I always want my websites to be secure and fast. &lt;a class="reference external" href="https://http2.github.io/"&gt;HTTP/2&lt;/a&gt; is the latest enhancement to the HTTP protocol that can provide significant performance improvements:&lt;/p&gt;
&lt;blockquote class="epigraph"&gt;
&lt;p&gt;The primary goals for HTTP/2 are to reduce latency by enabling full request and response multiplexing, minimize protocol overhead via efficient compression of HTTP header fields, and add support for request prioritization and server push.&lt;/p&gt;
&lt;p class="attribution"&gt;&amp;mdash;&lt;a class="reference external" href="https://hpbn.co/http2/"&gt;High Performance Browser Networking - O'Reilly&lt;/a&gt;:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It turns out all browsers that support HTTP/2 &lt;a class="reference external" href="http://caniuse.com/#feat=http2"&gt;also require TLS (HTTPS)&lt;/a&gt;. So my first step was &lt;a class="reference external" href="https://tech.agilitynerd.com/nginx-https-lets-encrypt-and-django.html"&gt;adding HTTPS support to agilitycourses.com&lt;/a&gt; which also provides a backbone on which I'll implement secure user profiles. Then I wanted to enable HTTP/2 to see if it improved the speed of pages served to end users.&lt;/p&gt;
&lt;div class="section" id="but-first-an-os-upgrade"&gt;
&lt;h2&gt;But First an OS Upgrade&lt;/h2&gt;
&lt;p&gt;After a little research I found that recent releases of &lt;a class="reference external" href="https://nginx.org/en/"&gt;NGINX&lt;/a&gt; support HTTP/2 and those versions are packaged with Ubuntu 16.04 LTS. That made upgrading to this latest long term support OS version a better approach than just upgrading NGINX on my older OS. I basically went through the &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-upgrade-to-ubuntu-16-04-lts"&gt;standard upgrade process&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I had to work through a number of small issues upgrading my older websites; the biggest change was converting my upstart scripts to systemd scripts. But the beauty of hosting with virtual servers is it was trivial to create an image of my existing server, spin up a temporary server using that image, and then practice the ugprade/migration on the temporary server. I was able to move some configuration changes back to my live server and test out the changes to my sites' &lt;a class="reference external" href="http://www.fabfile.org/"&gt;Fabric&lt;/a&gt; deployment scripts.&lt;/p&gt;
&lt;p&gt;Once everything was working I configured my websites that contain user modifiable data into maintenance mode on my temporary server. Then I pointed the DNS for all my domains to the temporary server. After DNS propagated I shutdown Postgres and the webserver on my original server and created a backup image. Then I went through the upgrade process. Once I validated the migration was successful I switched the DNS back to my original server and shutdown the temporary server. After a couple days I deleted the temporary server and the pre-migration backup image.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="enabling-http-2"&gt;
&lt;h2&gt;Enabling HTTP/2&lt;/h2&gt;
&lt;p&gt;Once I was on NGINX version 1.10 the change to the website to enable HTTP/2 was as simple as changing the virtual server from:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
server {
    listen 443 ssl;
    listen [::]:443 ssl;
...
}
&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
...
}
&lt;/pre&gt;
&lt;p&gt;Then I just reloaded the Nginx configuration: &lt;tt class="docutils literal"&gt;sudo systemctl reload nginx&lt;/tt&gt;. That was it!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing"&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;Here's the network view of the timing of requests for a page using HTTP/1.1 over HTTPS in Chrome:&lt;/p&gt;
&lt;img alt="Diagram of HTTP requests for a page showing requests being delayed by previous requests for the same domain." class="thumbnail" src="/images/agilitycourses-http1.png" /&gt;
&lt;p&gt;Here's the same page using HTTP/2 over HTTPS:&lt;/p&gt;
&lt;img alt="Diagram of HTTP requests for a page showing requests being multiplexed with previous requests for the same domain." class="thumbnail" src="/images/agilitycourses-http2.png" /&gt;
&lt;p&gt;The obvious difference is in the &lt;em&gt;Timeline - Start Time&lt;/em&gt; column. In the first diagram you can see the &amp;quot;waterfall&amp;quot; queueing up of requests for images as all the open sockets to the domain were in use. In the second diagram you can see them all interleaved as they are multiplexed across the same connection.&lt;/p&gt;
&lt;p&gt;Also the bottom line is the page is fully loaded in 1.73 sec using HTTP/1.1 and in 1.16 sec using HTTP/2 for a &lt;strong&gt;33% end user page load improvement!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next-steps"&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;So that was a nice free speed up and there are plenty of areas I can still investigate to further improve the performance of this web site:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Bring 3rd party resources back to my domain to save DNS lookups and see how that affects overall performance. This may hurt performance for HTTP/1.1 clients who are limited to the number of connections per domain.&lt;/li&gt;
&lt;li&gt;Change multiple SVG files (which are all steps in a progression) into a single file and use CSS to hide/show appropriate steps using a single image. This will greatly reduce the number of individual files downloaded and save some duplicated data within those files.&lt;/li&gt;
&lt;li&gt;Move Google Analytics locally. GA has a number of problems (short cache times and multiple files downloaded) so at the cost of periodically updating the files I could host them all locally and save some time.&lt;/li&gt;
&lt;li&gt;Disqus is even worse than GA when it comes to many file downloads, redirects, short cache times etc. I might change to have Disqus data loaded on user demand.&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Experiment with other HTTP/2 features:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.igvita.com/2015/08/17/eliminating-roundtrips-with-preconnect/"&gt;preconnect&lt;/a&gt; might help reduce the cost of DNS look ups that result from redirects like those used by Google Analytics, Google Fonts, and Disqus resources.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/"&gt;preload&lt;/a&gt; to load resources before they are accessed by JS during &lt;tt class="docutils literal"&gt;onload&lt;/tt&gt; or via user actions.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ"&gt;prefetch&lt;/a&gt; to load JS/images for future pages. There are some common workflows on this site that could benefit from prefetching the JS for subsequent pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="devops"></category><category term="nginx"></category><category term="ubuntu"></category><category term="https"></category><category term="ssl"></category><category term="http2"></category><category term="performance"></category></entry><entry><title>NGINX, HTTPS, Let's Encrypt, and Django</title><link href="https://tech.agilitynerd.com/nginx-https-lets-encrypt-and-django.html" rel="alternate"></link><published>2016-09-04T12:02:00-05:00</published><updated>2016-09-04T12:02:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-09-04:/nginx-https-lets-encrypt-and-django.html</id><summary type="html">&lt;p&gt;My &lt;a class="reference external" href="https://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt; website is served by &lt;a class="reference external" href="https://nginx.org/en/"&gt;nginx&lt;/a&gt; proxying to &lt;a class="reference external" href="http://gunicorn.org/"&gt;gunicorn&lt;/a&gt; running my &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; application. I'll be adding user accounts soon so I wanted to convert the site to be more secure by using HTTPS encryption. Also &lt;a class="reference external" href="https://webmasters.googleblog.com/2014/08/https-as-ranking-signal.html"&gt;Google has announced it will likely prefer sites using HTTPS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The site is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My &lt;a class="reference external" href="https://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt; website is served by &lt;a class="reference external" href="https://nginx.org/en/"&gt;nginx&lt;/a&gt; proxying to &lt;a class="reference external" href="http://gunicorn.org/"&gt;gunicorn&lt;/a&gt; running my &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; application. I'll be adding user accounts soon so I wanted to convert the site to be more secure by using HTTPS encryption. Also &lt;a class="reference external" href="https://webmasters.googleblog.com/2014/08/https-as-ranking-signal.html"&gt;Google has announced it will likely prefer sites using HTTPS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The site is running on Ubuntu 14.04 LTS. I won't recount the whole process, I followed some great resources and I'll discuss a couple adjustments that might be helpful to others.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;I basically followed the instructions in this excellent Digital Ocean tutorial: &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04"&gt;How To Secure Nginx with Let's Encrypt on Ubuntu 14.04&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I confirmed via the &lt;a class="reference external" href="https://www.ssllabs.com/ssltest/analyze.html"&gt;SSL Labs SSL Server Test&lt;/a&gt; that my IPv4 and IPv6 server configurations had &amp;quot;A+&amp;quot; ratings.&lt;/li&gt;
&lt;li&gt;While looking for other SSL testing sites I came across &lt;a class="reference external" href="https://securityheaders.io/"&gt;securityheaders.io&lt;/a&gt; developed by &lt;a class="reference external" href="https://scotthelme.co.uk/"&gt;Scott Helme&lt;/a&gt;. My initial score was a sad &amp;quot;D&amp;quot;. The site has snippets for NGINX and Apache configuration changes and in depth articles describing the how and the why.&lt;/li&gt;
&lt;li&gt;While investigating the changes to the HTTP Headers to improve my test score I came across this &lt;a class="reference external" href="https://github.com/jonnybarnes/nginx-conf"&gt;nginx-conf GitHub repository&lt;/a&gt;. Specifically the idea of putting the header settings into an &lt;a class="reference external" href="https://github.com/jonnybarnes/nginx-conf/blob/master/conf/includes/security-headers.conf"&gt;NGINX include file&lt;/a&gt;. I have several other domains on the same server and will also be converting them. I used that idea to include ssl and header configurations into any virtual host.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's my &lt;cite&gt;/etc/nginx/ssl.conf&lt;/cite&gt; file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# From https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
&lt;/pre&gt;
&lt;p&gt;And my &lt;cite&gt;/etc/nginx/security_headers.conf&lt;/cite&gt; file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# See https://github.com/jonnybarnes/nginx-conf/blob/master/conf/includes/security-headers.conf
add_header X-Xss-Protection &amp;quot;1; mode=block&amp;quot;;
add_header X-Content-Type-Options &amp;quot;nosniff&amp;quot;;
add_header Content-Security-Policy &amp;quot;default-src https: data: 'unsafe-inline' 'unsafe-eval'&amp;quot;;
add_header Strict-Transport-Security max-age=15768000;
&lt;/pre&gt;
&lt;p&gt;So my server blocks with all these edits are now:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# redirect http://www.tld and http://tld to https://www.tld
server {
    listen 80;
    listen [::]:80;
    server_name www.agilitycourses.com agilitycourses.com;

    # letsencrypt location
    location ^~ /.well-known/ {
        allow all;
        root /usr/share/nginx/html/;
    }
    location / {
        return 301 https://www.agilitycourses.com$request_uri;
    }
}

# redirect https://tld to https://www.tld
server {
    listen 443 ssl;
    listen [::]:443 ipv6only=on ssl;

    server_name agilitycourses.com;
    # certificates are needed here too
    ssl_certificate /etc/letsencrypt/live/agilitycourses.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/agilitycourses.com/privkey.pem;
    return 301 https://www.agilitycourses.com$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name www.agilitycourses.com;
    root /home/agilitycourses/production/current/;

    ssl_certificate /etc/letsencrypt/live/agilitycourses.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/agilitycourses.com/privkey.pem;

    include /etc/nginx/ssl.conf;
    include /etc/nginx/security_headers.conf;
    ...
}
&lt;/pre&gt;
&lt;p&gt;So now I have an &amp;quot;A&amp;quot; score from &lt;cite&gt;securityheaders.io&lt;/cite&gt;&lt;/p&gt;
&lt;ol class="arabic" start="5"&gt;
&lt;li&gt;&lt;p class="first"&gt;The Digital Ocean tutorial sets up a root crontab entry to automatically update the SSL Certificate. I decided to also update the letsencrypt client software automatically:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# m h  dom mon dow   command
20 2 * * 1 cd /opt/letsencrypt &amp;amp;&amp;amp; git pull
30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew &amp;gt;&amp;gt; /var/log/le-renew.log
35 2 * * 1 /etc/init.d/nginx reload
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;The last change I made was to pass along the presence/absence of HTTPS from NGINX to Gunicorn/Django via the &lt;cite&gt;X-Forwarded-Proto&lt;/cite&gt; header as &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.10/topics/security/#ssl-https"&gt;described in the Django SSL/HTTPS docs&lt;/a&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
location &amp;#64;proxy-to-app {
    proxy_pass http://agilitycourses-production-gunicorn;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Accept-Encoding &amp;quot;&amp;quot;;
    proxy_read_timeout 120;
    proxy_send_timeout 120;
    ...
}
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Based on the Django recommendations I also made these changes in my &lt;cite&gt;settings.py&lt;/cite&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# SSL settings
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_BROWSER_XSS_FILTER = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even with a lot of web browsing to learn about these settings the whole process only took a couple hours.
Now that I've done it once (and updated my &lt;a class="reference external" href="http://www.fabfile.org/"&gt;Fabric fabfile.py&lt;/a&gt;) it will be easier to convert my other domains.&lt;/p&gt;
</content><category term="devops"></category><category term="nginx"></category><category term="ubuntu"></category><category term="https"></category><category term="ssl"></category><category term="django"></category><category term="gunicorn"></category><category term="letsencrypt"></category></entry><entry><title>NGINX CGI Parameter Gotcha</title><link href="https://tech.agilitynerd.com/nginx-cgi-parameter-gotcha.html" rel="alternate"></link><published>2016-07-31T12:02:00-05:00</published><updated>2016-07-31T12:02:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-07-31:/nginx-cgi-parameter-gotcha.html</id><summary type="html">&lt;p&gt;When I first started the &lt;a class="reference external" href="http://agilitynerd.com"&gt;agilitynerd&lt;/a&gt;  blog in 2004 I had my &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; blogging CGI script running via &lt;a class="reference external" href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt;. Later on I moved all my sites to &lt;a class="reference external" href="https://nginx.org/en/"&gt;nginx&lt;/a&gt; or took advantage of nginx's caching features to have it act as a proxy in front of Apache. I finally decided to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I first started the &lt;a class="reference external" href="http://agilitynerd.com"&gt;agilitynerd&lt;/a&gt;  blog in 2004 I had my &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; blogging CGI script running via &lt;a class="reference external" href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt;. Later on I moved all my sites to &lt;a class="reference external" href="https://nginx.org/en/"&gt;nginx&lt;/a&gt; or took advantage of nginx's caching features to have it act as a proxy in front of Apache. I finally decided to remove Apache entirely and that meant solving running CGI scripts using nginx.&lt;/p&gt;
&lt;p&gt;After some googling I found FastCGI and &lt;a class="reference external" href="https://www.nginx.com/resources/wiki/start/topics/examples/fcgiwrap/"&gt;fcgiwrap&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I'm on Ubuntu so installation was as easy as:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo apt-get install fcgiwrap
&lt;/pre&gt;
&lt;p&gt;That setup the init script that starts the fcgi daemon. To run the cgi script(s) nginx has to be configured to parse apart the incoming URL, execute the appropriate script and pass along any arguments needed by the CGI script. Sounds easy.&lt;/p&gt;
&lt;p&gt;I only want to support running a single CGI script: &lt;tt class="docutils literal"&gt;index.cgi&lt;/tt&gt; and pass along the path after the root of URL as the argument to the script. Most examples are more generic and parse out any cgi script and any arguments.&lt;/p&gt;
&lt;p&gt;The key built-in to nginx to do the splitting is &lt;tt class="docutils literal"&gt;fastcgi_split_path_info&lt;/tt&gt; which takes a regex with two captured groups to parse out the script name and the arguments. These are stored in the &lt;tt class="docutils literal"&gt;$fastcgi_script_name&lt;/tt&gt; and the &lt;tt class="docutils literal"&gt;$fastcgi_path_info&lt;/tt&gt; variables respectively. This &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/understanding-and-implementing-fastcgi-proxying-in-nginx"&gt;Digital Ocean article&lt;/a&gt; discusses FastCGI and has an excellent discussion of the variables available and also used by &lt;tt class="docutils literal"&gt;fcgiwrap&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;So I created this configuration file that matches &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://agilitynerd.com/blog/foo.html&lt;/span&gt;&lt;/tt&gt; and invokes &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/home/agilitynerd/cgi-bin/index.cgi&lt;/span&gt; foo.html&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
location /blog/ {
    root /home/agilitynerd/cgi-bin/;

    fastcgi_split_path_info ^(/blog)(.*)$;
    include /etc/nginx/fastcgi_params;
    fastcgi_param DOCUMENT_ROOT /home/agilitynerd/cgi-bin/;
    fastcgi_param SCRIPT_NAME index.cgi$fastcgi_path_info;

    # Fastcgi socket
    fastcgi_pass  unix:/var/run/fcgiwrap.socket;
}
&lt;/pre&gt;
&lt;p&gt;You'll notice: &lt;tt class="docutils literal"&gt;include /etc/nginx/fastcgi_params&lt;/tt&gt; is used to get default values for the &lt;tt class="docutils literal"&gt;fastcgi_param&lt;/tt&gt; variables.&lt;/p&gt;
&lt;p&gt;And it didn't work. I kept getting errors:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?&amp;quot;
&lt;/pre&gt;
&lt;p&gt;Clearly I'm setting &lt;tt class="docutils literal"&gt;DOCUMENT_ROOT&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SCRIPT_NAME&lt;/tt&gt;. After almost a day of googling and testing (during which I found this helpful article on &lt;a class="reference external" href="https://blog.martinfjordvald.com/2013/06/debugging-nginx-errors/"&gt;nginx debugging&lt;/a&gt;) I temporarily commented out &lt;tt class="docutils literal"&gt;fastcgi_pass&lt;/tt&gt;, and returned the variables.&lt;/p&gt;
&lt;p&gt;I found that they were being set as I expected... Strange!&lt;/p&gt;
&lt;p&gt;Then I came across this &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/understanding-and-implementing-fastcgi-proxying-in-nginx"&gt;Digital Ocean article&lt;/a&gt; where they have a critical discussion on overriding variables in which they state:&lt;/p&gt;
&lt;blockquote&gt;
This inconsistency and unpredictability means that you cannot and should not rely on the backend to correctly interpret your intentions when setting the same parameter more than one time. The only safe solution is to only declare each parameter once. This also means that there is no such thing as safely overriding a default value with the fastcgi_param directive.&lt;/blockquote&gt;
&lt;p&gt;So in my case I commented out &lt;tt class="docutils literal"&gt;DOCUMENT_ROOT&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SCRIPT_FILENAME&lt;/tt&gt; in the &lt;tt class="docutils literal"&gt;/etc/nginx/fastcgi_params&lt;/tt&gt; file, reloaded nginx, and voila! Everything worked. Hope this helps you if you run in to the same problem.&lt;/p&gt;
</content><category term="devops"></category><category term="nginx"></category><category term="cgi"></category><category term="fcgiwrap"></category><category term="ubuntu"></category><category term="apache"></category></entry><entry><title>ReactJS SVG Path Player Component</title><link href="https://tech.agilitynerd.com/reactjs-svg-path-player-component.html" rel="alternate"></link><published>2016-01-24T12:00:00-06:00</published><updated>2016-01-24T12:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2016-01-24:/reactjs-svg-path-player-component.html</id><summary type="html">&lt;p&gt;I've been a big fan of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics"&gt;SVG&lt;/a&gt; images for many years for their light weight and resolution independence. I started playing with them back when most browsers needed a plugin to render them; which kept me from using them in web sites. Within the past few years SVG has become …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been a big fan of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics"&gt;SVG&lt;/a&gt; images for many years for their light weight and resolution independence. I started playing with them back when most browsers needed a plugin to render them; which kept me from using them in web sites. Within the past few years SVG has become natively supported by almost all browsers and mobile devices so I could finally use them on my &lt;a class="reference external" href="http://agilitycourses.com/"&gt;agilitycourses.com&lt;/a&gt; website to display dog agility obstacles and the sequences through them.&lt;/p&gt;
&lt;p&gt;My next enhancement to the site was to animate the shortest/fastest paths dogs could take through the obstacles. I had found the &lt;a class="reference external" href="http://snapsvg.io/"&gt;Snap.svg&lt;/a&gt; JavaScript library which is light weight, resonably well supported and, to make it even easier, I found a &lt;a class="reference external" href="http://icanbecreative.com/article/animate-element-along-svg-path/"&gt;great example of animating a path and a marker along the path&lt;/a&gt; using it.&lt;/p&gt;
&lt;p&gt;I decided to make a &amp;quot;media player component&amp;quot; that I could instantiate in multiple places in the site and realized if I generalized the component a little it might be useful for others.&lt;/p&gt;
&lt;p&gt;I also decided to use React to create it. React has a well defined component approach and I thought this would be a good learning experience with the library and the tooling/packaging required to share it.&lt;/p&gt;
&lt;p&gt;Here's what the SVG Path Player component looks like in action:&lt;/p&gt;
&lt;div class="thumbnail figure align-center"&gt;
&lt;img alt="Animated SVG Path Player in Action" src="https://tech.agilitynerd.com/images/ac-dog-path-animation.gif" /&gt;
&lt;/div&gt;
&lt;p&gt;Here's an article from my dog agility blog &lt;a class="reference external" href="http://agilitynerd.com/blog/agility/handling/multiple-dog-paths-challenge-handling.html"&gt;explaining how dog agility handlers can use this player&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="some-things-i-learned"&gt;
&lt;h2&gt;Some Things I Learned&lt;/h2&gt;
&lt;p&gt;I won't walk through the code, &lt;a class="reference external" href="http://saschwarz.github.io/react-svgpathplayer/"&gt;it's all on GitHub&lt;/a&gt;, but I'll give you some pointers and links to articles/videos I found useful.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;SVGPathPlayer&lt;/tt&gt; component itself renders the UI and provides the button's callback methods that call to a Snap.svg element that &amp;quot;owns&amp;quot; the SVG image it controls. The &lt;tt class="docutils literal"&gt;componentDidMount&lt;/tt&gt; method uses Snap.svg to load the SVG image and select the path(s), and optional marker within it. Within the &lt;tt class="docutils literal"&gt;render&lt;/tt&gt; method the element into which the SVG image is rendered is a React &lt;a class="reference external" href="https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"&gt;ref&lt;/a&gt; child element:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;svg-container svg-container-box&amp;quot;&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;svgImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I chose to store the &lt;tt class="docutils literal"&gt;ref&lt;/tt&gt; on &lt;tt class="docutils literal"&gt;this&lt;/tt&gt; and not in the state of the component since it doesn't impact the component state; it is a data member used only by Snap.&lt;/p&gt;
&lt;p&gt;Once loaded the &lt;tt class="docutils literal"&gt;render&lt;/tt&gt; method uses a &lt;tt class="docutils literal"&gt;Controls&lt;/tt&gt; component to show the animation status and allow the user to start, stop and step forward/backward within the animation. The &lt;tt class="docutils literal"&gt;Controls&lt;/tt&gt; component is a &amp;quot;stateless&amp;quot; or &lt;a class="reference external" href="https://medium.com/&amp;#64;dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.qqffkj3iv"&gt;&amp;quot;dumb&amp;quot; compone t&lt;/a&gt;; it's buttons invoke callbacks provided via it's &lt;tt class="docutils literal"&gt;props&lt;/tt&gt; by it's parent &amp;quot;smart component&amp;quot;: &lt;tt class="docutils literal"&gt;SVGPathPlayer&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Making &lt;tt class="docutils literal"&gt;Controls&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Spinner&lt;/tt&gt; dumb components made writing tests for them really easy. I might never reuse these components, but they made reasoning about responsibilities easier and helped me simplify the interfaces (props) passed in to each component. Going through the refactoring into components I also deleted some internal state I didn't really need in the original monolithic component.&lt;/p&gt;
&lt;p&gt;Writing the React code was straight forward and I found using ES6 syntax made it even easier. The big challenges with this project were packaging it as a reusable component that could be used in both &amp;quot;script&amp;quot; and &amp;quot;npm&amp;quot; installations, generating GitHub hosted pages, and automating the testing/packaging/deployment as part of the Travis-CI automation.&lt;/p&gt;
&lt;p&gt;There are a lot of boilerplate React application projects out there but not too many for reusable React components. I found &lt;a class="reference external" href="https://github.com/survivejs/react-component-boilerplate"&gt;survivejs/react-component-boilerplate&lt;/a&gt; to be very well supported and included most of the functionality I wanted. I also bought the ebook &lt;a class="reference external" href="http://survivejs.com/"&gt;SurviveJS - Webpack and React - From apprentice to master&lt;/a&gt; which has been continuously updated to incorporate all the recent changes in the Webpack/Babel tools (I still need to migrate my tooling to the latest Babel release).&lt;/p&gt;
&lt;p&gt;So checkout my project's &lt;tt class="docutils literal"&gt;package.json npm&lt;/tt&gt; &lt;a class="reference external" href="https://github.com/saschwarz/react-svgpathplayer/blob/master/package.json#L6"&gt;scripts&lt;/a&gt; and the &lt;tt class="docutils literal"&gt;webpack.config.babel.js&lt;/tt&gt; &lt;a class="reference external" href="https://github.com/saschwarz/react-svgpathplayer/blob/master/webpack.config.babel.js#L164"&gt;distribution configurations&lt;/a&gt; for the scripts/configurations to create all the pieces. Getting all of this to work is still a little complex and I should document how I got it working as well as creating my own boilerplate project. I know I would have benefitted from an annotated versions of those files.&lt;/p&gt;
&lt;p&gt;Another very helpful resource was &lt;a class="reference external" href="http://kentcdodds.com/"&gt;Kent C. Dodds&lt;/a&gt; egghead.io videos on &lt;a class="reference external" href="https://egghead.io/series/how-to-write-an-open-source-javascript-library"&gt;How to Write an Open Source JavaScript Library&lt;/a&gt; particularly for Travis-CI integration and using semantic-release.&lt;/p&gt;
&lt;p&gt;If you are looking to make your own redistributable React components I strongly recommend reviewing both of those resources and looking through the configuration of my component. I hope to write up the details after I find some time to migrate the webpack tooling to the latest versions.&lt;/p&gt;
&lt;/div&gt;
</content><category term="javascript"></category><category term="reactjs"></category><category term="javascript"></category><category term="SVG"></category></entry><entry><title>Upgrading Pelican and Migrating to GitHub Pages</title><link href="https://tech.agilitynerd.com/upgrading-pelican-migrating-gh-pages.html" rel="alternate"></link><published>2015-11-29T15:00:00-06:00</published><updated>2015-11-29T15:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2015-11-29:/upgrading-pelican-migrating-gh-pages.html</id><summary type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; for this blog for almost three years with source and output stored in a GitHub repository. The output files were then checked out and hosted as static content behind an &lt;a class="reference external" href="http://www.nginx.com/"&gt;NGINX web server&lt;/a&gt; on my VPS. Since I set that up GitHub introduced &lt;a class="reference external" href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; for this blog for almost three years with source and output stored in a GitHub repository. The output files were then checked out and hosted as static content behind an &lt;a class="reference external" href="http://www.nginx.com/"&gt;NGINX web server&lt;/a&gt; on my VPS. Since I set that up GitHub introduced &lt;a class="reference external" href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; with support for custom domains and all the &amp;quot;cool kids&amp;quot; started hosting their static web sites right on GitHub.&lt;/p&gt;
&lt;p&gt;I had some free time this weekend and decided to see what it would take to upgrade my Pelican version to the latest (3.6.3) and host my files on GitHub Pages. I had four steps to perform:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create a new environment with the latest Pelican&lt;/li&gt;
&lt;li&gt;Update my content files for the changes in Pelican versions&lt;/li&gt;
&lt;li&gt;Put output files into GitHub and verify them on GitHub Pages&lt;/li&gt;
&lt;li&gt;Move my subdomain to point to my GitHub Pages&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="create-new-environment"&gt;
&lt;h2&gt;Create New Environment&lt;/h2&gt;
&lt;p&gt;I didn't want to screw up my existing/working virtual environment so I created a new one containing Pelican and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; which does all the work of updating the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branch with the output:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="c1"&gt;# Create a new virtualenv
&lt;/span&gt;mkvirtualenv pelican-new
&lt;span class="c1"&gt;# Install pelican and ghp-import:
&lt;/span&gt;pip install pelican ghp-import
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="update-content"&gt;
&lt;h2&gt;Update Content&lt;/h2&gt;
&lt;p&gt;This was arguably the most painful part as I wasn't using appropriate reStructuredText markup for my images and the location of images required removing &lt;tt class="docutils literal"&gt;/static&lt;/tt&gt; from the path. So my markup went from:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
.. raw:: html

   &amp;lt;div class=&amp;quot;thumbnail&amp;quot;&amp;gt;

&amp;lt;img src=&amp;quot;/static/images/myimage.png&amp;quot; /&amp;gt;

.. raw:: html

   &amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;to this (which includes adding a missing &lt;tt class="docutils literal"&gt;alt&lt;/tt&gt; tag):&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
.. class:: thumbnail
.. figure:: {static}/images/myimage.png
   :alt: Clever alt image text goes here
&lt;/pre&gt;
&lt;p&gt;Those changes were mostly mechanical and using &lt;tt class="docutils literal"&gt;figure::&lt;/tt&gt; in place of &lt;tt class="docutils literal"&gt;raw::&lt;/tt&gt; also cleaned up the mark up. I tested the changes locally and confirmed all modified pages where displaying correctly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="convert-to-github-pages"&gt;
&lt;h2&gt;Convert to GitHub Pages&lt;/h2&gt;
&lt;p&gt;This setup is now documented in the Pelican docs on &lt;a class="reference external" href="http://docs.getpelican.com/en/3.6.3/tips.html#publishing-to-github"&gt;Publish to GitHub&lt;/a&gt; and is easy.&lt;/p&gt;
&lt;p&gt;It looks like the clever setup is to put the source for the blog in the &lt;tt class="docutils literal"&gt;master&lt;/tt&gt; branch and then check the output of running &lt;tt class="docutils literal"&gt;pelican&lt;/tt&gt; into a branch called &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt;.  The &lt;a class="reference external" href="https://github.com/davisp/ghp-import"&gt;ghp-import python package&lt;/a&gt; does all the work of creating and updating the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branch from the &lt;tt class="docutils literal"&gt;output&lt;/tt&gt; directory for you!&lt;/p&gt;
&lt;p&gt;The first thing I did was to switch to my &lt;tt class="docutils literal"&gt;master&lt;/tt&gt; branch and then remove the &lt;tt class="docutils literal"&gt;content&lt;/tt&gt; directory and all of it's files:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
git checkout master
rm -rf output
&lt;/pre&gt;
&lt;p&gt;Then I edited the &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt; file to exclude the &lt;tt class="docutils literal"&gt;output&lt;/tt&gt; directory.&lt;/p&gt;
&lt;p&gt;I wanted to keep my existing blog working until I worked out all the kinks in the migration. So I delayed pointing DNS to the GitHub pages. That meant I needed to temporarily change the URL of the blog to match where it will be hosted on GitHub pages. So I edited the &lt;tt class="docutils literal"&gt;publishconf.py&lt;/tt&gt; configuration file and changed the &lt;tt class="docutils literal"&gt;SITEURL&lt;/tt&gt; temporarily from:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;SITEURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://steve.agilitynerd.com'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;to it's location on GitHub Pages:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;SITEURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://saschwarz.github.io/steve-agilitynerd'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Get the URL by clicking on the Settings tab for the GitHub repository:&lt;/p&gt;
&lt;div class="thumbnail figure"&gt;
&lt;img alt="Screenshot of GitHub settings showing URL for GitHub pages" src="https://tech.agilitynerd.com/images/github-pages-url.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Now that the &lt;tt class="docutils literal"&gt;master&lt;/tt&gt; branch is set up I checked in and commited the changes:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
git commit -a -m&lt;span class="s2"&gt;&amp;quot;Migration to GitHub Pages&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now I followed the instructions in the Pelican docs to generate the output and add it to the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branch via &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; (except they show using &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt; which I use for local development)&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
pelican content -o output -s publishconf.py
ghp-import output
&lt;/pre&gt;
&lt;p&gt;or, since I opted to have Pelican automation setup, I did:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
make github
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; committed and pushed the output to GitHub and I tested that files/images were correctly being served by going to the GitHub Pages URL in my browser.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="move-subdomain-to-github-pages"&gt;
&lt;h2&gt;Move Subdomain to GitHub Pages&lt;/h2&gt;
&lt;p&gt;This step is well documented in the GitHub help page: &lt;a class="reference external" href="https://help.github.com/articles/about-custom-domains-for-github-pages-sites/"&gt;About custom domains for GitHub Pages sites&lt;/a&gt;. In my case I was already using a subdomain for my Pelican blogs so I just followed their instructions.&lt;/p&gt;
&lt;p&gt;On my VPS's DNS configuration screen I deleted my subdomain's &lt;tt class="docutils literal"&gt;A&lt;/tt&gt; record pointing to my VPS and added a &lt;tt class="docutils literal"&gt;CNAME&lt;/tt&gt; record pointing to my GitHub &lt;cite&gt;.io&lt;/cite&gt; account.&lt;/p&gt;
&lt;p&gt;Then &lt;strong&gt;don't followed these instructions:&lt;/strong&gt; &lt;a class="reference external" href="https://help.github.com/articles/adding-a-cname-file-to-your-repository/"&gt;Adding a CNAME file to your repository&lt;/a&gt; to setup a &lt;tt class="docutils literal"&gt;CNAME&lt;/tt&gt; file in the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branch. The instructions work but &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; deletes the content of the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branch before re-adding files and that deletes the &lt;tt class="docutils literal"&gt;CNAME&lt;/tt&gt; file you just added!&lt;/p&gt;
&lt;p&gt;After some googling I found Tip #2 in the &lt;a class="reference external" href="http://docs.getpelican.com/en/latest/tips.html#extra-tips"&gt;Pelican Tips&lt;/a&gt; and followed their instructions. I added the following to my &lt;tt class="docutils literal"&gt;publishconf.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;STATIC_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'images'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'extra/CNAME'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;EXTRA_PATH_METADATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'extra/CNAME'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'CNAME'&lt;/span&gt;&lt;span class="p"&gt;},}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Then I created the &lt;tt class="docutils literal"&gt;CNAME&lt;/tt&gt; file in the new &lt;tt class="docutils literal"&gt;content/extra&lt;/tt&gt; directory with the name of my subdomain in it:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;steve.agilitynerd.com&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Undo the edit to &lt;tt class="docutils literal"&gt;publishconf.py&lt;/tt&gt; so it uses the subdomain name:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;SITEURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://steve.agilitynerd.com'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Commit that edit to the &lt;tt class="docutils literal"&gt;master&lt;/tt&gt; branch and then regenerate the output and commit it to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gh-pages&lt;/span&gt;&lt;/tt&gt; branches:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
git commit -a -m&lt;span class="s2"&gt;&amp;quot;Done with migration to sub domain&amp;quot;&lt;/span&gt;
git push
make github
&lt;/pre&gt;
&lt;p&gt;Opened the browser to my subdomain and verified that images and links within the site were working correctly. I went back to my VPS and disabled the subdomains from NGINX and deleted the blog check outs to free some resources.  Two fewer websites to maintain on my VPS!&lt;/p&gt;
&lt;/div&gt;
</content><category term="devops"></category><category term="pelican"></category><category term="github"></category><category term="python"></category></entry><entry><title>Django Migrating Models from an Abstract Base Class to a Concrete Base Class</title><link href="https://tech.agilitynerd.com/django-migrate-abstract-concrete-base-class.html" rel="alternate"></link><published>2015-11-15T05:00:00-06:00</published><updated>2015-11-15T05:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2015-11-15:/django-migrate-abstract-concrete-base-class.html</id><summary type="html">&lt;p&gt;On my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt; I had been modeling three types of dog agility courses using an abstract base class &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt; with three child classes: &lt;tt class="docutils literal"&gt;Box&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;StarBox&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;DoubleBox&lt;/tt&gt;. This created three tables in the database prepended with the &lt;a class="reference external" href="http://djangoproject.com"&gt;Django&lt;/a&gt; application name &lt;tt class="docutils literal"&gt;box&lt;/tt&gt;: &lt;tt class="docutils literal"&gt;box_box&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;box_starbox&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;box_doublebox&lt;/tt&gt;. I needed to add …&lt;/p&gt;</summary><content type="html">&lt;p&gt;On my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt; I had been modeling three types of dog agility courses using an abstract base class &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt; with three child classes: &lt;tt class="docutils literal"&gt;Box&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;StarBox&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;DoubleBox&lt;/tt&gt;. This created three tables in the database prepended with the &lt;a class="reference external" href="http://djangoproject.com"&gt;Django&lt;/a&gt; application name &lt;tt class="docutils literal"&gt;box&lt;/tt&gt;: &lt;tt class="docutils literal"&gt;box_box&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;box_starbox&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;box_doublebox&lt;/tt&gt;. I needed to add a relationship to all three classes from a new table and, rather than creating three separate tables relating to each child table, I decided to convert the &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt; class to a concrete class/table and relate the new class/table to it instead of each child class. For my purposes the extra join to the child class won't impact performance significantly (if it does I'd move the identity of the type of subclass into a column in the parent &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt; table and delete the child tables/models).&lt;/p&gt;
&lt;p&gt;I didn't find any examples of this type of migration online so I thought I write down my notes in case they are useful to others.&lt;/p&gt;
&lt;p&gt;This ends up being a schema migration to put columns in place for inserting data into the parent table, a data migration to populate that table and the new many-to-many table(s), and then another schema migration to remove the temporary columns.&lt;/p&gt;
&lt;p&gt;After playing around with a few approaches I found it was easiest to put temporary join ids on the parent class that I could use during the migration and then remove them when I was done. I added these fields to the parent in the &lt;tt class="docutils literal"&gt;models.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;subclass&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;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;subclassid&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;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and removed the &lt;tt class="docutils literal"&gt;abstract = True&lt;/tt&gt; Meta class attribute from &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I struggled for a while when I found I couldn't control the &lt;tt class="docutils literal"&gt;OneToOneField&lt;/tt&gt; Django automatically creates from the child classes to the parent. I then saw this &lt;a class="reference external" href="http://stackoverflow.com/a/32997081/457935"&gt;StackOverflow answer on a table inheritance question&lt;/a&gt; which gave the null/blank field attribute that you'll see I use below.&lt;/p&gt;
&lt;div class="section" id="backup-your-database"&gt;
&lt;h2&gt;Backup Your Database&lt;/h2&gt;
&lt;p&gt;It took me a few attempts to get this right so backups are wise...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="first-schema-migration"&gt;
&lt;h2&gt;First Schema Migration&lt;/h2&gt;
&lt;p&gt;Now that the models are prepared I created the first database migration adding a default of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-1&lt;/span&gt;&lt;/tt&gt; for the foreign key from the existing child tables to their new concrete parent (which I'll remove manually):&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python ./manage.py makemigrations box --settings&lt;span class="o"&gt;=&lt;/span&gt;dev_settings
You are trying to add a non-nullable field &lt;span class="s1"&gt;'course_ptr'&lt;/span&gt; to box without a default&lt;span class="p"&gt;;&lt;/span&gt; we can&lt;span class="s1"&gt;'t do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
&amp;gt;&amp;gt;&amp;gt; -1
You are trying to add a non-nullable field '&lt;/span&gt;course_ptr&lt;span class="s1"&gt;' to doublebox without a default; we can'&lt;/span&gt;t &lt;span class="k"&gt;do&lt;/span&gt; that &lt;span class="o"&gt;(&lt;/span&gt;the database needs something to populate existing rows&lt;span class="o"&gt;)&lt;/span&gt;.
Please &lt;span class="k"&gt;select&lt;/span&gt; a fix:
 &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; Provide a one-off default now &lt;span class="o"&gt;(&lt;/span&gt;will be &lt;span class="nb"&gt;set&lt;/span&gt; on all existing rows&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; Quit, and &lt;span class="nb"&gt;let&lt;/span&gt; me add a default in models.py
Select an option: &lt;span class="m"&gt;1&lt;/span&gt;
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can &lt;span class="k"&gt;do&lt;/span&gt; e.g. timezone.now&lt;span class="o"&gt;()&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; -1
You are trying to add a non-nullable field &lt;span class="s1"&gt;'course_ptr'&lt;/span&gt; to starbox without a default&lt;span class="p"&gt;;&lt;/span&gt; we can&lt;span class="s1"&gt;'t do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
&amp;gt;&amp;gt;&amp;gt; -1
Migrations for '&lt;/span&gt;box&lt;span class="err"&gt;'&lt;/span&gt;:
  0004_auto_20151114_1255.py:
    - Create model Course
    - Remove field created from box
    - Remove field generator from box
    - Remove field id from box
    - Remove field sequence from box
    - Remove field short_url from box
    - Remove field skills from box
    - Remove field created from doublebox
    - Remove field generator from doublebox
    - Remove field id from doublebox
    - Remove field sequence from doublebox
    - Remove field short_url from doublebox
    - Remove field skills from doublebox
    - Remove field created from starbox
    - Remove field generator from starbox
    - Remove field id from starbox
    - Remove field sequence from starbox
    - Remove field short_url from starbox
    - Remove field skills from starbox
    - Add field course_id to box
    - Add field course_ptr to box
    - Add field course_id to doublebox
    - Add field course_ptr to doublebox
    - Add field course_id to starbox
    - Add field course_ptr to starbox
&lt;/pre&gt;
&lt;p&gt;This automatic migration drops the columns in the subclass tables and with them all the existing data (including keys used in foreign key tables) is lost. But at least I can modify the migration to do what I need for the first migration. The steps will be:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Keep the &lt;tt class="docutils literal"&gt;CreateModel&lt;/tt&gt; of the parent class, &lt;tt class="docutils literal"&gt;Course&lt;/tt&gt;, table.&lt;/li&gt;
&lt;li&gt;Manually edit the &lt;tt class="docutils literal"&gt;AddField&lt;/tt&gt; of the &lt;tt class="docutils literal"&gt;OneToOneField&lt;/tt&gt; on the child classes to keep the existing primary key on the table during the migration. Change them from this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'box'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'course_ptr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;field&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;OneToOneField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_link&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="n"&gt;auto_created&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="n"&gt;primary_key&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="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'box.Course'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;preserve_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;to remove the primary_key, default and add null/blank parameters:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'box'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'course_ptr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;field&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;OneToOneField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_link&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="n"&gt;auto_created&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="n"&gt;null&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="n"&gt;blank&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="n"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'box.Course'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;preserve_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/pre&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;Delete all the &lt;tt class="docutils literal"&gt;RemoveField&lt;/tt&gt; entries in the migration. They'll be added in our final migration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want to see/validate/test the SQL that will be run you can use the &lt;tt class="docutils literal"&gt;sqlmigrate&lt;/tt&gt; management command (just give it your app name and the number of the migration):&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python ./manage.py sqlmigrate box &lt;span class="m"&gt;0004&lt;/span&gt;
BEGIN&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE &lt;span class="s2"&gt;&amp;quot;box_course&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt; serial NOT NULL PRIMARY KEY, &lt;span class="s2"&gt;&amp;quot;sequence&amp;quot;&lt;/span&gt; varchar&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; NOT NULL, &lt;span class="s2"&gt;&amp;quot;short_url&amp;quot;&lt;/span&gt; varchar&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; NOT NULL, &lt;span class="s2"&gt;&amp;quot;created&amp;quot;&lt;/span&gt; timestamp with &lt;span class="nb"&gt;time&lt;/span&gt; zone NOT NULL, &lt;span class="s2"&gt;&amp;quot;generator&amp;quot;&lt;/span&gt; varchar&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; NOT NULL, &lt;span class="s2"&gt;&amp;quot;subclass&amp;quot;&lt;/span&gt; integer NOT NULL, &lt;span class="s2"&gt;&amp;quot;subclassid&amp;quot;&lt;/span&gt; integer NOT NULL&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE &lt;span class="s2"&gt;&amp;quot;box_course_skills&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt; serial NOT NULL PRIMARY KEY, &lt;span class="s2"&gt;&amp;quot;course_id&amp;quot;&lt;/span&gt; integer NOT NULL, &lt;span class="s2"&gt;&amp;quot;skill_id&amp;quot;&lt;/span&gt; integer NOT NULL, UNIQUE &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_id&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;skill_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_box&amp;quot;&lt;/span&gt; ADD COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; integer NULL UNIQUE&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_box&amp;quot;&lt;/span&gt; ALTER COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; DROP DEFAULT&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_doublebox&amp;quot;&lt;/span&gt; ADD COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; integer NULL UNIQUE&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_doublebox&amp;quot;&lt;/span&gt; ALTER COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; DROP DEFAULT&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_starbox&amp;quot;&lt;/span&gt; ADD COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; integer NULL UNIQUE&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_starbox&amp;quot;&lt;/span&gt; ALTER COLUMN &lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt; DROP DEFAULT&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_course_skills&amp;quot;&lt;/span&gt; ADD CONSTRAINT &lt;span class="s2"&gt;&amp;quot;box_course_skills_course_id_4bbae33e06b494d4_fk_box_course_id&amp;quot;&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="s2"&gt;&amp;quot;box_course&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; DEFERRABLE INITIALLY DEFERRED&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_course_skills&amp;quot;&lt;/span&gt; ADD CONSTRAINT &lt;span class="s2"&gt;&amp;quot;box_course_skills_skill_id_35b3dcfd6d387281_fk_box_skill_id&amp;quot;&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;skill_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="s2"&gt;&amp;quot;box_skill&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; DEFERRABLE INITIALLY DEFERRED&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX &lt;span class="s2"&gt;&amp;quot;box_course_skills_ea134da7&amp;quot;&lt;/span&gt; ON &lt;span class="s2"&gt;&amp;quot;box_course_skills&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX &lt;span class="s2"&gt;&amp;quot;box_course_skills_d38d4c39&amp;quot;&lt;/span&gt; ON &lt;span class="s2"&gt;&amp;quot;box_course_skills&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;skill_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_box&amp;quot;&lt;/span&gt; ADD CONSTRAINT &lt;span class="s2"&gt;&amp;quot;box_box_course_ptr_id_9f73cfe60a5d542_fk_box_course_id&amp;quot;&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="s2"&gt;&amp;quot;box_course&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; DEFERRABLE INITIALLY DEFERRED&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_doublebox&amp;quot;&lt;/span&gt; ADD CONSTRAINT &lt;span class="s2"&gt;&amp;quot;box_doublebox_course_ptr_id_6b112382d489a445_fk_box_course_id&amp;quot;&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="s2"&gt;&amp;quot;box_course&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; DEFERRABLE INITIALLY DEFERRED&lt;span class="p"&gt;;&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;&amp;quot;box_starbox&amp;quot;&lt;/span&gt; ADD CONSTRAINT &lt;span class="s2"&gt;&amp;quot;box_starbox_course_ptr_id_25fd8909f85eb93a_fk_box_course_id&amp;quot;&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;course_ptr_id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="s2"&gt;&amp;quot;box_course&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; DEFERRABLE INITIALLY DEFERRED&lt;span class="p"&gt;;&lt;/span&gt;

COMMIT&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If you are happy then save and run the migration:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python ./manage.py python migrate box
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="data-migration"&gt;
&lt;h2&gt;Data Migration&lt;/h2&gt;
&lt;p&gt;I decided to use SQL (via &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.8/ref/migration-operations/#runsql"&gt;RunSQL&lt;/a&gt; ) for the data migration since it was easier/faster than instantiating each Django model instance as part of the migration. I didn't write reverse migrations since I won't be needing them.&lt;/p&gt;
&lt;p&gt;Here's my approach:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Copy subclass rows into parent &lt;tt class="docutils literal"&gt;course&lt;/tt&gt; table with the &lt;tt class="docutils literal"&gt;subclass&lt;/tt&gt; column set to a unique value for the subclass (just used a number for each subclass: 1, 2 &amp;amp; 3) and &lt;tt class="docutils literal"&gt;subclassid&lt;/tt&gt; set to each the child table's &lt;tt class="docutils literal"&gt;id&lt;/tt&gt; (primary key) value. Together they are a composite key that will be used to tie the parent records back to the child records and their many-to-many relationships.&lt;/li&gt;
&lt;li&gt;Update the subclass &lt;tt class="docutils literal"&gt;course_ptr&lt;/tt&gt; foreign key column with the primary key id of the &lt;tt class="docutils literal"&gt;course&lt;/tt&gt; table rows having the subclass's id and subclass number value.&lt;/li&gt;
&lt;li&gt;Insert subclass's many-to-many table data into the corresponding many-to-many parent table.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create an empty migration:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python manage.py makemigrations --empty box
&lt;/pre&gt;
&lt;p&gt;Then add the migration queries to it (repeat the following for each of the subclasses giving each a different number):&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# insert data from subclass into parent class with subclass 'number' and primary key/id&lt;/span&gt;
    &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;INSERT INTO box_course (sequence, short_url, created, generator, subclass, subclassid)
                      SELECT sequence, short_url, created, generator, 1, id
                      FROM box_box;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# update subclass primary key to point to parent class (notice composite key values):&lt;/span&gt;
    &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UPDATE box_box box SET course_ptr_id=course.id FROM box_course course WHERE course.subclassid=box.id AND course.subclass=1;&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# insert child's many-to-many foreign key references into it's parent's many-to-many table&lt;/span&gt;
    &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;INSERT INTO box_course_skills (course_id, skill_id)
                      SELECT box.course_ptr_id, skills.id
                      FROM box_box box JOIN box_box_skills skills
                      ON box.id = skills.box_id&amp;quot;&amp;quot;&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;div class="section" id="final-schema-migration"&gt;
&lt;h2&gt;Final Schema Migration&lt;/h2&gt;
&lt;p&gt;Then it is time to edit the &lt;tt class="docutils literal"&gt;models.py&lt;/tt&gt; file and remove the temporary members/fields in the parent class: &lt;tt class="docutils literal"&gt;subclass&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;subclassid&lt;/tt&gt;. Then create the schema migration which will drop those columns and the migrated columns from the child tables:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python manage.py makemigrations box
  You are trying to add a non-nullable field &lt;span class="s1"&gt;'course_ptr'&lt;/span&gt; to doublebox without a default&lt;span class="p"&gt;;&lt;/span&gt; we can&lt;span class="s1"&gt;'t do that (the database needs something to populate existing rows).
  Please select a fix:
   1) Provide a one-off default now (will be set on all existing rows)
   2) Quit, and let me add a default in models.py
  Select an option: 1
  Please enter the default value now, as valid Python
  The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
  &amp;gt;&amp;gt;&amp;gt; -1
  You are trying to add a non-nullable field '&lt;/span&gt;course_ptr&lt;span class="s1"&gt;' to starbox without a default; we can'&lt;/span&gt;t &lt;span class="k"&gt;do&lt;/span&gt; that &lt;span class="o"&gt;(&lt;/span&gt;the database needs something to populate existing rows&lt;span class="o"&gt;)&lt;/span&gt;.
  Please &lt;span class="k"&gt;select&lt;/span&gt; a fix:
   &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; Provide a one-off default now &lt;span class="o"&gt;(&lt;/span&gt;will be &lt;span class="nb"&gt;set&lt;/span&gt; on all existing rows&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; Quit, and &lt;span class="nb"&gt;let&lt;/span&gt; me add a default in models.py
  Select an option: &lt;span class="m"&gt;1&lt;/span&gt;
  Please enter the default value now, as valid Python
  The datetime and django.utils.timezone modules are available, so you can &lt;span class="k"&gt;do&lt;/span&gt; e.g. timezone.now&lt;span class="o"&gt;()&lt;/span&gt;
  &amp;gt;&amp;gt;&amp;gt; -1
  You are trying to change the nullable field &lt;span class="s1"&gt;'course_ptr'&lt;/span&gt; on box to non-nullable without a default&lt;span class="p"&gt;;&lt;/span&gt; we can&lt;span class="s1"&gt;'t do that (the database needs something to populate existing rows).
  Please select a fix:
   1) Provide a one-off default now (will be set on all existing rows)
   2) Ignore for now, and let me handle existing rows with NULL myself (e.g. adding a RunPython or RunSQL operation in the new migration file before the AlterField operation)
   3) Quit, and let me add a default in models.py
  Select an option: 1
  Please enter the default value now, as valid Python
  The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
  &amp;gt;&amp;gt;&amp;gt; -1
  Migrations for '&lt;/span&gt;box&lt;span class="err"&gt;'&lt;/span&gt;:
    0006_auto_20151114_1708.py:
      - Remove field created from box
      - Remove field generator from box
      - Remove field id from box
      - Remove field sequence from box
      - Remove field short_url from box
      - Remove field skills from box
      - Remove field subclass from course
      - Remove field subclassid from course
      - Remove field created from doublebox
      - Remove field generator from doublebox
      - Remove field id from doublebox
      - Remove field sequence from doublebox
      - Remove field short_url from doublebox
      - Remove field skills from doublebox
      - Remove field created from starbox
      - Remove field generator from starbox
      - Remove field id from starbox
      - Remove field sequence from starbox
      - Remove field short_url from starbox
      - Remove field skills from starbox
      - Alter field course_ptr to doublebox
      - Alter field course_ptr to starbox
      - Alter field course_ptr on box
&lt;/pre&gt;
&lt;p&gt;You see management command detects that the child fields still haven't been deleted and that the default value for inserts of the children's parent reference still doesn't exist. Lastly the migration converts the &lt;tt class="docutils literal"&gt;OneToOne&lt;/tt&gt; field back to a primary key.&lt;/p&gt;
&lt;p&gt;Then migrate a final time:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
$ python ./manage.py migrate box
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="wrap-up"&gt;
&lt;h2&gt;Wrap Up&lt;/h2&gt;
&lt;p&gt;I hope this helps if you need this type of migration. It may look a little complicated at first, but all it amounts to is:&lt;/p&gt;
&lt;p&gt;Step 1. Remove abstract inheritance and add temporary fields to the parent class for identifying each subclass's records in the parent table when migrating the data.&lt;/p&gt;
&lt;p&gt;Step 2. Migrate the child data to the parent class with the subclass composite keys. Use new parent primary keys to migrate tables with foreign key that have moved to the parent class.&lt;/p&gt;
&lt;p&gt;Step 3. Drop columns used in migration on the parent and child tables.&lt;/p&gt;
&lt;p&gt;Let me know if you've found other/better solutions!&lt;/p&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="python"></category><category term="agilitycourses"></category><category term="django"></category><category term="database"></category><category term="migration"></category></entry><entry><title>Django REST Registration with django-rest-auth and django-allauth</title><link href="https://tech.agilitynerd.com/django-rest-registration-with-django-rest-auth.html" rel="alternate"></link><published>2014-10-26T03:02:00-05:00</published><updated>2014-10-26T03:02:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2014-10-26:/django-rest-registration-with-django-rest-auth.html</id><summary type="html">&lt;p&gt;I'm creating a mobile app for my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses website&lt;/a&gt; and I'm using &lt;a class="reference external" href="http://www.django-rest-framework.org/"&gt;django-rest-framework&lt;/a&gt; to provide a REST API for use by the client application. In order to provide authentication and registration I'm using &lt;a class="reference external" href="http://django-allauth.readthedocs.org/en/latest/"&gt;django-allauth&lt;/a&gt;. Lastly I use &lt;a class="reference external" href="https://github.com/Tivix/django-rest-auth/"&gt;django-rest-auth&lt;/a&gt; to provide REST resources for authentication and registration.&lt;/p&gt;
&lt;p&gt;I implemented and tested …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm creating a mobile app for my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses website&lt;/a&gt; and I'm using &lt;a class="reference external" href="http://www.django-rest-framework.org/"&gt;django-rest-framework&lt;/a&gt; to provide a REST API for use by the client application. In order to provide authentication and registration I'm using &lt;a class="reference external" href="http://django-allauth.readthedocs.org/en/latest/"&gt;django-allauth&lt;/a&gt;. Lastly I use &lt;a class="reference external" href="https://github.com/Tivix/django-rest-auth/"&gt;django-rest-auth&lt;/a&gt; to provide REST resources for authentication and registration.&lt;/p&gt;
&lt;p&gt;I implemented and tested &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-rest-framework&lt;/span&gt;&lt;/tt&gt; and then added in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt;. But when I went to integrate &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-rest-auth&lt;/span&gt;&lt;/tt&gt; POSTing to the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/rest-auth/registration/&lt;/span&gt;&lt;/tt&gt; resource was generating a traceback:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Exception Type: TypeError at /rest-auth/registration/
Exception Value: add_message() argument must be an HttpRequest object, not 'Request';.
&lt;/pre&gt;
&lt;p&gt;It turns out &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt;'s &lt;tt class="docutils literal"&gt;allauth.account.adapter.DefaultAccountAdapter&lt;/tt&gt; uses Django's messaging middleware to give feedback to users when HTML templates are used. When &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;rest-auth&lt;/span&gt;&lt;/tt&gt; invokes the view it is is passing in a &lt;tt class="docutils literal"&gt;Request&lt;/tt&gt;. I took a look at the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;rest-auth&lt;/span&gt;&lt;/tt&gt; demo application and saw that it's &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file had &lt;tt class="docutils literal"&gt;django.contrib.messages&lt;/tt&gt; disabled. Which keeps this traceback from happening.&lt;/p&gt;
&lt;p&gt;Disabling messaging is a reasonable thing to do if the service is only handling REST data. For now I'd like to use the same service for both HTML and REST traffic. So I needed a way to disable messaging in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I found &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt; allows configuring/replacing the account adapter so I subclassed &lt;tt class="docutils literal"&gt;DefaultAccountAdapter&lt;/tt&gt; and stubbed out the &lt;tt class="docutils literal"&gt;add_message&lt;/tt&gt; method. I put it in my &amp;quot;glue&amp;quot; application (called &lt;tt class="docutils literal"&gt;main&lt;/tt&gt;) in a file called &lt;tt class="docutils literal"&gt;adapters.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MessageFreeAdapter(DefaultAccountAdapter):
    &amp;quot;&amp;quot;&amp;quot;
    django-allauth's `allauth.account.adapter.DefaultAccountAdapter` uses Django's messaging middleware to give feedback to users. When using django-rest-auth for registration/login JSON-REST requests a traceback is generated when the `HTTPRequest` is passed into `django.contrib.messages.add_messages` when a `Request` is expected:

    Exception Type: TypeError at /rest-auth/registration/
    Exception Value: add_message() argument must be an HttpRequest object, not &amp;amp;#39;Request&amp;amp;#39;.

    If messaging cannot be disabled (it is used by other applications) using this subclass
    disables messaging for allauth/django-rest-auth.

    In settings.py add ACCOUNT_ADAPTER = 'main.adapters.MessageFreeAdapter'
    &amp;quot;&amp;quot;&amp;quot;
    def add_message(self, request, level, message_template,
                    message_context={}, extra_tags=''):
        pass
&lt;/pre&gt;
&lt;p&gt;Then I set the &lt;tt class="docutils literal"&gt;ACCOUNT_ADAPTER&lt;/tt&gt; variable in &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; to use this new adapter:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ACCOUNT_ADAPTER = 'main.adapters.MessageFreeAdapter'
&lt;/pre&gt;
&lt;p&gt;So now I can continue to use Django messaging and use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt; in the same Django project.&lt;/p&gt;
</content><category term="webdev"></category><category term="python"></category><category term="REST"></category><category term="API"></category><category term="agilitycourses"></category><category term="django"></category><category term="django-rest-framework"></category><category term="django-allauth"></category><category term="django-rest-auth"></category><category term="mobile"></category></entry><entry><title>Integrated Coverage Analysis with Coveralls</title><link href="https://tech.agilitynerd.com/coverage-analysis-with-coveralls.html" rel="alternate"></link><published>2013-12-15T17:00:00-06:00</published><updated>2013-12-15T17:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2013-12-15:/coverage-analysis-with-coveralls.html</id><summary type="html">&lt;p&gt;When I converted &lt;a class="reference external" href="https://github.com/saschwarz/django-periodicals"&gt;django-periodicals&lt;/a&gt; to use &lt;a class="reference external" href="https://tech.agilitynerd.com/cookiecutter-django-do-the-right-thing.html"&gt;cookiecutter-djangopackage&lt;/a&gt; I was running &lt;a class="reference external" href="https://pypi.python.org/pypi/coverage"&gt;coverage.py&lt;/a&gt; in my &lt;a class="reference external" href="https://travis-ci.org"&gt;Travis-CI&lt;/a&gt; &lt;tt class="docutils literal"&gt;.travis.yml&lt;/tt&gt; to report the coverage results to the command line log. The coverage results were interesting but didn't really alter my development practice much.&lt;/p&gt;
&lt;p&gt;Over the years I've had differing options about coverage testing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I converted &lt;a class="reference external" href="https://github.com/saschwarz/django-periodicals"&gt;django-periodicals&lt;/a&gt; to use &lt;a class="reference external" href="https://tech.agilitynerd.com/cookiecutter-django-do-the-right-thing.html"&gt;cookiecutter-djangopackage&lt;/a&gt; I was running &lt;a class="reference external" href="https://pypi.python.org/pypi/coverage"&gt;coverage.py&lt;/a&gt; in my &lt;a class="reference external" href="https://travis-ci.org"&gt;Travis-CI&lt;/a&gt; &lt;tt class="docutils literal"&gt;.travis.yml&lt;/tt&gt; to report the coverage results to the command line log. The coverage results were interesting but didn't really alter my development practice much.&lt;/p&gt;
&lt;p&gt;Over the years I've had differing options about coverage testing/analysis. Like any programming metric you can &amp;quot;cook the books&amp;quot; and pump up the metric while not actually improving the quality or maintainability of the code being measured.  Minimally coverage testing can uncover unexercised corners of the code, especially error handling code. Nothing is worse than crashing an application with faulty error handling code.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;coverage.py&lt;/tt&gt; is trivial to run and it generates reports in various formats to make finding unexercised code simple. It is so easy there is no reason not to run it.&lt;/p&gt;
&lt;p&gt;So my local coverage testing showed 7% of my code wasn't exercised - &amp;quot;good enough&amp;quot; right?&lt;/p&gt;
&lt;p&gt;Then I discovered &lt;a class="reference external" href="https://coveralls.io/"&gt;Coveralls&lt;/a&gt;. Coveralls integrates with Travis and collects coverage data for each buid and displays it on their website. It was &lt;a class="reference external" href="https://github.com/coagulant/coveralls-python#usage-travis-ci"&gt;trivial to setup&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create a login on Coverall and enable your Travis-CI project.&lt;/li&gt;
&lt;li&gt;Add &lt;tt class="docutils literal"&gt;coveralls&lt;/tt&gt; to the project's test &lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Then add &lt;tt class="docutils literal"&gt;after_success: coveralls&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;.travis.yml&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next time the project is tested on Travis-CI the coverage results appear on coveralls.io. You can view the untested code in each file and Coveralls will track the increase/decrease of coverage in each file each time you check-in/test.&lt;/p&gt;
&lt;p&gt;They also have badges showing the percent coverage that you can embed in your reStructuredText documentation on GitHub and ReadTheDocs. And that's the insidious part of integrated open source development...&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cookiecutter-djangoproject&lt;/span&gt;&lt;/tt&gt; produces an application GitHub page that shows the Travis-CI test status and the PyPi package version. With the addition of the Coveralls badge it can now show the coverage percentage. That turned out to be a little bit of programming peer group pressure that made 93% coverge no longer &amp;quot;good enough&amp;quot;!&lt;/p&gt;
&lt;p&gt;So a few minutes and a few tests later I had tests that did exercise the full code base including error handling. That gave me a happy little green badge that displayed &amp;quot;coverage 100%&amp;quot;.&lt;/p&gt;
&lt;p&gt;So not only is Coveralls fully integrated with the GitHub - CI - Open Source infrastructure, and dead simple to use, it got this developer to push into all the corners of his code before releasing. And I now have the peace of mind that Travis &lt;em&gt;and&lt;/em&gt; Coveralls will be watching my back.&lt;/p&gt;
&lt;a class="reference external image-reference" href="http://badge.fury.io/py/django-periodicals"&gt;&lt;img alt="badge image" src="https://badge.fury.io/py/django-periodicals.png" /&gt;&lt;/a&gt;
&lt;a class="reference external image-reference" href="https://travis-ci.org/saschwarz/django-periodicals"&gt;&lt;img alt="badge image for travis" src="https://travis-ci.org/saschwarz/django-periodicals.png?branch=master" /&gt;&lt;/a&gt;
&lt;a class="reference external image-reference" href="https://coveralls.io/r/saschwarz/django-periodicals?branch=master"&gt;&lt;img alt="badge image for coveralls" src="https://coveralls.io/repos/saschwarz/django-periodicals/badge.png?branch=master" /&gt;&lt;/a&gt;
</content><category term="webdev"></category><category term="coverage"></category><category term="webdevelopment"></category><category term="github"></category><category term="django"></category><category term="testing"></category><category term="travis"></category><category term="python"></category></entry><entry><title>cookiecutter-djangopackage - Do the Right Thing</title><link href="https://tech.agilitynerd.com/cookiecutter-django-do-the-right-thing.html" rel="alternate"></link><published>2013-12-09T23:02:00-06:00</published><updated>2013-12-09T23:02:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2013-12-09:/cookiecutter-django-do-the-right-thing.html</id><summary type="html">&lt;p&gt;In preparation for upgrading and enhancing &lt;a class="reference external" href="http://googility.com"&gt;Googility.com&lt;/a&gt; I've started breaking out reusable applications, upgrading them, and open sourcing the code on GitHub. I wanted to follow development best practices and create high quality applications including these features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A full set of tests.&lt;/li&gt;
&lt;li&gt;Near 100% code coverage.&lt;/li&gt;
&lt;li&gt;Continuous Integration running …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;In preparation for upgrading and enhancing &lt;a class="reference external" href="http://googility.com"&gt;Googility.com&lt;/a&gt; I've started breaking out reusable applications, upgrading them, and open sourcing the code on GitHub. I wanted to follow development best practices and create high quality applications including these features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A full set of tests.&lt;/li&gt;
&lt;li&gt;Near 100% code coverage.&lt;/li&gt;
&lt;li&gt;Continuous Integration running on each check-in via &lt;a class="reference external" href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Documentation in Sphinx on &lt;a class="reference external" href="https://readthedocs.org/"&gt;Read the Docs (RTD)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Packaging/versioning compatible with &lt;a class="reference external" href="https://pypi.python.org/pypi"&gt;PyPi&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Packaging building/testing on multiple python versions using virtualenvs via &lt;a class="reference external" href="http://tox.readthedocs.org/en/latest/"&gt;tox&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I had started researching each aspect and was getting a little frustrated that there wasn't a best practice for tying everthing together. Then I came across &lt;a class="reference external" href="https://twitter.com/audreyr"&gt;Audrey Roy's&lt;/a&gt; &lt;a class="reference external" href="https://github.com/audreyr/cookiecutter"&gt;cookiecutter&lt;/a&gt; and &lt;a class="reference external" href="http://pydanny.com/"&gt;Daniel Greenfeld's&lt;/a&gt; &lt;a class="reference external" href="https://github.com/pydanny/cookiecutter-djangopackage"&gt;cookiecutter-djangopackage&lt;/a&gt;. &lt;tt class="docutils literal"&gt;cookiecutter&lt;/tt&gt; is a utility to create project directory structures and files from the command line. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cookiecutter-djangopackage&lt;/span&gt;&lt;/tt&gt; is a template for creating a reusable Django application.&lt;/p&gt;
&lt;p&gt;Yes there are other similar projects, and Django provides &lt;tt class="docutils literal"&gt;startproject&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;startapp&lt;/tt&gt; commands that can take template arguments. But since I've never used Travis, RTD or tox I really wanted to leverage more experienced developers' knowledge so I could set them up in a &amp;quot;smart&amp;quot; way.&lt;/p&gt;
&lt;p&gt;That's what I liked about &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cookiecutter-djangopackage&lt;/span&gt;&lt;/tt&gt; it came with sane defaults that worked out of the box &lt;a class="footnote-reference" href="#id2" id="id1"&gt;[1]&lt;/a&gt; and did smart stuff like wiring the version from the package's &lt;tt class="docutils literal"&gt;__init__.py&lt;/tt&gt; in to the documentation and &lt;tt class="docutils literal"&gt;setup.py&lt;/tt&gt;. The &lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt; used by &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt; is wired in to &lt;tt class="docutils literal"&gt;tox&lt;/tt&gt; and the &lt;tt class="docutils literal"&gt;README.rst&lt;/tt&gt; is used in the &lt;tt class="docutils literal"&gt;setup.py&lt;/tt&gt; and included in the Spinx docs.&lt;/p&gt;
&lt;p&gt;And there are other integrations that make it easy to release a professional Django application. In fact that's my long winded point - it makes it hard to not do the right thing! I might have skipped using one or more of these support technologies, but &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cookiecutter-djangopackage&lt;/span&gt;&lt;/tt&gt; made it easy for me to use them and focus on writing code, tests and documentation.&lt;/p&gt;
&lt;p&gt;So that is what I want to stress: with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cookiecutter-djangopackage&lt;/span&gt;&lt;/tt&gt; you &lt;em&gt;can&lt;/em&gt; create a packaged application, whose code is tested on multiple python/Django versions, tested for installation, installable via PyPi and nicely documented without much additional effort.&lt;/p&gt;
&lt;p&gt;So take look at my nearly released &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-periodicals&lt;/span&gt;&lt;/tt&gt; application to see how it all works on &lt;a class="reference external" href="https://github.com/saschwarz/django-periodicals"&gt;GitHub&lt;/a&gt;, &lt;a class="reference external" href="http://django-periodicals.readthedocs.org/en/latest/"&gt;RTD&lt;/a&gt; and &lt;a class="reference external" href="https://travis-ci.org/saschwarz/django-periodicals"&gt;Travis&lt;/a&gt;.&lt;/p&gt;
&lt;!-- rubric: Footnotes --&gt;
&lt;table class="docutils footnote" frame="void" id="id2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;I submitted a very minor &lt;a class="reference external" href="https://github.com/pydanny/cookiecutter-djangopackage/pull/13"&gt;pull request&lt;/a&gt; and found a related issue with application names that don't match their imported package name. (i.e. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-periodicals&lt;/span&gt;&lt;/tt&gt; is the application name and &lt;tt class="docutils literal"&gt;periodicals&lt;/tt&gt; is the package that is imported).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content><category term="webdev"></category><category term="python"></category><category term="django"></category><category term="googility"></category><category term="development"></category><category term="testing"></category><category term="travis"></category><category term="rtd"></category><category term="pypi"></category><category term="tox"></category><category term="virtualenv"></category><category term="webdevelopment"></category><category term="github"></category></entry><entry><title>Moved To Pelican</title><link href="https://tech.agilitynerd.com/moved-to-pelican.html" rel="alternate"></link><published>2013-03-17T12:00:00-05:00</published><updated>2013-03-17T12:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2013-03-17:/moved-to-pelican.html</id><summary type="html">&lt;p&gt;Way back in 2004, when the content in this blog was a category in the main &lt;a class="reference external" href="http://agilitynerd.com"&gt;AglityNerd blog&lt;/a&gt;, I used the Perl &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; application to serve the blog. When my dog agility readers complained/were confused by the sprinkling of tech postings I split off tech.agilitynerd in another Blosxom …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Way back in 2004, when the content in this blog was a category in the main &lt;a class="reference external" href="http://agilitynerd.com"&gt;AglityNerd blog&lt;/a&gt;, I used the Perl &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; application to serve the blog. When my dog agility readers complained/were confused by the sprinkling of tech postings I split off tech.agilitynerd in another Blosxom instance.&lt;/p&gt;
&lt;p&gt;Once modern web hosted blogs came in to existence I wanted to see how they worked and moved the tech content to &lt;a class="reference external" href="http://posterous.com"&gt;posterous&lt;/a&gt;. That was only OK, the source code formatting was painful but it did support email and web based content creation. I was reasonably content, not bugged enough to move again.&lt;/p&gt;
&lt;p&gt;Then posterous announced &lt;a class="reference external" href="http://blog.posterous.com/thanks-from-posterous"&gt;it is closing on April 30&lt;/a&gt; so I finally had to do something. This site really only needs a few features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;easy format in which to write content&lt;/li&gt;
&lt;li&gt;source code formatting&lt;/li&gt;
&lt;li&gt;Atom/RSS feeds&lt;/li&gt;
&lt;li&gt;comments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use Disqus for comments on my other sites so that meant I could go with a statically generated site. Also since I primarily code in Python I wanted a platform to which I could contribute. I came across &lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; and it fit the bill nicely.&lt;/p&gt;
&lt;p&gt;I exported the posterous posts, imported them through a temporary wordpress.com site using their posterous importer and finally re-exported them in wordpress format. After a quick bug fix to the pelican wordpress importer I had reStructured Text files for each post. After a few days of editing the embeded source code in the content files and fixing long broken links, I had the blog running and not looking bad at all.&lt;/p&gt;
&lt;p&gt;I wanted a Twitter Bootstrap based responsive layout and I found &lt;a class="reference external" href="https://github.com/azizmb/pelican-bootstrap-responsive-theme"&gt;azizmb's pelican-bootstrap-responsive-them&lt;/a&gt; which had a very pleasing layout. I tweaked it to have category and tag feeds and some other enhancements to get what you see today. I will see if Aziz is interested in pulling any of my changes back.&lt;/p&gt;
&lt;p&gt;So far I like using Pelican and I've made the &lt;a class="reference external" href="https://github.com/saschwarz/tech-agilitynerd"&gt;content of the blog&lt;/a&gt; and my &lt;a class="reference external" href="https://github.com/saschwarz/pelican-bootstrap-responsive-theme"&gt;edits to the theme&lt;/a&gt; available on github in case anyone is interested.&lt;/p&gt;
</content><category term="webdev"></category><category term="posterous"></category><category term="pelican"></category><category term="blosxom"></category><category term="bootstrap"></category></entry><entry><title>Google Analytics for JQuery Mobile With Internal and AJAX Pages</title><link href="https://tech.agilitynerd.com/google-analytics-for-jquery-mobile-withwithou.html" rel="alternate"></link><published>2013-01-21T02:00:00-06:00</published><updated>2013-01-21T02:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2013-01-21:/google-analytics-for-jquery-mobile-withwithou.html</id><summary type="html">&lt;p&gt;I added google analytics to my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt;&amp;nbsp;website and was
having problems getting the mobile version of the site to log analytics
data after the first page. Google found this very helpful blog
post:&amp;nbsp;&lt;a class="reference external" href="http://www.jongales.com/blog/2011/01/10/google-analytics-and-jquery-mobile/"&gt;Using Google Analytics with jQuery Mobile&lt;/a&gt;&amp;nbsp;and it got me very
close to my final …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I added google analytics to my &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt;&amp;nbsp;website and was
having problems getting the mobile version of the site to log analytics
data after the first page. Google found this very helpful blog
post:&amp;nbsp;&lt;a class="reference external" href="http://www.jongales.com/blog/2011/01/10/google-analytics-and-jquery-mobile/"&gt;Using Google Analytics with jQuery Mobile&lt;/a&gt;&amp;nbsp;and it got me very
close to my final solution.&lt;/p&gt;
&lt;p&gt;Since that time jQuery Mobile has changed to&amp;nbsp;&lt;a class="reference external" href="http://jquerymobile.com/demos/1.0/docs/api/events.html"&gt;pageinit events&lt;/a&gt; and
&lt;a class="reference external" href="http://jquerymobile.com/demos/1.0/docs/api/methods.html"&gt;jqmData&lt;/a&gt;&amp;nbsp;for page selectors, so I had to change the first line below
to match the recomendations for the 1.x version of jQuery Mobile that
I'm using:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;div:jqmData(role='page')&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;live&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pageinit'&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;)&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&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;hash&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="cm"&gt;/* urls begin with locale but track urls independent of locale: strip off leading locale */&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z_&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="c1"&gt;//, '');
&lt;/span&gt;      &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cm"&gt;/* strip # from hash */&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_gaq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'_setAccount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'UA-XXXXXXX-X'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_setDomainName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_setSiteSpeedSampleRate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_trackPageLoadTime'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_trackPageview'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;My mobile site uses both internal pages and AJAX loaded pages. So unlike
the blog post's solution, I needed to use URLs with and without
hashtags. Per the &lt;a class="reference external" href="http://_trackPageview"&gt;Google docs on _trackPageview&lt;/a&gt;&amp;nbsp;I also added a
leading slash to the hashed URL.&lt;/p&gt;
&lt;p&gt;Another change for my site is urls are localized, they begin with the
locale for the user's language via &lt;a class="reference external" href="https://github.com/carljm/django-localeurl"&gt;django-localeurl&lt;/a&gt;. So I strip the
leading &amp;quot;/es/&amp;quot;, &amp;quot;/ca-FR/&amp;quot; or &amp;quot;/en_GB/&amp;quot; from the URL before sending it
to Google.&lt;/p&gt;
&lt;p&gt;There are a couple other variables I push on the _gaq array. I&amp;nbsp;log my
mobile site (m.agilitycourses.com) as a subdomain of my desktop site so
I followed the settings from the &lt;a class="reference external" href="https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingSite#domainAndSubDirectory"&gt;Google analytics&lt;/a&gt;&amp;nbsp;and set the domain
name. I also enabled client side page load and site speed settings.&lt;/p&gt;
&lt;p&gt;So I put the code in the head of the original post in the head of my
pages and the code snippet above in the foot of the pages.&lt;/p&gt;
&lt;p&gt;Hope this is helpful for others.&lt;/p&gt;
</content><category term="webdev"></category><category term="ajax"></category><category term="googleanalytics"></category><category term="jquery"></category><category term="jquerymobile"></category></entry><entry><title>Create Posterous Posts from Google Forms</title><link href="https://tech.agilitynerd.com/create-posterous-posts-from-google-forms.html" rel="alternate"></link><published>2012-05-12T23:57:00-05:00</published><updated>2012-05-12T23:57:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2012-05-12:/create-posterous-posts-from-google-forms.html</id><summary type="html">&lt;p&gt;I've been organizing dog agility bloggers for&amp;nbsp;&lt;a class="reference external" href="http://dog-agility-blog-events.posterous.com/"&gt;Dog Agility Blog Action
Days&lt;/a&gt;&amp;nbsp;and wanted to automate updating our group's blog when each
blogger posted their article on the event/action days. For past events I
had the bloggers email me with the information and manually created each
entry in the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been organizing dog agility bloggers for&amp;nbsp;&lt;a class="reference external" href="http://dog-agility-blog-events.posterous.com/"&gt;Dog Agility Blog Action
Days&lt;/a&gt;&amp;nbsp;and wanted to automate updating our group's blog when each
blogger posted their article on the event/action days. For past events I
had the bloggers email me with the information and manually created each
entry in the blog.&lt;/p&gt;
&lt;p&gt;Since &lt;a class="reference external" href="http://posterous.com"&gt;posterous&lt;/a&gt; supports posting via email I knew I could create a
form on one of my web servers and have the page format the data so
posterous could turn it into a post. Here are the details for formatting
emails for posterous: &lt;a class="reference external" href="http://howdoi.posterous.com/how-to-get-the-most-out-of-posting-by-email"&gt;how to get the most out of posting by email&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Instead I stumbled upon an article on using Google Forms to send emails
via the Google infrastructure:&amp;nbsp;&lt;a class="reference external" href="http://www.labnol.org/internet/google-docs-email-form/20884/"&gt;Get Form Data from Google Docs in an
Email Message [Video Tutorial]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So all I had to do was create a form for my bloggers to fill out and
then have the script run by their submission format and send an email to
posterous to post.&lt;/p&gt;
&lt;p&gt;Here's my modified version of the form script:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sendFormByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// create a draft post - posterous will send you an email
&lt;/span&gt;  &lt;span class="c1"&gt;// when the draft is created.  var email = &amp;quot;draft&amp;#64;YOURBLOGNAMEHERE.posterous.com&amp;quot;;
&lt;/span&gt;  &lt;span class="c1"&gt;// e.namedValues is a dictionary where the keys
&lt;/span&gt;  &lt;span class="c1"&gt;// are the names of your form's questions. So you can pick
&lt;/span&gt;  &lt;span class="c1"&gt;// them out and combine them as you wish.
&lt;/span&gt;  &lt;span class="c1"&gt;//
&lt;/span&gt;  &lt;span class="c1"&gt;// My form contained five questions:
&lt;/span&gt;  &lt;span class="c1"&gt;// 1. 'Title of your blog as you want it to appear'
&lt;/span&gt;  &lt;span class="c1"&gt;// 2. 'The URL of your blog as you type it in your browser'
&lt;/span&gt;  &lt;span class="c1"&gt;// 3. 'Title of your article'
&lt;/span&gt;  &lt;span class="c1"&gt;// 4. 'The URL of your article copied from your browser'
&lt;/span&gt;  &lt;span class="c1"&gt;// 5. 'Short description/teaser about your article'
&lt;/span&gt;  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namedValues&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;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Title of your article'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;((tag: attitude))&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// add tags as you wish
&lt;/span&gt;  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;a href=&amp;quot;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'The URL of your blog as you type it in your browser'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&amp;quot;&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Title of your blog as you want it to appear'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/a &amp;gt; wrote:'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Short description/teaser about your article'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt; a href=&amp;quot;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'The URL of your article copied from your browser'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;quot;&amp;gt; Read the full article &amp;lt; /a &amp;gt;nn'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;n#end&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// so your email signature won't cause a problem
&lt;/span&gt;  &lt;span class="c1"&gt;// This is the MailApp service of Google Apps Script
&lt;/span&gt;  &lt;span class="c1"&gt;// that sends the email. You can also use GmailApp here.
&lt;/span&gt;  &lt;span class="nx"&gt;MailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Watch the following video for details   // http://youtu.be/z6klwUxRwQI
&lt;/span&gt;  &lt;span class="c1"&gt;// By Amit Agarwal - www.labnol.org
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;In the script you can see how I've accessed the form fields by the full
name and woven them into an email with links embedded - just have to
watch the use of double quotes within single quotes. Which produces blog
entries similar &lt;a class="reference external" href="http://dogagilityblogevents.wordpress.com/if-i-knew-then-proactive-handling"&gt;to this one I created manually&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So now I can just send the link to the Google form to participants and
they'll create drafts that I can tweak and/or just submit. It took
longer to write this post than it took to modify and test the script!&lt;/p&gt;
</content><category term="webdev"></category><category term="email"></category><category term="google"></category><category term="posterous"></category></entry><entry><title>My Favorite ORM and Python Anti-Patterns</title><link href="https://tech.agilitynerd.com/my-favorite-orm-anti-pattern.html" rel="alternate"></link><published>2012-05-12T23:57:00-05:00</published><updated>2012-05-12T23:57:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2012-05-12:/my-favorite-orm-anti-pattern.html</id><summary type="html">&lt;p&gt;At work I was looking at improving the performance of one of our slower
web pages. It can be rewarding to find a little piece of code that can
be easily optimized. This time there were several functions that were
adding 10+ sec to the page in worst case. It …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At work I was looking at improving the performance of one of our slower
web pages. It can be rewarding to find a little piece of code that can
be easily optimized. This time there were several functions that were
adding 10+ sec to the page in worst case. It wasn't a problem for most
clients, but when clients with who are related to many other clients hit
the page they'd experience terrible performance. Here's pseudo code for
the combination of anti-patterns that caused the problem:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Projects have users and users are in different organizations
# (project can contain multiple organization's users)
activeOrganizationProjectUsers = [x for x in project.users
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if x.active and x.organization == organization]
if activeOrganizationProjectUsers:
&amp;nbsp;&amp;nbsp;&amp;nbsp; # do something *NOT* using activeOrganizationProjectUsers
&lt;/pre&gt;
&lt;p&gt;There are two main problems with this code:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;It ignores the fact the project, users, and organization are backed
by an ORM&lt;/li&gt;
&lt;li&gt;The list comprehension is being used to find all matching elements
when only a single element is needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="ignoring-the-orm"&gt;
&lt;h2&gt;Ignoring the ORM&lt;/h2&gt;
&lt;p&gt;The code above wouldn't be too bad if these were just lists of objects
in memory. But being objects that are instantiated by an ORM a number of
database queries will be issued. In this particular case (w/o eager
loading across user to the organization table) the following queries
were executed:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Join project to user and get all users for the project's id&lt;/li&gt;
&lt;li&gt;For each user load their organization (one by one) if the user is
active&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So in the case where there were hundreds of users on a project there
were hundreds of queries executed and hundreds of User and Organization
instances were instantiated. Depending on the size of the objects (and
the ORM's behavior) it can take &amp;quot;real time&amp;quot; to fetch and instantiate all
these large objects.&lt;/p&gt;
&lt;p&gt;This code base has this kind of code sprinkled through out it. At one
time during it's development the developers were encouraged to treat ORM
backed objects as though they were Plain Old Python Objects (POPOs). The
developer wouldn't necessarily see the performance degradation using
small data sets either. This is one of the reasons why I like to tail
the database log (or use &lt;a class="reference external" href="http://github.com/robhudson/django-debug-toolbar"&gt;django-debug-toolbar&lt;/a&gt; if I'm using Django)
to see the queries go by.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-list-comprehensions-when-a-single-value-is-needed"&gt;
&lt;h2&gt;Using List Comprehensions When a Single Value is Needed&lt;/h2&gt;
&lt;p&gt;To make this situation worse, the activeOrganizationProjectUsers list
wasn't actually used. This is a combination of a Python anti-pattern and
the ORM anti-pattern. What was required was to determine if a single
active organization user existed.&lt;/p&gt;
&lt;p&gt;I believe the original developer(s) used the list comprehension solution
in a combination of ignorance and syntactic sugar. They didn't want to
write a new function to do the query and put it in the User class so
they used the existing class's API. The syntactic sugar was using the
list comprehension to get more values than the one that was needed. If
this wasn't a (potentially) expensive ORM backed operation the original
code could have been:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
activeOrganizationProjectUsers = False
for x in project.users:
&amp;nbsp;&amp;nbsp;&amp;nbsp; if x.active and x.organization == organization:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; activeOrganizationProjectUsers = True
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break
if activeOrganizationProjectUsers:
&amp;nbsp;&amp;nbsp;&amp;nbsp; # do something
&lt;/pre&gt;
&lt;p&gt;But this solution could still query all possible user/organization
combinations. The other question would be: which set is larger the
organization users or the project users? It is likely looping over the
organization's users looking for active ones would be more efficient
anyway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="remember-the-underlying-representation"&gt;
&lt;h2&gt;Remember the Underlying Representation&lt;/h2&gt;
&lt;p&gt;When performance matters remembering the objects are ORM backed is
important. So in this case a single query was all that was required
(SqlObject pseudo syntax):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
activeOrganizationProjectUsers = Users.selectBy(project=project,
                     &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; active=True,
                     &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; organization=organization).count() &amp;gt; 0
&lt;/pre&gt;
&lt;p&gt;If abstracting out the ORM's methods is important this new function
could be added to the appropriate class as a method. In my case making a
change to use a query resulted in cutting the page load time by two
orders of magnitude.&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="antipattern"></category><category term="development"></category><category term="orm"></category><category term="python"></category></entry><entry><title>Reducing the Cost of Client Side Analytics</title><link href="https://tech.agilitynerd.com/reducing-the-impact-of-client-side-analytics.html" rel="alternate"></link><published>2012-01-12T22:10:00-06:00</published><updated>2012-01-12T22:10:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2012-01-12:/reducing-the-impact-of-client-side-analytics.html</id><summary type="html">&lt;p&gt;I read&amp;nbsp;&lt;a class="reference external" href="http://blog.mozilla.com/webdev/2012/01/06/timing-amo-user-experience/"&gt;Andy McKay's blog post on timing user experience&lt;/a&gt;&amp;nbsp;on the
&lt;a class="reference external" href="http://blog.mozilla.com/webdev"&gt;Mozilla Webdev blog&lt;/a&gt;&amp;nbsp;the other day and it reminded me of an idea I was
thinking about for measuring client side timings at work. I had been
toying with the idea of rolling our own library to capture …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I read&amp;nbsp;&lt;a class="reference external" href="http://blog.mozilla.com/webdev/2012/01/06/timing-amo-user-experience/"&gt;Andy McKay's blog post on timing user experience&lt;/a&gt;&amp;nbsp;on the
&lt;a class="reference external" href="http://blog.mozilla.com/webdev"&gt;Mozilla Webdev blog&lt;/a&gt;&amp;nbsp;the other day and it reminded me of an idea I was
thinking about for measuring client side timings at work. I had been
toying with the idea of rolling our own library to capture JavaScript
rendering time for our JS heavy pages (grids of hundreds of lines of
data).&lt;/p&gt;
&lt;p&gt;Andy's post mentions the &lt;a class="reference external" href="http://yahoo.github.com/boomerang/doc/"&gt;boomerang JavaScript library&lt;/a&gt;&amp;nbsp;and when I was
reading it's docs they pointed out potential for abuse/load on the URL
used to report the timings. For each instrumented page boomerang can hit
the &amp;quot;beacon&amp;quot; URL to report the statistics it collects. So in the worst
case you could double your page hits - although for specific
pages/samples recording a few statistics shouldn't be too costly for low
volume sites.&lt;/p&gt;
&lt;p&gt;One solution is to only sample the pages/users of interest; selecting
the sample could occur on the server and/or client. But another solution
would be to collect statistics across multiple pages and periodically
send batches of analytics to the beacon URL.&lt;/p&gt;
&lt;p&gt;I've been playing with mobile web development for
&lt;a class="reference external" href="http://m.agilitycourses.com/"&gt;agilitycourses.com&lt;/a&gt;&amp;nbsp;lately and will soon let users store the courses
they create in localStorage on their browser. That got me thinking that
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/Web_Storage#Local_and_session_storage"&gt;sessionStorage&lt;/a&gt; could be used to store analytics across pages and then
periodically send the stats to the server. This would reduce the number
of hits on the beacon, allowing deployment to a larger sample of
clients. It also gets flushed once the session is closed and (if kept
small) doesn't prompt the user to approve storing the data.&lt;/p&gt;
&lt;p&gt;A lot of modern &lt;a class="reference external" href="http://www.delicious.com/redirect?url=http%3A//dev-test.nemikor.com/web-storage/support-test/"&gt;browsers support session storage&lt;/a&gt;&amp;nbsp;and for my purposes
only ones with support would be relevant - due to our browser support
policy at work.&lt;/p&gt;
&lt;p&gt;The other problem the boomerang docs discuss is&lt;a class="reference external" href="http://yahoo.github.com/boomerang/doc/howtos/howto-7.html"&gt;abuse of the beacon&lt;/a&gt;
(accidental or malicious). A solution would be to piggyback reporting of
analytics into application form post payloads. This is trickier to
implement and it suffers from coupling analytic reporting into the
application itself.&lt;/p&gt;
&lt;p&gt;To try to solve it some what generally... The client side JS library
could add a hidden field to any/some/specific forms into which it writes
the analytics data collected thus far. If it hooked the form submit
callback it could know if the form was successfully submitted and clear
the session storage.&lt;/p&gt;
&lt;p&gt;Server side middleware could detect the hidden analytics field in the
form and extract/store the data. It could also remove the field before
passing the request data along to the app server.&lt;/p&gt;
&lt;p&gt;All in all a fair amount of twiddling to keep from exposing a recording
URL to the outside world.&lt;/p&gt;
&lt;p&gt;Of course if an authenticated session was being used then abusers would
have to have a valid session to post to the beacon URL.&lt;/p&gt;
&lt;p&gt;I don't know if I will have time to play with the sessionStorage idea
but I think it might be a worthwhile extension to boomerang or other
client side analytics capture libraries.&lt;/p&gt;
</content><category term="webdev"></category><category term="analytics"></category><category term="boomerang"></category><category term="javascript"></category><category term="sessionstorage"></category><category term="webdevelopment"></category></entry><entry><title>YellowGrass - Web Based Issue Tracking</title><link href="https://tech.agilitynerd.com/yellowgrass-web-based-issue-tracking.html" rel="alternate"></link><published>2011-06-07T12:24:00-05:00</published><updated>2011-06-07T12:24:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2011-06-07:/yellowgrass-web-based-issue-tracking.html</id><summary type="html">&lt;p&gt;I was doing some reading on &lt;a class="reference external" href="http://www.mobl-lang.org/"&gt;mobl&lt;/a&gt;&amp;nbsp;and saw&amp;nbsp;that they&amp;nbsp;are using a free
web based service called &lt;a class="reference external" href="http://yellowgrass.org/features"&gt;YellowGrass&lt;/a&gt;&amp;nbsp;for issue tracking. It has some
nice features and seems easy to use.&amp;nbsp;Everything is tag based.&amp;nbsp;I think
I'll try to use it for tracking enhancements to &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was doing some reading on &lt;a class="reference external" href="http://www.mobl-lang.org/"&gt;mobl&lt;/a&gt;&amp;nbsp;and saw&amp;nbsp;that they&amp;nbsp;are using a free
web based service called &lt;a class="reference external" href="http://yellowgrass.org/features"&gt;YellowGrass&lt;/a&gt;&amp;nbsp;for issue tracking. It has some
nice features and seems easy to use.&amp;nbsp;Everything is tag based.&amp;nbsp;I think
I'll try to use it for tracking enhancements to &lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses.com&lt;/a&gt;.&lt;/p&gt;
</content><category term="webdev"></category><category term="development"></category><category term="issuetracking"></category></entry><entry><title>Django Shrink The Web django-stw 0.2.0 Released</title><link href="https://tech.agilitynerd.com/django-shrink-the-web-django-stw-020-released.html" rel="alternate"></link><published>2011-04-23T13:32:00-05:00</published><updated>2011-04-23T13:32:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2011-04-23:/django-shrink-the-web-django-stw-020-released.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; has announced a new API for free users using their new
&lt;a class="reference external" href="http://www.shrinktheweb.com/content/what-stw-preview-verification.html"&gt;preview verification&lt;/a&gt; feature. This change required changes to my
django-stw package.&lt;/p&gt;
&lt;p&gt;The changes (lifted from the &lt;a class="reference external" href="https://github.com/saschwarz/django-stw/blob/master/CHANGELOG.txt"&gt;CHANGELOG.txt&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;Changes to the &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag is NOT backward compatible with
version 0.0 …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; has announced a new API for free users using their new
&lt;a class="reference external" href="http://www.shrinktheweb.com/content/what-stw-preview-verification.html"&gt;preview verification&lt;/a&gt; feature. This change required changes to my
django-stw package.&lt;/p&gt;
&lt;p&gt;The changes (lifted from the &lt;a class="reference external" href="https://github.com/saschwarz/django-stw/blob/master/CHANGELOG.txt"&gt;CHANGELOG.txt&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;Changes to the &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag is NOT backward compatible with
version 0.0.1. The alt argument is no longer accepted.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag is now intended for use by free
accounts, it adds the required preview feature. It can also be used
by PRO account users wanting the preview functionality.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag now accepts PRO key-value
arguments in the same manner as the stwimage tag. This functionality
is shown in theexample template but may not yet be fully implemented
by the STW web service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Changes to the &lt;tt class="docutils literal"&gt;stwimage&lt;/tt&gt; template tag:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;stwimage&lt;/tt&gt; can now only be used for PRO features.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Common changes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Template tags now throw exceptions in their constructors instead of
in the render function so configuration errors are visible during
development.&lt;/li&gt;
&lt;li&gt;django-stw defines a key 'lang' for the SHRINK_THE_WEB dictionary
that can be passed along as a default to the preview tag. Alternately
a 'lang' keyword can be supplied in each template tag invocation.
django-stw defaults it to 'en'. This functionality is not yet
implemented by the STW web service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The v 0.2.0 package is &lt;a class="reference external" href="http://pypi.python.org/pypi?:action=display&amp;amp;name=django-stw&amp;amp;version=0.2.0"&gt;available on PyPi&lt;/a&gt;, as a &lt;a class="reference external" href="https://github.com/saschwarz/django-stw/archives/v0.2.0"&gt;source download on
github&lt;/a&gt;, or via &lt;a class="reference external" href="https://github.com/saschwarz/django-stw"&gt;git clone&lt;/a&gt;.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="shrinktheweb"></category></entry><entry><title>Python dict.get's Default Value is Always Evaluated</title><link href="https://tech.agilitynerd.com/python-dictgets-default-value-is-always-evalu.html" rel="alternate"></link><published>2011-04-05T17:16:00-05:00</published><updated>2011-04-05T17:16:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2011-04-05:/python-dictgets-default-value-is-always-evalu.html</id><summary type="html">&lt;p&gt;This is a gotcha I ran across in some production code that is obvious in
retrospect.&amp;nbsp;I was profiling the code to find places where we were
calling &lt;tt class="docutils literal"&gt;an_expensive_database_function&lt;/tt&gt; and came across code like
this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;doit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;an_expensive_database_function&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The original author probably assumed that …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is a gotcha I ran across in some production code that is obvious in
retrospect.&amp;nbsp;I was profiling the code to find places where we were
calling &lt;tt class="docutils literal"&gt;an_expensive_database_function&lt;/tt&gt; and came across code like
this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;doit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;an_expensive_database_function&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The original author probably assumed that if 'key' was present in the
&lt;tt class="docutils literal"&gt;kwargs&lt;/tt&gt; dictionary &lt;tt class="docutils literal"&gt;an_expensive_database_function&lt;/tt&gt; wouldn't be called;
that it would be short circuited in the same manner as Boolean
expressions. But since get is a function the arguments are always
evaluated on the way into the function. So in this case even if the
value of &lt;tt class="docutils literal"&gt;an_expensive_database_function&lt;/tt&gt; was already present in the
&lt;tt class="docutils literal"&gt;kwargs&lt;/tt&gt; dictionary the database function would be called again.&lt;/p&gt;
&lt;p&gt;Here is a &amp;quot;look before you leap&amp;quot; solution:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;doit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;'key'&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;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="c1"&gt;# assuming default value None isn't a valid value&lt;/span&gt;
         &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;an_expensive_database_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Here is the &amp;quot;easier to ask forgiveness than permission&amp;quot; solution:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;doit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;an_expensive_database_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
</content><category term="programming"></category><category term="python"></category></entry><entry><title>Blosxom Plugin for Generating Facebook Comment xids</title><link href="https://tech.agilitynerd.com/blosxom-plugin-for-generating-facebook-commen.html" rel="alternate"></link><published>2010-11-23T05:00:00-06:00</published><updated>2010-11-23T05:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-11-23:/blosxom-plugin-for-generating-facebook-commen.html</id><summary type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="http://www.blosxom.com/"&gt;Blosxom&lt;/a&gt; to power my dog agility blog for over 6
years. In the past year or so I've enabled &lt;a class="reference external" href="http://developers.facebook.com/docs/reference/plugins/comments"&gt;Facebook comments&lt;/a&gt; in
addition to my site's own comment plugin. I ran into a problem using
Facebook's comments: if the user enters a comment on a page and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="http://www.blosxom.com/"&gt;Blosxom&lt;/a&gt; to power my dog agility blog for over 6
years. In the past year or so I've enabled &lt;a class="reference external" href="http://developers.facebook.com/docs/reference/plugins/comments"&gt;Facebook comments&lt;/a&gt; in
addition to my site's own comment plugin. I ran into a problem using
Facebook's comments: if the user enters a comment on a page and the URL
has any additional URL parameters then the comment is only associated
with the page when accessed with those parameters, others hitting the
page w/o parameters won't see the comments.&lt;/p&gt;
&lt;p&gt;This behavior is &lt;a class="reference external" href="http://developers.facebook.com/docs/reference/fbml/comments_%28XFBML%29"&gt;documented by Facebook&lt;/a&gt; when the &lt;tt class="docutils literal"&gt;xid&lt;/tt&gt; attribute
isn't set in &lt;tt class="docutils literal"&gt;fb:comments&lt;/tt&gt; HTML element. I didn't think I'd encounter this
situation since my blog post URLs don't contain any parameters. However,
when people link to one of my articles within Facebook, Facebook appends
various parameters to the base URL.&lt;/p&gt;
&lt;p&gt;The solution is to specify an &lt;tt class="docutils literal"&gt;xid&lt;/tt&gt; attribute in the &lt;tt class="docutils literal"&gt;fb:comments&lt;/tt&gt; element
containing the URL encoded URL of the page (Facebook's default &lt;tt class="docutils literal"&gt;xid&lt;/tt&gt;).
This causes existing comments to show up and causes comments created
when the page is loaded with URL parameters to use the same encoded URL.&lt;/p&gt;
&lt;p&gt;So I created a simple Blosxom plugin to perform the encoding so the
encoded URL can be placed in the story.html template:&lt;/p&gt;
&lt;pre class="code perl literal-block"&gt;
&lt;span class="c1"&gt;# Blosxom Plugin: urlencode -*- perl -*-&lt;/span&gt;
&lt;span class="c1"&gt;# Author: Steve Schwarz &amp;lt;http://agilitynerd.com/&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# 2010-NOV-22    0.1 initial version.package urlencode;&lt;/span&gt;
&lt;span class="c1"&gt;# puts the urlencoded string of the URL for this page&lt;/span&gt;
&lt;span class="c1"&gt;# into $urlencode::url without any params# use this in fb.comments xid to give the same xid&lt;/span&gt;
&lt;span class="c1"&gt;# even when query params are provided&lt;/span&gt;
&lt;span class="c1"&gt;# -------------------&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;CGI&lt;/span&gt; &lt;span class="sx"&gt;qw/:standard/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;URI::Escape&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sub&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sub&lt;/span&gt; &lt;span class="nf"&gt;story&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$story_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$title_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$body_ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;&amp;#64;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;urlencode::&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uri_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$blosxom::url/${blosxom::path_info}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Then it is used in the story template:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;fb:comments width=&amp;quot;550&amp;quot; numposts=&amp;quot;5&amp;quot; xid=&amp;quot;$urlencode::url&amp;quot;&amp;gt;&amp;lt;/fb:comments&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Now my readers won't have to worry that their comments won't show up.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="comments"></category><category term="facebook"></category><category term="perl"></category></entry><entry><title>Obtain Short URLs and QR-Codes for Django Apps</title><link href="https://tech.agilitynerd.com/obtain-short-urls-and-their-qr-codes-for-djan.html" rel="alternate"></link><published>2010-10-22T04:00:00-05:00</published><updated>2010-10-22T04:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-10-22:/obtain-short-urls-and-their-qr-codes-for-djan.html</id><summary type="html">&lt;p&gt;Lately I've been interested in improving the interaction of my
&lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses&lt;/a&gt; website for mobile users. One such improvement is to add
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/QR_Code"&gt;QR Codes&lt;/a&gt; (aka 2D barcodes) representing the page URLs to the printed
representations of pages served as PDFs.&lt;/p&gt;
&lt;p&gt;I found that developers have reverse engineered the &amp;quot;api&amp;quot; of the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lately I've been interested in improving the interaction of my
&lt;a class="reference external" href="http://agilitycourses.com"&gt;agilitycourses&lt;/a&gt; website for mobile users. One such improvement is to add
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/QR_Code"&gt;QR Codes&lt;/a&gt; (aka 2D barcodes) representing the page URLs to the printed
representations of pages served as PDFs.&lt;/p&gt;
&lt;p&gt;I found that developers have reverse engineered the &amp;quot;api&amp;quot; of the
&lt;a class="reference external" href="http://goo.gl"&gt;goo.gl&lt;/a&gt; URL shortening web site. In my brief testing it is very fast.
What makes that service extra useful is by adding &amp;quot;.qr&amp;quot; to a shortened
URL it returns a PNG image of the QR Code for the shortened URL. That
made it perfect for providing both short text and QR Code URL
representations for my printed documents.&lt;/p&gt;
&lt;p&gt;I threw together a few functions and put them in a module to make it
easy to shorten a long URL, obtain the QR Code PNG and store it using
&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/topics/files/"&gt;Django's Storage functionality&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;osimport&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&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;simplejson&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;googl_shorten_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;
    Returns goo.gl shortened url for the provided long_url.
    Code taken from: http://djangosnippets.org/snippets/2220/
    Parameters:
    - `long_url`: the url to supply to goo.gl to be shortened.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'security_token'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;long_url&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://goo.gl/api/shorten'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&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;simplejson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="s1"&gt;'short_url'&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;googl_qrcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;googl_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;
    Return file containing qr code image file for the given goo.gl url.
    Parameters:
    - `googl_url`: url from which to obtain the qr code.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;googl_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.qr'&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_url_qr_code_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_image_file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;
    Return goo.gl shortened url and storage name of qr code corresponding to
    the shortened url for the supplied full url. Contacts goo.gl to shorten
    the supplied long url then downloads and stores the qr code image file
    in the storage instance using the file path and the shortened url name
    as the storage name.
    Parameters:
    - `long_url`: the url to shorten.
    - `storage': a Django storage instance into which to store the qr code
    image.
    - `storage_image_file_path`: file system path to prepend to shortened
    url. This path must exist prior to calling this function.
    &amp;quot;&amp;quot;&amp;quot;&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;googl_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;googl_shorten_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;qr_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;googl_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.qr'&lt;/span&gt;
        &lt;span class="n"&gt;qr_code_name&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;path&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="n"&gt;storage_image_file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qr_file_name&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;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qr_code_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;qr_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qr_code_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;qr_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;googl_qrcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;googl_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;qr_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;googl_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;qr_code_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;googl_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qr_code_name&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Yes, it has a nasty bare try/except. For my uses this is optional
functionality so I never want a failure to stop the main functionality
of the views that use it. Add exception handling appropriate for your
needs.&lt;/p&gt;
&lt;p&gt;The main entry point is &lt;tt class="docutils literal"&gt;get_url_qr_code_image()&lt;/tt&gt;. Here is an example
of its use (assuming you save the code in googl.py):&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;googl&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.files.storage&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;short_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qr_code_storage_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;googl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_url_qr_code_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://google.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_storage&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;short_urlu&lt;/span&gt;&lt;span class="s1"&gt;'http://goo.gl/mR2d'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;qr_code_storage_nameu&lt;/span&gt;&lt;span class="s1"&gt;'mR2d.qr'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qr_code_storage_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;'/home/dev/agilitycourses/static/mR2d.qr'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qr_code_storage_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;'mR2d.qr'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Hope you find this useful.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="googl"></category><category term="python"></category><category term="qrcode"></category></entry><entry><title>Adding pyrsvg to a virtualenv created with --no-site-packages</title><link href="https://tech.agilitynerd.com/adding-rsvg-to-a-virtualenv-created-with-no-s.html" rel="alternate"></link><published>2010-10-12T03:02:00-05:00</published><updated>2010-10-12T03:02:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-10-12:/adding-rsvg-to-a-virtualenv-created-with-no-s.html</id><summary type="html">&lt;p&gt;I set up my development and deployment environments on Ubuntu with
&lt;a class="reference external" href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--no-site-packages&lt;/span&gt;&lt;/tt&gt; option to isolate them from
packages in the system installation. My application uses &lt;a class="reference external" href="http://cairographics.org/pyrsvg/"&gt;pyrsvg&lt;/a&gt; and
it is installed by default as a system package. Consequently I had to
link the shared libraries it installs (w …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I set up my development and deployment environments on Ubuntu with
&lt;a class="reference external" href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--no-site-packages&lt;/span&gt;&lt;/tt&gt; option to isolate them from
packages in the system installation. My application uses &lt;a class="reference external" href="http://cairographics.org/pyrsvg/"&gt;pyrsvg&lt;/a&gt; and
it is installed by default as a system package. Consequently I had to
link the shared libraries it installs (w/in gtk) into my virtualenv.&lt;/p&gt;
&lt;p&gt;Here are the links I created (&lt;tt class="docutils literal"&gt;workon&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;cdsitepackages&lt;/tt&gt; are
&lt;a class="reference external" href="http://www.doughellmann.com/projects/virtualenvwrapper/"&gt;virtualenvwrapper&lt;/a&gt; shell aliases):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ workon project
$ cdsitepackages
$ ln -s /var/lib/python-support/python2.6/gtk-2.0/rsvg.so .
$ ln -s /var/lib/python-support/python2.6/gtk-2.0/gobject .
$ ln -s /var/lib/python-support/python2.6/gtk-2.0/glib .
&lt;/pre&gt;
</content><category term="python"></category><category term="python"></category><category term="rsvg"></category><category term="virtualenv"></category></entry><entry><title>Mobile Web Site Redirects in Django</title><link href="https://tech.agilitynerd.com/conditional-mobile-web-site-redirect-in-djang.html" rel="alternate"></link><published>2010-10-05T14:34:00-05:00</published><updated>2010-10-05T14:34:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-10-05:/conditional-mobile-web-site-redirect-in-djang.html</id><summary type="html">&lt;p&gt;For the mobile version of agilitycourses.com I wanted to follow the
approach Google appears to be using on some of its sites:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the user views agilitycourses.com from a desktop browser they
should see the standard/desktop version of the site.&lt;/li&gt;
&lt;li&gt;If the user views agilitycourses.com from …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;For the mobile version of agilitycourses.com I wanted to follow the
approach Google appears to be using on some of its sites:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the user views agilitycourses.com from a desktop browser they
should see the standard/desktop version of the site.&lt;/li&gt;
&lt;li&gt;If the user views agilitycourses.com from a mobile browser they
should be redirected to a mobile domain (m.agilitycourses.com).&lt;/li&gt;
&lt;li&gt;The mobile version of the website includes a link to the standard
version.&lt;/li&gt;
&lt;li&gt;If the mobile user chooses the standard website they should &amp;quot;stick&amp;quot;
on that site and not be redirected to the mobile site.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted to run two different websites but share templates and have the
templates and css change for the mobile site. That meant that I'd need
to set a variable(s) in the request to use to generate the appropriate
HTML. So I found the simplest mobile device detector &lt;a class="reference external" href="http://code.google.com/p/minidetector/"&gt;minidetector&lt;/a&gt; and
initially used that. I later found &lt;a class="reference external" href="http://github.com/shelfworthy/minidetector"&gt;Chris Drackett's fork&lt;/a&gt; has a number
of useful enhancements and switched to it.&lt;/p&gt;
&lt;p&gt;But minidetector didn't provide the ability to redirect to another site.
I found &lt;a class="reference external" href="http://www.packtpub.com/article/multiple-templates-in-django"&gt;Scott Newman's article&lt;/a&gt; on using multiple templates which had
a section on performing the redirect and storing the user's selection in
the session. So I forked Chris' minidetector and modified it to include
the redirect and session storage. At the same time I decided to store
all the minidetector variables into the session and add them, via
middleware, to the request so the raw request wouldn't have to be parsed
each time. My fork is &lt;a class="reference external" href="http://github.com/saschwarz/minidetector"&gt;available here&lt;/a&gt; with details on the new
configuration options.&lt;/p&gt;
&lt;p&gt;I'm using two domains so I can track analytics for the mobile and
non-mobile sites separately and allow users to bookmark the desired
site's pages. I use Google Analytics (via django-google-analytics) and
Awstats for analytics.&lt;/p&gt;
&lt;p&gt;Since I'm using two separate domain and sharing everything else I'm
using a setup similar to the one described by &lt;a class="reference external" href="http://www.nerdydork.com/mobile-app-on-subdomain-with-django.html"&gt;Dustin Davis&lt;/a&gt;. I have a
settings.py file and a mobile_settings.py that only overrides the
features I need:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from settings import *
SITE_ID = 2
CACHE_MIDDLEWARE_KEY_PREFIX = &amp;quot;m.ac-&amp;quot;
&lt;/pre&gt;
&lt;p&gt;I use a different memcached key prefix so the cached pages for the
mobile site don't clash with those for the desktop site.&lt;/p&gt;
&lt;p&gt;I setup m.agilitycourses on my server using the same &lt;a class="reference external" href="http://tech.agilitynerd.com/configuring-runit-for-gunicorn-and-django-ins"&gt;Gunicorn setup I
used for agilitycourses.com&lt;/a&gt; with the only changes being specifying the
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--bind&lt;/span&gt; address/port&lt;/tt&gt; and the name of the mobile settings file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/sh
GUNICORN=/home/user/virtualenvs/myapp/bin/gunicorn_django
ROOT=/home/user/source/myapp
PID=/var/run/myapp.pid
if [ -f $PID ]
&amp;nbsp;&amp;nbsp;&amp;nbsp; then rm $PID fi
cd $ROOT
exec $GUNICORN --bind 127.0.0.1:8001 -c $ROOT/gunicorn.conf.py --pid=$PID $ROOT/mobile_settings.py
&lt;/pre&gt;
&lt;p&gt;If my templates/content start to diverge more significantly between the
mobile and desktop sites I may set the TEMPLATE_DIRS differently in the
mobile_settings file. Or I can move to Dustin's approach and create a
new application containing the urls.py and views.py specific to my
mobile deployment. I would think diverging further would call for a
refactoring of the common functionality to its own application which
could be imported into separate code branches for each domain.&lt;/p&gt;
</content><category term="python"></category><category term="django"></category><category term="gunicorn"></category><category term="minidetector"></category><category term="mobile"></category></entry><entry><title>Debug Site for Website Redirects By Referer String</title><link href="https://tech.agilitynerd.com/debug-tool-for-mobile-website-selection-by-re.html" rel="alternate"></link><published>2010-09-29T18:42:00-05:00</published><updated>2010-09-29T18:42:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-09-29:/debug-tool-for-mobile-website-selection-by-re.html</id><summary type="html">&lt;p&gt;I'm adding an &amp;quot;m&amp;quot; subdomain to agilitycourses.com to provide a better
mobile browsing experience. I'm using the referrer string in Django
middleware (currently using &lt;a class="reference external" href="http://code.google.com/p/minidetector/"&gt;minidetector&lt;/a&gt;) to detect whether the
client is mobile and redirect them to the mobile site. Since it is
likely that some folks will/won't get …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm adding an &amp;quot;m&amp;quot; subdomain to agilitycourses.com to provide a better
mobile browsing experience. I'm using the referrer string in Django
middleware (currently using &lt;a class="reference external" href="http://code.google.com/p/minidetector/"&gt;minidetector&lt;/a&gt;) to detect whether the
client is mobile and redirect them to the mobile site. Since it is
likely that some folks will/won't get appropriately redirected I was
looking for an easy way for them to tell me when they were incorrectly
redirected. I'd need to know their referer string.&lt;/p&gt;
&lt;p&gt;A little googling turned up a nice one purpose website:
&lt;a class="reference external" href="http://www.whatismyreferrer.com/"&gt;www.whatismyreferrer.com/&lt;/a&gt;&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="mobile"></category><category term="referrer"></category><category term="webdevelopment"></category></entry><entry><title>Configuring Runit for Gunicorn and Django Installed in a Virtualenv on Ubuntu</title><link href="https://tech.agilitynerd.com/configuring-runit-for-gunicorn-and-django-ins.html" rel="alternate"></link><published>2010-09-08T03:08:00-05:00</published><updated>2010-09-08T03:08:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-09-08:/configuring-runit-for-gunicorn-and-django-ins.html</id><summary type="html">&lt;p&gt;I couldn't find any documentation that covered all the pieces for
configuring my latest Django site so I hope this helps someone else out.&lt;/p&gt;
&lt;p&gt;I had used &lt;tt class="docutils literal"&gt;mod_wsgi&lt;/tt&gt; under Apache for my other Django sites. But now I'm
using different python versions for the sites (until if/when I update …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I couldn't find any documentation that covered all the pieces for
configuring my latest Django site so I hope this helps someone else out.&lt;/p&gt;
&lt;p&gt;I had used &lt;tt class="docutils literal"&gt;mod_wsgi&lt;/tt&gt; under Apache for my other Django sites. But now I'm
using different python versions for the sites (until if/when I update
the older sites) and I wasn't getting the correct versions of some
python libraries (even though virtualenv apeared to be putting the
appropriate python packages at the start of the sys.path). So I decided
to configure Apache to ProxyPass to &lt;a class="reference external" href="http://gunicorn.org/"&gt;Gunicorn&lt;/a&gt; so I could run my
Django app in its virtualenv without it getting any other python
modules.&lt;/p&gt;
&lt;div class="section" id="installing-gunicorn"&gt;
&lt;h2&gt;Installing Gunicorn&lt;/h2&gt;
&lt;p&gt;I installed Gunicorn into the virtualenv for my application, which
simplifies using gunicorn from the command line. Assuming
&lt;tt class="docutils literal"&gt;/home/user/virtualenvs/myapp&lt;/tt&gt; is the location of the virtualenv:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ source /home/user/virtualenvs/myapp/bin/activate
$ pip install gunicorn

# or
$ easy_install gunicorn
&lt;/pre&gt;
&lt;p&gt;This copies &lt;tt class="docutils literal"&gt;gunicorn_django&lt;/tt&gt; to the &lt;tt class="docutils literal"&gt;/home/user/virtualenvs/myapp/bin&lt;/tt&gt;
directory. Test gunicorn with your app, assuming your Django app is
located at &lt;tt class="docutils literal"&gt;/home/user/source/myapp&lt;/tt&gt;, as follows:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ source /home/user/virtualenvs/myapp/bin/activate
(myapp)$ cd /home/user/source/myapp
(myapp)$ gunicorn_django
&lt;/pre&gt;
&lt;p&gt;Gunicorn starts myapp using the &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file in the current
directory on &lt;tt class="docutils literal"&gt;127.0.0.1:8000&lt;/tt&gt;. Ctrl-C to stop the process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-runit-on-ubuntu"&gt;
&lt;h2&gt;Installing Runit on Ubuntu&lt;/h2&gt;
&lt;p&gt;There are two &lt;a class="reference external" href="http://smarden.org/runit/index.html"&gt;runit&lt;/a&gt; packages. You want the one that only runs
services you add to it:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ sudo apt-get install runit
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
&amp;nbsp; runit-run socklog-run
The following NEW packages will be installed:
&amp;nbsp; runit0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 0B/113kB of archives.After this operation, 537kB of additional disk space will be used.
Selecting previously deselected package runit.
(Reading database ... 209845 files and directories currently installed.)
Unpacking runit (from .../runit_2.0.0-1ubuntu2_i386.deb) ...
Processing triggers for man-db ...
Setting up runit (2.0.0-1ubuntu2) ...
runsvdir (start) waiting
runsvdir (start) startingrunsvdir (start) pre-start
runsvdir (start) spawned, process 9575
runsvdir (start) post-start, (main) process 9575
runsvdir (start) running, process 9575
&lt;/pre&gt;
&lt;p&gt;You'll want to create a directory for the application and a run script
in &lt;tt class="docutils literal"&gt;/etc/service:&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ sudo mkdir /etc/service/myapp
$ sudo vi /etc/service/myapp/run
# enter the run script I'll show below
$ sudo chmod +x /etc/service/myapp/run
# stop runit from trying to run gunicorn until we are ready
$ sudo sv stop myapp
ok: down: myapp: 0s, normally up
&lt;/pre&gt;
&lt;p&gt;The example run script checked into Gunicorn had some syntax errors
and wasn't quite what I wanted. Here's my version:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
#!/bin/sh
GUNICORN=/home/user/virtualenvs/myapp/bin/gunicorn_django
ROOT=/home/user/source/myapp
PID=/var/run/myapp.pid

if [ -f $PID ]
&amp;nbsp;&amp;nbsp;&amp;nbsp; then rm $PID
fi

cd $ROOT
exec $GUNICORN -c $ROOT/gunicorn.conf.py --pid=$PID
&lt;/pre&gt;
&lt;p&gt;You can create a &lt;a class="reference external" href="http://gunicorn.org/configure.html"&gt;configuration file for gunicorn&lt;/a&gt; to use or just
create an empty file for now:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ touch /home/user/source/myapp/gunicorn.conf.py
&lt;/pre&gt;
&lt;p&gt;If you have multiple appserver you'll need to run gunicorn on
different ports, you can put the configuration in the gunicorn.conf.py
file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
bind = &amp;quot;127.0.0.1:8111&amp;quot;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-it-together"&gt;
&lt;h2&gt;Putting it Together&lt;/h2&gt;
&lt;p&gt;Now you can test that the run script works when run as root:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo /etc/service/myapp/run
&lt;/pre&gt;
&lt;p&gt;Gunicorn should start and start the appserver. If it fails you can
debug the script via:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo bash -x /etc/service/myapp/run
&lt;/pre&gt;
&lt;p&gt;Tell runit to start and keep gunicorn running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo sv start myapp
ok: run: myapp: (pid 7540) 0s
$ sudo sv status myapp
run: myapp: (pid 7540) 1s
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="apache"></category><category term="django"></category><category term="gunicorn"></category><category term="runit"></category><category term="ubuntu"></category><category term="virtualenv"></category></entry><entry><title>MOTD</title><link href="https://tech.agilitynerd.com/motd.html" rel="alternate"></link><published>2010-08-31T20:21:00-05:00</published><updated>2010-08-31T20:21:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-08-31:/motd.html</id><summary type="html">&lt;p&gt;I've always preferred maintainable code over clever code:&lt;/p&gt;
&lt;blockquote&gt;
Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it. - Brian W. Kernighan&lt;/blockquote&gt;
&lt;p&gt;Kernighan had it right all those …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've always preferred maintainable code over clever code:&lt;/p&gt;
&lt;blockquote&gt;
Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it. - Brian W. Kernighan&lt;/blockquote&gt;
&lt;p&gt;Kernighan had it right all those years ago.&lt;/p&gt;
</content><category term="programming"></category></entry><entry><title>Confidently Refactoring Django URLs, Views, and Templates</title><link href="https://tech.agilitynerd.com/confidently-refactoring-django-urls-views-and.html" rel="alternate"></link><published>2010-08-22T05:12:00-05:00</published><updated>2010-08-22T05:12:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-08-22:/confidently-refactoring-django-urls-views-and.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://googility.com/"&gt;Googility.com&lt;/a&gt; is my first Django website and under the covers the
oldest code looked like it. I had originally written it with the sole
intent of allowing people to enter dog agility businesses and websites
into a database that I could use to create a Dog Agility &lt;a class="reference external" href="http://www.google.com/cse/"&gt;Google Custom …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="http://googility.com/"&gt;Googility.com&lt;/a&gt; is my first Django website and under the covers the
oldest code looked like it. I had originally written it with the sole
intent of allowing people to enter dog agility businesses and websites
into a database that I could use to create a Dog Agility &lt;a class="reference external" href="http://www.google.com/cse/"&gt;Google Custom
Search Engine&lt;/a&gt;. The primary mistake I made was making the &amp;quot;project&amp;quot; (in
Django speak) effectively equivalent to the primary application. In
other words I didn't divide the major features of the site into
standalone applications (which would allow them to be more easily
reused, extended and tested).&lt;/p&gt;
&lt;p&gt;As I continued to work on it I learned more about organizing Django
projects. When I added the periodical search to the website I created it
as a standalone application. I recently split out my
&lt;a class="reference external" href="http://github.com/saschwarz/django-stw"&gt;django-shrinktheweb&lt;/a&gt; application from the main code base.&lt;/p&gt;
&lt;p&gt;The Custom Search Engine (CSE) functionality is a worthwhile application
that I'm planning on releasing as its own reusable application. I had
already created an application directory called &amp;quot;cse&amp;quot; into which I had
placed my models, views, urls, and tests specific to the CSE
functionality. But I wanted to make the following changes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Move CSE templates into a cse template subdirectory&lt;/li&gt;
&lt;li&gt;Name the templates to match the views that use them&lt;/li&gt;
&lt;li&gt;Name the urls in the urls.py prefixed with the application name (&amp;quot;cse_&amp;quot;)&lt;/li&gt;
&lt;li&gt;Covert all reverse() calls in the views and url template tags to use
the named urls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are enough changes that I was concerned that I might miss
something that would fail either in the view code or in rendering of the
templates.&lt;/p&gt;
&lt;p&gt;The Django test client makes it easy to test the forward and reverse url
matching, calling the view and rendering the template. It is kind of a
coarse grained test but the changes I was making were perfect for this
tool. Given a urls.py:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cse.views'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'^site/view/(?P&amp;lt;id&amp;gt;d+)/$'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cse_view'&lt;/span&gt;&lt;span class="p"&gt;),)&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and a view:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cse/view.html'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Display an end user read only view of the site information&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_object_or_404&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&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;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'site'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="s1"&gt;'labels'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_labels_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="p"&gt;},&lt;/span&gt;
                          &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I then wrote a test class to create the required test instances and
tests for each url to verify that the url can be found by name (via
reverse()), the url maps to a view, the view invokes the desired
template(s), and the {% url %} calls within the template can all be
resolved:&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.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.urlresolvers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cse.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Annotation&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewsTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&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;setUp&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&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;ROOT_URLCONF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;
        &lt;span class="c1"&gt;# can provide a custom urls.py for testing so the tests can be run when&lt;/span&gt;
        &lt;span class="c1"&gt;# the application is incorporated into another project&lt;/span&gt;
        &lt;span class="c1"&gt;# settings.ROOT_URLCONF = 'cse.tests.cse_test_urls'&lt;/span&gt;
        &lt;span class="c1"&gt;# override the template context processors if there are special ones in place&lt;/span&gt;
        &lt;span class="c1"&gt;# that either you want to test or want to avoid&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;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Create some instances on which we can invoke views&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;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;)&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;label&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Site Name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'http://example.com/'&lt;/span&gt;&lt;span class="p"&gt;)&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;annotation&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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;label&lt;/span&gt;&lt;span class="p"&gt;)&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;annotation&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;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&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="c1"&gt;# put settings back so the next tests aren't effected&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt; &lt;span class="o"&gt;=&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;ROOT_URLCONF&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt; &lt;span class="o"&gt;=&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;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_view&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&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;client&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;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cse_view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&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;annotation&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&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;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cse/view.html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The normal unittest asserts are available in the tests. I'm using one of
the &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/topics/testing/#assertions"&gt;special asserts provided by the Django test Client&lt;/a&gt; to verify that
the template I expected was used. All the templates used (due to
template inheritance) are collected by the client and can also be
verified.&lt;/p&gt;
&lt;p&gt;I used these tests in a TDD-ish manner, I wrote the test for a view, ran
the tests and kept resolving errors in the templates as I made the
changes in my bullet list. It made a tedious job simple and gave me good
confidence that I'd found all the renamed urls, views, and templates.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="googility"></category><category term="python"></category><category term="tdd"></category><category term="testing"></category></entry><entry><title>Haystack Search Result Ordering and Pre-Rendering Results</title><link href="https://tech.agilitynerd.com/haystack-search-result-ordering-and-pre-rende.html" rel="alternate"></link><published>2010-08-10T03:41:00-05:00</published><updated>2010-08-10T03:41:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-08-10:/haystack-search-result-ordering-and-pre-rende.html</id><summary type="html">&lt;p&gt;I use &lt;a class="reference external" href="http://haystacksearch.org/"&gt;Haystack&lt;/a&gt; and the Python &lt;a class="reference external" href="http://whoosh.ca/"&gt;Whoosh&lt;/a&gt; project to provide search
over ~3400 articles in my &lt;a class="reference external" href="http://googility.com"&gt;Googility.com&lt;/a&gt; database. I had originally
implemented the search in the &amp;quot;simplest way that works&amp;quot;. I was making
some other enhancement to Googility and noticed the search result page
had two undesirable&amp;nbsp; behaviors:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;I use &lt;a class="reference external" href="http://haystacksearch.org/"&gt;Haystack&lt;/a&gt; and the Python &lt;a class="reference external" href="http://whoosh.ca/"&gt;Whoosh&lt;/a&gt; project to provide search
over ~3400 articles in my &lt;a class="reference external" href="http://googility.com"&gt;Googility.com&lt;/a&gt; database. I had originally
implemented the search in the &amp;quot;simplest way that works&amp;quot;. I was making
some other enhancement to Googility and noticed the search result page
had two undesirable&amp;nbsp; behaviors:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The ordering of results was basically random for all matching
articles. For the domain of magazine article search having a bias
toward the most recent publications would be more desirable.&lt;/li&gt;
&lt;li&gt;Looking at the django-debug-toolbar output each element in the search
results was hitting the database twice (once for the Article instance
and again for its corresponding Periodical). So a single result page
was making as many as 60 database selects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Haystack provides mechanisms to help with both of these issues.&lt;/p&gt;
&lt;p&gt;Imposing an Order on the SearchQuerySet&lt;/p&gt;
&lt;p&gt;Haystack models search using an API based on Django's QuerySet. The
only thing to remember is it performs its queries over the Haystack
SearchIndex subclass(es) you create instead of over the Django ORM. So
you define a SearchIndex subclass that contains the data from the
application's model overwhich you'd like to search. You can also define
additional fields that can be used to modify the results of the query.
Here is my magazine Article search index:&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;haystack.sites&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;haystack&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;periodicals.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchIndex&lt;/span&gt;&lt;span class="p"&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;indexes&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;document&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="n"&gt;use_template&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="n"&gt;pub_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indexes&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;model_attr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'issue__pub_date'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The text field contains the &amp;quot;document&amp;quot; over which the search engine
(Whoosh) will actually perform the search. I'm using the template
feature that allows me to use Django templates to format the data
presented to the search engine.&lt;/p&gt;
&lt;p&gt;I added the pub_date field to the index to allow the matching search
results to be ordered by the pub_date field. The 'issue__pub_date'
syntax mirrors the Django QuerySet syntax and means extract the
&amp;quot;pub_date&amp;quot; attribute of the Article's &amp;quot;issue&amp;quot; attribute (it joins
Article to Publication and get's the Publication's published date).&lt;/p&gt;
&lt;p&gt;Then the urls.py is modified to change the SearchQuerySet passed to
the default haystacksearch view to order by the ArticleIndex's pub_date
attribute:&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;haystack.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;haystack.query&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchQuerySet&lt;/span&gt;

&lt;span class="c1"&gt;# query results with most recent publication date first&lt;/span&gt;
&lt;span class="n"&gt;sqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SearchQuerySet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-pub_date'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'^search/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;SearchView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                               &lt;span class="n"&gt;load_all&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="n"&gt;searchqueryset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'haystack_search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;snip&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Pre-Rendering Result HTML&lt;/p&gt;
&lt;p&gt;Since I have only a few thousand records I decided to follow the
&lt;a class="reference external" href="http://docs.haystacksearch.org/dev/best_practices.html#avoid-hitting-the-database"&gt;Haystack Best Practices for Not Hitting the Database&lt;/a&gt;. This solution
trades space in the Whoosh index files by generating the HTML that will
be displayed when each article matches along with the data used by
Whoosh to match articles to search keywords. The changes were pretty
simple. In the ArticleIndex:&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;haystack.sites&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;haystack&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;periodicals.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchIndex&lt;/span&gt;&lt;span class="p"&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;indexes&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;document&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="n"&gt;use_template&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="n"&gt;pub_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indexes&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;model_attr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'issue__pub_date'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# pregenerate the search result HTML for an Article&lt;/span&gt;
    &lt;span class="c1"&gt;# this avoids any database hits when results are processed&lt;/span&gt;
    &lt;span class="c1"&gt;# at the cost of storing all the data in the Haystack index&lt;/span&gt;
    &lt;span class="n"&gt;result_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indexes&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;indexed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_template&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="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;``&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The use_template keyword requires you to create a Django template file
that is used during index creation to build the HTML that will be
displayed. The only peculiarity I found was figuring out where the
template should live. On my system it was at
templates/search/indexes/periodicals/article_result_text.txt. I
understand the periodicals/article_result_text part but I haven't
looked into where the search/indexes is generated from. I imagine a
reverse() to find the url for the view and &amp;quot;indexes&amp;quot; is appended to
that...&lt;/p&gt;
&lt;p&gt;The final change is the template used to display the search results.
In order to not hit the database the object list generated by the
haystack SearchView is placed into the context used by the template and
only the result_text attribute should be accessed:&lt;/p&gt;
&lt;pre class="code xml literal-block"&gt;
{% if page.object_list %}
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;search-results-title&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Results &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;{{page.start_index}}&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;  - &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;{{page.end_index}}&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt; for &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;{{query}}&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;search-results-list&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
{% for result in page.object_list %}
  {{result.result_text|safe}}
{% endfor %}
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pagination&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;step-links&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {% if page.has_previous %}
          previous
      {% endif %}
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;current&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          Page {{ page.number }} of {{ page.paginator.num_pages }}
      &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      {% if page.has_next %}
          next
      {% endif %}
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;{% else %}&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;No matching articles found.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
{% endif %}
&lt;/pre&gt;
&lt;p&gt;The actual result is placed in the template via
{{result.result_text|safe}} the safe filter is required since the HTML
doesn't need to be escaped again - it was escaped by Django when it was
placed into the SearchIndex.&lt;/p&gt;
&lt;p&gt;So now my search results are in reverse chronological order and they
render using only 3 database queries and at least 10x faster than
before.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="haystack"></category><category term="search"></category><category term="whoosh"></category></entry><entry><title>Improving Google Ads and Google Search Descriptions</title><link href="https://tech.agilitynerd.com/improving-google-ads-and-google-search-descri.html" rel="alternate"></link><published>2010-08-03T01:46:00-05:00</published><updated>2010-08-03T01:46:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-08-03:/improving-google-ads-and-google-search-descri.html</id><summary type="html">&lt;p&gt;I was looking at the google search results for my &lt;a class="reference external" href="http://googility.com/"&gt;Googility web site&lt;/a&gt;
and noticed that the descriptions shown underneath the title often
contained text from my navigation links instead of content from the body
of the page:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="Google_description" src="/images/2010/08/13127405-google_description.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;I did some searching and found the Google Webmaster blog post about …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was looking at the google search results for my &lt;a class="reference external" href="http://googility.com/"&gt;Googility web site&lt;/a&gt;
and noticed that the descriptions shown underneath the title often
contained text from my navigation links instead of content from the body
of the page:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="Google_description" src="/images/2010/08/13127405-google_description.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;I did some searching and found the Google Webmaster blog post about
&lt;a class="reference external" href="http://googlewebmastercentral.blogspot.com/2007/09/improve-snippets-with-meta-description.html"&gt;description meta tags&lt;/a&gt;. Since almost all of the pages on Googility are
generated by fewer than a dozen Django templates I edited the templates
and inserted meta tags and filled the description in with data from each
database entry. This avoids boilerplate information that would be
ignored by Google and improves the descriptions shown to Google
searchers. Some of my pages have already been reindexed:&lt;/p&gt;
&lt;/p&gt;&lt;div class="p_embed p_image_embed"&gt;&lt;p&gt;&lt;img alt="Google_description_after" src="/images/2010/08/13127843-google_description_after.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/p&gt;&lt;p&gt;Yahoo and some other search sites use a &lt;a class="reference external" href="http://help.yahoo.com/l/us/yahoo/search/indexing/slurp-14.html"&gt;class robots-nocontent&lt;/a&gt; on any
page elements it should ignore for it's index, Unfortunately, Google
doesn't follow this standard. So I might end up making that edit to the
templates also. Looking at my site's log files it appears the Yahoo
spider is hitting my site more frequently than Google's and the Yahoo
index is more up to date. Looking at my analytics reports though Google
refers far more readers to my site than Yahoo...&lt;/p&gt;
&lt;p&gt;I also noticed that the ads served on pages containing mostly links
appeared to be using words in my navigation or other boilerplate instead
of the few lines of valuable content. More searching to the rescue and I
found this &lt;a class="reference external" href="https://www.google.com/adsense/support/bin/answer.py?answer=23168"&gt;Google Adsense article on section targeting&lt;/a&gt;. Once again
editing the dozen or so templates I used were easy to edit to add in
these HTML comment tags. Checking back a couple days later showed
improvements in the ads being generated for those pages. I keep an eye
on my Adsense click rate and see if there is any increase in ad clicks.&lt;/p&gt;
&lt;p&gt;So a couple simple edits made noticeable improvements not bad for a
couple hours investigation and implementation.&lt;/p&gt;
&lt;/p&gt;</content><category term="webdev"></category><category term="adsense"></category><category term="django"></category><category term="google"></category><category term="search"></category><category term="webdevelopment"></category></entry><entry><title>Firediff: track and save CSS edits in Firebug</title><link href="https://tech.agilitynerd.com/firediff-track-and-save-css-edits-in-firebug.html" rel="alternate"></link><published>2010-07-25T16:36:00-05:00</published><updated>2010-07-25T16:36:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-07-25:/firediff-track-and-save-css-edits-in-firebug.html</id><summary type="html">&lt;p&gt;When I'm making fiddly changes to a web page I like to tweak the CSS
using the Firefox Firebug plugin. It has the advantage of letting you
try changes quickly and see the effect. The downside has always been
that you had to then change the source CSS file to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I'm making fiddly changes to a web page I like to tweak the CSS
using the Firefox Firebug plugin. It has the advantage of letting you
try changes quickly and see the effect. The downside has always been
that you had to then change the source CSS file to include your changes.
Which increases the risk that you forget to include a change.&lt;/p&gt;
&lt;p&gt;I was thinking about that recently when I changed the color scheme on my
AgilityNerd blog and went searching for a way to at least identify the
changes. Turns out there is a nice plugin that provides exactly that
functionality. The &lt;a class="reference external" href="https://addons.mozilla.org/en-US/firefox/addon/13179/"&gt;Firediff plugin&lt;/a&gt; not only tracks changes to the CSS
it also allows you to save those changes for overwriting or diff'ing
into you CSS file(s).&lt;/p&gt;
&lt;p&gt;Like other firebug related extensions you need to enable this feature
per page by clicking on the arrow next to the new Changes tab.&lt;/p&gt;
&lt;p&gt;Here is a screenshot of the plugin in action:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="Firediff" src="/images/2010/07/12587332-firediff.png" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;You can see I've changed the line-height attribute - it shows the
previous and final values. A right mouse menu lets you save those
changes to a file or revert them.&lt;/p&gt;
&lt;p&gt;Another feature that can be configured in the Changes menu is whether to
track changes made by other JavaScript to the page. This is an
interesting feature if you were wondering how a JQuery or other
JavaScript modified the page for a given effect.&lt;/p&gt;
&lt;p&gt;This is a great plugin that I'll continue to use regularly.&lt;/p&gt;
</content><category term="webdev"></category><category term="css"></category><category term="firefoxaddon"></category><category term="webdevelopment"></category></entry><entry><title>Initial Release of django-stw</title><link href="https://tech.agilitynerd.com/initial-release-of-django-stw.html" rel="alternate"></link><published>2010-07-11T15:47:00-05:00</published><updated>2010-07-11T15:47:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-07-11:/initial-release-of-django-stw.html</id><summary type="html">&lt;p&gt;I have been using the free website thumbnail service from &lt;a class="reference external" href="http://www.shrinktheweb.com?a=988"&gt;Shrink The
Web&lt;/a&gt; on my dog agility search website &lt;a class="reference external" href="http://googility.com"&gt;Googility&lt;/a&gt; since I launched it.
It is quick and easy to use and it adds a lot to the look of the pages.&lt;/p&gt;
&lt;p&gt;I had created a simple &lt;a class="reference external" href="http://djangoproject.com/"&gt;Django&lt;/a&gt; template tag …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been using the free website thumbnail service from &lt;a class="reference external" href="http://www.shrinktheweb.com?a=988"&gt;Shrink The
Web&lt;/a&gt; on my dog agility search website &lt;a class="reference external" href="http://googility.com"&gt;Googility&lt;/a&gt; since I launched it.
It is quick and easy to use and it adds a lot to the look of the pages.&lt;/p&gt;
&lt;p&gt;I had created a simple &lt;a class="reference external" href="http://djangoproject.com/"&gt;Django&lt;/a&gt; template tag for inserting the little
snippet of HTML needed by their service.&lt;/p&gt;
&lt;p&gt;Recently they asked me to add support for their advanced features to my
template tag. I used this opportunity to convert my templatetag to a
Django application. This mostly makes it a lot easier to install but it
also let me to bundle tests and an example template with the template
tag.&lt;/p&gt;
&lt;p&gt;I kept the existing &lt;tt class="docutils literal"&gt;shrinkthewebimage&lt;/tt&gt; template tag and added a new
tag called &lt;tt class="docutils literal"&gt;stwimage&lt;/tt&gt; to enable the new features.&lt;/p&gt;
&lt;p&gt;I'm hosting the example page included in the package &lt;a class="reference external" href="http://googility.com/django-stw/"&gt;here&lt;/a&gt; so you can
see how the template tags work.&lt;/p&gt;
&lt;p&gt;I've hosted the &lt;a class="reference external" href="http://github.com/saschwarz/django-stw"&gt;project source on github&lt;/a&gt; and uploaded the &lt;a class="reference external" href="http://pypi.python.org/pypi/django-stw/"&gt;initial
release to the CheeseShop&lt;/a&gt; for easy installation.&lt;/p&gt;
</content><category term="python"></category><category term="django"></category><category term="github"></category><category term="googility"></category><category term="pypi"></category><category term="python"></category><category term="shrinktheweb"></category><category term="webdevelopment"></category></entry><entry><title>Embedding JSON Within Generated HTML</title><link href="https://tech.agilitynerd.com/embedding-json-within-generated-html.html" rel="alternate"></link><published>2010-07-08T21:03:00-05:00</published><updated>2010-07-08T21:03:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-07-08:/embedding-json-within-generated-html.html</id><summary type="html">&lt;p&gt;Ran into an interesting problem at work this past week that had a simple
and pleasing resolution. We have an in house developed JavaScript grid
on some of our pages and when users entered some text strings we'd
generate invalid &lt;a class="reference external" href="http://www.json.org/js.html"&gt;JSON&lt;/a&gt; payloads that would give the user an error
page …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ran into an interesting problem at work this past week that had a simple
and pleasing resolution. We have an in house developed JavaScript grid
on some of our pages and when users entered some text strings we'd
generate invalid &lt;a class="reference external" href="http://www.json.org/js.html"&gt;JSON&lt;/a&gt; payloads that would give the user an error
page. If they entered strings that looked like an HTML Entity i.e. &amp;amp;#13
which (with the addition of a trailing ; ) is a non-visible HTML
character (carriage return) the text wasn't displayed in the widget. To
further complicate things some of the content displayed in the grid is
HTML which is inserted into the grid as is and can contain escaped HTML
characters.&lt;/p&gt;
&lt;p&gt;The grid gets its content as a JSON payload from within a hidden div in
the HTML which is generated via a template mechanism. Heres a portion of
the template where &amp;lt;%= and %&amp;gt; stringifying of the value of the Python
variable(s)/code they surround:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;div style=&amp;quot;display:none;&amp;quot; id=&amp;quot;grid-init-args-&amp;lt;%= count %&amp;gt;&amp;quot;&amp;gt;
  &amp;lt;textarea&amp;gt;
  &amp;lt;!-- this is the JSON payload loaded via the grid JavaScript --&amp;gt;
  &amp;lt;%= [ columnsIndex, indexColumns, columns, rowBuffer, footerRows, formulas] %&amp;gt;
  &amp;lt;/textarea&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This approach has a number of problems:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;By using the template mechanism to create the JSON payload this
template was relying on the similarity of the string representation
of Python objects to JSON. After some testing I found the following
scenarios: If a string contained a single quote character the string
representation was a double quoted string around the text and the
single quote; a valid JSON string. If the string contained a double
quote character the string representation was a single quoted string
around the text and the double quote; &lt;a class="reference external" href="http://www.bennadel.com/blog/388-People-Please-Stop-Using-Single-Quotes-.htm"&gt;an invalid JSON string&lt;/a&gt;. If
the string contained both a single and a double quote the string
representation would be a single quoted string containing a slash
escaped single quote and the double quote; an invalid JSON string.
Depending on the browser (of course) the JSON string would fail to
parse correctly when the double quote was encountered within the
single quoted string.&lt;/li&gt;
&lt;li&gt;The JSON payload had to be HTML encoded (converting &amp;lt;, &amp;gt;, &amp;quot;, and &amp;amp;)
since it was parsed by the browser as HTML.&lt;/li&gt;
&lt;li&gt;The HTML encoding would encode or double encode HTML to be inserted
directly into the grid's DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The variation in single/double quoting was an easy fix, I changed to
&lt;a class="reference external" href="http://pypi.python.org/pypi/simplejson/"&gt;simplejson&lt;/a&gt;.dumps() which correctly double quotes key/values in dicts
and escapes embedded double quotes (single quotes don't need to be
escaped). I didn't time it but with the C extension it may be faster
than the template engine for our larger datasets.&lt;/p&gt;
&lt;p&gt;I played around with (not) encoding various portions of the payload and
then it hit me that I should change the grid to get its payload from a
non HTML element so that only HTML destined for insertion into the DOM
would be HTML encoded (which is as you'd expect for normal HTML
handling). I started changing the payload to be stored in JavaScript
generated in the template but didn't like the impact the change would
have on all the existing templates. So I started Googling and found &lt;a class="reference external" href="http://www.bennadel.com/blog/1603-jQuery-And-Script-Tags-As-Data-Containers.htm"&gt;Ben
Nadel's blog post on using script tags as data containers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So here's my solution:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;div style=&amp;quot;display:none;&amp;quot; id=&amp;quot;grid-init-args-&amp;lt;%= count %&amp;gt;&amp;quot;&amp;gt;
&amp;lt;script type=&amp;quot;application/json&amp;quot;&amp;gt;
&amp;lt;%= simplejson.dumps([ columnsIndex, indexColumns, columns, rowBuffer, footerRows, formulas]) %&amp;gt;
&amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;There were two changes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Used &lt;tt class="docutils literal"&gt;simplejson.dumps&lt;/tt&gt; to correctly double quote and escape double
quotes within the variables in the payload.&lt;/li&gt;
&lt;li&gt;Change the textarea to a script element.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By converting to a script tag within the hidden div the HTML parser no
longer parsed the content of the JSON payload. so the JSON payload only
needed to HTML encode HTML elements that were being inserted into the
DOM created by the grid.&lt;/p&gt;
&lt;p&gt;This change also meant I was able to delete the unnecessary HTML
encoding of non-HTML JSON payload data. Got to love solutions that
involve deleting code.&lt;/p&gt;
&lt;p&gt;Ultimately, we'll convert to loading the JSON payload as a separate AJAX
request from the page to the server, but for now this simplifies the
markup and handles all types of user input and HTML encoded characters
correctly.&lt;/p&gt;
</content><category term="webdev"></category><category term="html"></category><category term="javascript"></category><category term="json"></category><category term="python"></category><category term="webdevelopment"></category></entry><entry><title>Django Shrink The Web Template Tag Updated</title><link href="https://tech.agilitynerd.com/django-shrink-the-web-template-tag-updated.html" rel="alternate"></link><published>2010-07-01T23:00:00-05:00</published><updated>2010-07-01T23:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-07-01:/django-shrink-the-web-template-tag-updated.html</id><summary type="html">&lt;p&gt;I recently updated my &lt;a class="reference external" href="http://djangoproject.com/"&gt;Django&lt;/a&gt; template tag for simplifying the use of
&lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; images. They recently announced a CDN based
distribution of images and they took the opportunity to modify their
API.&lt;/p&gt;
&lt;p&gt;The updated template tag &lt;a class="reference external" href="http://djangosnippets.org/snippets/1744/"&gt;is on django snippets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The STW folks have asked be to extend …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently updated my &lt;a class="reference external" href="http://djangoproject.com/"&gt;Django&lt;/a&gt; template tag for simplifying the use of
&lt;a class="reference external" href="http://www.shrinktheweb.com/"&gt;Shrink The Web&lt;/a&gt; images. They recently announced a CDN based
distribution of images and they took the opportunity to modify their
API.&lt;/p&gt;
&lt;p&gt;The updated template tag &lt;a class="reference external" href="http://djangosnippets.org/snippets/1744/"&gt;is on django snippets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The STW folks have asked be to extend my template tag with support for
&lt;a class="reference external" href="http://www.shrinktheweb.com/content/shrinktheweb-pagepix-documentation.html"&gt;their PRO features&lt;/a&gt;. With luck I'll make that available sometime this
weekend.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="python"></category><category term="shrinktheweb"></category><category term="webdevelopment"></category></entry><entry><title>Multiple YouTube Videos per page using VideoLightBox</title><link href="https://tech.agilitynerd.com/multiple-youtube-videos-per-page-using-videol.html" rel="alternate"></link><published>2010-06-04T22:58:00-05:00</published><updated>2010-06-04T22:58:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-06-04:/multiple-youtube-videos-per-page-using-videol.html</id><summary type="html">&lt;p&gt;I decided to stop displaying the default &lt;a class="reference external" href="http://youtube.com"&gt;YouTube&lt;/a&gt; video players within
posts on my &lt;a class="reference external" href="http://agilitynerd.com/"&gt;AgilityNerd blog&lt;/a&gt; and I started looking for a light boxed
player. Their were two main reasons. The smallest video playback window
provided by YouTube for HD videos is too wide for my two column layout
and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I decided to stop displaying the default &lt;a class="reference external" href="http://youtube.com"&gt;YouTube&lt;/a&gt; video players within
posts on my &lt;a class="reference external" href="http://agilitynerd.com/"&gt;AgilityNerd blog&lt;/a&gt; and I started looking for a light boxed
player. Their were two main reasons. The smallest video playback window
provided by YouTube for HD videos is too wide for my two column layout
and now that I'm posting more videos the load time of the page is
delayed by the communication with all the off site webservers; serving
the YouTube static image of the video will be much faster/lighter
weight.&lt;/p&gt;
&lt;p&gt;I looked around and really liked the lightbox containing the default
YouTube player provided by &lt;a class="reference external" href="http://videolightbox.com"&gt;VideoLightBox&lt;/a&gt; and started playing around
with their demo. VideoLightBox (VLB) has an interesting approach. You
download an application (PC or Mac), configure how you want your
video(s) to look and it generates a directory of files on your local
disk (or uploads files to your website via FTP) along with an index.html
file from which you copy the code to put in the &amp;lt;head&amp;gt; and &amp;lt;body&amp;gt; of
your web page. For YouTube it also downloads a static image for each
selected video which is used as the image link within the HTML page.
Straight forward and works well.&lt;/p&gt;
&lt;p&gt;For my purposes there was a problem with their approach, its locates the
image used to launch the light box using an element id. This assumes a
single video or gallery of videos per web page. On my blog's main page
or the category pages there will be multiple videos (possibly multiple
videos within a single post). I figured a little bit of CSS and JQuery
hacking would solve the problem and it did.&lt;/p&gt;
&lt;p&gt;I decided to modify their HTML/CSS/JS to use a CSS class instead of an
element id to allow for multiple videos per page. At first I just
modified the generated files. Then I saw that VLB has template files in
their installation. So I started modifying the templates to output the
new code. Two hours later I bailed. Using &lt;a class="reference external" href="http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx"&gt;procmon&lt;/a&gt; it looks like the
client app reads the template files but then doesn't actually use the
files to generate the output files(?). I was only able to modify one of
the three template files that needed to change and have it effect the
generated files.&lt;/p&gt;
&lt;p&gt;I'm going to provide my edits to the VLB developers in case they are
interested.&lt;/p&gt;
&lt;p&gt;So the solution is to edit one of the template files and then edit two
of the generated files; not ideal but once you put the generated files
on your webserver you'll probably not touch them unless you are changing
CSS styles. The modifications aren't hard but you need to be careful and
typos will definitely break things. You should backup the VLB directory
before your start or be prepared to uninstall and reinstall from their
installation program.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Navigate to the VideoLightBox directory (i.e. C:Program Files
(x86)VideoLightBox)&lt;/li&gt;
&lt;li&gt;Change the permissions on the templates subdirectory to give your
user full access to overwrite the files&lt;/li&gt;
&lt;li&gt;For each directory in the templates subdirectory open the
videolightbox.js file in an programming editor (a keyboard macro
makes this trivial):&lt;ol class="arabic"&gt;
&lt;li&gt;Globally replace $(&amp;quot;#videogallery a[rel]&amp;quot;) with
$(&amp;quot;.videogallery a[rel]&amp;quot;).each(function(idx){$(this)&lt;/li&gt;
&lt;li&gt;Go to the end of the line and add });&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then generate the output files using the VLB executable for one or more
videos, saving the results to your local file system&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Navigate to the output directory&lt;/li&gt;
&lt;li&gt;Open the index.html file in an programming editor&lt;ol class="arabic"&gt;
&lt;li&gt;Globally replace #videogallery with .videogallery&lt;/li&gt;
&lt;li&gt;Globally replace id=&amp;quot;videogallery&amp;quot; with class=&amp;quot;videogallery&amp;quot;&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;In the engine/css subdirectory open the videolightbox.css file in an
programming editor&lt;ol class="arabic"&gt;
&lt;li&gt;Globally replace #videogallery with .videogallery&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then you can copy the files just as specified by the VLB installation
instructions.&lt;/p&gt;
&lt;p&gt;The other change I'll be making for my deployment is to rename the video
images. They are named 0.png, 1.png, etc. I'm going to put them all in a
directory on my resource webserver so I'll rename the files and their
references in the code copied from the index.html to use the YouTube
video id.&lt;/p&gt;
&lt;p&gt;I'll be changing my existing web posts over to this new scheme over
time...&lt;/p&gt;
</content><category term="webdev"></category><category term="css"></category><category term="html"></category><category term="javascript"></category><category term="jquery"></category><category term="video"></category><category term="visuallightbox"></category><category term="webdevelopment"></category><category term="youtube"></category></entry><entry><title>Blosxom hitcounter, favorites and lastread plugins for MongoDB</title><link href="https://tech.agilitynerd.com/blosxom-hitcounter-favorites-and-lastread-plugins-for-mongodb.html" rel="alternate"></link><published>2010-05-17T10:48:00-05:00</published><updated>2010-05-17T10:48:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2010-05-17:/blosxom-hitcounter-favorites-and-lastread-plugins-for-mongodb.html</id><summary type="html">&lt;p&gt;When I wrote my plugins for tracking the visits to each page
(&lt;tt class="docutils literal"&gt;hitcounter&lt;/tt&gt;), presenting the counts by popularity (&lt;tt class="docutils literal"&gt;favorites&lt;/tt&gt;), and
displaying the most recently read posts (&lt;tt class="docutils literal"&gt;lastread&lt;/tt&gt;) I used Perl's
&lt;tt class="docutils literal"&gt;Storable&lt;/tt&gt; module as a simple way to serialize the data structure to and
from disk. At the time I wasn't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I wrote my plugins for tracking the visits to each page
(&lt;tt class="docutils literal"&gt;hitcounter&lt;/tt&gt;), presenting the counts by popularity (&lt;tt class="docutils literal"&gt;favorites&lt;/tt&gt;), and
displaying the most recently read posts (&lt;tt class="docutils literal"&gt;lastread&lt;/tt&gt;) I used Perl's
&lt;tt class="docutils literal"&gt;Storable&lt;/tt&gt; module as a simple way to serialize the data structure to and
from disk. At the time I wasn't too concerned with performance, my blog
&lt;a class="reference external" href="http://agilitynerd.com"&gt;agilitynerd.com&lt;/a&gt; didn't suffer under the load of the disk reads/writes.&lt;/p&gt;
&lt;p&gt;But periodically I get DOS'd and my website becomes unresponsive. Not
too suprising since Blosxom has to run as a plain CGI script; but I'm
certain the disk writes of these plugins weren't helping. Occasionally
the Storable on disk would become corrupted which would take down the
site (not enough exception handling in the plugins).&lt;/p&gt;
&lt;p&gt;I decided to go with &lt;a class="reference external" href="http://mongodb.org"&gt;Mongo&lt;/a&gt; as the backing data store to remove the
disk reads/write from the cgi script. It supports an increment operation
(ala memcached) so multiple threads can update the hit count and MongoDB
will &amp;quot;do the right thing&amp;quot;. It also has a query API that made rewriting
favorites and lastread trivial.&lt;/p&gt;
&lt;div style="float:right;padding 20px;"&gt;&lt;p&gt;&lt;img alt="Poweredmongodbgreen75" src="/images/2010/05/9077760-PoweredMongoDBgreen75.png" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;I haven't tested the performance but my website, especially the
favorites page, seems faster. Once I write a JavaScript client to update
the hitcounts I'll be able to move the website to a fully cached
deployment while still tracking hits which should make the load of
running the agilitynerd blog very low.&lt;/p&gt;
&lt;p&gt;I've hosted the plugins on github:
&lt;a class="reference external" href="http://github.com/saschwarz/blosxom-mongodb-plugins"&gt;http://github.com/saschwarz/blosxom-mongodb-plugins&lt;/a&gt; The mongohitcounter
plugin can be configured to automatically import existing count data
from the hitcounter plugin's Storable. So conversion to the new plugins
just requires updating the template's variables to the new plugin names.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="mongodb"></category><category term="perl"></category><category term="webdevelopment"></category></entry><entry><title>Using django-sitemap with django-tagging</title><link href="https://tech.agilitynerd.com/using-django-sitemap-with-django-tagging.html" rel="alternate"></link><published>2009-11-27T17:33:00-06:00</published><updated>2009-11-27T17:33:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2009-11-27:/using-django-sitemap-with-django-tagging.html</id><summary type="html">&lt;p&gt;I was adding &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/contrib/sitemaps/"&gt;django-sitemap&lt;/a&gt; to &lt;a class="reference external" href="http://googility.com/"&gt;googility.com&lt;/a&gt; yesterday and found
that Tags don't implement &lt;tt class="docutils literal"&gt;get_absolute_url()&lt;/tt&gt;. Which makes sense since
the site developer would want to decide how to expose them in the URL
space.&lt;/p&gt;
&lt;p&gt;It is also arguable that links to pages displaying the tag view already
exist in the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was adding &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/contrib/sitemaps/"&gt;django-sitemap&lt;/a&gt; to &lt;a class="reference external" href="http://googility.com/"&gt;googility.com&lt;/a&gt; yesterday and found
that Tags don't implement &lt;tt class="docutils literal"&gt;get_absolute_url()&lt;/tt&gt;. Which makes sense since
the site developer would want to decide how to expose them in the URL
space.&lt;/p&gt;
&lt;p&gt;It is also arguable that links to pages displaying the tag view already
exist in the page for models that are already in the sitemap so they
don't need to be put in the sitemap explicitly. For example, a page for
an Article might be at &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/article/django-11-release&lt;/span&gt;&lt;/tt&gt; and that page would
contain the links to pages linked with the tags for that article e.g.
&lt;tt class="docutils literal"&gt;/tag/django/&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;/tag/python/&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;But I figured having the tag pages indexed by Google would be useful. It
also allows a different priority to be specified for the pages. So I
made a little class that derives from &lt;tt class="docutils literal"&gt;GenericSitemap&lt;/tt&gt; that allows the url
and suffix for the Tag name to be specified:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class SlugSitemap(GenericSitemap):
&amp;quot;&amp;quot;&amp;quot;Use for objects that don't implement get_absolute_url
   but have a slug field used in creating their url&amp;quot;&amp;quot;&amp;quot;

def __init__(self, info_dict, priority=None, changefreq=None):
    GenericSitemap.__init__(self, info_dict,
                            priority=priority,
                            changefreq=changefreq)
    self.url = info_dict.get('url', '/')
    self.slugfield = info_dict['slugfield']
    self.suffix = info_dict.get('suffix', '')

def location(self, obj):
    return &amp;quot;%s%s%s&amp;quot; % (self.url,
                       getattr(obj, self.slugfield),
                       self.suffix)
&lt;/pre&gt;
&lt;p&gt;Here's how I use it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sitemaps = {'tag_detail': SlugSitemap({'queryset':Tag.objects,
                                       'url':'/tag/',
                                       'slugfield':'name',
                                       'suffix':'/'},
                                       changefreq='monthly',
                                       priority='0.5'),
}
&lt;/pre&gt;
&lt;p&gt;The urls for tags are at /tag/&lt;em&gt;slugname&lt;/em&gt;/ where /tag/ is prepended to
tag.name and / is appended to the end&lt;/p&gt;
&lt;p&gt;This class can be used to create sitemap entries for any url
parameterized on a single field of an instance returned by the QuerySet.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="djangositemap"></category><category term="djangotagging"></category><category term="python"></category><category term="webdevelopment"></category></entry><entry><title>Firefox Web Developer Add-on AJAX NON-Caching Problem</title><link href="https://tech.agilitynerd.com/firefox-web-developer-add-on-ajax-non-caching-1.html" rel="alternate"></link><published>2008-12-30T04:08:00-06:00</published><updated>2008-12-30T04:08:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-12-30:/firefox-web-developer-add-on-ajax-non-caching-1.html</id><summary type="html">&lt;p&gt;I ran across some &amp;quot;interesting&amp;quot; behavior of the Firefox &lt;a class="reference external" href="https://addons.mozilla.org/en-US/firefox/addon/60"&gt;Web Developer
Add-on&lt;/a&gt; today. When editing JavaScript code I normally leave Web
Developer set to &amp;quot;disable cache&amp;quot;. This causes all images and,
significantly for my purposes, the JavaScript files to be downloaded
from the server on every page request. But I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I ran across some &amp;quot;interesting&amp;quot; behavior of the Firefox &lt;a class="reference external" href="https://addons.mozilla.org/en-US/firefox/addon/60"&gt;Web Developer
Add-on&lt;/a&gt; today. When editing JavaScript code I normally leave Web
Developer set to &amp;quot;disable cache&amp;quot;. This causes all images and,
significantly for my purposes, the JavaScript files to be downloaded
from the server on every page request. But I ran into a unexpected
problem with this setting that cost me a couple hours today.&lt;/p&gt;
&lt;p&gt;I was working on a legacy page today that uses an AJAX POST and the
response is sent to an IFRAME. The POST was successful, the results were
valid and present in the IFRAME. The problem was as soon as the content
of the IFRAME was eval'd() a GET was being sent to the server for the
URL of the page. This GET was a problem because it had a side effect of
clearing the session data (which contained the POSTed AJAX data) for the
current page. So when the page's FORM was finally POSTed the server
couldn't find the data in the session.&lt;/p&gt;
&lt;p&gt;After trying numerous code changes including disabling event handlers
and events, it finally occurred to me that the cache disable feature of
the add-on might be causing the unexpected GET. Sure enough, once I
re-enabled caching the GET stopped being sent.&lt;/p&gt;
&lt;p&gt;It was strange that the &lt;tt class="docutils literal"&gt;eval()&lt;/tt&gt; caused the GET to be requested. All
the other code around processing the IFRAME's data didn't trigger it,
only the eval. Strange but true.&lt;/p&gt;
&lt;p&gt;So just something to keep in mind when strange behavior occurs on AJAX
pages where session data is being used.&lt;/p&gt;
</content><category term="webdev"></category><category term="ajax"></category><category term="firefox"></category><category term="firefoxaddon"></category><category term="webdevelopment"></category></entry><entry><title>reCAPTCHA in Django</title><link href="https://tech.agilitynerd.com/recaptcha-in-django-1.html" rel="alternate"></link><published>2008-12-23T22:04:00-06:00</published><updated>2008-12-23T22:04:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-12-23:/recaptcha-in-django-1.html</id><summary type="html">&lt;p&gt;I first read about &lt;a class="reference external" href="http://recaptcha.net/"&gt;ReCAPTCHA&lt;/a&gt; in &lt;a class="reference external" href="http://www.wired.com/techbiz/it/magazine/15-07/ff_humancomp?currentPage=all"&gt;this article in Wired magazine&lt;/a&gt;
last year.&lt;/p&gt;
&lt;p&gt;reCAPTCHA provides a free CAPTCHA web service that
pairs together two words from OCR scanned books. One of the words is
known and the other couldn't be recognized. The user types in both words
not knowing which …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I first read about &lt;a class="reference external" href="http://recaptcha.net/"&gt;ReCAPTCHA&lt;/a&gt; in &lt;a class="reference external" href="http://www.wired.com/techbiz/it/magazine/15-07/ff_humancomp?currentPage=all"&gt;this article in Wired magazine&lt;/a&gt;
last year.&lt;/p&gt;
&lt;p&gt;reCAPTCHA provides a free CAPTCHA web service that
pairs together two words from OCR scanned books. One of the words is
known and the other couldn't be recognized. The user types in both words
not knowing which is unknown to the system. As reCAPTCHA collects the
responses for the unknown word they get human verified character
recognition. So the millions of users of the system are clearing up
millions of unrecognized words. It is a very clever human &amp;quot;cloud
computing&amp;quot; system using only seconds of human effort for each use of the
system.&lt;/p&gt;
&lt;div class="thumbnail" style="float:right;"&gt;&lt;p&gt;&lt;img alt="recaptchalogo" src="/images/2009/11/5014940-media_httpdataagilitynerdcomimagesrecaptchalogogif_IkgnpackrkjkaCy.gif" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;I'm using a &lt;a class="reference external" href="/comment-spam-and-wbcaptcha-plugin-enhancement-1.html"&gt;FIGLet based ASCII CAPTCHA&lt;/a&gt; on my websites since it was
easy to integrate into the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; writeback plugin. But I wanted to
give reCAPTCHA a try while converting my &lt;a class="reference external" href="http://googility.com/"&gt;Googility&lt;/a&gt; site to &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;.
&lt;a class="reference external" href="http://seeknuance.com/"&gt;John DeRosa&lt;/a&gt; made my job trivial by &lt;a class="reference external" href="http://seeknuance.com/2008/03/18/integrating-recaptcha-with-django/"&gt;writing up the steps with a clear example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I followed his directions which involved installing the
recaptcha-client python library on my dev and production systems and
obtaining a free public/private license key from the reCAPTCHA site.
Then I updated my Django view and template files for the one form that
needed CAPTCHA protection. It was dead simple and working within
minutes. The only minor addition I'd make to John's article is of course
you need to pass the &lt;tt class="docutils literal"&gt;captcha_error&lt;/tt&gt; variable from your view to the
template:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
return render_to_response('edit.html', {'form': form, 'captcha_error':captcha_error})
&lt;/pre&gt;
&lt;p&gt;So give reCAPTCHA a try for your next project. It was so easy to do I
might even convert my Blosxom blogs to use it via Lars Engel's
&lt;a class="reference external" href="http://blog.berlund.de/public/other/recaptcha"&gt;recaptcha plugin&lt;/a&gt;.&lt;/p&gt;
</content><category term="webdev"></category><category term="django"></category><category term="recaptcha"></category></entry><entry><title>Improving Site Navigation - Add Titles to Blosxom Pages With storytitle</title><link href="https://tech.agilitynerd.com/improving-site-navigation-add-titles-to-blosx-1.html" rel="alternate"></link><published>2008-06-20T22:03:00-05:00</published><updated>2008-06-20T22:03:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-06-20:/improving-site-navigation-add-titles-to-blosx-1.html</id><summary type="html">&lt;p&gt;I've always wanted my &lt;a class="reference external" href="http://agilitynerd.com/blog/"&gt;AgilityNerd&lt;/a&gt; blog to serve as a reference for
interesting dog agility subjects. Consequently, I'm interested in making
it as easy as possible for readers to locate, explore, and learn more
about the sport. So improving navigation is a strong interest to me.&lt;/p&gt;
&lt;p&gt;I have been reading …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've always wanted my &lt;a class="reference external" href="http://agilitynerd.com/blog/"&gt;AgilityNerd&lt;/a&gt; blog to serve as a reference for
interesting dog agility subjects. Consequently, I'm interested in making
it as easy as possible for readers to locate, explore, and learn more
about the sport. So improving navigation is a strong interest to me.&lt;/p&gt;
&lt;p&gt;I have been reading &lt;a class="reference external" href="http://www.amazon.com/Designing-Web-Navigation-Optimizing-Experience/dp/0596528108?t=agili-20"&gt;Designing Web Navigation by James Kalbach&lt;/a&gt;. In the
section discussing Browser Mechanisms Kalbach discusses the back and
forward buttons and the Session History drop down. The session history
is usually a menu that drops down from the back button (or near it). It
lists the pages the user has visited in reverse order showing their page
titles. Kalbach states: &amp;quot;Session history is a good reason to supply
meaningful browser titles&amp;quot;. That reminded me that for my blog the
session history has always just displayed &amp;quot;AgilityNerd&amp;quot; for each page
visited on my site. I was leaving out a valuable aid to my readers who
use the session history to find where they had been previously.&lt;/p&gt;
&lt;p&gt;Another benefit of setting the title in pages is in tracking site
statistics. It had always bothered me that GoogleAnalytics never showed
statistics for my pages by page title.&lt;/p&gt;
&lt;p&gt;So these two things finally moved me to take some action. I had been
thinking about writing a &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; plugin to set the title element in
the HTML head section but I've always been too busy. Luckily I didn't
have to.&lt;/p&gt;
&lt;p&gt;I found out about the storytitle plugin from Nick Leverton's
&lt;a class="reference external" href="http://www.leverton.org/blosxom"&gt;Serendipity&lt;/a&gt; blog. He had made some changes and released &lt;a class="reference external" href="http://www.leverton.org/blosxom/Software/Projects/Blosxom/storytitle.html"&gt;version 0.7
of the plugin&lt;/a&gt;. It was trivial to setup, I just followed the directions
in the plugin and had this site and agilitynerd.com/blog working within
minutes.&lt;/p&gt;
&lt;p&gt;So if you are a Blosxom user just grab this plugin. Otherwise, take the
time to set your page title sections so you can help your readers and
help analyzing your own site's statistics&lt;/p&gt;
</content><category term="webdev"></category><category term="agilitynerd"></category><category term="blosxom"></category><category term="googleanalytics"></category></entry><entry><title>Blosxom Plugin lastcommented Enhancement</title><link href="https://tech.agilitynerd.com/blosxom-plugin-lastcommented-enhancement-1.html" rel="alternate"></link><published>2008-02-29T18:50:00-06:00</published><updated>2008-02-29T18:50:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-02-29:/blosxom-plugin-lastcommented-enhancement-1.html</id><summary type="html">&lt;p&gt;I had a comment entered on my &lt;a class="reference external" href="http://agilitynerd.com/blog"&gt;dog agility blog&lt;/a&gt; that I
subsequently deleted and of course my &lt;tt class="docutils literal"&gt;lastcommented&lt;/tt&gt; plugin recorded
the comment as the most recent comment. Since the plugin uses
&lt;tt class="docutils literal"&gt;Storable&lt;/tt&gt; to store the data I couldn't manually edit the file to
remove the entry.&lt;/p&gt;
&lt;p&gt;So I modified …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had a comment entered on my &lt;a class="reference external" href="http://agilitynerd.com/blog"&gt;dog agility blog&lt;/a&gt; that I
subsequently deleted and of course my &lt;tt class="docutils literal"&gt;lastcommented&lt;/tt&gt; plugin recorded
the comment as the most recent comment. Since the plugin uses
&lt;tt class="docutils literal"&gt;Storable&lt;/tt&gt; to store the data I couldn't manually edit the file to
remove the entry.&lt;/p&gt;
&lt;p&gt;So I modified my &lt;tt class="docutils literal"&gt;lastcommented&lt;/tt&gt; plugin to allow you to specify the,
base zero, index of the entry on the URL and delete the entry. For
example to delete the second entry in the list (index 1):&lt;/p&gt;
&lt;blockquote&gt;
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://example.com/blog/?lastcommentremove=1&lt;/span&gt;&lt;/tt&gt;&lt;/blockquote&gt;
&lt;p&gt;To keep mischief makers from deleting all your comments the plugin has a
variable in its configuration section called &lt;tt class="docutils literal"&gt;lastcommentremove&lt;/tt&gt; that
you set to a non zero value to enable this feature. Since this is
probably a relatively infrequent activity (if you have blacklisting or
wbcaptcha comment spam protection enabled) I figured this was a
sufficient mechanism.&lt;/p&gt;
&lt;p&gt;You can &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/lastcommented"&gt;download the latest version of this plugin here&lt;/a&gt;.&lt;/p&gt;
&lt;/p&gt;</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Minor Additional Mods to Blosxom moreenties Plugin</title><link href="https://tech.agilitynerd.com/minor-additional-mods-to-blosxom-moreenties-p-1.html" rel="alternate"></link><published>2008-02-24T16:24:00-06:00</published><updated>2008-02-24T16:24:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-02-24:/minor-additional-mods-to-blosxom-moreenties-p-1.html</id><summary type="html">&lt;p&gt;I had previously extended Jason Clark's &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/plugins/moreentries"&gt;moreentries&lt;/a&gt; plugin for
&lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; to allow adding images to links to the previous and next
group of articles/entries in the head or foot of a Blosxom weblog. While
attempting to have valid HTML on my blogs I found that I had left an …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had previously extended Jason Clark's &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/plugins/moreentries"&gt;moreentries&lt;/a&gt; plugin for
&lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; to allow adding images to links to the previous and next
group of articles/entries in the head or foot of a Blosxom weblog. While
attempting to have valid HTML on my blogs I found that I had left an img
tag unclosed. I also found that it wasn't as easy to change the styling
of the links as I had originally planned.&lt;/p&gt;
&lt;p&gt;So I've closed the open img tags and added the configuration variable
&lt;tt class="docutils literal"&gt;$selfstyle&lt;/tt&gt; into which you can place any CSS style information you'd
like added to the &lt;tt class="docutils literal"&gt;td&lt;/tt&gt; containing each page link. For example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Set to the CSS style for $moreentries::links
$selfstyle = 'style=&amp;quot;padding: 0 3px;&amp;quot;'; # set to '' for no style
&lt;/pre&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>New Scheme Using YouTube to Get "Google Juice"?</title><link href="https://tech.agilitynerd.com/new-scheme-using-youtube-to-get-google-juice-1.html" rel="alternate"></link><published>2008-02-15T04:32:00-06:00</published><updated>2008-02-15T04:32:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-02-15:/new-scheme-using-youtube-to-get-google-juice-1.html</id><summary type="html">&lt;p&gt;In the last couple days my YouTube account has fallen victim to a new
scheme by porn websites attempting to garner Google Juice. It is pretty
straightforward. A generic YouTube account is created with a user name
like &amp;quot;babiegirl123xyz&amp;quot; and the &amp;quot;user&amp;quot; has a single line profile with the
line …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the last couple days my YouTube account has fallen victim to a new
scheme by porn websites attempting to garner Google Juice. It is pretty
straightforward. A generic YouTube account is created with a user name
like &amp;quot;babiegirl123xyz&amp;quot; and the &amp;quot;user&amp;quot; has a single line profile with the
line &amp;quot;all my pics are at the link below&amp;quot;. The link is to an adult
sex/dating site.&lt;/p&gt;
&lt;p&gt;All by itself creating a bogus YouTube account with a link to your site
won't get a site listed any higher in a Google search. But the clever
part of this approach is the &amp;quot;user&amp;quot; subscribes to many YouTube channels.
Since YouTube users often have channel pages with links to all of their
subscribers, the more subscriptions the bogus user creates; the more
incoming links they have to their profile and consequently to their
adult website. But I'd think the Google algorithm wouldn't weigh links
from YouTube too heavily anyway...&lt;/p&gt;
&lt;p&gt;I came across this scheme because I got several subscription emails from
variants of the babiegirl accounts all with the same template of a
profile page and all linking to the same website. It doesn't really
matter much to me that they go to this trouble, but it is strange since
I don't display a list of my subscribers on my channel so they don't get
any real benefit.&lt;/p&gt;
&lt;p&gt;I guess another way this scheme could help increase traffic would be
through the subscription emails. Heck I clicked on the links to see what
the site was about. It could be there are enough folks who do the same
in response to the subscription email. If only a few end up becoming
members of the adult sites it could make this a worthwhile means for
these sites to get more page hits/subscribers.&lt;/p&gt;
&lt;p&gt;Sigh...&lt;/p&gt;
&lt;/p&gt;</content><category term="webdev"></category><category term="google"></category><category term="youtube"></category></entry><entry><title>Web Site Themes for Techies</title><link href="https://tech.agilitynerd.com/web-site-themes-for-techies-1.html" rel="alternate"></link><published>2008-02-04T05:34:00-06:00</published><updated>2008-02-04T05:34:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-02-04:/web-site-themes-for-techies-1.html</id><summary type="html">&lt;p&gt;I'm no graphic artist so when it comes time to create or change the look
of my website I always have a difficult time. So I've discovered some
tools and tips that might help other non artists create reasonable
looking websites.&lt;/p&gt;
&lt;p&gt;Here's the general approach:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Select an existing template or …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I'm no graphic artist so when it comes time to create or change the look
of my website I always have a difficult time. So I've discovered some
tools and tips that might help other non artists create reasonable
looking websites.&lt;/p&gt;
&lt;p&gt;Here's the general approach:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Select an existing template or layout to use as a framework&lt;/li&gt;
&lt;li&gt;Select a photo or logo that represents the theme/color scheme of the
website&lt;/li&gt;
&lt;li&gt;Create a background or background image using a sample of the
photo/logos color&lt;/li&gt;
&lt;li&gt;Sample colors from the background, photo or logo for highlighting
other elements of the website&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll present how I applied this approach to my latest website makeover
and I'll discuss a some websites/tools I found helpful in these areas.&lt;/p&gt;
&lt;div class="section" id="selecting-a-layout"&gt;
&lt;h2&gt;Selecting a Layout&lt;/h2&gt;
&lt;p&gt;Unfortunately I don't have any suggestions for templates for specific
blog engines (apart from searching with Google). But I'll try to make up
for that with my next discovery.&lt;/p&gt;
&lt;p&gt;If you are familiar with using CSS for website customization Alessandro
Fulciniti has created a set of 40 one, two, and three column layouts on
&lt;a class="reference external" href="http://blog.html.it/layoutgala/"&gt;his website&lt;/a&gt;. They are straight forward to follow if you've done any
CSS coding. If you haven't please skip ahead to the next section for
some other tips that might be useful.&lt;/p&gt;
&lt;p&gt;I have to admit to having some problem retrofitting my old CSS design.
My issue was with the rendering of the right hand column. When the
negative offset for the column was larger than the right margin for the
containing div the right column would be rendered at the bottom of the
page. It would look OK in Firefox but not in IE.&lt;/p&gt;
&lt;p&gt;Of course it is important to test any new layout in at least Internet
Explorer and Firefox. Now that Safari is available for Windows it is
easy to test on that browser as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="select-a-photo-or-logo"&gt;
&lt;h2&gt;Select a Photo or Logo&lt;/h2&gt;
&lt;p&gt;If you already have a color logo that you use for your business then you
can help reinforce that logo by using those colors in your site design.
Since my logo is currently black and white that didn't help me much.&lt;/p&gt;
&lt;p&gt;I was looking to change my site to reflect the winter season so I went
to &lt;a class="reference external" href="http://www.sxc.hu/"&gt;StockXCHNG&lt;/a&gt; and found a nice royalty free &lt;a class="reference external" href="http://www.sxc.hu/photo/911036"&gt;snow scene&lt;/a&gt; by
photographer &lt;a class="reference external" href="http://www.sxc.hu/profile/ijsendoorn"&gt;ijsendoorn&lt;/a&gt;. There are thousands of interesting photos
suitable for use in blogs hosted on Stock.XCHNG, it is a great resource
and I heartily thank the photographers who post their work.&lt;/p&gt;
&lt;p&gt;I then used the free &lt;a class="reference external" href="http://www.gimp.org/"&gt;Gimp&lt;/a&gt; image editing software to select a section
of the appropriate size for the header of my site. The Gimp is easy to
use and there is a fair amount of online help available if you are doing
something tricky. Of course you can use Photoshop or a similar program
if you have it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="create-a-background"&gt;
&lt;h2&gt;Create a Background&lt;/h2&gt;
&lt;p&gt;This is an area where I was really at a loss. I've never been good at
creating graphics, and a tileable image is even harder to create. There
are some tutorials you can find via Google but I just wasn't up for the
challenge. Of course you can use a complementary solid color background
by sampling your photo or logo (see the next section).&lt;/p&gt;
&lt;p&gt;Fortunately I came across a software program called &lt;a class="reference external" href="http://www.ransen.com/Gliftex/Default.htm"&gt;Gliftex - An
Infinity of Tileable Graphics Designs&lt;/a&gt;. This is an interesting program
that can create a multitude of different images suitable for tiling
across the background of a web page. Gliftex has a number of algorithms
for choosing the form, color scheme, and interpretation and generating
images. &lt;a class="reference external" href="http://graphicssoft.about.com/od/productreviews/l/aaglifticintro.htm"&gt;Here is a good overview&lt;/a&gt; of the product along with some
examples.&lt;/p&gt;
&lt;p&gt;Gliftex has a feature that makes it perfect for creating a tileable
background image for a website, it can sample an image and use the
colors in the image as its palette of colors. So I took my snow scene
section and had Gliftex sample it. I pressed the Form and Interpretation
buttons a number of times and after a while came up with these
understated and simple tileable images:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image0" src="https://data.agilitynerd.com/images/bluebg05.png" /&gt;
&lt;img alt="image1" src="https://data.agilitynerd.com/images/bluebg03.png" /&gt;
&lt;img alt="image2" src="https://data.agilitynerd.com/images/bluebg02.png" /&gt;
&lt;img alt="image3" src="https://data.agilitynerd.com/images/bluebg01.png" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;As far as I'm concerned the $ 50 US for the Gliftex Home download was a
great investment. They have a free demo version available for download
if you want to play around before you buy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sample-colors-for-highlights"&gt;
&lt;h2&gt;Sample Colors for Highlights&lt;/h2&gt;
&lt;p&gt;So now I had an image for the header and a repeating image for the
background. I just needed to add some matching highlights for some other
areas of my blog to tie the whole theme together. I decided to color the
headings of my right and left sidebars to match one of the darker colors
of the header image. But how to pick out colors from the image?&lt;/p&gt;
&lt;p&gt;Gimp to the rescue again. I loaded my image again and selected Tools &amp;gt;
Color Picker. You are then presented with an eye dropper pointer. I just
clicked on interesting colors in my image to obtain a color value that I
could type into my theme's style sheet for the background colors. In the
picture below you can see the color value is &amp;quot;bec7d0&amp;quot; which you would
write as #bec7d0 in the style sheet.&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image4" src="https://data.agilitynerd.com/images/GimpColorPicker.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="section" id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;While there are an many additional changes to my theme I could make I am
pretty happy with the cool, blue-ish, wintry theme I've created:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image5" src="https://data.agilitynerd.com/images/blog_snow_theme_thumb.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Take a look at &lt;a class="reference external" href="http://agilitynerd.com/blog/"&gt;AgilityNerd&lt;/a&gt; to see it in action or, depending on when
you read this, to see my next seasonal theme. I hope some of these tips
might be helpful for helping you create your next theme.&lt;/p&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="sitedesign"></category></entry><entry><title>Stock.XCHNG - Free Stock Photos</title><link href="https://tech.agilitynerd.com/stockxchng-free-stock-photos-1.html" rel="alternate"></link><published>2008-01-19T19:41:00-06:00</published><updated>2008-01-19T19:41:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-01-19:/stockxchng-free-stock-photos-1.html</id><summary type="html">&lt;p&gt;I was looking at templates for &lt;a class="reference external" href="http://www.zen-cart.com/"&gt;ZenCart&lt;/a&gt; and came across some templates
that use images from &lt;a class="reference external" href="http://www.sxc.hu/"&gt;Stock.XCHNG&lt;/a&gt;. Over an hour ago I started browsing
the images and am really impressed with the quality and breadth of
photos that artists are making available for free use.&lt;/p&gt;
&lt;p&gt;I'll definitely use some …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was looking at templates for &lt;a class="reference external" href="http://www.zen-cart.com/"&gt;ZenCart&lt;/a&gt; and came across some templates
that use images from &lt;a class="reference external" href="http://www.sxc.hu/"&gt;Stock.XCHNG&lt;/a&gt;. Over an hour ago I started browsing
the images and am really impressed with the quality and breadth of
photos that artists are making available for free use.&lt;/p&gt;
&lt;p&gt;I'll definitely use some of these images to spice up my sites.&lt;/p&gt;
</content><category term="webdev"></category><category term="sitedesign"></category></entry><entry><title>Feed2JS - Adding AgilityNerd Headlines to Your Website</title><link href="https://tech.agilitynerd.com/feed2js-adding-agilitynerd-headlines-to-your-1.html" rel="alternate"></link><published>2008-01-11T22:24:00-06:00</published><updated>2008-01-11T22:24:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-01-11:/feed2js-adding-agilitynerd-headlines-to-your-1.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Updated 16-Aug-2006 to reflect Feed2JS's move to http://feed2js.org&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I never thought anyone would want me to &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Web_syndication"&gt;syndicate&lt;/a&gt; my blog and put
links to my latest articles on their website. But Marj Kibby in
Australia with her very nice dog raising and training blog &lt;a class="reference external" href="http://marjkibby.blogspot.com/"&gt;Choose and
Raise a Puppy …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Updated 16-Aug-2006 to reflect Feed2JS's move to http://feed2js.org&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I never thought anyone would want me to &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Web_syndication"&gt;syndicate&lt;/a&gt; my blog and put
links to my latest articles on their website. But Marj Kibby in
Australia with her very nice dog raising and training blog &lt;a class="reference external" href="http://marjkibby.blogspot.com/"&gt;Choose and
Raise a Puppy&lt;/a&gt; did just that. So I started thinking about how I might
provide an HTML feed that could be included into blogs using client-side
JavaScript as not all web/blog writers are developers who can integrate
this data on the server side.&lt;/p&gt;
&lt;p&gt;Well Google to the rescue: the &lt;a class="reference external" href="http://feed2js.org/"&gt;Feed To JavaScript&lt;/a&gt; Open Source project
provides a really great service that exactly fits the bill. If you have
a website or blog and wish to list the latest articles from another
website you can just fill out a form on the Feed2JS site and get the
HTML to paste into your website. All you do is supply the RSS feed from
the source site to Feed2JS, select the options and click Preview or
Generate JavaScript to get the code you need. Feed2JS does all the
&amp;quot;heavy lifting&amp;quot;.&lt;/p&gt;
&lt;p&gt;Here is an example of the generated HTML as it would appear in your
website using the current data from this site:&lt;/p&gt;
&lt;p&gt;The code for the above is a single (long) line of code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;script src=&amp;quot;http://feed2js.org/feed2js.php?src=http%3A%2F%2Fagilitynerd.com%2Fblog%2Findex.rss10&amp;amp;chan=y&amp;amp;desc=0&amp;amp;date=n&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;There are many other ways to configure/display the information from
AgilityNerd on your site. Just go to:
&lt;a class="reference external" href="http://feed2js.org/index.php?s=build"&gt;http://feed2js.org/index.php?s=build&lt;/a&gt; and enter:
&lt;a class="reference external" href="http://agilitynerd.com/blog/index.rss10"&gt;http://agilitynerd.com/blog/index.rss10&lt;/a&gt; in the URL text entry box. Then
answer the other questions.&lt;/p&gt;
&lt;p&gt;Feed2JS kindly caches the latest RSS feed from the source site you
specify and replies with the above HTML each time someone visits a page
on your site. This has the added advantage of not taxing the webserver
of the source site to feed your website; which is nice for sites like
this one with a small monthly bandwidth quota.&lt;/p&gt;
&lt;p&gt;Feed2JS also has a number of ways to style the HTML that is delivered so
you can change the look of the links to match your site. Of course, web
developers fluent in CSS can just style the HTML directly.&lt;/p&gt;
&lt;p&gt;For &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; bloggers I've been playing around developing a new plugin
based on the headlines plugin to provide this same functionality. I hope
to post this new plugin when/if I get it working. Either way I strongly
recommend Feed2JS, it is a great project developed in the true spirit of
the Open Source movement.&lt;/p&gt;
</content><category term="webdev"></category><category term="syndication"></category></entry><entry><title>New hitcounter, favorites Blosxom Plugin Features</title><link href="https://tech.agilitynerd.com/new-hitcounter-favorites-blosxom-plugin-featu-1.html" rel="alternate"></link><published>2008-01-09T13:25:00-06:00</published><updated>2008-01-09T13:25:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-01-09:/new-hitcounter-favorites-blosxom-plugin-featu-1.html</id><summary type="html">&lt;p&gt;After fixing a bug in these plugins I decided to add some new features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The links generated by the favorites plugin used to display the path
and filename of each popular article. It now displays the article
title and the filename is displayed when hovering over the link. This
makes …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;After fixing a bug in these plugins I decided to add some new features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The links generated by the favorites plugin used to display the path
and filename of each popular article. It now displays the article
title and the filename is displayed when hovering over the link. This
makes for a much nicer looking and more useful display. Thanks to
&lt;a class="reference external" href="http://blosxom.ookee.com/blog/"&gt;Doug&lt;/a&gt; for the &lt;a class="reference external" href="http://blosxom.ookee.com/blog/plugins/hitcounter_and_favorites_plugins.html"&gt;suggestion&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The hitcounter plugin now tests for existence of articles before
adding them to its hash of counts.&lt;/li&gt;
&lt;li&gt;Requesting a page which no longer exists deletes the page and its
counts from the hash of counts. Since previous versions of this
plugin would record non-existent pages they could appear in the
favorites listing. So now you can delete them by clicking on the link
in the favorites page.&lt;/li&gt;
&lt;li&gt;As pages previously stored in the hitcounter hash of counts are
visited their titles will be added so they will appear in the
favorites' generated links.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both plugins must be upgraded together and will upgrade the existing
hitcounter database as pages are accessed. The new versions can be
downloaded here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/favorites"&gt;favorites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/hitcounter"&gt;hitcounter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Notes on Configuring Postfix on Ubuntu Gutsy to Send Email via Google Apps</title><link href="https://tech.agilitynerd.com/notes-on-configuring-postfix-on-ubuntu-gutsy-1.html" rel="alternate"></link><published>2008-01-05T23:53:00-06:00</published><updated>2008-01-05T23:53:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-01-05:/notes-on-configuring-postfix-on-ubuntu-gutsy-1.html</id><summary type="html">&lt;p&gt;Here are some notes I took on configuring my &lt;a class="reference external" href="http://www.slicehost.com/"&gt;Slicehost&lt;/a&gt; Ubuntu Gutsy
installation to use Postfix to send emails via Google Apps. I am far
from an expert on postfix configuration but maybe these notes will be
helpful to others needing this configuration.&lt;/p&gt;
&lt;p&gt;These sites contain the key information:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://behindmyscreen.newsvine.com/_news/2006/12/31/501615-configuringubuntu-postfix-and-gmail-in-101-easy-steps"&gt;Behind …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Here are some notes I took on configuring my &lt;a class="reference external" href="http://www.slicehost.com/"&gt;Slicehost&lt;/a&gt; Ubuntu Gutsy
installation to use Postfix to send emails via Google Apps. I am far
from an expert on postfix configuration but maybe these notes will be
helpful to others needing this configuration.&lt;/p&gt;
&lt;p&gt;These sites contain the key information:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://behindmyscreen.newsvine.com/_news/2006/12/31/501615-configuringubuntu-postfix-and-gmail-in-101-easy-steps"&gt;Behind My Screen - Configuring[Ubuntu] Postfix and Gmail in 10+1
Easy Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://prantran.blogspot.com/2007/01/getting-postfix-to-work-on-ubuntu-with.html"&gt;The Prancing Tarantula - Getting Postfix to work on Ubuntu with
Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://souptonuts.sourceforge.net/postfix_tutorial.html"&gt;Mike Chirico - Gmail on Home Linux Box using Postfix and Fetchmail&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I basically followed the Behind My Screen tutorial (read the comments
too) with the updates from The Prancing Tarantula and the following
changes.&lt;/p&gt;
&lt;p&gt;My Ubuntu server install didn't have the Thawte certificates installed
by default so I installed them:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo aptitude install ca-certificates
&lt;/pre&gt;
&lt;p&gt;Then you can append that file to your /etc/postfix/cacert.pem If you
&amp;quot;sudo su&amp;quot; before doing the append to the file you won't get messed up by
the shell:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo su
# cat /etc/ssl/certs/Thawte_Premium_Server_CA.pem &amp;gt;&amp;gt;
/etc/postfix/cacert.pem
&lt;/pre&gt;
&lt;p&gt;Since I have Google Apps setup for my domain I don't just want to relay
email as &amp;quot;&lt;a class="reference external" href="mailto:user&amp;#64;gmail.com"&gt;user&amp;#64;gmail.com&lt;/a&gt;&amp;quot;, I want the email to be sent as though it
came from my domain (&amp;quot;&lt;a class="reference external" href="mailto:me&amp;#64;agilitynerd.com"&gt;me&amp;#64;agilitynerd.com&lt;/a&gt;&amp;quot;). This requires some simple
changes to the config files.&lt;/p&gt;
&lt;p&gt;In my transport file I have:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
agilitynerd.com smtp:[smtp.gmail.com]:587
&lt;/pre&gt;
&lt;p&gt;In my generic file I have:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
demo&amp;#64;myservername.agilitynerd.com me&amp;#64;agilitynerd.com
&lt;/pre&gt;
&lt;p&gt;Where &amp;quot;demo&amp;quot; is the login name and &amp;quot;myservername&amp;quot; is my slicename. In my
sasl_passwd file I have:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[smtp.gmail.com]:587 me&amp;#64;agilitynerd.com:me_gmail_account_password
&lt;/pre&gt;
&lt;p&gt;After restarting postix you can test sending email from your server:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo aptitude install mailx
$ mailx -s &amp;quot;test email&amp;quot; someotheraccount&amp;#64;gmail.com &amp;lt;
~/sometestfile_to_send
&lt;/pre&gt;
&lt;p&gt;Check your logfiles for errors/warnings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo tail /var/log/mail.\*
&lt;/pre&gt;
&lt;p&gt;I hope these notes might help folks &amp;quot;get over the hump&amp;quot; if they are
setting up the same configuration.&lt;/p&gt;
</content><category term="devops"></category><category term="postfix"></category><category term="slicehost"></category><category term="ubuntu"></category></entry><entry><title>hitcounter, favorites, lastcommented and lastread Blosxom Plugin Updates</title><link href="https://tech.agilitynerd.com/hitcounter-favorites-lastcommented-and-lastre-1.html" rel="alternate"></link><published>2008-01-01T18:44:00-06:00</published><updated>2008-01-01T18:44:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2008-01-01:/hitcounter-favorites-lastcommented-and-lastre-1.html</id><summary type="html">&lt;p&gt;I found a bug in my hitcounter, favorites, lastread and lastcommented
plugins due to my not using the correct Perl Storable functions. For
some reason I didn't use the locking versions of retrieve() and
nstore(). So when my old webhost had some problem causing long page load
times (and many …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I found a bug in my hitcounter, favorites, lastread and lastcommented
plugins due to my not using the correct Perl Storable functions. For
some reason I didn't use the locking versions of retrieve() and
nstore(). So when my old webhost had some problem causing long page load
times (and many simultaneous requests) I ended up having my favorites
data file written as zero sized.&lt;/p&gt;
&lt;p&gt;So I changed to Storable::lock_retrieve() and Storable::lock_nstore()
for all four plugins. The new versions are here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/favorites"&gt;favorites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/hitcounter"&gt;hitcounter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/lastcommented"&gt;lastcommented&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/lastread"&gt;lastread&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I never heard any bug reports about these plugins, but I'm sorry if
anyone ran into any difficulties due to these bugs.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Taking Better Photos - Building a Light Box</title><link href="https://tech.agilitynerd.com/taking-better-photos-building-a-light-box-1.html" rel="alternate"></link><published>2007-12-31T19:15:00-06:00</published><updated>2007-12-31T19:15:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-31:/taking-better-photos-building-a-light-box-1.html</id><summary type="html">&lt;p&gt;I have often been disappointed with the quality of photos of objects
I've posted on my site. My biggest problems have been providing good
lighting and a nice background (&lt;a class="reference external" href="http://agilitynerd.com/blog/agility/equipment/PVCCutter.html"&gt;see this for example&lt;/a&gt;). So I was
Googling to find a photographer's light box to build when I found &lt;a class="reference external" href="http://www.pbase.com/wlhuber/light_box_light_tent"&gt;Bill
Huber's …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have often been disappointed with the quality of photos of objects
I've posted on my site. My biggest problems have been providing good
lighting and a nice background (&lt;a class="reference external" href="http://agilitynerd.com/blog/agility/equipment/PVCCutter.html"&gt;see this for example&lt;/a&gt;). So I was
Googling to find a photographer's light box to build when I found &lt;a class="reference external" href="http://www.pbase.com/wlhuber/light_box_light_tent"&gt;Bill
Huber's great directions for building your own Light Box&lt;/a&gt;. So I built
my own light box based on Bill's design.&lt;/p&gt;
&lt;p&gt;I got two 10 foot lengths of 1/2 PVC and the fittings Bill outlines.
Nancy picked up a couple yards of white cloth from the fabric store. I
went to a craft store and bought 10 different colored sheets of poster
board about 20&amp;quot; by 26&amp;quot;. I already had some goose neck desk type lamps
into which I put some compact fluorescent bulbs.&lt;/p&gt;
&lt;p&gt;I figured I'd size the box so that I could use the poster board oriented
in either direction. That made my pipes for the width and depth 28&amp;quot; by
22&amp;quot; (to leave room for the pipe going into the fittings). I made two
sets of legs at 12&amp;quot; and 16&amp;quot; long. I got to use my &lt;a class="reference external" href="http://agilitynerd.com/blog/agility/equipment/PVCCutter.html"&gt;PVC Cutter&lt;/a&gt; to
quickly cut the pipe. I didn't bother gluing any of the pipe together
since the friction fit is plenty tight enough. I also wanted to be able
to easily make the box smaller if I over estimated the size I would
need. It is a pretty sizeable box.&lt;/p&gt;
&lt;div class="section" id="light-box-with-backgrounds-and-cover-sheet-pulled-back"&gt;
&lt;h2&gt;Light Box with Backgrounds and Cover Sheet Pulled Back&lt;/h2&gt;
&lt;div class="thumbnail centered"&gt;&lt;p&gt;&lt;img alt="image0" src="https://data.agilitynerd.com/images/LightBox.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;So if you are looking to take photos for your blog or for selling items
on eBay this is a simple and inexpensive project. We'll see if it helps
my photos look nicer.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="project"></category></entry><entry><title>Blog Spammers Using URL Encoding</title><link href="https://tech.agilitynerd.com/blog-spammers-using-url-encoding-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/blog-spammers-using-url-encoding-1.html</id><summary type="html">&lt;p&gt;I was getting hit by comment spammers in the last week who were using
&lt;a class="reference external" href="http://www.w3schools.com/tags/ref_urlencode.asp"&gt;URL-encoding&lt;/a&gt; of their addresses to get around the comment
blacklisting filter I use. By replacing regular characters with the
multi-character encoded representation of those characters within the
URL the spammers were able to post comment spam …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was getting hit by comment spammers in the last week who were using
&lt;a class="reference external" href="http://www.w3schools.com/tags/ref_urlencode.asp"&gt;URL-encoding&lt;/a&gt; of their addresses to get around the comment
blacklisting filter I use. By replacing regular characters with the
multi-character encoded representation of those characters within the
URL the spammers were able to post comment spam with links to casino and
porn websites to my blog.&lt;/p&gt;
&lt;p&gt;This type of spam is a reversal of the same method for hiding your own
email address in a web page so it won't be harvested by email spammers
(see for example &lt;a class="reference external" href="http://www.unicom.com/chrome/a/000388.html"&gt;Chip Rosenthal's Blog entry&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I just added a regular expressions in my blacklist file to block the use
of URL encoded characters in all links. If your blog comment software
supports this approach you might want to do the same thing.&lt;/p&gt;
</content><category term="webdev"></category><category term="spam"></category></entry><entry><title>Blosxom - Default Flavour Plugin - Fixes Unknown Flavour Error</title><link href="https://tech.agilitynerd.com/blosxom-default-flavour-plugin-fixes-unknown-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/blosxom-default-flavour-plugin-fixes-unknown-1.html</id><summary type="html">&lt;p&gt;I previously &lt;a class="reference external" href="/blosxom-removing-the-unknown-flavour-error-1.html"&gt;blogged a solution&lt;/a&gt; for the Unknown Flavour error that
involved modifying the blosxom.cgi script itself. I recently found my
modification caused requests for the RSS 0.91 feed and atom feeds to
return the default flavour instead of the desired feed. I suspect any
plugin using a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I previously &lt;a class="reference external" href="/blosxom-removing-the-unknown-flavour-error-1.html"&gt;blogged a solution&lt;/a&gt; for the Unknown Flavour error that
involved modifying the blosxom.cgi script itself. I recently found my
modification caused requests for the RSS 0.91 feed and atom feeds to
return the default flavour instead of the desired feed. I suspect any
plugin using a flavour that inserted itself into the Blosxom template
hash table without corresponding flavour file(s) probably didn't work
correctly after my modification.&lt;/p&gt;
&lt;p&gt;While investigating a fix for that bug I noticed that a better solution
would be to override the &lt;a class="reference external" href="https://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; template subroutine. So I created
the defaultflavour plugin. This plugin fixes the bug by first applying
any registered flavour templates if the requested template files are not
found. If that registered flavour templates don't exist the plugin
finally applies the default flavour template files.&lt;/p&gt;
&lt;p&gt;Download version 0.1 of the defaultflavour plugin &lt;a class="reference external" href="https://data.agilitynerd.com/downloads/defaultflavour_0.1.tar"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No configuration of the plugin is required. Just copy it into the plugin
directory.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Blosxom - Hit Counter and Favorites Plugins</title><link href="https://tech.agilitynerd.com/blosxom-hit-counter-and-favorites-plugins-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/blosxom-hit-counter-and-favorites-plugins-1.html</id><summary type="html">&lt;p&gt;I added a &lt;a class="reference external" href="http://agilitynerd.com/static/Favorites.html"&gt;Favorites page&lt;/a&gt; to the side menu of my AgilityNerd site.
Since then I've been updating it manually about once a month based on
the AWStats reports from my web hosting provider (HostMagix). This task
is unnecessarily complicated because AWStats keeps page counts
independently for each URL, which …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I added a &lt;a class="reference external" href="http://agilitynerd.com/static/Favorites.html"&gt;Favorites page&lt;/a&gt; to the side menu of my AgilityNerd site.
Since then I've been updating it manually about once a month based on
the AWStats reports from my web hosting provider (HostMagix). This task
is unnecessarily complicated because AWStats keeps page counts
independently for each URL, which includes the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; flavour
(filename extension). For my purposes I don't want to distinguish
between say HitCounterFavorites.html and HitCounterFavorites.htm&lt;/p&gt;
&lt;p&gt;So I decided to write a plugin to track the hits per page and &amp;quot;lump&amp;quot;
together counts independently from the file extension. This plugin
allows me to display the hit count of visitors for each page on each
page. I wrote a second plugin to use the hit count data to automatically
generate my Favorites page.&lt;/p&gt;
&lt;div class="section" id="hitcounter-plugin"&gt;
&lt;h2&gt;Hitcounter Plugin&lt;/h2&gt;
&lt;p&gt;I based the hitcounter plugin on the categories plugin written by &lt;a class="reference external" href="http://molelog.molehill.org/"&gt;Todd
Larason&lt;/a&gt;. The plugin has a couple features of interest:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$reset_count&lt;/tt&gt; flag within the plugin lets you provide a starting
count value for any page. For example to set a page's count to 10
append &amp;quot;?count=10&amp;quot; to the page's URL. Disable this flag once you've
set your counts to avoid mischevious count setting by outsiders.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$retrieve_only_flavour flag&lt;/tt&gt; within the plugin can be set to a
flavour you want to use for retrieving counts without incrementing
the count. Use this flavour to view the counts for URLs of interest.&lt;/li&gt;
&lt;li&gt;You can add filters to the &lt;cite&gt;start()&lt;/cite&gt; subroutine to exclude certain
requests from updating your counters. I exclude RSS and Atom feed
requests from my counts.&lt;/li&gt;
&lt;li&gt;As of version 0.5 you can filter out loading and incrementing page
counts for specific user agents via the &lt;tt class="docutils literal"&gt;ignore_agents array&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hitcounter plugin stores the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$blosxom::path&lt;/span&gt;&lt;/tt&gt; as the key in a hash
whose value is the count of hits. The hash is stored in a file in the
data directory. The same hash is used for the entire site. As each page
is &amp;quot;hit&amp;quot; the value is incremented and stored in the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$hitcounter::count&lt;/span&gt;&lt;/tt&gt;
variable. I put this variable in the footer of my pages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="favorites-plugin"&gt;
&lt;h2&gt;Favorites Plugin&lt;/h2&gt;
&lt;p&gt;My favorites plugin uses the data file containing the hash of paths and
counts as its input. This plugin generates an HTML unordered list of the
most visited URLs with the following configuration options:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$num_entries&lt;/tt&gt; variable controls how many URLs are listed.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$include_counts&lt;/tt&gt; variable controls if directories will be
included in the list of entries.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$include_counts&lt;/tt&gt; variable controls whether or not the number of
counts is appended to each list entry.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$anchor_format variable&lt;/tt&gt; controls if the full path or only the
filename is displayed in the list entry.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$anchor_link&lt;/tt&gt; variable controls whether or not the entry is
wrapped in an anchor &amp;lt;a&amp;gt;.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$excludes&lt;/tt&gt; variable controls which pages are excluded from
consideration. I exclude the main URL for my site from showing up in
the list.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$groups&lt;/tt&gt; array holds regexps that will generate separate unordered
lists grouping together pages matching each regexp of &lt;tt class="docutils literal"&gt;$num_entries&lt;/tt&gt;
list elements. See the configuration section for an example. I use
this feature for my Favorites page to group pages for agility and
tech categories into separate lists.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;$url_flavour&lt;/tt&gt; variable lets you specify the flavour to use for
anchors to non-category URLs. It defaults to to
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$$blosxom::default_flavour&lt;/span&gt;&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;You can add filters to the &lt;tt class="docutils literal"&gt;start()&lt;/tt&gt; subroutine to exclude certain
requests from doing the work to generate the HTML. For example, I
only run this plugin for my Favorites page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When this plugin runs the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$favorites::count&lt;/span&gt;&lt;/tt&gt; variable is populated with
the HTML. I then put this variable in my Favorites.txt page.&lt;/p&gt;
&lt;p&gt;Download version 0.5 of the hitcounter plugin &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/hitcounter"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Download version 0.1 of the favorites plugin
&lt;a class="reference external" href="http://data.agilitynerd.com/downloads/favorites"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Comments/bug reports are welcome.&lt;/p&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Blosxom Plugins: lastcommented and lastread</title><link href="https://tech.agilitynerd.com/blosxom-plugins-lastcommented-and-lastread-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/blosxom-plugins-lastcommented-and-lastread-1.html</id><summary type="html">&lt;p&gt;In preparation for an overhaul of my website I wrote two new &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt;
plugins. The lastread plugin allows me to create a list of links to the
last &lt;em&gt;n&lt;/em&gt; articles that were viewed by readers. The lastcommented plugin
allows me to create a list of links to the last &lt;em&gt;n …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;In preparation for an overhaul of my website I wrote two new &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt;
plugins. The lastread plugin allows me to create a list of links to the
last &lt;em&gt;n&lt;/em&gt; articles that were viewed by readers. The lastcommented plugin
allows me to create a list of links to the last &lt;em&gt;n&lt;/em&gt; articles that had
comments added by readers.&lt;/p&gt;
&lt;p&gt;I took pains with these plugins so they only write their state files to
disk when a single article is read (not when the index pages are hit) or
when a comment is actually posted. I've also made it optional to not
rewrite the state file if an already existing entry in the list is
visited/commented again. There are a lot of Blosxom plugins that do a
lot of work on every page access and I didn't want to add to the server
load.&lt;/p&gt;
&lt;p&gt;The configuration of each plugin is pretty straightforward. Both plugins
require the Storable plugin for storing the state files. Only the
lastcommented plugin requires any coding; whatever comment plugin you
use needs to set a variable in order for lastcommented to do any work.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://agilitynerd.com/downloads/lastread"&gt;Download the lastread plugin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Updated 2007-06-23 to version 0.3 to fix unshift() error and ignore
unknown file requests and filter out user defined HTTP Agents.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;`Download the lastcommented plugin`_
Updated 2007-06-23 to version 0.2 to fix unshift() error.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hopefully I'll have my new site design using these plugins available in
a few weeks.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Blosxom - Removing the Unknown Flavour Error</title><link href="https://tech.agilitynerd.com/blosxom-removing-the-unknown-flavour-error-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/blosxom-removing-the-unknown-flavour-error-1.html</id><summary type="html">&lt;p&gt;After my &lt;a class="reference external" href="/blosxom-plugin-to-block-referer-spam-1.html"&gt;recent Referer Spam attacks&lt;/a&gt; I've been checking my logs and
&lt;a class="reference external" href="http://awstats.sourceforge.net"&gt;AWStats&lt;/a&gt; reports daily. I noticed that occasionally I'd get people
requesting pages with incorrect suffixes. i.e. foo.ht or bar.|id|
(some of these are probably spammers too). A &amp;quot;feature&amp;quot; of &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; is
that it will still …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After my &lt;a class="reference external" href="/blosxom-plugin-to-block-referer-spam-1.html"&gt;recent Referer Spam attacks&lt;/a&gt; I've been checking my logs and
&lt;a class="reference external" href="http://awstats.sourceforge.net"&gt;AWStats&lt;/a&gt; reports daily. I noticed that occasionally I'd get people
requesting pages with incorrect suffixes. i.e. foo.ht or bar.|id|
(some of these are probably spammers too). A &amp;quot;feature&amp;quot; of &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; is
that it will still serve the page with the default layout or flavour.
This feature causes a not too helpful and ugly error message to be
displayed on the top of the page:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Error: I'm afraid this is the first I've heard of an &amp;quot;ht&amp;quot; flavoured
Blosxom. Try dropping the &amp;quot;/+ht&amp;quot; bit from the end of the URL.
&lt;/pre&gt;
&lt;p&gt;Of course removing the suffix from the URL doesn't usually work; the
client is served with an empty (but flavoured) page. So I though a
better solution would be to have Blosxom serve my default_flavour
whenever the flavour couldn't be identified. A little Googling later
turned up &lt;a class="reference external" href="http://brutalhugs.com/flavours/#mod_default_template"&gt;this Blosxom mod by James Vasille&lt;/a&gt;. James explicitly loads a
flavour file named &amp;quot;default&amp;quot; whenever the requested flavour can't be
found.&lt;/p&gt;
&lt;p&gt;For my site it is more appropriate to serve the $default_flavour when
the requested flavour can't be found. That way I didn't have copy/create
head.default, foot.default, etc files. So my code change is almost the
same as James':&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$template =   sub {
  my ($path, $chunk, $flavour) = &amp;#64;_;
  do {
    return join '', &amp;lt;$fh&amp;gt; if $fh-&amp;gt;open(&amp;quot;&amp;lt; $datadir/$path/$chunk.$flavour&amp;quot;);
  } while ($path =~ s/(/*[^/]*)$// and $1);
  # Begin added code
  do {
    return join '', &amp;lt;$fh&amp;gt; if $fh-&amp;gt;open(&amp;quot;&amp;lt; $datadir/$path/$chunk.$default_flavour&amp;quot;);
  } while ($path =~ s/(/*[^/]*)$// and $1);
  # End added code
  return join '', ($template{$flavour}{$chunk} || $template{error}{$chunk} || '');
};
&lt;/pre&gt;
&lt;p&gt;So now a less ugly page will be provided when an unknown suffix/flavour
is requested.&lt;/p&gt;
&lt;div class="section" id="update-19-apr-2005-this-algorithm-has-a-bug-that-has-been-fixed-by-the-defaultflavour-plugin"&gt;
&lt;h2&gt;Update 19-Apr-2005 - this algorithm has a bug that has been fixed by the &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/defaultflavour_0.1.tar"&gt;defaultflavour plugin&lt;/a&gt;.&lt;/h2&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Comment Spam and wbcaptcha Plugin Enhancements</title><link href="https://tech.agilitynerd.com/comment-spam-and-wbcaptcha-plugin-enhancement-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/comment-spam-and-wbcaptcha-plugin-enhancement-1.html</id><summary type="html">&lt;p&gt;I have been fighting automated comment spam robots (see also
&lt;a class="reference external" href="http://agilitynerd.posterous.com/refererblock-version-02-1"&gt;refererblock update&lt;/a&gt;, and &lt;a class="reference external" href="http://agilitynerd.posterous.com/blosxom-hit-counter-and-favorites-plugins-1"&gt;hit counter changes&lt;/a&gt;) with my modified
writeback plugin that uses a blacklist of spam words and URLs. However,
I'm getting tired of updating the blacklist and removing spam comments
that get through the filter. So I decided …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been fighting automated comment spam robots (see also
&lt;a class="reference external" href="http://agilitynerd.posterous.com/refererblock-version-02-1"&gt;refererblock update&lt;/a&gt;, and &lt;a class="reference external" href="http://agilitynerd.posterous.com/blosxom-hit-counter-and-favorites-plugins-1"&gt;hit counter changes&lt;/a&gt;) with my modified
writeback plugin that uses a blacklist of spam words and URLs. However,
I'm getting tired of updating the blacklist and removing spam comments
that get through the filter. So I decided to install the &lt;a class="reference external" href="http://varg.dyndns.org/psi/pub/code/misc/wbcaptcha.html"&gt;wbcaptcha
plugin&lt;/a&gt; written by &lt;a class="reference external" href="http://varg.dyndns.org/psi/pub/index.html"&gt;Pasi Savolainen&lt;/a&gt;. His plugin uses the &lt;a class="reference external" href="http://www.figlet.org"&gt;FIGlet&lt;/a&gt;
program to generate an ASCII &amp;quot;image&amp;quot; of random letters:&lt;/p&gt;
 &lt;div class="centered"&gt;&lt;pre&gt;
              _
  ___  _ __  | |__    __ _
 / _ \| '_ \ | '_ \  / _` |
|  __/| | | || | | || (_| |
 \___||_| |_||_| |_| \__,_|
 &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A person leaving a comment then enters the letters (enha for the image
above) as a &amp;quot;key&amp;quot; along with their comments. The plugin then validates
that the key matches the generated image before it allows the comment to
be posted. This is similar to the approach used by other blog software
to distinguish humans from automated spam programs.&lt;/p&gt;
&lt;p&gt;Pasi's plugin worked by executing figlet in a subshell whenever someone
wanted to post a comment. I wanted to change this approach since I don't
have the ability to install figlet on my web host and I didn't want to
pay the performance cost of calling a program through the shell.&lt;/p&gt;
&lt;p&gt;So I found the &lt;a class="reference external" href="http://search.cpan.org/~jpierce/Text-FIGlet-1.06/FIGlet.pm"&gt;Text::FIGlet&lt;/a&gt; Perl package on &lt;a class="reference external" href="http://cpan.org/"&gt;CPAN&lt;/a&gt; and made a few
small edits to Pasi's plugin to allow it to invoke the FIGlet Perl code
without the shell. This simplified the plugin and sped up it's
performance. I've sent my changes to Pasi in case he wants to
incorporate them in a future release of his plugin.&lt;/p&gt;
&lt;p&gt;I've decided to leave my blacklist in place, since I'm sure I'll still
be facing human comment spammers looking to promote their websites. The
last change I can think to make to further discourage spammers will be
to change all &amp;lt;a&amp;gt; elements in comments to include the &lt;a class="reference external" href="http://googleblog.blogspot.com/2005/01/preventing-comment-spam.html"&gt;rel=&amp;quot;nofollow&amp;quot;&lt;/a&gt;
attribute supported by most portal/search websites.&lt;/p&gt;
&lt;p&gt;I also took this opportunity to add some name anchors to my foot.htm and
foot.html files so clicking on the &amp;quot;Comments&amp;quot; link on my index pages now
takes you directly to the comments section of the individual article.
Similarly, selecting &amp;quot;Add Your Comment&amp;quot; and Posting a comment also show
the writeback for and the last added comment respectively. This makes
for less scrolling on long articles and multiple comments.&lt;/p&gt;
&lt;p&gt;Unfortunately, I don't think this will actually reduce the spammer hits
on my site since these are due to automated &amp;quot;bots&amp;quot;. I doubt the spammers
actually check if their comments get posted, they are just hoping for a
comment to &amp;quot;get through&amp;quot; occaisionally so their sites can can get some
&lt;a class="reference external" href="http://search.cpan.org/~jpierce/Text-FIGlet-1.06/FIGlet.pm"&gt;&amp;quot;Google Juice&amp;quot;&lt;/a&gt;. So they'll keep pounding on my site although
hopefully fewer spam comments will get through.&lt;/p&gt;
&lt;p&gt;Please email me at the address shown in the right hand menu bar if you
have any problems posting comments.&lt;/p&gt;
</content><category term="webdev"></category></entry><entry><title>Directing Search Engines to Blosxom Content Pages</title><link href="https://tech.agilitynerd.com/directing-search-engines-to-blosxom-content-p-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/directing-search-engines-to-blosxom-content-p-1.html</id><summary type="html">&lt;p&gt;I've noticed that folks who come to my site by querying through a search
engine often end up on my main page and the article they are searching
for has already rolled off the front page. This is because the search
engine robots tend to choose the index pages as …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've noticed that folks who come to my site by querying through a search
engine often end up on my main page and the article they are searching
for has already rolled off the front page. This is because the search
engine robots tend to choose the index pages as more relevant than the
content pages linked to by the index pages. This is especially
frustrating to me since I want my blog to serve as a reference for other
Agility enthusiasts - so making my articles easily retrievable from
search engines is important to me. It turns out this is a pretty common
problem faced by many blogs.&lt;/p&gt;
&lt;p&gt;I found that &lt;a class="reference external" href="http://jclark.org"&gt;Jason Clark&lt;/a&gt; had run into this problem with his
&lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; blog and resolved it by using the following meta element on
his index pages: &amp;lt;meta name=&amp;quot;robots&amp;quot; content=&amp;quot;noindex,follow&amp;quot; &amp;gt;. &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/robottweak2.html"&gt;His
post describes&lt;/a&gt; how he conditionally includes these robot meta tags
through his head flavour files (using the interpolate_fancy plugin and
his storystate plugin) only on his index pages and not on other pages.&lt;/p&gt;
&lt;p&gt;For my site it is even easier. I use separate head.index pages to style
my index pages differently from my htm/html pages. So I just directly
inserted the &amp;lt;meta name=&amp;quot;robots&amp;quot; content=&amp;quot;noindex,follow&amp;quot; &amp;gt; into my
head.index pages.&lt;/p&gt;
&lt;p&gt;There are a number of other robots meta tags that I might consider
adding in the future. &lt;a class="reference external" href="http://www.bauser.com/websnob/"&gt;Websnob&lt;/a&gt; has a &lt;a class="reference external" href="http://www.bauser.com/websnob/meta/robots.html"&gt;thorough explanation&lt;/a&gt; of the
tags and the robots that use them.&lt;/p&gt;
&lt;p&gt;Hopefully over the next few months these changes will cause search
engines to give their users links directly to the articles in my site.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Image Theft Ranting And Blocking</title><link href="https://tech.agilitynerd.com/image-theft-ranting-and-blocking-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/image-theft-ranting-and-blocking-1.html</id><summary type="html">&lt;p&gt;I was really disappointed yesterday when I checked my blog's statistics
and found that someone from a Hungarian Agility discussion board had
directly linked course images from my website. Direct linking of an
image is placing the URL of the image directly in a webpage hosted on
another server. Each …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was really disappointed yesterday when I checked my blog's statistics
and found that someone from a Hungarian Agility discussion board had
directly linked course images from my website. Direct linking of an
image is placing the URL of the image directly in a webpage hosted on
another server. Each time a browser loads that page the direct link
causes the other server to send the image to the client's browser. So
each time the forum is viewed my server has to send the images to the
forum's viewer.&lt;/p&gt;
&lt;p&gt;I purposely try to make my image sizes small to make it possible for
dial up visitors to view my site without too much delay. So direct
linking doesn't impact my bandwidth costs too much; so far it is about
1Mb a day. The additional burden it puts on the webserver should be
small too, but since I don't own the shared server I don't really know.&lt;/p&gt;
&lt;div class="section" id="ranting"&gt;
&lt;h2&gt;Ranting&lt;/h2&gt;
&lt;p&gt;The aspect of this that most irritates me is that someone would copy my
work without attribution. That is the only thing I ask of visitors to my
site who wish to reuse my content. The &lt;a class="reference external" href="http://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;license link&lt;/a&gt; at the bottom of
each page should make this clear. I really don't think this is it too
much to ask.&lt;/p&gt;
&lt;p&gt;I take copyright infringement very seriously. To me it isn't just the
legal requirements of using other's material in accordance with their
wishes that is important, taking credit, explicitly or implicitly, for
another's work is just wrong. This is one of those &lt;a class="reference external" href="http://www.robertfulghum.com/books.php#book1"&gt;All I Really Need To
Know I Learned In Kindergarten&lt;/a&gt; concepts: &lt;a class="reference external" href="http://www.peace.ca/kindergarten.htm"&gt;Don't take things that
aren't yours&lt;/a&gt;. As a friend of Nancy's says &amp;quot;Some people don't have good
home training&amp;quot;.&lt;/p&gt;
&lt;p&gt;Lastly, the Agility community is still a small community and the online
Agility community even more so. I guess I am naive, but I hoped that the
members of our community wouldn't do things like this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="blocking"&gt;
&lt;h2&gt;Blocking&lt;/h2&gt;
&lt;p&gt;My first step was emailing the webmaster of the site. The site was
entirely in Hungarian so it is possible the webmaster may not have
understood my English request. In any event, after 24 hours they hadn't
removed the links.&lt;/p&gt;
&lt;p&gt;So I went to look for a technical solution. There are a few well known
technical solutions for this problem. A search of Google for &lt;a class="reference external" href="http://www.google.com/search?q=blocking+direct+linking"&gt;blocking
direct linking&lt;/a&gt; or &lt;a class="reference external" href="http://www.google.com/search?q=blocking+hotlinking"&gt;blocking hotlinking&lt;/a&gt; will turn them all up. The
most useful solutions include:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Rename the direct linked images&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
This mean updating all posts one your site to match the new name.
But if you only have a few images and/or posts to them you can do this on a per direct link basis.&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Randomly generate image file names that change over time&lt;/p&gt;
&lt;p&gt;This is usually used for photo galleries where there is no text referring to each image.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Serve images through a script&lt;/p&gt;
&lt;p&gt;This script would reject requests for images based on information in
the request. This can be a compute intensive approach since it causes
requests even from your own site to go through the script. For sites
where you don't have control over the webserver this may be required.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Use a Rewrite rule to serve a different image to non-local referers&lt;/p&gt;
&lt;p&gt;This is the technique I used.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Based on this &lt;a class="reference external" href="http://altlab.com/htaccess_tutorial.html"&gt;altlab.com article&lt;/a&gt; I originally added these rules to my
.htaccess file on my server:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www.)?agilitynerd.com/.\* [NC]
RewriteRule .\*.(jpg\|jpeg\|gif\|png\|bmp)$ /images/nodirectlink.g [L,NC]
&lt;/pre&gt;
&lt;p&gt;I then created an image called nodirectlink.g shown below.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="redirected-image"&gt;
&lt;h2&gt;Redirected Image&lt;/h2&gt;
&lt;div class="p_embed p_image_embed"&gt;&lt;p&gt;&lt;img alt="image0" src="https://data.agilitynerd.com/images/nodirectlink.g" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Don't use the same filename suffix as one of the real image filenames
you use or you'll loop the rewrite engine.&lt;/p&gt;
&lt;p&gt;I had forgotten about images direct linked by RSS feed readers that
access the root. Rather than rewrite those requests I moved my Rewrite
rules into the .htaccess file in the images directory. I also decided to
not send the image after all, I'll just fail the request. There is no
sense in even wasting the bandwidth, the clients will now get the broken
image icon from their browser. The official &lt;a class="reference external" href="http://httpd.apache.org/docs/1.3/misc/rewriteguide.html"&gt;Apache URL Rewriting
Guide&lt;/a&gt; describes this in the Blocked Inline-Images section. So here is
my final solution:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www.)?agilitynerd.com/.\*$ [NC]
RewriteRule .\*.(jpg\|jpeg\|gif\|png\|bmp)$ - [F]
&lt;/pre&gt;
&lt;p&gt;I took this opportunity to modify some of my very first articles and
move their images into the /images directory. Those images were being
served by the Blosxom binary plugin back when I thought it was a
good idea. Now I won't pay any extra processing cost for those images.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So now I have a technical solution to a human problem. There is a chance
that this change may cause some viewers of this site to not see images
on this site. But hopefully that should be a very, very small number of
people. But if you think you are getting my blocking image incorrectly
Please email me: steve at agilitynerd.com.&lt;/p&gt;
&lt;p&gt;I'm still disappointed I was forced to resort to this change.&lt;/p&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="apache"></category><category term="sysadmin"></category></entry><entry><title>Modifications to Blosxom entriescache PluginHandles Articles With Future meta-creation_date</title><link href="https://tech.agilitynerd.com/modifications-to-blosxom-entriescache-pluginh-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/modifications-to-blosxom-entriescache-pluginh-1.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://fletcher.freeshell.org"&gt;Fletcher Penney&lt;/a&gt; created the original &lt;a class="reference external" href="http://fletcher.freeshell.org/computers/web/blosxom/entries_cache/"&gt;entriescache&lt;/a&gt; &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt;
plugin. It is designed to cache all the articles and their creation
dates in a Blosxom blog to avoid the overhead of scanning the file
system for new/modified files. He merged the meta plugin functionality
into the plugin to allow authors to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="http://fletcher.freeshell.org"&gt;Fletcher Penney&lt;/a&gt; created the original &lt;a class="reference external" href="http://fletcher.freeshell.org/computers/web/blosxom/entries_cache/"&gt;entriescache&lt;/a&gt; &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt;
plugin. It is designed to cache all the articles and their creation
dates in a Blosxom blog to avoid the overhead of scanning the file
system for new/modified files. He merged the meta plugin functionality
into the plugin to allow authors to date their entries without relying
on the file creation date (this makes it easy to move from one web
hosting provider to another without having your article dates get
reset).&lt;/p&gt;
&lt;p&gt;Unfortunately, articles posted to a Blosxom blog with
meta-creation_date's in the future would be displayed immediately. I
had wanted to modify Blosxom to allow me post articles with future
creation dates and have them hidden until that date. After a &lt;a class="reference external" href="http://groups.yahoo.com/group/blosxom/message/10664"&gt;request
for this functionality on the Blosxom email list&lt;/a&gt;, I looked into the
required modifications and found it wasn't too difficult to make them.&lt;/p&gt;
&lt;p&gt;You can download the modified plugin &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/entriescache"&gt;here&lt;/a&gt;. Let me know if you have
any questions or comments.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category></entry><entry><title>Refererblock Version 0.2</title><link href="https://tech.agilitynerd.com/refererblock-version-02-1.html" rel="alternate"></link><published>2007-12-29T23:16:00-06:00</published><updated>2007-12-29T23:16:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2007-12-29:/refererblock-version-02-1.html</id><summary type="html">&lt;p&gt;I came up with two improvements to the first release of my &lt;a class="reference external" href="http://agilitynerd.posterous.com/blosxom-plugin-to-block-referer-spam-1"&gt;refererblock
plugin&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the referer string matches the site's URL it passed immediately
and isn't checked against the blacklist.&lt;/li&gt;
&lt;li&gt;The blacklist.txt file was being read even if the referer string was
empty. Now it is only read …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I came up with two improvements to the first release of my &lt;a class="reference external" href="http://agilitynerd.posterous.com/blosxom-plugin-to-block-referer-spam-1"&gt;refererblock
plugin&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the referer string matches the site's URL it passed immediately
and isn't checked against the blacklist.&lt;/li&gt;
&lt;li&gt;The blacklist.txt file was being read even if the referer string was
empty. Now it is only read if the referer string is not empty and it
isn't for the site's URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These optimizations do improve the performance of the plugin. My testing
on a PIII 800MHz running Fedora Core 3 Linux with Apache 2.0 showed the
following average latencies:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;1.5 ms - Empty referer string or current domain.&lt;/li&gt;
&lt;li&gt;2.0 ms - Referer string matching the first regex of the example
blacklist file.&lt;/li&gt;
&lt;li&gt;3.0 ms - Referer string matching the final regex of the example
blacklist file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was kind of surprised at how little additional time was required to
load the blacklist file and process the regular expressions. This is
probably due to the file remaining in the disk cache for subsequent
requests. Of course your mileage may vary.&lt;/p&gt;
&lt;p&gt;Download version 0.2 of the plugin &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/refererblock_0.2.tar"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See my &lt;a class="reference external" href="/blosxom-plugin-to-block-referer-spam-1"&gt;original plugin description&lt;/a&gt; for installation, configuration,
and testing information. Please let me know if you use this plugin or if
you have comments or suggestions for improving it.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category><category term="referrer"></category><category term="spam"></category></entry><entry><title>Those Darn Spammers Blosxom - Hit Counter and Writeback Changes</title><link href="https://tech.agilitynerd.com/those-darn-spammersblosxom-hit-counter-and-wr-1.html" rel="alternate"></link><published>2006-08-11T19:00:00-05:00</published><updated>2006-08-11T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2006-08-11:/those-darn-spammersblosxom-hit-counter-and-wr-1.html</id><summary type="html">&lt;p&gt;Over the past year I'd noticed that comment and trackback spammers had
been hitting the same dozen or so pages of my blog multiple times daily.
(It is probably the same person/group who took a snapshot of the
articles on my front page at that time and just reuses …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over the past year I'd noticed that comment and trackback spammers had
been hitting the same dozen or so pages of my blog multiple times daily.
(It is probably the same person/group who took a snapshot of the
articles on my front page at that time and just reuses those URLs for
all of their different domains). Last I calculated, about 50% of my
overall website traffic is due to spammers. This constant barrage of
hits skews my &lt;a class="reference external" href="http://awstats.sourceforge.net"&gt;AWStats&lt;/a&gt; statistics and, more importantly, skews the
results on my &lt;a class="reference external" href="http://agilitynerd.com/blog/static/Favorites.html"&gt;Favorites page&lt;/a&gt;. So I took a little time to work on this
problem.&lt;/p&gt;
&lt;p&gt;FWIW I'm also starting to see a lot of spam coming from &amp;quot;blogs&amp;quot; being
setup on &lt;a class="reference external" href="http://blogspot.com"&gt;BlogSpot&lt;/a&gt; that are a single page all of whose links point to
the real site. Some of these bogus blogs use the BlogSpot temmplate
which contains a flag used to alert BlogSpot admins to content in
violation of their Terms of Service. This allows anyone to report the
bogus blog by just clicking on the flag. Other &amp;quot;blogs&amp;quot; just use their
own HTML and BlogSpot support would have to be sent an email with the
offending site's URL.&lt;/p&gt;
&lt;p&gt;So anyway, I've taken the following steps:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Disabled trackback comments entirely using the configuration variable
in the writeback plugin. I've never received a legitimate trackback
ping.&lt;/li&gt;
&lt;li&gt;Modified my modified version of the writeback plugin to set a
variable $rejected with a 1 if the comment was rejected or if
trackback was attempted.&lt;/li&gt;
&lt;li&gt;Modified my HitCounter plugin to read the $writeback::rejected
variable and then not increase the counter for the spammed page.&lt;/li&gt;
&lt;li&gt;I had to change the ordering of the hitcounter plugin to run after
writeback so the variable would be set correctly when hitcounter ran.&lt;/li&gt;
&lt;li&gt;Set the $hitcounter::reset_count variable and reset the counts of
the spammed pages back to &amp;quot;reasonable&amp;quot; counts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another couple hours wasted messing around against spammers.&lt;/p&gt;
&lt;p&gt;I've &lt;a class="reference external" href="/blosxom-plugin-to-block-referer-spam-1.html"&gt;previously written&lt;/a&gt; how I've been using comment content
blacklisting to reject comment, trackback, and referer (sic) spam. My
current blacklist file has over 40 regular expressions containing over
250 words and patterns. I update it whenever a spam comment slips
through. So that is an ongoing almost daily effort. I might just have to
go to the trouble of getting a &lt;a class="reference external" href="http://varg.dyndns.org/psi/pub/code/misc/wbcaptcha.html"&gt;CAPTCHA&lt;/a&gt; plugin to work.&lt;/p&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category><category term="spam"></category></entry><entry><title>How Safe is Your Personal Information in the Hands of Website Developers?</title><link href="https://tech.agilitynerd.com/how-safe-is-your-personal-information-in-the-1.html" rel="alternate"></link><published>2005-06-23T19:00:00-05:00</published><updated>2005-06-23T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2005-06-23:/how-safe-is-your-personal-information-in-the-1.html</id><summary type="html">&lt;p&gt;I was going through the webserver statistics for this site to see if any
new sites had linked to any of my articles (it is always nice to see
that what I have to say is useful to someone). Anyway, I ran across
someone who had come to my site …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was going through the webserver statistics for this site to see if any
new sites had linked to any of my articles (it is always nice to see
that what I have to say is useful to someone). Anyway, I ran across
someone who had come to my site through a Google query (I won't mention
what the query was for reasons you'll soon see). I ran the same query on
Google to see what else came up since it was a rather unique query.
Another Google link was for a site that looked like it had raw data -
not your usual HTML pages.&lt;/p&gt;
&lt;p&gt;When I went to the site I found what looked like a website developer's
development directory wide open to the internet. There were at least
three company's websites sitting in subdirectories. The file referred to
in the Google result page was a backup of an SQL database dump file. Not
just any database file - a backup of all the customer information for
running one site's shopping cart database. It included names, addresses,
email addresses, and phone numbers! (I didn't poke around to see if it
had any more sensitive data).&lt;/p&gt;
&lt;p&gt;I was able to figure out the original data owner's domain name from some
info in the header of the file. So I just sent them an email letting
them know that their customer information is posted for all to see on
someone else's website. It will be interesting to see if they respond. I
hope it is just their website developer who has a test server running
and accidentally left this SQL dump in a publicly accessible area of
their webserver. I'd hate to think this data was stolen from the real
website and being used for spamming purposes.&lt;/p&gt;
&lt;p&gt;As a software developer I've read numerous cautionary tales of
accidental (and malicious) data theft occurring when real customer data
is used in test systems. I just never imagined I'd stumble across such
an egregious privacy violation. So this experience makes me wonder about
all the online systems into which we type our personal information. All
it takes is one careless developer (not even a malicious one) to expose
our private information to a much wider audience...&lt;/p&gt;
</content><category term="webdev"></category><category term="development"></category><category term="privacy"></category></entry><entry><title>Blosxom Plugin to Block Referer Spam</title><link href="https://tech.agilitynerd.com/blosxom-plugin-to-block-referer-spam-1.html" rel="alternate"></link><published>2005-02-12T20:00:00-06:00</published><updated>2005-02-12T20:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2005-02-12:/blosxom-plugin-to-block-referer-spam-1.html</id><summary type="html">&lt;p&gt;Like so many other bloggers who allow comments on their websites and
blog articles, I was facing increasing &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Blog_spam"&gt;comment spam&lt;/a&gt; as my blog got
noticed by more spammers. The size of this problem is illustrated by
&lt;a class="reference external" href="http://www.google.com/search?q=%22comment+spam%22"&gt;this Google query for &amp;quot;comment spam&amp;quot;&lt;/a&gt; that returned 1.5 million hits.
For the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Like so many other bloggers who allow comments on their websites and
blog articles, I was facing increasing &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Blog_spam"&gt;comment spam&lt;/a&gt; as my blog got
noticed by more spammers. The size of this problem is illustrated by
&lt;a class="reference external" href="http://www.google.com/search?q=%22comment+spam%22"&gt;this Google query for &amp;quot;comment spam&amp;quot;&lt;/a&gt; that returned 1.5 million hits.
For the uninitiated comment spam is like email spam for blogs; the
spammer inserts fake comments in a blog where either the comment text
contains from one to dozens of links to the spammer's websites. When web
search sites &amp;quot;spider&amp;quot; the blog the links to the spammer's site are
treated as &amp;quot;endorsements&amp;quot; of the spammer's sites and the spammer's sites
are raised to the top of the search site's result lists.&lt;/p&gt;
&lt;p&gt;There is another growing type of blog spam called &lt;a class="reference external" href="http://www.spywareinfo.com/articles/referer_spam/"&gt;referer spam&lt;/a&gt; (yes
it is &lt;a class="reference external" href="http://dictionary.reference.com/search?q=referer"&gt;officially misspelled&lt;/a&gt;). When a web surfer clicks on a link in a
web page that sends them to another web page most web browsers fill in
the URL of the referring page into the request called the HTTP_REFERER.
Some websites and blogs capture that page link information when they are
on the receiving end of a web request. These sites might have a section
on each page indicating the sites that link to that page. These links
are referer links.&lt;/p&gt;
&lt;p&gt;Referer spam uses the same mechanism as comment spam to raise the search
sites ranking of the spammer's websites. But referer spammer's don't
post comments; they post fake referrals to a website. The are hoping
that the website or blog displays links of the sites that refer to them.
So when the website is spidered the search ranking is raised.&lt;/p&gt;
&lt;div class="section" id="blosxom-plugins-addressing-spam"&gt;
&lt;h2&gt;Blosxom Plugins Addressing Spam&lt;/h2&gt;
&lt;p&gt;Like so many bloggers once I started getting comment spam I was able to
manually delete them as they occurred. But that got old fast. After some
Googling I discovered &lt;a class="reference external" href="http://www.lathi.net"&gt;Doug Alcorn's&lt;/a&gt; &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; &lt;a class="reference external" href="http://www.lathi.net/twiki-bin/view/Main/BlogSpam"&gt;writeback blacklist
plugin&lt;/a&gt;. I had been using the original &lt;a class="reference external" href="http://www.blosxom.com/plugins/input/writeback.htm"&gt;writeback&lt;/a&gt; plugin. Doug's
improvements provided enough protection (so far) with less than a dozen
regular expressions removing all my comment spam.&lt;/p&gt;
&lt;p&gt;Referer spam started hitting me three weeks ago. What was most
infuriating was that I don't display any links of referring sites on my
site at all. So all these spammers were succeeding in doing was skewing
my site statistics and using my bandwidth with their fake referer
attacks several times a day.&lt;/p&gt;
&lt;p&gt;Of course all the referer spam site's addresses contained one or more of
the same dozen blacklisted words I had already configured for comment
spam. About the same time I saw &lt;a class="reference external" href="http://jclark.org"&gt;Jason Clark&lt;/a&gt; post his &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/plugins/deferer/deferer-0-1i.html"&gt;deferer
plugin&lt;/a&gt; to return a 301 permanent redirect for the IP address of one
particular referer spammer who was attacking his site. I thought that by
combining Doug's blacklisting plugin with Jason's immediate redirect
plugin I could reduce the referer spam from my log files.&lt;/p&gt;
&lt;p&gt;This plugin hasn't removed the entries entirely from my logs, since the
initial request is still logged with a 301 status. But it has stopped
the subsequent downloading of images for the pages whose content is now
not served. Now that the log contains 301 status messages for these
requests they are ignored by my host's statistics program (&lt;a class="reference external" href="http://awstats.sourceforge.net"&gt;Advanced Web
Statistics - AWStats&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;On a re-reading of Jason's blog entry for deferer he also mentions the
idea of white and black lists for referer filtering. So I might have
subliminally remembered his idea and implemented my refererblock plugin
based on his idea. In any event, I fully credit Jason and Doug for
giving me the ideas and code with which to put together my plugin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="referer-block-plugin"&gt;
&lt;h2&gt;Referer Block Plugin&lt;/h2&gt;
&lt;p&gt;The refererblock plugin's tar file can be downloaded &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/refererblock_0.1.tar"&gt;here&lt;/a&gt;. It
contains the refererblock plugin already named 000refererblock so that
it runs before all other plugins (you want to discard the blacklisted
requests before all legitimate requests). A sample blacklist.txt file is
provided and contains some example regexs. It uses the same blacklist
file format and file name as Doug's writeback modification (I took the
code from his plugin with only cosmetic changes). See Doug's website for
links to the Movable Type and other blacklists.&lt;/p&gt;
&lt;p&gt;The only configuration variable you can set is $log_blacklisted. If set
to a full path file name the script logs the UTC date/time, referer
string, and the page to which they were referring. You could use the
frequency of words in the rejected referer strings to fine tune the
content and ordering of your blacklist.txt file to match the spammers
hitting your site. Be aware that this file isn't trimmed so you might
want to keep an eye on its size.&lt;/p&gt;
&lt;p&gt;Lastly, the zip file contains a simple Perl script you can use to test
the plugin. Execute it as:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
referer_test.pl http://example.com http://referer-spam.com
where the first URL is your website and the second URL is the referer
&lt;/pre&gt;
&lt;p&gt;to be sent in the request. This script uses &lt;a class="reference external" href="HTTP::Request"&gt;HTTP::Request&lt;/a&gt; to send the
request. The script returns the status &amp;quot;200 OK&amp;quot; for the requested page
if it isn't blacklisted and &amp;quot;301 Moved Permanently&amp;quot; if the referer is
blacklisted.&lt;/p&gt;
&lt;p&gt;Please let me know if you use this plugin or if you have comments or
suggestions for improving it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="plugin"></category><category term="spam"></category></entry><entry><title>Online Button Creation Tool</title><link href="https://tech.agilitynerd.com/online-button-creation-tool-1.html" rel="alternate"></link><published>2004-12-08T20:00:00-06:00</published><updated>2004-12-08T20:00:00-06:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-12-08:/online-button-creation-tool-1.html</id><summary type="html">&lt;p&gt;I was reading the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; email list tonight and came across an
interesting &lt;a class="reference external" href="http://rbpark.ath.cx"&gt;photoblog by Rob Park&lt;/a&gt;. After enjoying some of his photos,
I noticed his &lt;a class="reference external" href="http://rpbark.ath.cx/news/buttons.html"&gt;post&lt;/a&gt; about an &lt;a class="reference external" href="http://kalsey.com/tools/buttonmaker"&gt;online form for creating the XML, RSS,
etc. buttons&lt;/a&gt; popular on so many websites. &lt;a class="reference external" href="http://kalsey.com"&gt;Adam Kalsey&lt;/a&gt; has created a
simple (dare …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was reading the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; email list tonight and came across an
interesting &lt;a class="reference external" href="http://rbpark.ath.cx"&gt;photoblog by Rob Park&lt;/a&gt;. After enjoying some of his photos,
I noticed his &lt;a class="reference external" href="http://rpbark.ath.cx/news/buttons.html"&gt;post&lt;/a&gt; about an &lt;a class="reference external" href="http://kalsey.com/tools/buttonmaker"&gt;online form for creating the XML, RSS,
etc. buttons&lt;/a&gt; popular on so many websites. &lt;a class="reference external" href="http://kalsey.com"&gt;Adam Kalsey&lt;/a&gt; has created a
simple (dare I say elegant?) form that gives you just enough inputs to
completely control the look of the buttons. The image is generated in
PNG format.&lt;/p&gt;
&lt;p&gt;Had I known of this site before it would have saved me a couple hours
messing around with Fireworks creating the buttons on my AgilityNerd
&lt;a class="reference external" href="http://agilitynerd.com/blog/static/subscribe.html"&gt;Feeds&lt;/a&gt; page...&lt;/p&gt;
</content><category term="webdev"></category><category term="sitedesign"></category><category term="tools"></category></entry><entry><title>AgilityNerd Site Enhancements</title><link href="https://tech.agilitynerd.com/agilitynerd-site-enhancements-1.html" rel="alternate"></link><published>2004-10-29T23:16:00-05:00</published><updated>2004-10-29T23:16:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-10-29:/agilitynerd-site-enhancements-1.html</id><summary type="html">&lt;p&gt;I've spent some time over the past couple nights trying to improve
the access to information on my site. As a software developer it is
often more fun to play around with the technology than it is to do
&amp;quot;real&amp;quot; work.&lt;/p&gt;
&lt;p&gt;I've made the following enhancements:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Added Quick Links to …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I've spent some time over the past couple nights trying to improve
the access to information on my site. As a software developer it is
often more fun to play around with the technology than it is to do
&amp;quot;real&amp;quot; work.&lt;/p&gt;
&lt;p&gt;I've made the following enhancements:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Added Quick Links to the right hand navigation to allow folks to get
to the three main Agility sections of the website without going to the
&lt;a class="reference external" href="http://agilitynerd.com/blog/navigate"&gt;Navigate&lt;/a&gt; page.
Used the [[Blosxom]] &lt;a class="reference external" href="http://www.blosxom.com/plugins/include/file.htm"&gt;file plugin&lt;/a&gt; to include the new HTML in all
pages.&lt;/li&gt;
&lt;li&gt;Changed my &lt;a class="reference external" href="http://agilitynerd.com/blog/agility/handling/glossary"&gt;AgilityGlossary&lt;/a&gt; page to have a preface with an
alphabetical listing ofdefinitions. I ended up using three new
Blosxom plugins to make thiswork: &lt;a class="reference external" href="http://www.blosxom.com/plugins/general/config.htm"&gt;config&lt;/a&gt;, &lt;a class="reference external" href="http://www.blosxom.com/plugins/display/postheadprefoot.htm"&gt;postheadprefoot&lt;/a&gt; and
&lt;a class="reference external" href="http://blosxom.ookee.com/blosxom/plugins/v2/sort_order-v0i85"&gt;sort_order&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For other Blosxom users I've updated my &lt;a class="reference external" href="http://tech.agilitynerd.com/site_plugins.html"&gt;site pluginspage&lt;/a&gt; with the
details.&lt;/p&gt;
&lt;p&gt;Hopefully these changes along with the page links and previous/nextpage
arrows at the bottom of the page will give everyone enough ways to move
easily around the sight.&lt;/p&gt;
</content><category term="webdev"></category></entry><entry><title>Modifications to Blosxom moreenties PluginLinks to a Configurable Number of Index Pages</title><link href="https://tech.agilitynerd.com/modifications-to-blosxom-moreenties-pluginlin-1.html" rel="alternate"></link><published>2004-10-21T19:00:00-05:00</published><updated>2004-10-21T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-10-21:/modifications-to-blosxom-moreenties-pluginlin-1.html</id><summary type="html">&lt;p&gt;Jason Clark created the &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/plugins/moreentries"&gt;moreentries&lt;/a&gt; plugin for &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; to allow
adding links to the previous and next group of articles/entries in the
head or foot of a Blosxom weblog. He solved a problem that most weblogs
have; it can be difficult for visitors to browse older articles.
Although he …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Jason Clark created the &lt;a class="reference external" href="http://jclark.org/weblog/WebDev/Blosxom/plugins/moreentries"&gt;moreentries&lt;/a&gt; plugin for &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; to allow
adding links to the previous and next group of articles/entries in the
head or foot of a Blosxom weblog. He solved a problem that most weblogs
have; it can be difficult for visitors to browse older articles.
Although he calls the solution a hack, he nicely solved an important
problem.&lt;/p&gt;
&lt;p&gt;I had been using the morentries plugin but found the text links didn't
make it obvious to some visitors that there were more pages of entries.
So I added images to the links to make them stand out. But I decided it
might be more intuitive to use the same model used by search sites;
create a graphical and textual link for each page of articles along with
previous and next links.&lt;/p&gt;
&lt;p&gt;So I decided to modify Jason's plugin to provide this feature. If you
take a look at the bottom of my &lt;a class="reference external" href="http://agilitynerd.com/blog/"&gt;AgilityNerd&lt;/a&gt; home page page with more
than $blosxom::num_entries entries and scroll to the bottom) you'll see
grey dots, arrows, and page numbers each linking to a page of articles.&lt;/p&gt;
&lt;p&gt;You can download the modified plugin &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/moreentries.zip"&gt;here&lt;/a&gt;. The prolog of the file
describes the configuration options. I've included the public domain
icons I use on my site in the zip file for your experimentation. Since
there are a few configuration options I've put some examples below so
you can see how the new parameters control the html stored in
$moreentries::pagelinks.&lt;/p&gt;
&lt;p&gt;With these settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$numpagelinks = 10;$textlinks = 1;                # set to 1 to enable text links 0 to disable
$imagelinks = 0;        # set to 1 to enable image links 0 to disable
&lt;/pre&gt;
&lt;p&gt;A screenshot of the HTML generated when the fifth page is selected:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image0" src="https://data.agilitynerd.com/images/moreentries_pagelink_1.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;With these settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$numpagelinks = 10;
$textlinks = 0;                # set to 1 to enable text links 0 to disable
$imagelinks = 1;        # set to 1 to enable image links 0 to disable
$prevlinkimage = &amp;quot;/images/left.gif&amp;quot;;
$nextlinkimage = &amp;quot;/images/right.gif&amp;quot;;
$currentpageimage = &amp;quot;/images/ball.red.gif&amp;quot;;&amp;#64;pageimages = qw( /images/ball.gray.gif );
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;A screenshot of the HTML generated when the fifth page is selected:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image1" src="https://data.agilitynerd.com//images/moreentries_pagelink_6.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;With these settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$numpagelinks = 10;
$textlinks = 1;                # set to 1 to enable text links 0 to disable
$imagelinks = 1;        # set to 1 to enable image links 0 to disable
$prevlinkimage = &amp;quot;/images/left.gif&amp;quot;;
$nextlinkimage = &amp;quot;/images/right.gif&amp;quot;;
$currentpageimage = &amp;quot;/images/text.gif&amp;quot;;&amp;#64;pageimages = qw( /images/ball.gray.gif /images/ball.red.gif );
&lt;/pre&gt;
&lt;p&gt;A screenshot of the HTML generated when the fifth page is selected:&lt;/p&gt;
&lt;div class="thumbnail"&gt;&lt;p&gt;&lt;img alt="image2" src="https://data.agilitynerd.com//images/moreentries_pagelink_4.jpg" /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Thanks again to Jason for creating this plugin. It was fun to modify and
I hope it is useful for other Blosxom webloggers.&lt;/p&gt;
</content><category term="webdev"></category></entry><entry><title>See More... Added to Article Display on Index Pages</title><link href="https://tech.agilitynerd.com/see-more-added-to-article-display-on-index-pa-1.html" rel="alternate"></link><published>2004-09-26T19:00:00-05:00</published><updated>2004-09-26T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-09-26:/see-more-added-to-article-display-on-index-pa-1.html</id><summary type="html">&lt;p&gt;I know my Agility readers will give me a hard time about this
non-Agility post, but hey I'm &amp;quot;thinking about technology&amp;quot; too. I was
limiting to five the number of articles/posts on my main and index pages
to minimize the download time for non cable/DSL visitors. But I've …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I know my Agility readers will give me a hard time about this
non-Agility post, but hey I'm &amp;quot;thinking about technology&amp;quot; too. I was
limiting to five the number of articles/posts on my main and index pages
to minimize the download time for non cable/DSL visitors. But I've
noticed some searches hit my site for which an article was moved off my
main page and I'm sure they didn't hunt around for the missing post. So
I wanted to show more articles without increasing the download time.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; plugin &lt;a class="reference external" href="http://www.blosxom.com/plugins/display/seemore.htm"&gt;seemore&lt;/a&gt; automatically adds a link labeled &amp;quot;See
More...&amp;quot; in place of a special label in each article. Selecting the link
takes you to the full text of the article. This useful plugin was
developed by &lt;a class="reference external" href="http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/SeeMore/"&gt;Todd Larason&lt;/a&gt;, a blogger who's developed a number of
powerful Blosxom plugins.&lt;/p&gt;
&lt;p&gt;I made a minor modification to seemore, which I've forwarded to Todd, to
allow showing the entire article for a configurable number of articles
on a page. This lets you see the full text and diagrams for the latest
articles and only seeing the first couple paragraphs of the older
articles.&lt;/p&gt;
&lt;p&gt;If you are a Blosxom blogger and would like my modifications you can
download my modified version &lt;a class="reference external" href="http://data.agilitynerd.com/downloads/seemore.zip"&gt;here&lt;/a&gt;. Unzip it and put it in your plugin
directory. By default it should work the same as Todd's original
version. A new configuration variable $show_all_of_stories_until can
be set to the number of stories on each index page that you'd like to
show in full.&lt;/p&gt;
&lt;p&gt;So I've modified most of my posts to include the seemore label and
increased the number of articles shown on each index page to
$blosxom::num_entries. I like this new site configuration; let me know
if you do too.&lt;/p&gt;
</content><category term="webdev"></category></entry><entry><title>Backup Your Data Lately?</title><link href="https://tech.agilitynerd.com/backup-your-data-lately-1.html" rel="alternate"></link><published>2004-08-20T19:00:00-05:00</published><updated>2004-08-20T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-08-20:/backup-your-data-lately-1.html</id><summary type="html">&lt;p&gt;A few weeks ago I bought a &lt;a class="reference external" href="http://www.ximeta.com/products/network_drives/netdisk/index.php"&gt;Ximeta NetDisk&lt;/a&gt; 120 GB external hard drive
to back up the data on my home computers. There are a number of vendors
making hard drives that support USB connections; this model is unique in
that it also supports direct ethernet connections. Unlike more …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few weeks ago I bought a &lt;a class="reference external" href="http://www.ximeta.com/products/network_drives/netdisk/index.php"&gt;Ximeta NetDisk&lt;/a&gt; 120 GB external hard drive
to back up the data on my home computers. There are a number of vendors
making hard drives that support USB connections; this model is unique in
that it also supports direct ethernet connections. Unlike more expensive
Network Attached Storage devices (more than $ 1000) this unit only cost
a little more than the hard drive itself; about $ 150. The only downside
is that it uses it's own proprietary network protocol which requires
installation of a driver on any computers using the drive.&lt;/p&gt;
&lt;p&gt;My goal was to put this drive on my network switch and back up data from
my XP, WinME, RedHat 8.0, and Win95 machines. It turns out this was a
little trickier than I expected. So I spent the better part of the
morning on a cool, grey Chicago day making this all work.&lt;/p&gt;
&lt;p&gt;The disk comes formatted for NTFS but in order for it to be shared on
the older Windows platforms and my Linux machine it needs to be
reformatted to use a FAT32 file system. Windows XP doesn't support
formatting drives for FAT32 so I had to install the Ximeta driver
software on my WinME machine and install the NetDisk on my network.&lt;/p&gt;
&lt;p&gt;Running fdisk and reformatting the hard drive is the only &amp;quot;scary&amp;quot; aspect
of the installation. Choosing the wrong drive or partition would be a
&lt;em&gt;bad&lt;/em&gt; thing. This &lt;a class="reference external" href="http://www.ximeta.com/support/guides/netdisk/ndas/98seme/05.php"&gt;document&lt;/a&gt; gives a good step by step description.&lt;/p&gt;
&lt;p&gt;I was skeptical that running fdisk over the network would work
correctly, but it did. At this point I was able to view the drive on
both my WinME and XP systems and copy data to the drive as if it was
locally connected.&lt;/p&gt;
&lt;p&gt;I was pretty sure making the drive work for Linux would be difficult.
Unfortunately, the PDF documents from the Ximeta website are unreadable
as they require installing the Korean Acrobat extensions... thankfully
Google has its &amp;quot;View has HTML&amp;quot; facility which let me read the RedHat
instructions. The docs on the install CDROM are viewable (but don't
include the RedHat docs).&lt;/p&gt;
&lt;p&gt;I'll spare you all the trial and error but after downloading the &lt;a class="reference external" href="http://www.ximeta.com/support/downloads/red_hat_8/index.php"&gt;driver
RPM&lt;/a&gt; from the website and installing it I couldn't configure and
connect to the drive on the network. It could be that ports required for
their protocol aren't opened on my Linux machine, but Ximeta doesn't
give any information on what ports are used by their driver. The admin
tool gives some cryptic error messages that Googling and the docs didn't
explain (the docs recommend reinstalling the drivers for any errors...).
I ended up connecting the drive directly via USB and was able to mount
the drive and backup my user and system accounts to the disk.&lt;/p&gt;
&lt;p&gt;So it looks like my goal of leaving the drive on the network and copying
to it from any computer will only work for Windows machines. But at
least I have a mechanism for backing up all my machines, that is easy
enough that I'll use it all the time. My next step is looking into
configuring &lt;a class="reference external" href="http://rsync.samba.org"&gt;rsync&lt;/a&gt; or a similar mechanism to only backup the changed
files to the NetDisk.&lt;/p&gt;
&lt;p&gt;In summary, I'd recommend this drive for anyone who is using Windows XP;
it is plug and play for that operating system. If you are computer savvy
you can make this hard drive play with other systems too.&lt;/p&gt;
&lt;p&gt;However you do it, &lt;strong&gt;backup your computer!&lt;/strong&gt;&lt;/p&gt;
</content><category term="devops"></category><category term="sysadmin"></category></entry><entry><title>Centering DIVs in CSS</title><link href="https://tech.agilitynerd.com/centering-divs-in-css-1.html" rel="alternate"></link><published>2004-08-15T00:00:00-05:00</published><updated>2004-08-15T00:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-08-15:/centering-divs-in-css-1.html</id><summary type="html">&lt;p&gt;I took a look at this site on an 800x600 resolution monitor and didn't
like how the original layout looked. Having the menu box on the left hand
side with a &lt;tt class="docutils literal"&gt;20px&lt;/tt&gt; margin to the left used up too much screen space. So I
decided to move the menu box …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I took a look at this site on an 800x600 resolution monitor and didn't
like how the original layout looked. Having the menu box on the left hand
side with a &lt;tt class="docutils literal"&gt;20px&lt;/tt&gt; margin to the left used up too much screen space. So I
decided to move the menu box to the right of the main body of the page.&lt;/p&gt;
&lt;p&gt;I could have hard-coded the box position; but decided that I wanted
to have the main body remain centered regardless of browser size.
That would complicate the positioning of the menu box (which is it's
own div not contained with the main page). Once again &lt;a class="reference external" href="http://www.alistapart.com"&gt;A List Apart&lt;/a&gt; had
a good &lt;a class="reference external" href="http://www.alistapart.com/articles/journey/"&gt;article&lt;/a&gt; describing how they got their CSS centering to work.&lt;/p&gt;
&lt;p&gt;The ALA article had links to these two pages describing the methods
I used: &lt;a class="reference external" href="http://bluerobot.com/web/css/center1.html"&gt;Auto Width Margins&lt;/a&gt; and &lt;a class="reference external" href="http://bluerobot.com/web/css/center2.html"&gt;NegativeMargins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The AgilityNerd site has two main divs: #page for the main body
and#sidebar for the box containing the menu. I used the Auto Width
Margins method to center the main body:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#page { font-size: 13px; background: white; border: 1px solid black;
top: 0px; width: 640px; margin: 0px auto; margin-bottom: 50px;
text-align: left;}
&lt;/pre&gt;
&lt;p&gt;The two lines of interest are &lt;tt class="docutils literal"&gt;margin: 0px auto;` and &lt;span class="pre"&gt;``text-align:&lt;/span&gt; left;&lt;/tt&gt;.
Thelater is required because I used the IE 5.x workaround of
specifying &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;text-align:&lt;/span&gt; center;&lt;/tt&gt; in the body element.&lt;/p&gt;
&lt;p&gt;So now that the main body of the site was centered, I wanted the menu to
be floating off to the right of the main body. The Negative
Margins method let me horizontally center the divand also offset it
horizontally by any amount. I offset it by half the(fixed) width of the
main body plus a little space. The additionalspace keeps IE 5.x
browser's miscalculation of sizes from causing theborders to not meet
correctly:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#sidebar { background: white; font-size: 12px; position: fixed;
width: 140px; height: auto; top: 70px; left: 50%; margin-left:
330px; text-align: center; border: 1px solid black; }
&lt;/pre&gt;
&lt;p&gt;Unlike the article, I used &lt;tt class="docutils literal"&gt;position: fixed&lt;/tt&gt; instead of absolute. This
allows the menubox to float in place during scrolling (but only for CSS
compliant browsers like: Mozilla/Netscape).&lt;/p&gt;
</content><category term="webdev"></category><category term="css"></category><category term="webdevelopment"></category></entry><entry><title>CSS for Print Media</title><link href="https://tech.agilitynerd.com/css-for-print-media-1.html" rel="alternate"></link><published>2004-08-14T23:16:00-05:00</published><updated>2004-08-14T23:16:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-08-14:/css-for-print-media-1.html</id><summary type="html">&lt;p&gt;In keeping with my desire to have this site's layout be easy to read,I
wanted it to look good in print too. The layout uses &lt;a class="reference external" href="http://www.w3.org/Style/CSS/"&gt;CSS&lt;/a&gt; instead
of HTML tables. This lets me position the divscontaining various text for
the screen and it allows me to positionand hide text …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In keeping with my desire to have this site's layout be easy to read,I
wanted it to look good in print too. The layout uses &lt;a class="reference external" href="http://www.w3.org/Style/CSS/"&gt;CSS&lt;/a&gt; instead
of HTML tables. This lets me position the divscontaining various text for
the screen and it allows me to positionand hide text differently for the
printed media.&lt;/p&gt;
&lt;p&gt;Any CSS book covers the basics of changing the display,
position, margins, and padding along with the use of separate style
sheets forprint and screen media. But I ran into problems with printing
mysite's pages with the Mozilla/Netscape browsers. Only
the first page of my main div would print. Alittle Googling turned up
this excellent &lt;a class="reference external" href="http://www.alistapart.com/articles/goingtoprint/"&gt;article&lt;/a&gt; on &lt;a class="reference external" href="http://www.alistapart.com"&gt;A ListApart&lt;/a&gt;. ALA is a great resource for
web developers.&lt;/p&gt;
&lt;p&gt;The only clarification I have is that since both the screen and the print
CSS files are included, the print CSS file's elements may needto set
additional properties as well as reseting properties that existin the
screen media file. Otherwise the definitions for the screen will be used.
I had to set some div's position properties to static to get them back
into the document flow.&lt;/p&gt;
</content><category term="webdev"></category></entry><entry><title>AgilityNerd Blosxom Plugins</title><link href="https://tech.agilitynerd.com/agilitynerd-blosxom-plugins-1.html" rel="alternate"></link><published>2004-08-10T19:00:00-05:00</published><updated>2004-08-10T19:00:00-05:00</updated><author><name>Steve Schwarz</name></author><id>tag:tech.agilitynerd.com,2004-08-10:/agilitynerd-blosxom-plugins-1.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Updated 20-Jun-08 for addition of storytitle&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm really impressed with the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; &lt;a class="reference external" href="http://blosxom.com/documentation/users/plugins.html"&gt;plugin&lt;/a&gt; mechanism. &lt;a class="reference external" href="http://www.raelity.org/"&gt;Rael
Dornfest&lt;/a&gt;, author of Blosxom, put hooks at strategic points in the page
generation process. At each hook point the main script iterates all the
plugins present in the plugin directory. Plugins that implement the hook …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Updated 20-Jun-08 for addition of storytitle&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm really impressed with the &lt;a class="reference external" href="http://blosxom.sourceforge.net/"&gt;Blosxom&lt;/a&gt; &lt;a class="reference external" href="http://blosxom.com/documentation/users/plugins.html"&gt;plugin&lt;/a&gt; mechanism. &lt;a class="reference external" href="http://www.raelity.org/"&gt;Rael
Dornfest&lt;/a&gt;, author of Blosxom, put hooks at strategic points in the page
generation process. At each hook point the main script iterates all the
plugins present in the plugin directory. Plugins that implement the hook
function can then modify the state or add additional data for use in the
page presentation.&lt;/p&gt;
&lt;p&gt;Consequently, a plugin only implements the hook functions it needs
invoked to provide its features. Some powerful plugins need only
implement one or two hook functions and are implemented with a couple
dozen lines of Perl code.&lt;/p&gt;
&lt;p&gt;When I started building this site I found it a little confusing to sort
through all the plugins and understand their functionality. The ease of
creating and modifying plugins has allowed a lot of developers to
contribute to the Blosxom plugin &lt;a class="reference external" href="http://blosxom.com/plugins"&gt;registry&lt;/a&gt;. I found it helpful when
Blosxom sites listed the plugins that implemented their site's features.
So here is the list of plugins I use.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/syndication/atomfeed.htm"&gt;atomfeed&lt;/a&gt; - Provides the AgilityNerd Atom feed. Point
your browser to &lt;a class="reference external" href="http://agilitynerd.com/blog/index.atom"&gt;http://agilitynerd.com/blog/index.atom&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/breadcrumbs.htm"&gt;breadcrumbs&lt;/a&gt; - Creates the click-able trail to the current position in
the weblog's path shown at the top of all .index pages and at the bottom
of each article.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/binary.htm"&gt;binary&lt;/a&gt; - Used to supply images from the directory in which an article
exists. Modified to use LWP::MediaTypes since MIME::Types isn't
installed on this web server. I also commented out the interpolate()
function and added it to the one in interpolate_fancy to make this
plugin coexist with interpolate fancy.&lt;/p&gt;
&lt;p&gt;I no longer use binary because it is faster to just have Apache serve
binary files directly from one or more directories outside of the
Blosxom src tree.&lt;/p&gt;
&lt;p&gt;I've stopped using this plugin since it requires Blosxom to look at each
file to determine whether or not it should be skipped. So I'm starting
to put all binary files in my images directory outside of Blosxom. This
has the added advantage that I can just put &amp;quot;/images&amp;quot; and &amp;quot;/video&amp;quot; in by
robots.txt file so my images and videos don't get indexed by search
engine robots.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/category/categories.htm"&gt;categories&lt;/a&gt; - Displays the tree of paths/categories of articles on the
&lt;a class="reference external" href="http://agilitynerd.com/blog/navigate/"&gt;navigate&lt;/a&gt; page. Slightly modified to remove the root directory from
the display. Added a hide feature to hide specified directories entirely
- used this to prune subdirectories for my side bar menu. Removed
flavour ending from all generated links since all paths are valid links,
are shorter for people emailing links around, and would always use the
default flavour. Modified to not calculate breadcrumbs since I use the
breadcrumbs plugin for that feature.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/general/config.htm"&gt;config&lt;/a&gt; - Allows any Perl variable to be set differently in any
category/directory. I use this to change the sort order for my
&lt;a class="reference external" href="http://agilitynerd.com/blog/agility/glossary/"&gt;glossary&lt;/a&gt; page to be alphabetical order by changing the sort_order
plugin's sorder variable.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/indexing/entries_cache.htm"&gt;entries_cache&lt;/a&gt; - Speeds up processing of the site by caching the
article index to avoid scanning directories until a configured amount of
time has elapsed. Also caches article creation time to allow for editing
of articles without changing their timestamps.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/blosxom-hit-counter-and-favorites-plugins-1.html"&gt;favorites&lt;/a&gt; - Used to automatically generate my &lt;a class="reference external" href="http://agilitynerd.com/blog/static/Favorites.html"&gt;Favorites page&lt;/a&gt; based on the visitor hit counts recorded by my hitcounter plugin.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/include/file.htm"&gt;file&lt;/a&gt; - Used to include the navigational side div into all pages
without copy/paste of the same HTML.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/search/find.htm"&gt;find&lt;/a&gt; - Provides search capabilities for all articles, comments, and
linkbacks on the web site. HTML slightly modified to remove the advanced
search link. Also set the hidden &amp;quot;path&amp;quot; input to the empty string so the
entire site is searched regardless of where in the hierarchy the search
was initiated.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/text/foreshortened.htm"&gt;foreshortened&lt;/a&gt; - Ends articles at the end of the first sentence. Used
to provide the shortened version of articles for use in RSS and Atom
feeds.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/headlines.htm"&gt;headlines&lt;/a&gt; - Used to determine all the entries in my &lt;a class="reference external" href="http://agilitynerd.com/blog/agility/glossary/"&gt;glossary&lt;/a&gt;
category and create links in alphabetical order. Requires
interpolate_fancy.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/files/hide.htm"&gt;hide&lt;/a&gt; - Excludes directories of articles from the index views (like
the agilitynerd.com main page) but still allows them to be searched by
find.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/blosxom-hit-counter-and-favorites-plugins-1.html"&gt;hitcounter&lt;/a&gt; - Used to automatically generate the
number of visitors displayed in the footer for each page on my site.
These hit counts per page are used by my favorites plugin to generate my
&lt;a class="reference external" href="http://agilitynerd.com/blog/static/Favorites.html"&gt;Favorites page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/interpolate/interpolate_fancy.htm"&gt;interpolate_fancy&lt;/a&gt; - Used to remove the space allocated for the
results of find and the breadcrumbs plugin when their variables aren't
defined. I think I'll use this more in the future to dynamically change
the page format.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/blosxom-plugins-lastcommented-and-lastread-1.html"&gt;lastcommented&lt;/a&gt; - Used to automatically
generate the list of the last 10 articles that were commented shown in
the side bar on each page of the site.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/blosxom-plugins-lastcommented-and-lastread-1.html"&gt;lastread&lt;/a&gt; - Used to automatically generate
the list of the last 10 articles that were read shown in the side bar on
each page of the site.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/moreentries.htm"&gt;moreentries&lt;/a&gt; - Creates the &amp;quot;Previous&amp;quot; and &amp;quot;Next&amp;quot; links at the bottom
of each index or search page when there are more than
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$blosxom::num_entries&lt;/span&gt;&lt;/tt&gt; entries. I've modified this plugin as I described
&lt;a class="reference external" href="/minor-additional-mods-to-blosxom-moreenties-p-1.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/postheadprefoot.htm"&gt;postheadprefoot&lt;/a&gt; - Allows each category/directory to have unique text
inserted after the header and/or before the footer. I use this on my
&lt;a class="reference external" href="http://agilitynerd.com/blog/agility/glossary/"&gt;glossary&lt;/a&gt; page to add a category specific subsection. I had to &lt;a class="reference external" href="http://groups.yahoo.com/group/blosxom/message/9364"&gt;modify
the script&lt;/a&gt; to have paths resolve correctly.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/general/redirect.htm"&gt;redirect&lt;/a&gt; - Unmodified version of this plugin. I had to use it when I
moved a directory within my tree so search engine and email list links
would redirect correctly.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/refererblock-version-02-1.html"&gt;refererblock&lt;/a&gt; - A blacklist driven plugin to permanently
redirect referer spam. The redirect probably has little effect on the
receipt of referer spam but the 301 redirect code keeps the spammer's
sites out of my site statistics.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/syndication/rss10.htm"&gt;rss10&lt;/a&gt; - Provides the extra information to provide a valid RSS 1.0
feed for syndication. Slightly modified to work with the writeback
plugin.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/display/seemore.htm"&gt;seemore&lt;/a&gt; - Adds &amp;quot;See more...&amp;quot; link within the text of a post when the
post is shown in an index page. The location of the link is controlled
by placing a special tag in the posting source file. I have &lt;a class="reference external" href="/see-more-added-to-article-display-on-index-pa-1.html"&gt;modified&lt;/a&gt;
this plugin to show the first &amp;quot;n&amp;quot; entries of an index page in full.
Furthe&lt;/p&gt;
&lt;p&gt;r modified to always create a link to my html flavoured pages.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://blosxom.ookee.com/blosxom/plugins/v2/sort_order-v0i85"&gt;sort_order&lt;/a&gt; - Allows sorting by date, directory names, or file name.
I modified it to not read the URI parameters and just use a
configuration variable. Then I used the config plugin to change the sort
order for my &lt;a class="reference external" href="http://agilitynerd.com/blog/agility/glossary/"&gt;glossary&lt;/a&gt; page to sort by file name instead of in reverse
chronological order.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.leverton.org/blosxom/Software/Projects/Blosxom/storytitle.html"&gt;storytitle&lt;/a&gt; - sets the &amp;lt;title&amp;gt; element in the HTML &amp;lt;head&amp;gt; with the
title of the article or the name of the category. I use it
to aid in navigation and site statistics]].&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/date/timezone2.htm"&gt;timezone&lt;/a&gt; - Adjusts timestamps automatically created or entered using
meta-creation_date: to be offset correctly to my timezone; which is
different from the web server's timezone.&lt;/p&gt;
&lt;p&gt;I no longer use timezone since I can set the timezone on my
Slicehost server.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="/blosxom-default-flavour-plugin-fixes-unknown-1.html"&gt;defaultflavour&lt;/a&gt; - Provides the user with the
default Blosxom flavoured feed when an unknown flavour is requested.
This keeps the user from getting the Unknown Flavour error across the
top of the page.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://varg.dyndns.org/psi/pub/code/misc/wbcaptcha.html"&gt;wbcaptcha&lt;/a&gt; - Provides an ASCII image via &lt;a class="reference external" href="http://www.figlet.org"&gt;FIGlet&lt;/a&gt; when visitors enter
comments to stop spam bots from saturating my blog comments. See this article for &lt;a class="reference external" href="/comment-spam-and-wbcaptcha-plugin-enhancement-1.html"&gt;my modifications&lt;/a&gt; to this plugin.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.blosxom.com/plugins/text/wikiwordish.htm"&gt;wikiwordish&lt;/a&gt; - Provides WikiWord-like linking to Wiki articles, local
article file names and modified to replace WikiWords with HTML &amp;lt;a&amp;gt;
links. This plugin saves me from having to enter links to common sites
to which I always refer. I had to name this file 00wikiwordish so that
WikiWords would be correctly replaced in RSS and Atom feeds.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.lathi.net/twiki-bin/view/Main/BlogSpam"&gt;writeback blacklist plugin&lt;/a&gt; - Provides comment and TrackBack
capability. This is Doug Alcorn's blacklist modified version to help
fight comment spam. I've slightly modified mine to try to protect
comment poster's from spam by obfuscating their email addresses.&lt;/p&gt;
&lt;p&gt;In case anyone is trying to get these plugins to &amp;quot;play&amp;quot; together I have
them named as follows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;000refererblock&lt;/li&gt;
&lt;li&gt;001redirect&lt;/li&gt;
&lt;li&gt;002defaultflavour&lt;/li&gt;
&lt;li&gt;005wikiwordish&lt;/li&gt;
&lt;li&gt;007google_highlight&lt;/li&gt;
&lt;li&gt;008wbcaptcha&lt;/li&gt;
&lt;li&gt;01atomfeed&lt;/li&gt;
&lt;li&gt;01breadcrumbs&lt;/li&gt;
&lt;li&gt;01categories&lt;/li&gt;
&lt;li&gt;01config&lt;/li&gt;
&lt;li&gt;01entriescache&lt;/li&gt;
&lt;li&gt;01favorites&lt;/li&gt;
&lt;li&gt;01file&lt;/li&gt;
&lt;li&gt;01find&lt;/li&gt;
&lt;li&gt;01foreshortened&lt;/li&gt;
&lt;li&gt;01fullcategories&lt;/li&gt;
&lt;li&gt;01headlines&lt;/li&gt;
&lt;li&gt;01hide&lt;/li&gt;
&lt;li&gt;01moreentries&lt;/li&gt;
&lt;li&gt;01postheadprefoot&lt;/li&gt;
&lt;li&gt;01rss10&lt;/li&gt;
&lt;li&gt;01seemore&lt;/li&gt;
&lt;li&gt;01sort_order&lt;/li&gt;
&lt;li&gt;01storytitle&lt;/li&gt;
&lt;li&gt;01writeback&lt;/li&gt;
&lt;li&gt;02hitcounter&lt;/li&gt;
&lt;li&gt;02lastcommented&lt;/li&gt;
&lt;li&gt;02lastread&lt;/li&gt;
&lt;li&gt;02recentwritebacks&lt;/li&gt;
&lt;li&gt;50interpolate_fancy&lt;/li&gt;
&lt;/ul&gt;
</content><category term="webdev"></category><category term="blosxom"></category><category term="perl"></category><category term="plugin"></category></entry></feed>