<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0">
<channel>
<title>Fun with ones and zeros - django</title>
<description><![CDATA[Barry's notes on computer software and hardware]]></description>
<link>/blog/tags/django</link>
<lastBuildDate>Thu, 28 May 2026 07:54:11 -0700</lastBuildDate>
<item>
<title>bpgsql 2.0 alpha 1</title>
<link>/blog/entries/bpgsql-20-alpha-1</link>
<pubDate>Mon, 22 Sep 2008 12:58:00 -0700</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[<p>For many years I've been using <a href="/software/bpgsql">bpgsql</a>, my own pure-Python PostgreSQL client, and I've finally sat down and got things somewhat polished up enough to put together as a real package.  </p>
<p>One thing that motivated the work was the desire to use in with Django - after seeing psycopg2 do some funny things when used under mod_wsgi.    There's no doubt it's slower, but it's much easier to hack on, and might be of interest to people running Djano under other Pythons such as PyPy or Jython.  Getting it to pass all the Django unittests really ironed out a lot of bugs, so I think it's in fairly decent shape now.</p>]]></description>
</item>
<item>
<title>Django comments</title>
<link>/blog/entries/django-comments</link>
<pubDate>Sat, 19 Jan 2008 13:14:02 -0800</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[
<p>I've tried adding a comments feature to this site, using the stock <code>django.contrib.comments</code> application, mostly following the <a href="http://code.djangoproject.com/wiki/UsingFreeComment">instructions from the Django Wiki</a>, and also using James Bennet's <a href="http://code.google.com/p/django-comment-utils/">comment-utils</a> described <a href="http://www.b-list.org/weblog/2007/jun/25/hacking-comments-without-hacking-comments/">here</a>.
</p>
<p>The Wiki is pretty thorough, but one thing I think it missed was that in the template for displaying comments, you should have a 
</p>
<div class="source"><pre><span class="x">&lt;a name=&quot;c</span><span class="cp">{{</span> <span class="nv">comment.id</span> <span class="cp">}}</span><span class="x">&quot; /&gt;</span>
</pre></div>
<p>Near the beginning of where your comment starts being output, so the standard <code>get_absolute_url()</code> method for the <code>FreeComment</code> model has a place to point to.
</p>
<p>A couple things that were not really talked about in the <code>comment-utils</code> docs were <a href="http://www.voidspace.org.uk/python/akismet_python.html">where to get the Python Akismet library</a>, and the need for a <code>comment_utils/comment_notification_email.txt</code> template if you want e-mail notifications.  I ended up with something simple like:
</p>
<div class="source"><pre><span class="x">Comment: http://127.0.0.1</span><span class="cp">{{</span> <span class="nv">comment.get_absolute_url</span> <span class="cp">}}</span><span class="x"></span>
<span class="x">From: </span><span class="cp">{{</span> <span class="nv">comment.person_name</span> <span class="cp">}}</span><span class="x"></span>

<span class="x">-----</span>
<span class="cp">{{</span> <span class="nv">comment.comment</span> <span class="cp">}}</span><span class="x"></span>
<span class="x">-----</span>

<span class="x">Admin: http://127.0.0.1/admin/comments/freecomment/</span><span class="cp">{{</span><span class="nv">comment.id</span><span class="cp">}}</span><span class="x">/</span>
</pre></div>
<p>I'm a bit skeptical about how well the <a href="http://www.akismet.com/">Akismet</a> spam-filtering service will work, but we'll see how it goes.
</p>
]]></description>
</item>
<item>
<title>Migrating Django databases</title>
<link>/blog/entries/migrating-django-databases</link>
<pubDate>Mon, 07 Jan 2008 10:36:36 -0800</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[
<p>I started out a few <a href="http://www.djangoproject.com">Django</a> projects using SQLite as the DB backend a while back, and decided to upgrade them to PostgreSQL.  Turns out using the <code>manage.py</code> <code>dumpdata</code> and <code>loaddata</code>, the switch can be fairly smooth.  The steps turned out to be something like:
</p>
<ul>
 <li><p>Create empty database in PostgreSQL
</p>

 </li>

 <li><p>Copy the existing project's <code>settings.py</code> to a <code>new_settings.py</code>
</p>

 </li>

 <li><p>Edit <code>new_settings.py</code> to replace the SQLite connection info with PgSQL info.
</p>

 </li>

 <li><p>Run <code>./manage.py syncdb --settings=new_settings</code> (note that there's not a '.py' at the end of that parameter) to create the PgSQL tables (and also test the connection info).  Say 'no' when asked if you want to create a user.
</p>

 </li>

 <li><p>Run <code>./manage.py dbshell --settings=new_settings</code> to get into the psql shell, to clean out a few tables populated by <code>syncdb</code>
</p>
<ul>
 <li>
     <code>delete from auth_permission;</code>
 </li>

 <li>
     <code>delete from django_content_type;</code>
 </li>
</ul>

 </li>

 <li><p>Dump the data from the old DB backend with: <code>./manage.py dumpdata &gt;olddata.json</code>
</p>

 </li>

 <li><p>Load the data into the new DB backend with: <code>./manage.py loaddata --settings=new_settings olddata.json</code>
</p>

 </li>

 <li><p>Swap the settings files: <code>mv settings.py old_settings.py; mv new_settings.py settings.py</code>
</p>

 </li>

 <li><p>Restart the server
</p>

 </li>
</ul>
<p>Barring any complications, the entire switch can take just a few minutes.  Of course, for anything you really care about you'd do some more testing before going live, but that's up to you.
</p>


]]></description>
</item>
<item>
<title>PostgreSQL full text search with Django</title>
<link>/blog/entries/postgresql-full-text-search-django</link>
<pubDate>Wed, 02 Jan 2008 05:00:00 -0800</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[
<p><a href="http://www.postgresql.org">PostgreSQL</a> 8.3 is coming out soon with full text search integrated into the core database system.  It's pretty well documented in <a href="http://www.postgresql.org/docs/8.3/static/textsearch.html">chapter 12 of the PostgreSQL docs</a>.   The docs are a bit intimidating, but it turns out to be pretty easy to use with <a href="http://www.djangoproject.com">Django</a>.
</p>
<p>Let's say you're doing a stereotypical blog application, named 'blog', and have a model for entries such as:
</p>
<div class="source"><pre><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>

<span class="k">class</span> <span class="nc">Entry</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">title</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mf">128</span><span class="p">)</span>
    <span class="n">body</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
</pre></div>
<p>after adding your app, and doing a <code>syncdb</code>, you should have a PostgreSQL table named <code>blog_entry</code>.  You'll need to connect to the database with <code>psql</code>, or probably more conveniently with <code>manage.py dbshell</code> to execute a few SQL commands to setup full text searching.
</p>
<h3>PostgreSQL Setup</h3>
<p>First, you'll want to add a column to hold a <code>tsvector</code> for the blog entry, which is a preprocessed version of  text optimized for searching.
</p>
<div class="source"><pre><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">blog_entry</span> <span class="k">ADD</span> <span class="k">COLUMN</span> <span class="n">body_tsv</span> <span class="n">tsvector</span><span class="p">;</span>
</pre></div>
<p>Next, you'll want to add a trigger to update the <code>body_tsv</code> column whenever a record is inserted or updated:
</p>
<div class="source"><pre><span class="k">CREATE</span> <span class="k">TRIGGER</span> <span class="n">tsvectorupdate</span> <span class="k">BEFORE</span> <span class="k">INSERT</span> <span class="k">OR</span> <span class="k">UPDATE</span> <span class="k">ON</span> <span class="n">blog_entry</span> 
    <span class="k">FOR</span> <span class="k">EACH</span> <span class="k">ROW</span> <span class="k">EXECUTE</span> <span class="k">PROCEDURE</span> <span class="n">tsvector_update_trigger</span><span class="p">(</span><span class="n">body_tsv</span><span class="p">,</span> <span class="s1">&#39;pg_catalog.english&#39;</span><span class="p">,</span> <span class="n">body</span><span class="p">);</span>
</pre></div>
<p>You'll probably also want an index on that field, to make searches more efficient:
</p>
<div class="source"><pre><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">blog_entry_tsv</span> <span class="k">ON</span> <span class="n">blog_entry</span> <span class="k">USING</span> <span class="n">gin</span><span class="p">(</span><span class="n">body_tsv</span><span class="p">);</span>
</pre></div>
<p>Lastly, if you already have records in the <code>blog_entry</code> table, you'll want to update the <code>body_tsv</code> column for those records (any new records will be automatically taken care of by the trigger).
</p>
<div class="source"><pre><span class="k">UPDATE</span> <span class="n">blog_entry</span> <span class="k">SET</span> <span class="n">body_tsv</span><span class="o">=</span><span class="n">to_tsvector</span><span class="p">(</span><span class="n">body</span><span class="p">);</span>
</pre></div>
<p>As a test, to search for the words 'hello world' for example, you could try:
</p>
<div class="source"><pre><span class="k">SELECT</span> <span class="n">title</span> <span class="k">FROM</span> <span class="n">blog_entry</span> <span class="k">WHERE</span> <span class="n">body_tsv</span> <span class="o">@@</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">&#39;hello world&#39;</span><span class="p">);</span>
</pre></div>
<p>and get back a list of entries with those words in the body.
</p>
<h3>Full text searching in Django</h3>
<p>Full text searching through the Django ORM is possible using the  <code>.extra()</code> queryset modifier.  To fetch the same entries with 'hello world' we searched for in raw SQL, you'd do something like:
</p>
<div class="source"><pre><span class="n">q</span> <span class="o">=</span> <span class="s">&#39;hello world&#39;</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Entry</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">extra</span><span class="p">(</span>
    <span class="n">where</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;body_tsv @@ plainto_tsquery(</span><span class="si">%s</span><span class="s">)&#39;</span><span class="p">],</span> 
    <span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="n">q</span><span class="p">])</span>
<span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">queryset</span><span class="p">:</span>
    <span class="k">print</span> <span class="n">entry</span><span class="o">.</span><span class="n">title</span>
</pre></div>
<p>The full text features in PostgreSQL also allow for ranking the search results using the PostgreSQL <code>ts_rank_cd()</code> function, and generating fragments of documents with search terms highlighted using the <code>ts_headline()</code> function.  An example of raw SQL for doing this might be:
</p>
<div class="source"><pre><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="n">ts_headline</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">query</span><span class="p">)</span> <span class="k">AS</span> <span class="n">snippet</span><span class="p">,</span> <span class="n">ts_rank_cd</span><span class="p">(</span><span class="n">body_tsv</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="k">AS</span> <span class="n">rank</span>
  <span class="k">FROM</span> <span class="n">blog_entry</span><span class="p">,</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">&#39;hello world&#39;</span><span class="p">)</span> <span class="k">AS</span> <span class="n">query</span>
  <span class="k">WHERE</span> <span class="n">body_tsv</span> <span class="o">@@</span> <span class="n">query</span>
  <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">rank</span> <span class="k">DESC</span><span class="p">;</span>
</pre></div>
<p>The Django ORM can generate an equivalent query with
</p>
<div class="source"><pre><span class="n">q</span> <span class="o">=</span> <span class="s">&#39;hello world&#39;</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Entry</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">extra</span><span class="p">(</span>
    <span class="n">select</span><span class="o">=</span><span class="p">{</span>
        <span class="s">&#39;snippet&#39;</span><span class="p">:</span> <span class="s">&quot;ts_headline(body, query)&quot;</span><span class="p">,</span>
        <span class="s">&#39;rank&#39;</span><span class="p">:</span> <span class="s">&quot;ts_rank_cd(body_tsv, query, 32)&quot;</span><span class="p">,</span>
        <span class="p">},</span>
    <span class="n">tables</span><span class="o">=</span><span class="p">[</span><span class="s">&quot;plainto_tsquery(</span><span class="si">%s</span><span class="s">) as query&quot;</span><span class="p">],</span>
    <span class="n">where</span><span class="o">=</span><span class="p">[</span><span class="s">&quot;body_tsv @@ query&quot;</span><span class="p">],</span>
    <span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="n">q</span><span class="p">]</span>
    <span class="p">)</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s">&#39;-rank&#39;</span><span class="p">)</span>

<span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">queryset</span><span class="p">:</span>
    <span class="k">print</span> <span class="n">entry</span><span class="o">.</span><span class="n">title</span><span class="p">,</span> <span class="n">entry</span><span class="o">.</span><span class="n">snippet</span><span class="p">,</span> <span class="n">entry</span><span class="o">.</span><span class="n">rank</span>
</pre></div>

<h3>Addendum, 2008-04-30</h3>
<p>With the new <a href="http://code.djangoproject.com/wiki/QuerysetRefactorBranch">Queryset Refactor</a> (QS-RF) branch of Django, one of the few backwards-incompatible changes is that you can't use an <code>order_by</code> <strong>method</strong> with a field generated by an <code>extra</code> method.  Instead, ordering by that field should be specified using an <code>order_by</code> <strong>parameter</strong> in the <code>extra</code> method, as in (the change is only in the last two lines)
</p>
<p>Also, having a function call in the 'tables' parameter no longer seems to work, so as a workaround for now I've been repeating plainto_tsquery() 3 times:
</p>
<div class="source"><pre><span class="n">q</span> <span class="o">=</span> <span class="s">&#39;hello world&#39;</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Entry</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">extra</span><span class="p">(</span>
    <span class="n">select</span><span class="o">=</span><span class="p">{</span>
        <span class="s">&#39;snippet&#39;</span><span class="p">:</span> <span class="s">&quot;ts_headline(body, plainto_tsquery(</span><span class="si">%s</span><span class="s">))&quot;</span><span class="p">,</span>
        <span class="s">&#39;rank&#39;</span><span class="p">:</span> <span class="s">&quot;ts_rank_cd(body_tsv, plainto_tsquery(</span><span class="si">%s</span><span class="s">), 32)&quot;</span><span class="p">,</span>
        <span class="p">},</span>
    <span class="n">where</span><span class="o">=</span><span class="p">[</span><span class="s">&quot;body_tsv @@ plainto_tsquery(</span><span class="si">%s</span><span class="s">)&quot;</span><span class="p">],</span>
    <span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="n">q</span><span class="p">],</span>
    <span class="n">select_params</span><span class="o">=</span><span class="p">[</span><span class="n">q</span><span class="p">,</span> <span class="n">q</span><span class="p">],</span>
    <span class="n">order_by</span><span class="o">=</span><span class="p">(</span><span class="s">&#39;-rank&#39;</span><span class="p">,)</span>
    <span class="p">)</span>
</pre></div>
<p>There is a <strong>lot</strong> more that can be done with the PostgreSQL full text search feature, check <a href="http://www.postgresql.org/docs/8.3/static/textsearch.html">the docs</a> for more info.  But hopefully this is enough to get started with.
</p>
<h3>Addendum, 2009-01-28</h3>
<p><a href="http://www.rossp.org">Ross Poulton</a> has a writup on <a href="http://www.rossp.org/blog/2009/jan/28/django-postgresql-fulltext/">Full-text searching in Django with PostgreSQL and tsearch2</a> 
    - using PostgreSQL 8.1 (on a Debian system) where tsearch2 was a contibuted addon module.
</p>
]]></description>
</item>
<item>
<title>mod_scgi redirection</title>
<link>/blog/entries/modscgi-redirection</link>
<pubDate>Thu, 11 Jan 2007 10:39:38 -0800</pubDate>
<author>bp@barryp.org (Barry Pederson)</author>
<description><![CDATA[
<p>While working on a new Django project, I noticed something odd about running it under mod_scgi: if you were POSTing to a URL, <code>/foo</code> for example, and the view for that URL did a relative redirect, as in <code>django.http.HttpResponseRedirect('/bar')</code>, the 302 redirect wasn't making it back to the browser.  Instead, the browser was acting like the result of <code>POST /foo</code> was a <code>200 OK</code> followed by the data you'd receive from  <code>GET /bar</code>, without the browser knowing that it coming from a new location.  The big drawback to this is that if you do a reload, the browser tries to POST to <code>/foo</code> again, instead of just <code>GET /bar</code>.  The Django docs recommend always responding to POSTs with redirects, just for this reason.
</p>
<p>Strictly speaking, redirects <em>should</em> be absolute URLs  (see section <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">14.30</a> in the HTTP specs), and if you use one of those, it acts as expected.   Django is full of relative redirects, but the framework at this time doesn't seem to try and convert them to absolute.  There is ticket <a href="http://code.djangoproject.com/ticket/987">#987</a> in the Django Trac that talks about this a bit.  <br />
</p>
<p>Browsers seem to handle relative redirects OK through, and that behavior doesn't occur with the Django test http server.  Having mod_scgi conceal what Django is doing is not so good.
</p>
<p>Digging into the mod_scgi sourcecode <code>apache2/mod_scgi.c</code> reveals a section of code that's causing this change:
</p>
<div class="source"><pre><span class="n">location</span> <span class="o">=</span> <span class="n">apr_table_get</span><span class="p">(</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">headers_out</span><span class="p">,</span> <span class="s">&quot;Location&quot;</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="n">location</span> <span class="o">&amp;&amp;</span> <span class="n">location</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">&#39;/&#39;</span> <span class="o">&amp;&amp;</span>
    <span class="p">((</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">status</span> <span class="o">==</span> <span class="n">HTTP_OK</span><span class="p">)</span> <span class="o">||</span> <span class="n">ap_is_HTTP_REDIRECT</span><span class="p">(</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">status</span><span class="p">)))</span> <span class="p">{</span>

    <span class="n">apr_brigade_destroy</span><span class="p">(</span><span class="n">bb</span><span class="p">);</span>

    <span class="c">/* Internal redirect -- fake-up a pseudo-request */</span>
    <span class="n">r</span><span class="o">-&gt;</span><span class="n">status</span> <span class="o">=</span> <span class="n">HTTP_OK</span><span class="p">;</span>

    <span class="c">/* This redirect needs to be a GET no matter what the original</span>
<span class="c">    * method was.</span>
<span class="c">    */</span>
    <span class="n">r</span><span class="o">-&gt;</span><span class="n">method</span> <span class="o">=</span> <span class="n">apr_pstrdup</span><span class="p">(</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">,</span> <span class="s">&quot;GET&quot;</span><span class="p">);</span>
    <span class="n">r</span><span class="o">-&gt;</span><span class="n">method_number</span> <span class="o">=</span> <span class="n">M_GET</span><span class="p">;</span>

    <span class="n">ap_internal_redirect_handler</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">OK</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>Tossing that section of code causes mod_scgi to leave the relative redirects alone.
</p>
]]></description>
</item>
</channel>
</rss>