<?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>Death of a Gremmie &#187; shoutbox</title>
	<atom:link href="http://deathofagremmie.com/tag/shoutbox/feed/" rel="self" type="application/rss+xml" />
	<link>http://deathofagremmie.com</link>
	<description>by Brian Neal</description>
	<lastBuildDate>Wed, 14 Jul 2010 02:31:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Anatomy of a Shoutbox; Part 6 &#8211; Conclusion: Delete In Place</title>
		<link>http://deathofagremmie.com/2008/12/24/anatomy-of-a-shoutbox-part-6-conclusion-delete-in-place/</link>
		<comments>http://deathofagremmie.com/2008/12/24/anatomy-of-a-shoutbox-part-6-conclusion-delete-in-place/#comments</comments>
		<pubDate>Wed, 24 Dec 2008 06:55:35 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=106</guid>
		<description><![CDATA[Welcome to part 6, the final post on the development of the new shoutbox. In case you missed the earlier parts, here they are: part 1, part 2, part 3, part 4, part 5. Here we are going to discuss the final feature: deleting a shout in place off the shout history page. The idea [...]]]></description>
			<content:encoded><![CDATA[<p>Welcome to part 6, the final post on the development of the new shoutbox. In case you missed the earlier parts, here they are:  <a href="../2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/">part 1</a>, <a href="../2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/">part 2</a>, <a href="../2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/">part 3</a>, <a href="../2008/12/14/anatomy-of-a-shoutbox-part-4-smilies/">part 4</a>, <a href="http://deathofagremmie.com/2008/12/20/anatomy-of-a-shoutbox-part-5-edit-in-place/">part 5</a>. Here we are going to discuss the final feature: deleting a shout in place off the shout history page.</p>
<p>The idea here is to add a delete link on the shouts that the user created as she is viewing the shout history. We will use the<a href="http://jquery.com/"> jQuery library</a> to dynamically add click handlers to these links. The click handler functions will make an AJAX post to the server and request that a shout be deleted. The server view function will verify that the user sending the request actually created the shout in question. If so, the shout will be deleted from the database and then a response sent back to the client. This response will be then be used by our Javascript to hide the deleted shout. Thus we will have deleted a shout without refreshing the entire page.</p>
<p>The first thing I did was modify the shout history template to output a link underneath a shout if the current user authored that shout. This template (shoutbox/view.html) was presented in <a href="http://deathofagremmie.com/2008/12/20/anatomy-of-a-shoutbox-part-5-edit-in-place/">part 5</a>. I&#8217;ll just show the new code inside the for loop that adds these links:</p>
<pre class="brush: python">    {% ifequal user shout.user %}
        | &lt;a href=&quot;#&quot; class=&quot;shout-del&quot; id=&quot;shout-del-{{ shout.id }}&quot;&gt;Delete&lt;/a&gt;
    {% endifequal %}
</pre>
<p>The important thing to note is our links don&#8217;t go anywhere, they have the CSS class &#8220;shout-del&#8221;, and we give them an id of &#8220;shout-del-xx&#8221; where xx is the id of the shout from the database.</p>
<p>I then modified the shoutbox_app.js document ready function to look for all links that have the class &#8220;shout-del&#8221; and I attach a click function to them. The click function asks the user via the Javascript confirm() function if they really want to delete the shout. If they confirm, I then use javascript regular expression magic to pull out the shout&#8217;s  primary key from the &lt;a&gt; tag&#8217;s id, and send a POST request to the server to delete that shout. The relevant snippet is shown below.</p>
<pre class="brush: javascript">     $(&#039;.shout-del&#039;).click(function () {
         if (confirm(&#039;Really delete this shout?&#039;)) {
             id = this.id;
             if (id.match(/shout-del-(\d+)/)) {
                $.post(&#039;/shout/delete/&#039;, { id : RegExp.$1 }, function(id) {
                    id = &#039;#shout-del-&#039; + id
                    $(id).parents(&#039;tr&#039;).hide();
                    $(&#039;div.shoutbox-history table tr:visible:even&#039;).removeClass(&#039;odd&#039;);
                    $(&#039;div.shoutbox-history table tr:visible:odd&#039;).addClass(&#039;odd&#039;);
                    }, &#039;text&#039;);
             }
         }
         return false;
     });
</pre>
<p>We are posting to the URL /shout/delete, and passing along the shout ID in the id parameter. We also supply a callback function that gets called when the delete happens successfully. More on that later. Finally the click function returns false so that the default action of clicking on a link is not executed.</p>
<p>We now turn to the server, and examine the view function that handles the URL /shout/delete. You&#8217;ll notice similar code as presented in part 5&#8242;s edit view function. This time I have discovered Django&#8217;s classes that derive from HttpResponse, and I use them in the error cases.</p>
<pre class="brush: python">def delete(request):
    &quot;&quot;&quot;This view deletes a shout. It is called by AJAX from the shoutbox history view.&quot;&quot;&quot;
    if request.user.is_authenticated():
        id = request.POST.get(&#039;id&#039;, None)
        if id is None:
            return HttpResponseBadRequest()
        try:
            shout = Shout.objects.get(pk=id)
        except Shout.DoesNotExist:
            return HttpResponseBadRequest()
        if request.user != shout.user:
            return HttpResponseForbidden()
        shout.delete()
        return HttpResponse(id)

    return HttpResponseForbidden()
</pre>
<p>We check to see if the user is authenticated before proceeding. We then retrieve the shout ID from the POST QueryDict. From the ID we retrieve the actual shout from the database using the Django ORM. And finally, if and only if the user making the request is the same user that created the shout, we delete it. If all of that went well, we send back a positive HTTP response that includes the ID of the shout that was deleted.</p>
<p>Now refer back to our Javascript, posted above.  In the callback function we receive the ID as text from the server. We then convert the numeric ID to the CSS ID and use jQuery to select the parent &lt;tr&gt; of our &lt;a&gt; tag, and hide it. Wow! That was a very compact way to do all of that work.</p>
<p>The remaining logic takes care of the table presentation. I used CSS to style every other row in the shout history table with a class called &#8220;odd&#8221;. Since we just hid one row, that scheme got messed up. Luckily, the compact notation of jQuery again comes to the rescue and we can quickly restyle all odd visible rows with our class &#8220;odd&#8221;, taking care to remove the style from the new even rows first.</p>
<p>And there we have a shoutbox written in Django and Javascript for the new version of my site. Not bad for my first attempt, and it certainly was much easier than I imagined. All in all, there is a lot less Python code than the existing PHP code, and we even get neat-o AJAX&#8217;y features that the old one didn&#8217;t have. So I think we will go with this for the first pass of the site. Once I get more comfortable with jQuery I may decide to redo the scrolling itself with it. We could also look into posting shouts to the database without a complete page refresh. There is always something to go back and refactor as you learn more about your tools and best practices. I hope someone found this quick series of posts useful.</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/24/anatomy-of-a-shoutbox-part-6-conclusion-delete-in-place/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Anatomy of a Shoutbox; Part 5 &#8211; Edit In Place</title>
		<link>http://deathofagremmie.com/2008/12/20/anatomy-of-a-shoutbox-part-5-edit-in-place/</link>
		<comments>http://deathofagremmie.com/2008/12/20/anatomy-of-a-shoutbox-part-5-edit-in-place/#comments</comments>
		<pubDate>Sun, 21 Dec 2008 05:25:42 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[jeditable]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=93</guid>
		<description><![CDATA[In this post I&#8217;ll show how I got an edit-in-place feature working with the shoutbox. In case you missed the earlier parts, here they are: part 1, part 2, part 3, part 4. The first thing I did was create a &#8220;shout history&#8221; view. This allows you to view the shouts in a full page [...]]]></description>
			<content:encoded><![CDATA[<p>In this post I&#8217;ll show how I got an edit-in-place feature working with the shoutbox. In case you missed the earlier parts, here they are:  <a href="../2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/">part 1</a>, <a href="../2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/">part 2</a>, <a href="../2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/">part 3</a>, <a href="http://deathofagremmie.com/2008/12/14/anatomy-of-a-shoutbox-part-4-smilies/">part 4</a>.</p>
<p>The first thing I did was create a &#8220;shout history&#8221; view. This allows you to view the shouts in a full page context. In my views.py file I created this:</p>
<pre class="brush: python">def view(request, page=1):
    &quot;&quot;&quot;This view allows one to view the shoutbox history.&quot;&quot;&quot;
    paginator = DiggPaginator(Shout.objects.all(), SHOUTS_PER_PAGE, body=5, tail=3, margin=3, padding=2)
    try:
        the_page = paginator.page(int(page))
    except InvalidPage:
        raise Http404

    return render_to_response(&#039;shoutbox/view.html&#039;, {
        &#039;page&#039;: the_page,
        },
        context_instance = RequestContext(request))</pre>
<p>Here I am using the <a href="http://www.djangosnippets.org/snippets/773/">DiggPaginator</a>, which is a very nice paginator that gives you &#8220;<a title="Digg.com" href="http://digg.com/">Digg</a>-style&#8221; pagination. I then pass the page object to the template which does most of the interesting work.</p>
<p>In order to get the edit-in-place funcitonality to work, I am leveraging the <a title="Jeditable" href="http://www.appelsiini.net/projects/jeditable">Jeditable</a> <a title="jQuery" href="http://jquery.com/">jQuery</a> plugin. In order to use this plugin, in my shoutbox/view.html template I include &lt;script&gt; tags to bring in both the jQuery javascript library and the Jeditable plugin code. I then loop over the shouts for the page and display them along with the avatar and links to the user that made the shout. You&#8217;ll also see me using the <strong>smilify</strong> fiilter I discussed in part 4.</p>
<p><span id="more-93"></span>
<pre class="brush: html">{% extends &#039;base.html&#039; %}
&lt;pre&gt;{% load avatar_tags %}
{% load smiley_tags %}
{% block custom_css %}
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{{ MEDIA_URL }}css/shoutbox_app.css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{{ MEDIA_URL }}css/pagination.css&quot; /&gt;
{% endblock %}
{% block custom_js %}
&lt;script type=&quot;text/javascript&quot; src=&quot;{{ MEDIA_URL }}js/jquery-1.2.6.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;{{ MEDIA_URL }}js/jquery.jeditable.mini.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;{{ MEDIA_URL }}js/shoutbox_app.js&quot;&gt;&lt;/script&gt;
{% endblock %}
{% block title %}Shout History{% endblock %}
{% block content %}
&lt;h2&gt;Shout History&lt;/h2&gt;
{% if page.object_list %}
{% include &#039;core/pagination.html&#039; %}

&lt;div class=&quot;shoutbox-history&quot;&gt;
&lt;table&gt;
{% for shout in page.object_list %}
&lt;tr class=&quot;{% cycle &#039;even&#039; &#039;odd&#039; %}&quot;&gt;
&lt;th&gt;
&lt;a href=&quot;{% url bio-view_profile username=shout.user.username %}&quot;&gt;{% avatar shout.user %}&lt;/a&gt;
&lt;a href=&quot;{% url bio-view_profile username=shout.user.username %}&quot;&gt;{{ shout.user.username }}&lt;/a&gt;
&lt;/th&gt;
&lt;td&gt;
&lt;div {% ifequal user shout.user %}class=&quot;edit&quot; id=&quot;shout-{{ shout.id }}&quot;{% endifequal %}&gt;{{ shout.shout|smilify|urlize }}&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span class=&quot;date&quot;&gt;{{ shout.shout_date|date:&quot;D M d Y H:i:s&quot; }}&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
{% endfor %}
&lt;/table&gt;
&lt;/div&gt;

{% include &#039;core/pagination.html&#039; %}
{% else %}
&lt;p&gt;No shouts at this time.&lt;/p&gt;
{% endif %}
{% endblock %}</pre>
<p>In order to get the edit-in-place working, I need to enclose the shouts the user can edit in a special div with the class &#8220;edit&#8221;. I do this inside the loop, adding the class attribute if the current user matches the shout user. I also give each of these divs an id of &#8220;shout-xx&#8221; where xx is the id of the shout being displayed. I thought about just using the shout id as the id, but thought this might clash with other page elements, so I prefixed it with &#8220;shout-&#8221;. This adds a tiny bit of work back in the view code, but not much.</p>
<p>Now that I have &#8220;tagged&#8221; the divs the user can edit, I configure the plugin with the following bit of jQuery magic in the shoutbox_app.js file:</p>
<pre class="brush: javascript">$(document).ready(function() {
     $(&#039;.edit&#039;).editable(&#039;/shout/edit/&#039;, {
         loadurl : &#039;/shout/text/&#039;,
         indicator : &#039;Saving...&#039;,
         tooltip   : &#039;Click to edit your shout...&#039;,
         submit : &#039;OK&#039;,
         cancel : &#039;Cancel&#039;
     });
 });
</pre>
<p>This code instructs jQuery to find all elements with the class &#8220;edit&#8221; and call the editable() function on them. The first parameter to editable() is the URL to send the edited text to when the user hits enter or clicks OK. In our case, this is /shout/edit. I&#8217;ll show this view in a minute. The next parameter to the editable() function is a javascript options object with several interesting options:</p>
<ul>
<li>loadurl &#8211; this is the URL that Jeditable will call to retrieve the text to edit. Recall that our text is <em>smilified</em>, so we don&#8217;t want the user edited the XHTML markup. We want the user to edit the original shout text.</li>
<li>indicator &#8211; after saving the edit, the Jeditable plugin will display this text to give the user feedback something is happening while it posts the edited text to the server.</li>
<li>tooltip &#8211; Jeditable will display this text as a tooltip when the user hovers their mouse over the div.</li>
<li>submit &#8211; Provides the text for a submit button.</li>
<li>cancel &#8211; Provides the text for a cancel button.</li>
</ul>
<p>Holy crap that was easy. Now all we need to do is provide those 2 view functions. One to receive and save the edited text, and one that is called to retrieve the original shout. First, here is the /shout/view view function that retrieves the original text.</p>
<pre class="brush: python">
shout_id_re = re.compile(r&#039;shout-(\d+)&#039;)
def text(request):
    &quot;&quot;&quot;This view function retrieves the text of a shout; it is used in the in-place
    editing of shouts on the shoutbox history view.&quot;&quot;&quot;
    if request.user.is_authenticated():
        m = shout_id_re.match(request.GET.get(&#039;id&#039;, &#039;&#039;))
        if m is None:
            return HttpResponse(u&#039;&#039;)
        try:
            shout = Shout.objects.get(pk=m.group(1))
        except Shout.DoesNotExist:
            return HttpResponse(u&#039;&#039;)
        return HttpResponse(shout.shout)

    return HttpResponse(u&#039;&#039;)</pre>
<p>Jeditable is expecting that this function return the raw shout, not XHTML markup. It will pass the id of the div the user wants to edit as a GET parameter. First we check to see if the user is authenticated as a minimal check. Note that we don&#8217;t use the login_required decorator, because that would redirect the Jeditable request to the login page if the user wasn&#8217;t logged in. We then use a regular expression to pull the numeric part out of the div&#8217;s id (recall that it comes in the form <em>shout-xx</em>). With the id in hand we simply query the shout object using the Django ORM. We then return the shout text as an HttpResponse.</p>
<p>Finally here is the view function that Jeditable posts the edited data to.</p>
<pre class="brush: python">def edit(request):
    &quot;&quot;&quot;This view accepts a shoutbox edit from the shoutbox history view.&quot;&quot;&quot;
    if request.user.is_authenticated():
        m = shout_id_re.match(request.POST.get(&#039;id&#039;, &#039;&#039;))
        if m is None:
            return HttpResponse(u&#039;&#039;)
        try:
            shout = Shout.objects.get(pk=m.group(1))
        except Shout.DoesNotExist:
            return HttpResponse(u&#039;&#039;)
        if request.user != shout.user:
            return HttpResponse(u&#039;&#039;)
        new_shout = request.POST.get(&#039;value&#039;, &#039;&#039;).strip()
        if new_shout == &#039;&#039;:
            return HttpResponse(u&#039;&#039;)
        shout.shout = new_shout
        shout.save()
        return render_to_response(&#039;shoutbox/render_shout.html&#039;, {
            &#039;shout&#039;: shout,
            },
            context_instance = RequestContext(request))

    return HttpResponse(u&#039;&#039;)</pre>
<p>This function does do a lot of error checking, so it looks more complicated than it really is. This time Jeditable is expecting us to return XHTML markup, and it passes 2 things to us as POST parameters: the div id appears as &#8220;id&#8221; and the new text appears as &#8220;value&#8221;.</p>
<p>In short, this function checks to make sure the user posting the new shout text is the same user that created the shout in the first place. If this is the case, it simply updates the shout text and saves it back to the database. We then render a template to return the new XHTML markup for the div. Remember that we have to smilify the text, so our template looks like this:</p>
<pre class="brush: html">{% load smiley_tags %}
{{ shout.shout|smilify|urlize }}
</pre>
<p>We load our smiley tags discussed in part 4, and filter the shout through our smilify filter and the Django urlize filter.</p>
<p>One thing that is very useful to add at this point is some CSS for our editable div tags. I added a :hover rule that changed the background color and mouse cursor. This provides additional visual cues to the user that the text can edited when the mouse is hovered over it.</p>
<p>And that is basically it! Here is a screen shot of a user editing a shout in-place on the shout history page.</p>
<div id="attachment_100" class="wp-caption alignnone" style="width: 598px"><img class="size-full wp-image-100" title="Edit-in-place in action" src="http://deathofagremmie.com/wp-content/uploads/2008/12/edit-in-place.png" alt="Edit-in-place in action" width="588" height="410" /><p class="wp-caption-text">Edit-in-place in action</p></div>
<p>I plan on adding one more blog post about the shoutbox. All we have left to do is an in-place delete of a shout on the shout history page. Stay tuned for part 6.</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/20/anatomy-of-a-shoutbox-part-5-edit-in-place/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Anatomy of a Shoutbox; Part 4 &#8211; Smilies!</title>
		<link>http://deathofagremmie.com/2008/12/14/anatomy-of-a-shoutbox-part-4-smilies/</link>
		<comments>http://deathofagremmie.com/2008/12/14/anatomy-of-a-shoutbox-part-4-smilies/#comments</comments>
		<pubDate>Sun, 14 Dec 2008 19:43:15 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>
		<category><![CDATA[smilies]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=79</guid>
		<description><![CDATA[I&#8217;ve been making some great progress on the shoutbox. I&#8217;ve added smilies and I used a jQuery plugin to get the edit-in-place working. It&#8217;s awesome! Meanwhile, while it is fresh in my mind, I&#8217;ll post about the smilies here. In case you missed the earlier parts, here they are: part 1, part 2, part 3. [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been making some great progress on the shoutbox. I&#8217;ve added smilies and I used a jQuery plugin to get the edit-in-place working. It&#8217;s awesome! Meanwhile, while it is fresh in my mind, I&#8217;ll post about the smilies here.</p>
<p>In case you missed the earlier parts, here they are:  <a href="http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/">part 1</a>, <a href="http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/">part 2</a>, <a href="http://deathofagremmie.com/2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/">part 3</a>.</p>
<p>One of the &#8220;fun&#8221; features of the shoutbox is the smilies, or emoticons. In a nutshell, certain character sequences are translated into small images in the &#8220;shout&#8221; text. Furthermore, all of the images are displayed below the shoutbox, and by clicking on them they get added to the shout the user is typing via some simple javascript.</p>
<p>I can see myself using this smiley function in lots of places: shoutbox, user comments, photo of the day, forums, etc. So I created a new application for it, separate from the shoutbox application.</p>
<p>I read up on django&#8217;s custom filter documentation, and spent a fair amount of time scratching my head over the <a href="http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#filters-and-auto-escaping">auto-escaping feature</a>. It suddenly occured to me that what I need is similar to the django <a href="http://docs.djangoproject.com/en/dev/ref/templates/builtins/#urlize">urlize filter</a>: go through some text and replace certain phrases with HTML. In the urlize case this is an &lt;a&gt; tag. In my case this is an &lt;img&gt; tag. I then studied the urlize filter source to steer me. Mine turned out a bit simpler as you will see below.</p>
<p>First let&#8217;s start with the model. I wanted to be able to easily edit and add smilies via the admin interface. The legacy shoutbox allowed many &#8220;phrases&#8221; to map to a single smiley, but it did this in a redundant way. I decided to keep things simple and only allow one phrase per smiley. Part of this was driven by the fact that if the user clicks on a smiley I need to add the phrase to the text they are typing. If there are multiple phrases, which do I use? I suppose I could have picked the first one, but this was going against my &#8220;let&#8217;s keep this simple for now&#8221; philosophy. Here is the model I came up with:</p>
<p><span id="more-79"></span></p>
<pre class="brush: python">class Smiley(models.Model):
    image = models.ImageField(upload_to=&#039;smiley/images/&#039;)
    title = models.CharField(max_length=32)
    code = models.CharField(max_length=32)

    objects = SmileyManager()

    class Meta:
        verbose_name_plural = &#039;Smilies&#039;
        ordering = (&#039;title&#039;, )

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return self.image.url

    def html(self):
        if self.image:
            return u&#039;&lt;img src=&quot;%s&quot; alt=&quot;%s&quot; title=&quot;%s&quot; /&gt;&#039; % \
                    (self.get_absolute_url(), self.title, self.title)
        return u&#039;&#039;
    html.allow_tags = True
</pre>
<p>This is pretty straightforward. I&#8217;ll explain the custom manager in a bit. I wanted to be able to show the smiley image in the admin section, so I added a html() member function to the Smiley class. This would be useful in templates also. In order to get the HTML to display correctly in the admin, I needed to add an &#8220;allow_tags&#8221; attribute set to True to the html() function. I can then use the html() function in the admin class in my admin.py file:</p>
<pre class="brush: python">from django.contrib import admin
from smiley.models import Smiley

class SmileyAdmin(admin.ModelAdmin):
    list_display = (&#039;title&#039;, &#039;code&#039;, &#039;html&#039;)

admin.site.register(Smiley, SmileyAdmin)</pre>
<p>And with just that little bit of code I now have a nice admin interface to add smilies.</p>
<div id="attachment_84" class="wp-caption alignnone" style="width: 160px"><a href="http://deathofagremmie.com/wp-content/uploads/2008/12/admin.png"><img class="size-thumbnail wp-image-84" title="Admin View of Smilies" src="http://deathofagremmie.com/wp-content/uploads/2008/12/admin-150x150.png" alt="Admin View of Smilies" width="150" height="150" /></a><p class="wp-caption-text">Admin View of Smilies</p></div>
<p>I then turned my attention to creating a custom filter for &#8220;smilifying&#8221; text. Again, I studied the urlize filter to see what was being done there (splitting the text into words), but steered my code towards the example in the documentation on how to handle auto-escaping. I realized that I could be hitting the database a lot to read the smiley definitions trying to smilify text, so I decided to cache the smiley information. A convenient way to do this was to add a custom Smiley model manager. I knew I needed a dictionary that mapped smiley code phrases to &lt;img&gt; HTML. And I probably would need to cache the smiley objects themselves. I handled this in a custom model manager (in models.py):</p>
<pre class="brush: python">class SmileyManager(models.Manager):
    smiley_map = None
    smilies = None

    def get_smiley_map(self):
        if self.smiley_map is None:
            smilies = self.all()
            self.smiley_map = {}
            for s in smilies:
                self.smiley_map[s.code] = s.html()
        return self.smiley_map

    def get_smilies(self):
        if self.smilies is None:
            self.smilies = self.all()
        return self.smilies

    def clear_cache(self):
        self.smiley_map = None
        self.smilies = None
</pre>
<p>The get_smiley_map() and get_smilies() use caching techniques to minimize database hits. I added a clear_cache() function to force a re-read of the database. I envision that if I ever need to add or delete a smiley from the admin interface I will need to add a custom admin view that calls clear_cache(). We&#8217;ll leave that as a hook for now and cross that bridge later. Now my filter code can simply call the Smiley objects manager to get the &#8220;smiley map&#8221;.</p>
<p>Here is my new filter called, of course, &#8220;smilify&#8221;. I put this in the templatetags directory in a file called smiley_tags.py:</p>
<pre class="brush: python">import re
from django import template
from django.template.defaultfilters import stringfilter
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

from smiley.models import Smiley

register = template.Library()

word_split_re = re.compile(r&#039;(\s+)&#039;)

@register.filter
@stringfilter
def smilify(value, autoescape=False):
    &quot;&quot;&quot;A filter to &quot;smilify&quot; text by replacing text with HTML img tags of smilies.&quot;&quot;&quot;
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x

    smiley_map = Smiley.objects.get_smiley_map()

    words = word_split_re.split(value)
    for i, word in enumerate(words):
        if word in smiley_map:
            words[i] = smiley_map[word]
        else:
            words[i] = esc(words[i])
    return mark_safe(u&#039;&#039;.join(words))
smilify.needs_autoescape = True
</pre>
<p>The basic idea is to split the text into words. If a word is in our smiley map, substitute it with the corresponding &lt;img&gt; tag. Otherwise we need to escape the word. At the end we join the words together and mark the resulting string safe.</p>
<p>I then updated my shoutbox template to use this new filter and voila we had smilies!</p>
<div id="attachment_85" class="wp-caption alignnone" style="width: 253px"><a href="http://deathofagremmie.com/wp-content/uploads/2008/12/shoutbox1.png"><img class="size-full wp-image-85" title="Smilies!" src="http://deathofagremmie.com/wp-content/uploads/2008/12/shoutbox1.png" alt="Smilies" width="243" height="330" /></a><p class="wp-caption-text">Smilies</p></div>
<p>Finally I needed a way to present all of the smileys to the user. These smilies would be clickable, and doing so would add the code for that smiley to whatever input box the comment or shout would be using. I created an inclusion tag called smiley_farm in my smiley_tags.py file to handle this. It uses the Smiley manager&#8217;s get_smilies functions, which caches the smiley objects:</p>
<pre class="brush: python">@register.inclusion_tag(&#039;smiley/smiley_farm.html&#039;)
def smiley_farm():
    &quot;&quot;&quot;An inclusion tag that displays all of the smilies in clickable form.&quot;&quot;&quot;
    return {&#039;smilies&#039;: Smiley.objects.get_smilies(), }
</pre>
<p>The template smiley_farm.html looks like this:</p>
<pre class="brush: html">&lt;div class=&quot;smiley_farm&quot;&gt;
{% for s in smilies %}
    &lt;img src=&quot;{{ s.image.url }}&quot; alt=&quot;{{ s.code }}&quot; title=&quot;{{ s.title }} {{ s.code }}&quot;
        onclick=&quot;sb_smiley_click(&#039; {{ s.code }} &#039;);&quot; /&gt;
{% endfor %}
&lt;/div&gt;
</pre>
<p>I enclose the smilies in a div so I can easily style them with CSS. In particular I added a cursor: pointer style to the images so that the mouse pointer will indicate to the user that the smilies can be clicked. When you click on a smiley, the sb_smiley_click() javascript function will run, which simply adds the smiley code phrase to an input text with a certain id:</p>
<pre class="brush: javascript">function sb_smiley_click(code)
{
    var txt = document.getElementById(&quot;shoutbox-smiley-input&quot;);
    txt.value += code;
    txt.focus();
}</pre>
<p>As I learn more about jQuery I hope to revisit this code and add the onclick function to the images after the page has loaded.</p>
<p>Finally, I added a bit of javascript to hide the smiley farm until the user clicks a button labeled &#8220;Smilies&#8221; to reveal them to conserve screen real estate. This idea and behavior is copied from the legacy shoutbox.</p>
<div id="attachment_86" class="wp-caption alignnone" style="width: 243px"><a href="http://deathofagremmie.com/wp-content/uploads/2008/12/shoutbox2.png"><img class="size-full wp-image-86" title="Shoutbox with Smiley Farm" src="http://deathofagremmie.com/wp-content/uploads/2008/12/shoutbox2.png" alt="Shoutbox with Smiley Farm" width="233" height="432" /></a><p class="wp-caption-text">Shoutbox with Smiley Farm</p></div>
<p>That was a long post but with these pretty simple pieces in place I now have smilies for the shoutbox and other applications to use!</p>
<p>In the next post I&#8217;ll show how I got the edit-in-place functionality working.</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/14/anatomy-of-a-shoutbox-part-4-smilies/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Anatomy of a Shoutbox; Part 3 &#8211; View &amp; Template</title>
		<link>http://deathofagremmie.com/2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/</link>
		<comments>http://deathofagremmie.com/2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/#comments</comments>
		<pubDate>Tue, 09 Dec 2008 01:03:06 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=71</guid>
		<description><![CDATA[And now we continue on with the shoutbox development. If you missed the earlier posts, here they are: part 1, part 2. After getting the model and admin parts working (discussed in part 2), I turned to the view and template. I decided to create an inclusion tag for the shoutbox. My site&#8217;s base template [...]]]></description>
			<content:encoded><![CDATA[<p>And now we continue on with the shoutbox development. If you missed the earlier posts, here they are: <a href="http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/">part 1</a>, <a href="http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/">part 2</a>.</p>
<p>After getting the model and admin parts working (discussed in <a href="http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/">part 2</a>), I turned to the view and template. I decided to create an <a href="http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#inclusion-tags">inclusion tag</a> for the shoutbox. My site&#8217;s base template will then use the tag to display the shoutbox on every page of the site.</p>
<p>Writing the inclusion tag itself was pretty easy. All I need to do is retrieve the last 10 shouts and render a template with the results. So, after studying the Django docs, I created my <strong>templatetags</strong> sub-directory, the obligatory empty <strong>__init__.py</strong> file, and my <strong>shoutbox_tags.py</strong> file, which contained this code:</p>
<pre class="brush: python">from django import template
from shoutbox.models import Shout

register = template.Library()

@register.inclusion_tag(&#039;shoutbox/shoutbox.html&#039;, takes_context=True)
def shoutbox(context):
   shouts = Shout.objects.all()[:10]
   return {
      &#039;shouts&#039;: shouts,
      &#039;user&#039;: context[&#039;user&#039;],
   }</pre>
<p>I&#8217;m using the newer Python decorator syntax here. The first argument to the inclusion_tag decorator function is the name of the template I am going to render. The needs_context argument set to True says that I will want to access the context. The reason for this is I want to display a form for authenticated users, so I will need to query the user variable from the context for that.</p>
<p>All I do here is retrieve the last 10 shouts, then return a dictionary that will be fed to my template.</p>
<p>Now to write the actual template itself. I wrote this template in a series of &#8220;baby-steps&#8221;, which is my usual mode of operation. My steps were as follows:</p>
<ol>
<li>Write code that only displays the shouts as paragraphs inside a div tag.</li>
<li>Played with the styles and wrote css to make it look decent.</li>
<li>Got the form post working with the view function.</li>
<li>Got the thing to scroll by leveraging some 3rd party javascript.</li>
</ol>
<p>I should show you the template at each step, but I&#8217;m writing this after the fact, and it really didn&#8217;t change <em>that</em> much from beginning to end. I&#8217;ll present the final version here and discuss it.</p>
<pre class="brush: python">{% extends &#039;side_block.html&#039; %}
{% block block_title %}Shoutbox{% endblock %}
{% block block_content %}
{% if shouts %}
&lt;div id=&quot;marqueecontainer&quot; onmouseover=&quot;copyspeed=pausespeed&quot; onmouseout=&quot;copyspeed=marqueespeed&quot;&gt;
&lt;div id=&quot;vmarquee&quot; style=&quot;position: absolute; width: 98%;&quot;&gt;
   {% for shout in shouts reversed %}
      &lt;p&gt;
      &lt;span class=&quot;shoutbox-user&quot;&gt;{{ shout.user.username }}:&lt;/span&gt;
      &lt;span class=&quot;shoutbox-shout&quot;&gt;{{ shout.shout|urlizetrunc:15 }}&lt;/span&gt;
      &lt;span class=&quot;shoutbox-date&quot;&gt;{{ shout.shout_date|date:&quot;D M d Y H:i:s&quot; }}&lt;/span&gt;
      &lt;/p&gt;
   {% endfor %}
&lt;/div&gt;
&lt;/div&gt;
{% endif %}
&lt;center&gt;&lt;a href=&quot;{% url shoutbox-view page=1 %}&quot;&gt;Shout History&lt;/a&gt;&lt;/center&gt;
{% if user.is_authenticated %}
&lt;center&gt;
&lt;form action=&quot;{% url shoutbox-shout %}&quot; method=&quot;post&quot;&gt;
   &lt;input type=&quot;text&quot; maxlength=&quot;2048&quot; size=&quot;20&quot; name=&quot;msg&quot; value=&quot;&quot; /&gt;
   &lt;br /&gt;
   &lt;input type=&quot;submit&quot; value=&quot;Shout&quot; /&gt;
&lt;/form&gt;
&lt;/center&gt;
{% else %}
&lt;p&gt;Please login or register to shout.&lt;/p&gt;
{% endif %}
{% endblock %}
</pre>
<p>First of all, this template extends another called side_block.html. I decided that all my &#8220;blocks&#8221; will have common styling and features, and I will collect that information there. This template will provide values for the block title and block content.</p>
<p>Lines 4-16 display the 10 latest shouts. The two divs are needed for the javascript scrolling, which I&#8217;ll explain in a minute, so you can ignore them for now. The for loop is the real action here. I simply loop over the shouts, producing a paragraph for each one. The ordering we setup for the shouts in the model is reverse chronological order. On the current SG101 site, the legacy shoutbox scrolls up, displaying the oldest of the 10 shouts first. To achieve this, we use the nifty <strong>reversed</strong> keyword on the for loop, which gives me exactly what I want.</p>
<p>The other interesting thing in the loop is the use of the <a href="http://docs.djangoproject.com/en/dev/ref/templates/builtins/#urlizetrunc">urlizetrunc</a> filter. As you may recall from <a href="http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/">part 1</a>, one of my requirements is to minimize or truncate URL&#8217;s. I headed down the path of writing my own function to do this, and spent about half hour on it, trying to come to terms with Django&#8217;s escaping protocol, when it suddenly occurred to me that I can&#8217;t be the first person who had a use-case for this. So I quickly scanned the <a href="http://docs.djangoproject.com/en/dev/ref/templates/builtins/#built-in-filter-reference">Django filter documentation</a>, and sure enough, a function close enough to what I wanted was already in the can! The legacy shoutbox shortens URLs by simply displaying them as the text [URL]. The urlize filter doesn&#8217;t do this, but urlizetrunc is close enough to what I need that I used it as-is. It simply truncates URLs after a certain length.</p>
<p>The last half of the template decides whether to display a shout form to the user, depending on whether she is authenticated or not. If not authenticated, I simply display a reminder to login or register (hmm, I need to make those links to the appropriate pages). If authenticated, I display the shout form. I could have used a Django form here; perhaps the inclusion tag could have passed it to the template. However, the form is so simple I didn&#8217;t even bother. All I need is one text input and a submit button, which I would have had to write anyway.</p>
<p>I will now show you the view function that this form posts to. It is quite simple.</p>
<pre class="brush: python">@login_required
def shout(request):
    if request.method == &#039;POST&#039;:
        msg = request.POST.get(&#039;msg&#039;, &#039;&#039;)
        if msg != &#039;&#039;:
            shout = Shout(user=request.user, shout=msg)
            shout.save()

    return HttpResponseRedirect(request.META.get(&#039;HTTP_REFERER&#039;, &#039;/&#039;))
</pre>
<p>The first thing to notice is I have decorated my view function with the login_required decorator. This ensures only registered users can shout. Sure, we only display the form to registered users, but that doesn&#8217;t stop someone from crafting their own form and posting to the same URL. I then pull the msg out of the POST Querydict, and let it default to the empty string if it isn&#8217;t there. If the msg isn&#8217;t empty, I create and save a new Shout object, recording the user that made it and the msg value. As you may recall, the shout_date field will be automatically initialized to the current date and time by Django when I save the new shout. I then redirect to the same page the user was looking at (recall that the shoutbox will appear on every page of the site). If this can&#8217;t be determined (which normally should not happen) I simply go to the site root.</p>
<p>With this machinery in place, I was able to submit shouts just like the current (legacy) shoutbox on SG101. Sweet! This was fun, but now it gets even better. How to make it scroll?</p>
<p>I had no interest in writing the scrolling code, so I spent a little while googling. I did not consider reusing the legacy shoutbox javascript, as it required the javascript to be sent with the page (it was dynamic), and the code wasn&#8217;t properly escaped and broke XHTML compliance. I really want to learn more about the <a href="http://jquery.com/">jQuery</a> library, and I did look for a jQuery plugin for this task. However I decided to save jQuery for another day, and found some <a href="http://www.dynamicdrive.com/dynamicindex2/cmarquee2.htm">nice code</a> on the venerable <a href="http://www.dynamicdrive.com/">Dynamic Drive website</a>. This code was similar to the legacy shoutbox code, so I was comfortable with it. I liked it because it simply scrolled a div upwards, and required no inline javascript. I think the legacy shoutbox script must have used an earlier version of this same script because it was so similar. To integrate this with my template code, I simply had to wrap my shouts inside 2 divs, and include the javascript in my site&#8217;s base template. And wow it worked on the first try, much to my surprise. I was smiling at how quickly this came together.</p>
<p>Wow, we have nearly all the functionality that I wanted completed already. All that is left is the ability to review the shout history, and let users edit and delete their shouts. I completed the shout history portion, so I will show that in the next post. For editing and deleting, I think I might want to do something AJAX-y and let the user edit the shout in-place. Hmmm, maybe I will learn some jQuery to make that easier. And maybe then I can rewrite the scrolling with jQuery. More to come!</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/08/anatomy-of-a-shoutbox-part-3-view-template/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Anatomy of a Shoutbox; Part 2 &#8211; Model and Admin</title>
		<link>http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/</link>
		<comments>http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/#comments</comments>
		<pubDate>Mon, 08 Dec 2008 00:51:29 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=65</guid>
		<description><![CDATA[One of the things that really attracted me to Django was that it has an ORM. Of course I had never seen an ORM before and didn&#8217;t even know that term until later. An ORM (Object-Relational Mapping) allows you to seamlessly create Python objects from database tables, and vice versa. So you never have to [...]]]></description>
			<content:encoded><![CDATA[<p>One of the things that really attracted me to Django was that it has an <strong>ORM</strong>. Of course I had never seen an ORM before and didn&#8217;t even know that term until later. An ORM (Object-Relational Mapping) allows you to seamlessly create Python objects from database tables, and vice versa. So you never have to code your own SQL unless you really want to do something fancy.</p>
<p>For the shoutbox app, we need a very simple model. Here is my <strong>models.py</strong> file:</p>
<pre class="brush: python">from django.db import models
from django.contrib.auth.models import User

class Shout(models.Model):
   user = models.ForeignKey(User)
   shout_date = models.DateTimeField(auto_now_add=True)
   shout = models.TextField()

   def __unicode__(self):
      shout = self.shout[:60]
      return u&#039;Shout from %s: %s&#039; % (self.user.username, shout)

   class Meta:
      ordering = (&#039;-shout_date&#039;, )</pre>
<p>There is nothing too out of the ordinary here. Each shout was made by a registered user, so we need a ForeignKey to the User table. We record the date and time the shout was made (using auto_now_add initializes this field for us when the object is saved), and finally the shout itself is just a TextField.</p>
<p>The __unicode__ function will be used in the admin interface. Here we just show who the shout is from and then a truncated version of the shout. Finally, we can tell Django our default ordering of Shouts is in reverse chronological order via the ordering attribute in the embedded Meta class.</p>
<p>Now we turn to the <strong>admin.py</strong> file to enable and configure an automatic admin interface for Shouts. Again, this is an amazing feature of Django that really sold me on the idea of trying this framework out.</p>
<pre class="brush: python">from django.contrib import admin
from shoutbox.models import Shout

class ShoutAdmin(admin.ModelAdmin):
   list_display = (&#039;shout_date&#039;, &#039;__unicode__&#039;)
   raw_id_fields = (&#039;user&#039;, )

admin.site.register(Shout, ShoutAdmin)
</pre>
<p>Here we derive a class from Django&#8217;s admin.ModelAdmin class to configure our admin interface. Our main display listing should consist of two columns: the shout_date and then the __unicode__ representation of each Shout. This will invoke the __unicode__ member function on each Shout object displayed.</p>
<p>Here I am using the raw_id_fields feature for the first time. Since I have over 2000 users currently on SG101, I do not want to load all that information into a select box everytime I drill into a Shout. The raw_id_field simply displays the raw user id along with the username. If I really want to change the user, the field has a pop-up control to allow me to load all the usernames. I should have done this on a few other applications I previously wrote. This is the constant refactoring I wrote about earlier.</p>
<p>And that&#8217;s it! After running syncdb I can now add Shouts to the database via the admin interface, all in less than 25 lines of code from me! In part 3 I will show how I display the shoutbox as a &#8220;block&#8221; (to borrow the PHP-Nuke term) on the website. To do this, I created one of my first Django template tags and incorporate some 3rd party javascript to accomplish the scrolling of the shouts. Stay tuned.</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/07/anatomy-of-a-shoutbox-part-2-model-and-admin/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Anatomy of a Shoutbox; Part 1- Requirements</title>
		<link>http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/</link>
		<comments>http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/#comments</comments>
		<pubDate>Wed, 03 Dec 2008 00:31:34 +0000</pubDate>
		<dc:creator>gremmie</dc:creator>
				<category><![CDATA[SG101 2.0]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[sg101]]></category>
		<category><![CDATA[shoutbox]]></category>

		<guid isPermaLink="false">http://deathofagremmie.com/?p=38</guid>
		<description><![CDATA[One of the applications from the current SG101 website I want to bring over is the shoutbox. Shoutboxes are very popular in the Nuke community, and I was very surprised at how much my users took to it. It may very well be the 2nd most popular application after the forums. It is kind of [...]]]></description>
			<content:encoded><![CDATA[<p>One of the applications from the current <a title="SG101" href="http://surfguitar101.com">SG101 website</a> I want to bring over is the shoutbox. Shoutboxes are very popular in the Nuke community, and I was very surprised at how much my users took to it. It may very well be the 2nd most popular application after the forums. It is kind of a community <a title="Twitter" href="http://twitter.com">Twitter</a> for the site (hmmm did the Twitter guys get the idea for Twitter from the lowly shout box?).</p>
<p>The current site is using a shoutbox script developed by the now defunct ourscripts.net site. The script was obviously a labor of love by the original developer, but to my taste it is over-engineered and somewhat heavy handed in terms of resources it uses. It is highly configurable; it has admin options for changing the style (theme), user banning, IP banning, a bad word filter, configurable emoticons, etc. After running this script for over two years now I think I really only need the following features.</p>
<ol>
<li>Only registered users can shout.</li>
<li>Shouts should scroll (users love this for some reason).</li>
<li>Any URLs in the shout should be made clickable (and preferably shortened or truncated).</li>
<li>Emoticons (smilies) are absolutely required.</li>
<li>Shout history should be displayable.</li>
<li>Users should be able to edit or delete their shouts in the history.</li>
<li>XHTML compliant (the current script is not).</li>
<li>The admin should be able to edit and delete shouts.</li>
</ol>
<p>And that&#8217;s basically it. I may decide to have the thing auto-prune shouts, as Django doesn&#8217;t currently have a bulk delete feature in the auto-generated admin interface (but that is coming in Django 1.1).</p>
<p>I&#8217;m going to be using emoticons in other comment and forum applications all over the site, so thinking ahead here, it is probably best to create that functionality in a separate Django application that the shoutbox will use.</p>
<p>Okay, in keeping with my limited time and my new attempt to blog more but in more manageable pieces, I&#8217;ll stop here. The next few posts will detail the stages of development of this application.</p>
]]></content:encoded>
			<wfw:commentRss>http://deathofagremmie.com/2008/12/02/anatomy-of-a-shoutbox-part-1-requirements/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
