<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Elf Sternberg</title>
	<atom:link href="http://www.elfsternberg.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.elfsternberg.com</link>
	<description>Done, and gets things smart.</description>
	<lastBuildDate>Thu, 19 Aug 2010 01:24:29 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Django and Asynchronous Gearman, Together At Last!</title>
		<link>http://www.elfsternberg.com/2010/08/18/django-asynchronous-gearman/</link>
		<comments>http://www.elfsternberg.com/2010/08/18/django-asynchronous-gearman/#comments</comments>
		<pubDate>Thu, 19 Aug 2010 01:24:29 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[web development]]></category>
		<category><![CDATA[asynchronous]]></category>
		<category><![CDATA[gearman]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=829</guid>
		<description><![CDATA[I searched for &#8220;django gearman&#8221; on Google and Bing, and found precious little.  There isn&#8217;t much out there, so I&#8217;ve decided to put together my own example, using Gearman as a queue manager.
If you don&#8217;t know what Gearman is, it&#8217;s a &#8220;generic application framework to farm out work to other machines or processes that are [...]]]></description>
			<content:encoded><![CDATA[<p>I searched for &#8220;django gearman&#8221; on Google and Bing, and found precious little.  There isn&#8217;t much out there, so I&#8217;ve decided to put together my own example, using Gearman as a queue manager.</p>
<p>If you don&#8217;t know what Gearman is, it&#8217;s a &#8220;generic application framework to farm out work to other machines or processes that are better suited to do the work.&#8221;  You have Gearman clients and workers: clients dispatch jobs to a configurable table of gearman servers, which in turn dispatch the job to any idle worker processes.  Clients and workers can be on separate machines; it is the collection of gearman servers that makes decisions about which worker to accept a given task.  This can be very useful when you have a long-running process, such as audio or video processing (or, in my case, the automatic LaTeX-ification of documentation).</p>
<p>As a (utterly trivial and inadequate) example, I&#8217;m going to show you how to communicate between a client triggered with Django, a worker that does some work, and then how the message gets back to Django that the process has been run.  This example is trivial because it assumes both processes are on the same machine; it is inadequate because it uses no sychronization to ensure that the message passed back to the Django process isn&#8217;t accidentally destroyed by a race condition.</p>
<p>Let&#8217;s start with the basics:</p>
<pre>virtualenv --no-site-packages geardemo
cd geardemo
source bin/activate
pip install gearman django
django-admin startproject geardemo
cd geardemo
./manage.py startapp testapp
mkdir workers</pre>
<p>Here, I&#8217;ve created a virtual environment in which we&#8217;re going to run our example, installed gearman and django, started a django project, and in that project created our app and a directory for the workers.</p>
<p>What I want to do is create a view in my testapp that dispatches jobs to Gearman.  The view will take a single argument from its web page and pass that to Gearman.  I also want to pass my session key, because I&#8217;ll be using the session object to receive notice that the process is done.  In testapp/views.py:</p>
<pre>from django.shortcuts import render_to_response
from django.template import RequestContext
from gearman import GearmanClient, Task

from django.conf import settings

try:
    import cPickle as pickle
except ImportError:
    import pickle

def run(request):
    if request.method == "POST":
        jobname = request.POST.get('name')

        if jobname.strip():
            client = GearmanClient(["127.0.0.1"])
            req = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
            arg = (jobname, req)
            pik = pickle.dumps(arg, pickle.HIGHEST_PROTOCOL)
            res = client.dispatch_background_task("work", pik)

        status = request.session.get('worker.status', [])
        return render_to_response('view.html', { 'status': status },
            context_instance=RequestContext(request))</pre>
<p>The tricky part, for me at least, was figuring out how to pass multiple objects to the worker process.  They have to be <em>pickled; </em>the gearmand servers are written in C and will blindly pass any stream of bytes from the clients to the workers, so to pass multiple objects, they must be serialized into a string using <tt>pickle.dumps</tt> before handing them to gearman.</p>
<p>The worker process has its own mysteries.  In the client, I get the status from the session object, which means that I have to populate the session object within the worker.  This is inadequate because there&#8217;s no synchronization process at work; for example, the django process could <em>read</em> the status object, the worker process could <em>read </em>then <em>write,</em> then the django process could <em>write</em>, and erase the worker process&#8217;s changes.  To do this, I pass the session key to the worker in the client request above.  This is likewise inadequate because there&#8217;s no guarantee the worker and the client are on the same system&#8211; but this will work if you&#8217;re using memcached as your session store.  In fact, I recommend using memcached and a smarter synchronization process, one perhaps with atomic increments, independent of sessions, for this kind of communication.</p>
<p>Here&#8217;s the file workers/worker.py:</p>
<pre>from django.core.management import setup_environ
from django.utils.importlib import import_module
from gearman import GearmanWorker
import time
import sys
import os.path

try:
    import cPickle as pickle
except ImportError:
    import pickle

sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
import settings

setup_environ(settings)
from django.conf import settings
engine = import_module(settings.SESSION_ENGINE)

def dothework(gjob):
    job, session_key = pickle.loads(gjob.arg)
    for i in xrange(0, 6):
        time.sleep(1)

    if session_key:
        session = engine.SessionStore(session_key)
        status = session.get('worker.status', [])
        status.append('done with %s' % job)
        session['worker.status'] = status
        session.save()

    return True

worker = GearmanWorker(['127.0.0.1'])
worker.register_function("work", dothework
worker.work()</pre>
<p>There&#8217;s a certain amount of rigamorale in importing the Django session object.  Using sys.path to put settings.py within our papth, importing the session object, setting up the environment, then re-importing the django settings object gives us access to the SESSION_ENGINE, and just like the client I need pickle to get at the argument and the session key passed from the django process.    This process merely sleeps for six seconds, then writes back to the user&#8217;s session that his process is done, with the word passed in from the client.</p>
<p>Note that both the client and the server use a token, &#8220;work&#8221;, to communicate which function they want run at the worker&#8217;s end.</p>
<p>One last file.  Here&#8217;s view.html, the template for our example:</p>
<p>﻿﻿</p>
<pre>&lt;h1&gt;Test&lt;/h1&gt;
&lt;p&gt;Current Status: &lt;/p&gt;
{% if not status %}
&lt;p&gt;No recent status updates&lt;/p&gt;
{% else %}
&lt;ul&gt;
{% for s in status %}
&lt;li&gt;{{ s }}&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;
{% endif %}

&lt;hr&gt;
&lt;form action="." method="POST"&gt;
{% csrf_token %}
&lt;label for="name"&gt;Job Name: &lt;input type="text" name="name"&gt;&lt;/label&gt;
&lt;input type="submit" value="submit"&gt;
&lt;/form&gt;</pre>
<p>Very simple and straightforward.  We&#8217;re not even using forms.  Just if there&#8217;s status then show it, and ask for another job to do.</p>
<p>To run this:</p>
<pre>./manage syncdb
gearmand -d
cd workers
python workers.py &amp;
cd ..
./manage runserver</pre>
<p>If you did everything right, you can now browse to port 8000 on your server and get the view above.  Pass it job names, and six seconds after you do, you&#8217;ll get a response.  If you stack the jobs, the later ones will take longer because Gearman is running them in a serial queue, not in parallel.</p>
<p>And that&#8217;s it.</p>
<p>Full source code to the geardemo program <a href="http://elfsternberg.com/projects/geartest.tar.gz">is available</a>.  You will still have to set up the virtualenv on your own.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/08/18/django-asynchronous-gearman/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Sharpening the blade, part MCMXVII: Nine Amazing Hours.</title>
		<link>http://www.elfsternberg.com/2010/08/12/sharpening-blade-part-mcmxvii-amazing-hours/</link>
		<comments>http://www.elfsternberg.com/2010/08/12/sharpening-blade-part-mcmxvii-amazing-hours/#comments</comments>
		<pubDate>Fri, 13 Aug 2010 01:02:34 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=824</guid>
		<description><![CDATA[I resolved, earlier this year, to start finishing some of my projects.  That hasn&#8217;t gone too well&#8211; the day jobbe appears to have chewed up most of my time.  But after my Palm V died, I no longer had a chiming program to help me get into the rhythm of the day.  I&#8217;ve since replaced [...]]]></description>
			<content:encoded><![CDATA[<p>I resolved, earlier this year, to start finishing some of my projects.  That hasn&#8217;t gone too well&#8211; the day jobbe appears to have chewed up most of my time.  But after my Palm V died, I no longer had a chiming program to help me get into the rhythm of the day.  I&#8217;ve since replaced it on my Palm T|X, but I&#8217;ve wanted something that I could feed through my headphones, rather than rely on the Palm, which chimes loud enough the whole house or office can hear it.</p>
<p>So, my first foray into times and chimes is done.  I used some free bamboo photoshop brushes, and jQuery, but the HTML, CSS, graphics, layout and so forth is all mine.  The script is 100% HTML5, there&#8217;s a lot of CSS3, and it uses HTML5 native audio, so this thing doesn&#8217;t work at all on IE (unless you&#8217;re running IE9; I don&#8217;t know what the capabilities of IE9 are yet).  Basically it works on latest &amp; greatest: Chrome, Safari 3, Firefox 3.6 (maybe 3.5?) and so forth.  The whole project took approximately 10 hours of development time.</p>
<h2><a title="Nine Amazing Hours" href="http://ninehours.elfsternberg.com/" target="_blank">Nine Amazing Hours</a></h2>
<p>Critiques, comments, suggestions are all welcome.  The functionality is what I want it to be; if you want a full-fledged meditation timer well, maybe I&#8217;ll do that next.</p>
<p>And one beg: The idea behind these projects is to learn how to, as my design teachers put it, &#8220;﻿design, develop, market and maintain a project.&#8221;  I have no idea how to <em>market</em> something like this.  How would I go about telling people about it?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/08/12/sharpening-blade-part-mcmxvii-amazing-hours/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How surprising!  Chrome and Firefox disagree on the HTML5 Spec</title>
		<link>http://www.elfsternberg.com/2010/08/11/surprising-chrome-firefox-disagree-html5-spec/</link>
		<comments>http://www.elfsternberg.com/2010/08/11/surprising-chrome-firefox-disagree-html5-spec/#comments</comments>
		<pubDate>Thu, 12 Aug 2010 03:21:51 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=822</guid>
		<description><![CDATA[Oh, what a surprise!  Firefox implements the HTML5 &#8220;article&#8221; tag as display: inline, and Chrome implements it as display:block.
How annoying.  Well, it&#8217;s a simple CSS setting to make it work as expected.
(Edit: I see, as always, A List Apart is way ahead of me on this.  By almost a year!)
]]></description>
			<content:encoded><![CDATA[<p>Oh, what a surprise!  Firefox implements the HTML5 &#8220;article&#8221; tag as <em>display: inline</em>, and Chrome implements it as <em>display:block.</em></p>
<p>How annoying.  Well, it&#8217;s a simple CSS setting to make it work as expected.</p>
<p>(Edit: I see, as always, A List Apart <a href="http://www.alistapart.com/articles/get-ready-for-html-5/">is way ahead of me on this</a>.  By almost a year!)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/08/11/surprising-chrome-firefox-disagree-html5-spec/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adding ReCaptcha to Django</title>
		<link>http://www.elfsternberg.com/2010/08/11/adding-recaptcha-django/</link>
		<comments>http://www.elfsternberg.com/2010/08/11/adding-recaptcha-django/#comments</comments>
		<pubDate>Wed, 11 Aug 2010 23:51:52 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=818</guid>
		<description><![CDATA[I learned today how to enable ReCaptcha for Django.  It&#8217;s fairly trivial.  I&#8217;ll show you how to enable this for account registration.
First, go and create a key pair for your site.  You don&#8217;t even have to give them an email address, which is nice.
Install the recaptcha client library on your site:
pip install [...]]]></description>
			<content:encoded><![CDATA[<p>I learned today how to enable ReCaptcha for Django.  It&#8217;s fairly trivial.  I&#8217;ll show you how to enable this for account registration.</p>
<p>First, go and <a href="https://www.google.com/recaptcha/admin/create">create a key pair</a> for your site.  You don&#8217;t even have to give them an email address, which is nice.</p>
<p>Install the recaptcha client library on your site:</p>
<pre>pip install recaptcha-client</pre>
<p>You&#8217;ll have to override or replace any registration templates you have, and add this to the form, somewhere in the form (usually, right above the submit button):</p>
<pre>&lt;p&gt;Please be a human, and not some spamming robot:&lt;/p&gt;

&lt;script type="text/javascript"
    src="http://www.google.com/recaptcha/api/challenge?k=YOUR_RECAPTCHA_PUBLIC_KEY{{ captcha_error }}"&gt;
&lt;/script&gt;

&lt;noscript&gt;
    &lt;iframe src="http://www.google.com/recaptcha/api/noscript?k=YOUR_RECAPTCHA_PUBLIC_KEY{{ captcha_error }}"
        height="300" width="500" frameborder="0"&gt;&lt;/iframe&gt;&lt;br&gt;
    &lt;textarea name="recaptcha_challenge_field" rows="3" cols="40"&gt;
    &lt;/textarea&gt;
    &lt;input type="hidden" name="recaptcha_response_field"
        value="manual_challenge"&gt;
&lt;/noscript&gt;</pre>
<p>Add your private key to your <tt>settings.py</tt> file, and code your &#8220;accounts.view.create&#8221; this way:</p>
<pre>def create(request, template_name='accounts/create.html',
    redirect_field_name=REDIRECT_FIELD_NAME):

    user_form = None
    captcha_error = ""
    redirect_to = request.REQUEST.get(redirect_field_name, '')

    if request.method == "POST":
        captcha_response = captcha.submit(
            request.POST.get("recaptcha_challenge_field", None),
            request.POST.get("recaptcha_response_field", None),
            settings.RECAPTCHA_PRIVATE_KEY,
            request.META.get("REMOTE_ADDR", None))

        if not captcha_response.is_valid:
            captcha_error = "&amp;error=%s" % captcha_response.error_code
        else:
            # perform other registration checks as needed...
            # success!
            return HttpResponseRedirect(redirect_to)

    if not user_form:
        user_form = UserForm(prefix="user")

    return render_to_response(template_name, {
        'captcha_error': captcha_error,
        'user_form': user_form},
        context_instance=RequestContext(request))</pre>
<p>And that&#8217;s it.  You have ReCaptcha enabled.  I see that the python library includes an HTML generator, but it&#8217;s for recaptcha.net, and I decided to use the newer google addresses.</p>
<p>By the way, I&#8217;m not sure why, but I much prefer the <tt>form = None</tt> sentinel method of checking for form initialization.  I think it&#8217;s a lot cleaner than a metric ton of <tt>else</tt> statements.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/08/11/adding-recaptcha-django/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Django Signals enable Aspect-Oriented programming.</title>
		<link>http://www.elfsternberg.com/2010/07/29/lightbulb-django-signals-aspectoriented-programming/</link>
		<comments>http://www.elfsternberg.com/2010/07/29/lightbulb-django-signals-aspectoriented-programming/#comments</comments>
		<pubDate>Thu, 29 Jul 2010 15:47:20 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[aspect-oriented-programming]]></category>
		<category><![CDATA[signals]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=795</guid>
		<description><![CDATA[It&#8217;s funny what a day&#8217;s wrestling with a hard problem can lead to as inspiration.
I&#8217;m going to start with a piece of Django code that someone else wrote: Django Activity Stream, a simple piece of code that lets you track &#8220;everything&#8221; the actors in your system do: every bookmark made, every comment made, every story [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s funny what a day&#8217;s wrestling with a hard problem can lead to as inspiration.</p>
<p>I&#8217;m going to start with a piece of Django code that someone else wrote: <a href="http://github.com/justquick/django-activity-stream">Django Activity Stream</a>, a simple piece of code that lets you track &#8220;everything&#8221; the actors in your system do: every bookmark made, every comment made, every story read in my current publishing system.  If you&#8217;re an author, you want to track when your stories are read, and when comments are made on your stories.  If you&#8217;re a reviewer, you want to know when someone replies to your review, or gives you a thumbs up.  And so forth an so on.</p>
<p>The trouble with all this is that, in order for it to work, you would have to touch all of your different modules with function calls like <tt>action.send()</tt>, which would just clutter every piece of code with knowledge about this logging facility.  Not cool.  The activity stream is orthagonal to your business logic, but critical to your goal: this is known as a <a href="http://en.wikipedia.org/wiki/Cross-cutting_concern">cross-cutting concern</a>.  Having to touch all of those Django applications in order to get logging becomes known as <em>tangling</em>.</p>
<p>Django provides an easier way of monitoring its own internal activity, called <em>signals.</em> A &#8220;Signal&#8221; is a specialized object that can publish when a certain event has occurred, and then other systems can subscribe to receive the signal when the event occurs.   </p>
<p>Django provides a comprehensive collection of signals associated with the database, and since most Django applications are MVC, when the model is altered a database event occurs, and we can easily intercept those and use them to our advantage.   The most common signal used is post_save, which is emitted whenever <em>anything</em> saves to the database.   Here&#8217;s an example that listens for the creation of a FacebookUser (which also creates a standard django User as well), and guarantees that a Profile for the user will also be generated:</p>
<pre>from facebookuser.models import FacebookUser
from django.db.models.signals import post_save

def set_up_profile_for_remote_user(sender, **kwargs):
    if not kwargs['created']:
        return
    Profile(user = kwargs['instance'].user).save()

post_save.connect(set_up_profile_for_remote_user, sender=FacebookUser)</pre>
<p>The <tt>post_save.connect</tt> method says &#8220;Listen for database saves, and if the sending object is a FacebookUser, call the function <tt>set_up_profile_for_remote_user</tt>.&#8221;  The callback gets three arguments: the calling class (FacebookUser), the specific instance that was just saved, and a flag indicating whether or not that class was just created, or merely modified.  The last two are sent as named arguments, so most signals use the kwargs syntax.</p>
<p>When used with the activity stream (as with logging), the database is emitting all of these events.  The activity stream has a method for capturing what object emitted the event, a &#8220;verb&#8221; associated with that event, and an optional target object.  All we need to do is capture the various database events, associate them with actions, and route them to the stream.  For this, I have my various event generators, I have the activity stream, and I have a small project-specific application I name &#8220;hub,&#8221; into which I just put the signal routers:</p>
<pre>def record_comment(sender, **kwargs):
    if not kwargs['created']:
        return
    comment = kwargs['instance']
    action.send(sender = comment.user, verb = 'commented',
                target = comment, public = True)

post_save.connect(record_comment, sender = Comment)</pre>
<p>By using a simple application silo like &#8220;hub,&#8221; neither the activity stream application, nor any of the various applications being monitored, need know anything about one another, yet the cross-cutting concerns of the activity stream can be fully addressed.  This ease of dealing with such concerns is the cornerstone of ﻿﻿<a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming">Aspect-oriented programming</a>, and is a good way of easing into an understanding of AOP.</p>
<p>A couple of caveats: One, Django signals are synchronous.  In these examples, immediately after the database write ends the signal receivers are called sequentially within the thread of the original request event.  Any signal handler that takes a significant time to resolve ought to be kicked out to a queue manager.  (This fact seems to surprise a lot of people when they encounter it.) Two, the order in which callbacks are called for a given signal cannot be set by the developer.  If any given signal has multiple subscribers, do not rely on the order of those subscribers to handle the signal.</p>
<p>There are ﻿<a href="http://docs.djangoproject.com/en/dev/topics/signals/">quite a few Django signals</a> covering pre- and post-saving, deleting, and HTTP request management, and it&#8217;s possible that you can always write your own if you feel your application is doing something unique enough the standard set won&#8217;t work for you, but must publish so other applications can listen in and get signaled as needed.  But for the most part, the standard set provides you with awesome capabilities that should get you on your way.</p>
<p>p.s. After a while handling signals, you may notice that the callback <tt>record_comment</tt> calls <tt>action.send</tt>, which is itself a signal publisher, the generic &#8220;add an action to the Action table&#8221; handler.  The idea behind activity stream is that you would have lots of publishers of actions, and only one subscriber listening for these events.  By further centralizing the activity stream, we avoid the kind of clutter seen in Pinax, which is riddled with &#8220;optional&#8221; calls to the notification application.  Take this example from the microblogging application:</p>
<pre>
try:
    from notification import models as notification
except ImportError:
    notification = None
...
    if notification:
        notification.send([reply_recipient], "tweet_reply_received", {'tweet': instance,})</pre>
<div>This is exactly the kind of cross-cutting concern (note the use of a signal publisher for notification!) that would be better handled with a signal from the microblogging app.  Perhaps the app could have published &#8220;tweet received&#8221; as a signal, with an optional &#8220;module for common associated applications&#8221; dispatching that event to logging, activity streams, notifications or whatever as available.  The presence of explicit publication would have signaled to any developer to look for subscribers, so I don&#8217;t think this is a dangerous case of unlabeled subroutines and the potential for spaghetti code that goes with it.  Most Django apps are small silos of code; it would not have been onerous to separate out and isolate this cross-cutting concern from the rest of the microblogging application&#8217;s functionality.</div>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/07/29/lightbulb-django-signals-aspectoriented-programming/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How not to do 404s: Salon.com</title>
		<link>http://www.elfsternberg.com/2010/07/16/404s-saloncom/</link>
		<comments>http://www.elfsternberg.com/2010/07/16/404s-saloncom/#comments</comments>
		<pubDate>Fri, 16 Jul 2010 17:20:25 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=789</guid>
		<description><![CDATA[There are plenty of places where you can find great examples of 404 pages.  Salon.com is not one of them.
I learned this today because I followed some links off an article about George Carlin that were poorly coded.  Those links took me to Salon&#8217;s 404 page, which uses Salon&#8217;s standard layout, complete with ads. [...]]]></description>
			<content:encoded><![CDATA[<p>There are <a href="http://www.1stwebdesigner.com/inspiration/unique-404-error-pages-inspiration/">plenty</a> of <a href="http://techreviews.in/21-inspirational-and-creative-404-pages/">places</a> where you can find great examples of 404 pages.  Salon.com is not one of them.</p>
<p>I learned this today because I followed some links off an article about George Carlin that were poorly coded.  Those links took me to Salon&#8217;s 404 page, which uses Salon&#8217;s standard layout, <em>complete with ads. </em>That is a major faux-pax; when the user is lost, the last thing he or she wants is to be sold to.  I understand that Salon needs to make money and ad impressions are its chief vehicle for doing so, but the add blare was such a major turn-off I almost gave up struggling to find what I wanted.</p>
<p>The 404 is incredibly unhelpful.  So are most in the galleries you might find on the Internet.  A 404 ought to make a positively heroic effort to decode what you were trying to do and help you find it.  If you recently re-arranged your website, hopefully the arrangement has some sort of algorithmic relationship: &#8220;pages named x-y-z.html can now be found in y/z/x.html,&#8221; for example.  Helping people find that relationship for themselves and understand your new layout is far better than just saying &#8220;Er, you screwed up.&#8221;</p>
<p>PS,  It&#8217;s &#8220;<em>We screwed up.&#8221;</em> Never blame the user.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/07/16/404s-saloncom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adding Opensearch to Django</title>
		<link>http://www.elfsternberg.com/2010/07/15/adding-opensearch-django/</link>
		<comments>http://www.elfsternberg.com/2010/07/15/adding-opensearch-django/#comments</comments>
		<pubDate>Thu, 15 Jul 2010 16:46:30 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=779</guid>
		<description><![CDATA[Inspired by Five Web Files That Will Improve Your Website, I decided this morning to implement OpenSearch on the Indieflix Website.  (It&#8217;s not up yet, we&#8217;re still beta&#8217;ing it, and it&#8217;s along with a massive list of changes that still need testing, so don&#8217;t go looking for it.)  OpenSearch is a way to [...]]]></description>
			<content:encoded><![CDATA[<p>Inspired by <a href="http://sixrevisions.com/web-standards/5-web-files-that-will-improve-your-website/">Five Web Files That Will Improve Your Website</a>, I decided this morning to implement OpenSearch on the Indieflix Website.  (It&#8217;s not up yet, we&#8217;re still beta&#8217;ing it, and it&#8217;s along with a massive list of changes that still need testing, so don&#8217;t go looking for it.)  OpenSearch is a way to turn your site&#8217;s search feature into an RSS feed: you define for (other) search engines how and what you search on your site, and it automagically creates relationships so that your site&#8217;s search can be included in remote results.  Normally, the results would be returned in an XML container for RSS or Atom, but HTML&#8217;s fine for some applications.</p>
<p>As a trivial example, I&#8217;m going to add your website&#8217;s search engine to Firefox&#8217;s drop-down of search engines.  I&#8217;m going to assume that you already have a search engine enabled on Django.  Haystack, or Djapian, or Merquery, or something along those lines.</p>
<p>First, you need a file called <tt>opensearch.xml</tt>.  I put this into my template base directory:</p>
<pre>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"&gt;
  &lt;ShortName&gt;Search My Website&lt;/ShortName&gt;
  &lt;Description&gt;Search for my brilliance.&lt;/Description&gt;
  &lt;Image width="16" height="16" type="image/png"&gt;http://mywebsite.com{{ MEDIA_URL }}img/favicon.png&lt;/Image&gt;
  &lt;Url type="text/html" template="http://mywebsite.com/search/?q={searchTerms}"/&gt;
&lt;/OpenSearchDescription&gt;</pre>
<p>Obviously, there are things to modify here: your real short name, description, image and search paths.   You know where those go.</p>
<p>This goes into your base urls.py (if you haven&#8217;t imported <tt>direct_to_template</tt>, now is the right time):</p>
<pre>url(r'^opensearch.xml$', direct_to_template,
    { 'template': 'opensearch.xml',
      'mimetype': 'application/opensearchdescription+xml' },
    name="opensearch"),</pre>
<p>Again, if you changed the names or locations of the template, you have no one but yourself to blame if stuff blows up.   The name opensearch.xml is <em>not</em> particularly important or required, in either the template (obviously) or in the deployed URL.   To make external spiders and browsers know about your website, this goes into your <tt>base.html</tt> or <tt>site_base.html</tt>, somewhere in the headers with your metainformation and style sheets and so forth.</p>
<pre>
&lt;link rel="search" type="application/opensearchdescription+xml"          
    title="Indieflix Search"           
    href="{% url opensearch %}" /&gt;
</pre>
<p>And that&#8217;s it.  </p>
<p>To test this, load up your home page in Firefox, then click on the search bar&#8217;s drop-down.  You should see your &#8220;short name&#8221; offered as a search plug-in.  Select it and type some search terms, and Firefox will know to use your search engine, and the results will be from your site.</p>
<p>Pretty cool.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/07/15/adding-opensearch-django/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[Headdesk] In Django, Querysets are Lazy!</title>
		<link>http://www.elfsternberg.com/2010/07/13/headdesk-django-querysets-lazy/</link>
		<comments>http://www.elfsternberg.com/2010/07/13/headdesk-django-querysets-lazy/#comments</comments>
		<pubDate>Tue, 13 Jul 2010 17:13:11 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=767</guid>
		<description><![CDATA[As I&#8217;ve been working on a project at Indieflix, I&#8217;ve been evaluating other people&#8217;s code, including drop-ins, and for the past couple of days a pattern has emerged that, at first, bugged the hell out of me.  Django has these lovely things called context processors&#8211; they allow you to attach specific elements of code [...]]]></description>
			<content:encoded><![CDATA[<p>As I&#8217;ve been working on a project at <a href="http://www.indieflix.com/">Indieflix</a>, I&#8217;ve been evaluating other people&#8217;s code, including drop-ins, and for the past couple of days a pattern has emerged that, at first, bugged the hell out of me.  Django has these lovely things called context processors&#8211; they allow you to attach specific elements of code to the request context before the router and page managers are invoked; the idea is that there are global needs you can attach to the context in which your request will ultimately be processed, and this context can be grown, organically, from prior contexts.</p>
<p>Nifty.  I kept noticing, however, an odd trend: programmers attaching potentially large querysets to the context.  Take, for example, the bookmarks app: it has a context processor that connects a user&#8217;s bookmarks to the context, so when time comes to process the request if the programmer wants the list of the user&#8217;s bookmarks, there it is.</p>
<p>It took me three days to realize that this is not wasteful.  It says so right there in the goddamn manual: <em><a href="http://docs.djangoproject.com/en/dev/topics/db/queries/#id3">QuerySets are lazy</a> &#8212; the act of creating a QuerySet doesn&#8217;t involve any database activity.</em> So building up the queryset doesn&#8217;t trigger a query until you actually need the first item of the queryset.  It just sits there, a placeholder for the request, until you invoke it in, say, a template.  Which means that you can attach all manner of <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">readers</a> to your contexts, and they won&#8217;t do a database hit or other I/O until you drop a <tt>for</tt> loop into a template somewhere.  I&#8217;ve been avoiding this technique for no good reason.</p>
<p>This also means that pure &#8220;display this list and provide links to controlling pages, but provide no controls of your own&#8221; pages are pure templates that can be wrapped in generic views.</p>
<p>Ah!  And <em>this</em> is why I didn&#8217;t get why generic views were such a big deal.  Information is often so context sensitive so how could a generic view possibly be useful?  The examples in the book are all tied to modifying the local context in <tt>urls.py</tt>, but that&#8217;s not really where the action is.  The action is in the context processors, which are building that context.</p>
<p>I feel like Yoda&#8217;s about to walk in and say, &#8220;Good, but much to learn you still have, padawan.&#8221;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/07/13/headdesk-django-querysets-lazy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Photoshop Vista-like website Tutorial, done with the GIMP.</title>
		<link>http://www.elfsternberg.com/2010/06/25/photoshop-vistalike-website-tutorial-gimp/</link>
		<comments>http://www.elfsternberg.com/2010/06/25/photoshop-vistalike-website-tutorial-gimp/#comments</comments>
		<pubDate>Fri, 25 Jun 2010 22:06:34 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Design]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=760</guid>
		<description><![CDATA[Every day, I have little stabs of envy when I see the Photoshop tutorials for zarious cool things people do with Photoshop, and wonder why we can&#8217;t do that with GIMP.  Well, we can.  In that spirit, I&#8217;ve taken How to Create a Stunning Vista-Inspired Menu and reproduced it with the GIMP.  So [...]]]></description>
			<content:encoded><![CDATA[<p>Every day, I have little stabs of envy when I see the Photoshop tutorials for zarious cool things people do with Photoshop, and wonder why we can&#8217;t do that with GIMP.  Well, we can.  In that spirit, I&#8217;ve taken <a href="http://psd.tutsplus.com/interface-tutorials/how-to-create-a-stunning-vista-inspired-menu/">How to Create a Stunning Vista-Inspired Menu</a> and reproduced it with the GIMP.  So I tried to reproduce the results.  The work is pretty close.<br />
<a href="http://www.elfsternberg.com/wp-content/uploads/2010/06/VistaStyle.png"><img class="alignleft size-medium wp-image-761" title="Vista Style" src="http://www.elfsternberg.com/wp-content/uploads/2010/06/VistaStyle-300x167.png" alt="" width="300" height="167" /></a></p>
<p>1. Create a new canvas, 600 x 335, background white.  Add two new guides (Image -&gt; Guides -&gt; New Guide), one at 285px and one at 310px.</p>
<p>2. Create two new layers, 600&#215;25 pixels.  Label them &#8220;upper bar&#8221; and &#8220;lower bar&#8221; (if you can&#8217;t figure out which is which, well&#8230;), and put them into the 25-pixel high gaps created by the guides.</p>
<p>3. Fill the top bar with a vertical gradient, light on top, dark on the bottom,  #787b7d to #35393d.  Fill the bottom bar with a vertical gradient, light on top, dark on bottom, #0c0c0c to #000000.  Set the bars to 90% opacity, normal over the white background.</p>
<p>4. Draw a new line across the very top of the top bar, color #484b4d. To do this, from the top bar, select a 2-pixel high area at the very top of the bar (from the guide to guide +2) stretching the entire width of the area.  Copy, paste as floating selection, then click &#8220;new layer&#8221; in the new layer dialog to make this its own layer.  Fill with color.  Duplicate this line and put it right under the first line; fill with color #9fa2a4.</p>
<p>5. Create a new layer, 2 pixels wide and 19 pixels high. This will be the top half of a separator bar for your menu.  Place it so the bottom of this tiny layer rests on the second guide.  Fill it with a gradient, #4d6672 #676a6d top to bottom.</p>
<p>Create a similar layer, 2 pixels wide and 21 pixels high.  This will be the top half of the separator.  Place it right under the other half, and just fill it with #43474b.  You can merge this layer down with the one above, and just have a &#8220;divider.&#8221;</p>
<p>At this point, it might be wise to delete the guides.  I found they weren&#8217;t helpful, and often misplaced the divider.</p>
<p>Clone the divider (Layer Dialog, duplicate button on the bottom) and place it at regular intervals, to make the menu.  Fill the space between with text, color #ffffff.</p>
<p>6. Pick a background.  I used <a href="http://www.flickr.com/photos/cedric_foll/2071790285/">Forest on a Foggy Day</a> (CC Licensed), but you can surely find your own favorites from around the net.  Crop and scale it to the size of our demo, and place it as the background layer.  (You may choose to delete, or merely cover, the original white background.)</p>
<p>7. Now for the glassy see-through &#8220;Vista&#8221; effect.  Click on the background layer.  With the select tool, select a rectangle about 200&#215;160, starting at 105px down and (here&#8217;s the tricky part) -15 pixels left.  Select &#8220;rounded corners&#8221;, 15px radius.  Copy, then Paste As New Image.</p>
<p>With the new image, do a Gaussian Blur, 10px radius, then up the canvas size by 10 pixels (Image -&gt; Canvas Size), and using the same dialog center the image on the new canvas.  Then, with the image, add a drop shadow (Layer -&gt; Layer Effects -&gt; Drop Shadow, Opacity 60%, Angle 135°, Distance 2, Spread 5, Layer knocks out drop shadow: Yes, Merge layers: Yes).  Crop the image to its essentians (Image -&gt; Autocrop).</p>
<p>Copy the new image, then go back to the other image and paste it.  If you did it right, you left the old selection active, and the new selection will drop into place.  If not, then you should paste it as a new layer and position it by hand at 0&#215;105 (The part to the left was cut off in transit to the new image, remember?).</p>
<p>8. Add text to your glassy portion, and user the Layer Drop Shadow as described above on the words.  You may have to mess with it a bit to get it right, but the angle must match the angle of the glass, or it won&#8217;t look right.</p>
<p>The free template for this Photoshop to GIMP experiment is available: <a href="http://elfsternberg.com/projecs/Vista_Style.xcf">Vista Style</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/06/25/photoshop-vistalike-website-tutorial-gimp/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The sad truths about subscription based sites.</title>
		<link>http://www.elfsternberg.com/2010/06/07/sad-truths-subscription-based-sites/</link>
		<comments>http://www.elfsternberg.com/2010/06/07/sad-truths-subscription-based-sites/#comments</comments>
		<pubDate>Mon, 07 Jun 2010 19:51:19 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=758</guid>
		<description><![CDATA[According to a service that tracks such things, the most popular types of websites people put for which there is a subscription fee include (in order from most to least popular)

How to lose weight
Health products &#8211; Vitamins, minerals, colon blow
Starting a home-based business
How to make money from your existing business
How to market on the Internet
How [...]]]></description>
			<content:encoded><![CDATA[<p>According to a service that tracks such things, the most popular types of websites people put for which there is a subscription fee include (in order from most to least popular)</p>
<ul>
<li>How to lose weight</li>
<li>Health products &#8211; Vitamins, minerals, colon blow</li>
<li>Starting a home-based business</li>
<li>How to make money from your existing business</li>
<li>How to market on the Internet</li>
<li>How to make money through investments</li>
<li>Personal improvement</li>
<li>How to turn a hobby into a business</li>
<li>How to move to a new state/country</li>
<li>How to practice your religion</li>
</ul>
<p> I&#8217;m not sure why, but I find this list incredibly sad.  There are so many decent sites out there that give this information away, and most of what&#8217;s behind the paywalls is pure bullshit not worth the pixels it&#8217;s drawn with.</p>
<p>This just confirms what one commenter said earlier: most people don&#8217;t want a well-made website.  Well-made websites scare people into thinking there&#8217;s trickery, deceit, and too much intelligence behind them for the individual to keep up with.  Stupidity sells.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2010/06/07/sad-truths-subscription-based-sites/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
