Tuesday, November 1, 2011

Thoughts on my support of #occupy, as a software developer

Tomorrow, November 2nd, there is going to be a general strike in Oakland. Though I support #occupy, I'm not going to be on strike, for a few reasons:

  1. I also support the organization I'm working for, think they're doing good work, and feel that it is not incongruous with #occupy (though it's certainly not the same)
  2. I don't actually work in Oakland
Even though I'm not striking, I feel I can use my time and my skills at my job to help out.  I've been inspired by the work of some of my colleagues in helping to create design artifacts for use by #occupy-ers.  Here's a list of a few of the things I may be doing, and you might consider as well.
  • Help out #occupy tech support.  I've come across requests from several #occupy communities for additional IT hands (I don't know whether there is a central place where these requests are posted). If you feel so inclined, volunteer a little time to maintaining an #occupy web site, or posting minutes, etc. (if anyone knows more about what tech needs #occupy communities have, please speak up).
  • Help create logistical tools. The #occupy communities that I've seen are little micro-societies and, like any society, have a number of logistical considerations and concrete needs. Are there tools that could help? For instance, I liked the idea of needsoftheoccupiers which they described as "a wedding registry meets Freecycle for the Occupy movement".
  • Do something creative/educational/inspirational. #Occupy has people paying [at least superficial] attention to issues of economic [in]equality and social participation.  That doesn't happen too often, so take advantage of it by creating a visualization or an info-app that awakens peoples imagination and helps them see things that they wouldn't normally.  Things like BankMigration map and Slavery Footprint come to mind.
If you'd like to brainstorm or help out, I'm going to start a list of potential projects here, and I'll be in #occupyoakland on irc.indymediaorg tomorrow.  There are also other projects listed at OccupyHack and on github.

Update:

Something else that could perhaps use some help:
  • @benzotweet is "trying to develop a database solution for occupy... crazy requirements! no training, role based security, decentralized... #wiki?"
Also, for some inspiration, a quote from Justin Tunney (who appears to have done a lot of work on the #ows site, which is an impressive bit of code itself):
"This isn't a protest, it's a revolutionary movement empowering people to create their own change. ...We're trying to encourage people to organize and create their own change outside of the existing establishment through direct action."
Update 2:

An interesting idea for a project that came up last night: log tweets tagged with "#occupy..." that have pictures, pull out the geo exif data, and put it on a live-updating map ... kinda like http://www.artmapper.org/ (source at https://github.com/mertonium/muralmapper).

Update 3:

To stay abreast of tech-related opportunities to support #occupy, join the occupyhack googlegroup.

Friday, June 10, 2011

Generic Django Model Templates


Update 23 Jun 2011: I have renamed the django-model-filters package django-model-blocks. It now lets you easily override the template used for a given model. Check out the changes on Github or PyPI.


Tonight I'm writing my first Django custom filter. The problem I'm trying to solve is that I want generic templates. For a given model I want to be able to set up browseable index and detail pages with minimal effort. As it stands now, say I have the following model:


    ...
    class PepulatorModel (models.Model):
        serial_number = IntegerField()
        height = IntegerField()
        width = IntegerField()
        manufacture_date = DateTimeField()
        color = CharField(max_length=32)
    
        def __unicode__(self):
            return u'Pepulator #%s' % self.serial_number
    ...

Now say I want my users to be able to browse my pepulators in a simple way, with the following caveats:

  • They cannot edit pepulators, only view (rules out admin app)
  • I want to define the URL structure (rules out databrowse) to be something like:
    
        http://www.mysite.com/pepulators/
        http://www.mysite.com/pepulators/?color=red
        http://www.mysite.com/pepulators/012345/
    
  • I want to specify the base template so that it integrates well with the rest of my project (also rules out databrowse)

Currently, I can use the generic views ListView and DetailView, but I still have to write templates that go something like this:


    {% extends base.html %}
    
    {% block content %}
        <header>
            <h1>Pepulator #{{ pepulator.serial_number }}</h1>
        </header>
        
        <div>
            <span>Serial Number</span>
            <p>{{ pepulator.serial_number }}</p>
        </div>
        <div>
            <span>Height</span>
            <p>{{ pepulator.height }}</p>
        </div>
        <div>
            <span>Width</span>
            <p>{{ pepulator.width }}</p>
        </div>
        <div>
            <span>Manufacturer</span>
            <p>{{ pepulator.manufacturer }}</p>
        </div>
        <div>
            <span>Color</span>
            <p>{{ pepulator.color }}</p>
        </div>
    {% endblock %}

Okay, a bit verbose, but it's not going to kill me. However, now say I want to change some of the fields on my model. Well, then I have to remember to change the fields in my template as well (error-prone — this is why you don't violate DRY without good reason).

All I wanted was a simple view of my model!

So, I considered making an app that was leaner than databrowser and just provided generic templates to go with generic views. I found myself having to extend the generic views anyway, though, because there's no way to access a model instance's fields and field names without explicitly feeding them to the template's context. Then, I gleaned some inspiration from uni_forms: I'll make filters!

Now my plan is to be able to say, using the example of the Pepulator detail view above:


    {% extends base.html %}
    
    {% block content %}
        {{ pepulator|as_detail_html }}
    {% endblock %}

Sublime. (This must exist somewhere; but for now, I can't find it.)

So, I start off by creating my app


    $ python manage.py startapp generic_templates

Now, from the documentation on creating custom tags and filters, I see I should create a templatetags direcotry in my app. In here I'll put an __init__.py file and a module called generic_filters. This way, when I'm done, to use the filters, I'll put near the top of my template file:


    {% load generic_filters %}

I decided to start with the detail filter (as_detail_html), and to write a test first. I know generally what I want this to do, so I write the following test:


    """
    Test the generic filters
    """

    import datetime

    from django.test import TestCase
    from mock import Mock

    from django.db.models import Model, IntegerField, DateTimeField, CharField
    from django.template import Context, Template
    from generic_templates.templatetags import generic_filters as gf

    class DetailHtmlFilterTest (TestCase):

        def setUp(self):
            # Create a sample model
            class PepulatorModel (Model):
                serial_number = IntegerField(primary_key=True)
                height = IntegerField()
                width = IntegerField()
                manufacture_date = DateTimeField()
                color = CharField(max_length=32)
            
                def __unicode__(self):
                    return u'Pepulator #%s' % self.serial_number
            
            # Create a model instance
            now = datetime.datetime.now()
            self.m = PepulatorModel(
                serial_number = 123456,
                height = 25,
                width = 16,
                manufacture_date = now,
                color = 'chartreuse',
            )
            
            # Mock Django's get_template so that it doesn't load a real file;
            # instead just return a template that allows us to verify the context
            gf.get_template = Mock(
                return_value=Template('{{ instance|safe }}:{{ fields|safe }}'))
        
        
        def test_model_format(self):
            """Tests that a given model is formatted as expected."""
            
            expected_detail = (u"Pepulator #123456:[('serial number', 123456),"
              " ('height', 25), ('width', 16), ('manufacture date', %r),"
              " ('color', 'chartreuse')]") % self.m.manufacture_date
            detail = gf.as_detail_html(self.m)
            
            gf.get_template.assert_called_with('object_detail.html')
            self.assertEqual(detail, expected_detail)

In short, set up a model and an easy template, and check that the template is filled in correctly. Of course, since I haven't yet written my filter, this fails.

This (as_detail_html) was a straightforward method to write, but I did get tripped up because of the poor documentation available on Models' Meta classes. Here's the first go at the filter:


    from django.template import Context, Template
    from django.template.loader import get_template

    def as_detail_html(instance):
        """
        Template filter that returns the given instance as a template-formatted
        block.  Inserts two objects into the context:
          ``instance`` - The model instance
          ``fields`` - A list of (name, value)-pairs representing the instance's
                       fields
        """
        template = get_template('object_detail.html')
        fields = [(field.verbose_name, getattr(instance, field.name)) 
                  for field in instance._meta.fields]
        context = Context({'instance':instance, 'fields':fields})
        return template.render(context)

One other thing: I actually want to be able to use the filter in my templates, not call it directly in my code. I'm new here, so I write another test to make sure I understand what's going on:


        def test_filter_is_registered(self):
            """Test that the filter can be used from within a template"""
            
            template = Template(('{% load generic_filters %}'
                                 '{{ pepulator|as_detail_html }}'))
            context = Context({'pepulator':self.m})
            
            expected_detail = (u"Pepulator #123456:[('serial number', 123456),"
                " ('height', 25), ('width', 16), ('manufacture date', %r),"
                " ('color', 'chartreuse')]") % self.m.manufacture_date
            detail = template.render(context)
            
            gf.get_template.assert_called_with('object_detail.html')
            self.assertEqual(detail, expected_detail)

And it turns out all I have to do to satisfy it is change my module in the following way:


    from django.template import Context, Template, Library
    from django.template.loader import get_template

    register = Library()

    @register.filter
    def as_detail_html(instance):
        ...

Now I have a working object detail template. Yay! I figure I'll do the list the same way.

More on Github: https://github.com/mjumbewu/django-model-filters