Author: Matt Croydon

  • The Emerging Django Job Market

    A company in Cincinnati, Ohio is looking for a web developer. Check out the framework they’re using:

    We are developing e-commerce sites based on Python and Django framework. Experience in Python, object oriented programming, MySql, PostgreSQL, and other web programming technologies is requested. This person would also be responsible for maintaining other scripts on various websites we maintain and host. The main focus will be on e-commerce development.

    It’s great to see jobs emerging for this amazing little framework that has been public for just a month and a half. This one popped up on a feedster search for django in my aggregator. I can’t wait to see where we are a year from now.

  • Congratulations!

    Mike asked the question and Chrissy said yes. Congratulations you two!

  • Django: Big Integer Fields

    I submitted a patch to Django Ticket #399 (request for a bigint field type). It still needs testing but works at a quick glance on mysql. Here’s a shot of them in action from the admin interface (the input is just too small and just too big respectively):

    BigInt Admin

    Update: BigIntegerField works perfectly on PostgreSQL but because it doesn’t have an unsigned integer type (that I can find), PostitiveBigIntegerField isn’t going to make it all the way up to 18,446,744,073,709,551,615 without using an arbitrary precision NUMERIC or mapping zero to -9,223,372,036,854,775,808. Both solutions are messy and it would be a shame to have the mysql and postgres backends behave so differently. As an aside, it looks like this is already the case with mysql’s IntegerFields being UNSIGNED while Postgres just checks to make sure that the integers are positive before inserting.

    The best solution would probably be to employ backend-specific range checking for these monsterous numbers. That way you won’t end up out of range in PostgreSQL but you’re also not penalizing MySQL for being able to count to 18 bajillion. At this point it would be safe to drop in BigIntegerField as is (as soon as I check it out on sqlite), but PostitiveBigIntegerField still needs some pondering.

  • Django Model Syntax Change

    Adrian has committed the Django model syntax changes. Current models will have to be rewritten — once — but the new syntax looks a lot cleaner and I think it will reduce the learning curve for newcomers to the platform.

    Be sure to check out the full documentation as well as a brief screencast highlighting the changes. I will do my best to update my CRUD Generic Views tutorial as soon as possible to reflect the syntax change.

    Update: Here’s the updated tasks.py

  • Nokia 6682 Drops on Cingular

    Gizmodo notes that the 6682 is supposedly available from Cingular now, though it’s going to be a hot potato for some time. The Cingular online store has a “Maintenance In Progress” sign up, much like the Apple store “back soon” message, but oranger.

    In other news, Lets Talk no longer lists the 7610 but doesn’t have the 6682 listed either. Amazon still says coming soon.

    Like I said, this is going to be a hard phone to come by until stock levels improve. Good luck!

  • WordPress Trackback Validator Plugin

    I just saw the WordPress Trackback Validator plugin fly by my aggregator and immediately installed it. I knew Dan online back in middle school, so with this endorsement, I installed it instantly:

    The Computer Security Lab at Rice just released the first public version of the Trackback Validator plugin for WordPress blogs. Since I’ve been using it, I’ve had 100% classification accuracy on Trackbacks (read: every legit Trackback makes it through, not a single spam Trackback). Maybe Trackback isn’t quite so dead after all.

    The system checks to make sure that the URL of the trackback links to your page. This reminds me a lot of Sam Ruby’s feedback mechanism. As a bonus, there’s a great use of sparklines in the plugin page.

  • Google Talk: One IM Identity Among Many

    Gaim Servers

    So I’m cautiously optimistinc about Google Talk rocking the house. Their choice of protocols is superb. The voice stuff is very cool but is something I will probably have to do without until the chat protocol is documented and/or the fine folks ag Gaim add it to their app.

    Unfortunately talk.google.com is a bit of a walled garden, though it looks like they’re going to open up gateways with a few select partners, including the I-think-they’re-cool Gizmo Project. What would be really cool though (and is not mentioned at all in the FAQs) is a Skype/Gizmo/Etc-style GoogleOut and GoogleIn service. Perhaps this could be as simple as a partnership with the Gizmo guys or as intricite as rolling their own solution.

    For now anyway, talk.google.com is one of many (now seven) different IM servers I connect to every day. I had a need for a home and away AIM account before that was baked in to that service, and now I answer to both, so they’re there. I don’t actually use my jabber.com account a whole lot but the others (MSN, Yahoo!, jabber.org.uk) get enough use that I should really be connected to if I’m online.

    Sure, I’d love to connect to one server and have everyone on the planet be able to reach me, but until that happens, I’ll be on all 7.

  • Google Talk

    (23:13:21) gmail.com: The broken link has been fixed. Thanks for being our first users!

    Let the talk begin! I used these instructions for Gaim to get online last night after Russ popped on and let me know that all the cool kids were using Google Talk.

    Per usual the official client is Windows only, but since they’ve done the smart thing and used XMPP (Jabber), you can point just about any client at it and away you go. The setup (at least in Gaim) is a bit non-standard, but it works.

    I really want to sniff some packets on a windows box to see how they’re integrating the VOIP stuff over XMPP. Hopefully they’ve done the right thing again and are using SIP.

    Update: At this time it looks like Google does not provider any server to server connections and (to my knowledge) no gateways to the outside world. Please stay within the walled garden, and don’t stand on the grass. This seems to contradict a lot of the “user choice” and “open communication” bits scattered throughout the rest of the page.

    Their developer FAQ is interesting though. They’re using custom XMPP signalling to a soon-to-be-documented peer-to-peer chat protocol that just happens to work with iChat. They support a bunch of protocols too and are thinking about speex too.

    It looks like their first connections with the outside world will be federations with Sipphones Gizmo project and EarthLink’s Vling services. There’s no GoogleOut or GoogleIn at this time.

    You can find me at first initial last name (all one word) at gmail dot com.

  • Mono on ARM

    Thanks to ljl for pointing this out in #maemo:

    People may be interested to know that the initial pass at the JIT port to ARM is in svn.
    make test in mono/tests reports 123 pass, 70 fail.
    Most of the regression tests in mini/ work as well.

    Nice! There are still some hurdles to be overcome, but this is great news.

  • Cingular’s 6682 Pricing

    Take a peek at Darla’s blog if you’re curious about Cingular’s pricing of the Nokia 6682 both on and off contract. While I hope that Lets Talk or Amazon can do just a little bit better, but this is definitely a step in the right direction if indeed the picture is legit (which I’ll bet it is).

  • My New Class 1 Bluetooth Dongle

    I’ve had a trusty low power Bluetooth dongle for a couple of years now, but I jumped on the opportunity to pick up a Hawking Tech HBTC1 Class 1 adapter for $10 after rebates at CompUSA. I wasn’t able to determine on the taco if this puppy worked with Linux or not, but I decided that It would be useful even if it didn’t for $10.

    Unfortunately the definitive listing of Bluetooth devices that work under Linux with BlueZ has been taken off the air:

    Whether or not you’re selling them makes no difference. The problem is due to the distribution of them from your Web site. Please note that the use and distribution of non-qualified products is a violation of the Bluetooth License Agreement. As neither of these products have been qualified using Linux it is illegal to make them available for public use.

    Total bummer. For the time being anyway you can find the list thanks to The Internet Archive. My Hawking tech device wasn’t listed, but I can assure you that at least hcitool scan works just fine.

  • Newcomers to the Bookshelf

    Newcomers to my bookshelf

    Several new books have landed on to my bookshelf recently and I thought I’d take a minute to highlight them:

  • Nokia N70 Gets FCC Approval

    I went trawling the FCC website looking for dirt on the latest devices and their FCC approval status this morning when I stumbled across this one: The FCC has approved the N70 from Nokia UK. Take that at face value folks, it just means that the N70 is appoved for sale in the US, not that any carrier has picked it up for mass dispersal. It is a good sign though that progress is being made on the N70, and increases the chances of us seeing the N70 en masse slightly above the magic 8 ball saying “DON’T COUNT ON IT”.

    Still, it’s quite an interesting development!

  • Nokia 6682 Real Soon Now?

    MobileTracker mentioned that there’s a print ad for the 6682 in this month’s Wired (on page 71 of my copy). They’re jumping to the conclusion that the ad along with images of the 6682 stealthily existing on Cingular’s web server are imperical evidence that the 6682 is shipping Real Soon Now.

    I’m pretty skeptical at this point. This ad looks identical to one that was seen in another magazine sometime in July and it’s late August now. While I remain hopeful that the 6682 will ship sooner rather than later, I wouldn’t be suprised if it didn’t see the light of day until September or even October.

    The best unsubstantiated rumours by far are on this Howard Forums thread: Nokia 6682 Coming in July? It’s on something like page 39 now.

  • Hula Visual Upgrade

    Nat shows off the sexy new interface to Hula. The screencast looks quite impressive. Hula has definitely come a long way in a short time, even though it was an awesome little project when it was released as open source.

  • The Scourge of the iPod

    This weekend I went in search of an FM modulator to finish hooking up a Delphi Roady2 to the parental minivan (yes my parents have XM. Can anyone say tipping point?). A few years back you used to be able to walk in to a Best Buy or Radio Shack and pick up a crappy little FM modulator for $15-20 or so. I mean, it’s not rocket science. Audio in and a small FM transmitter. After The iPid Revolution however, every freaking FM modulator on the shelf costs like $50 and the vast majority of them are iPod white.

    Don’t get me wrong, I’m glad that the iPod in all its variations and those other mp3 players are so dang popular. The inner gadget geek in me loves that. But the iPod economy is killing me with the acccessory inflation.

    I really am amazed that people get away with charging so much for a hunk of plastic, an audio cord, a case, or the million other overpriced widgets that you can attach to your mp3 player of choice. My only hope is that the iPod bubble can’t last forever.

    Update: Okay, I managed to find the Belkin Tunecast II, which was the one I liked in the store for about half that on Amazon. Not so bad after all. The stuff is still overpriced on the shelf though.

  • Why I Miss CrashRecovery

    Session Saver

    CrashRecovery never treated me like this. SessionSaver never was able to recover from this happy fun loop. Then again, it has worked correctly almost all of the other times, so I really shouldn’t be complaining.

  • Django Generic Views: CRUD

    Note: I’ve not yet updated this to reflect the new model syntax. For the time being you can take a look at the new model syntax for tasks here.

    There are lots of gems buried in Django that are slowly coming to light. Generic views and specifically the CRUD (create, update, delete) generic views are extremely powerful but underdocumented. This brief tutorial will show you how to make use of CRUD generic views in your Django application.

    One of my first encounters with Rails was the simple todo list tutorial which managed to relate lots of useful information by creating a simple yet useful application. While I will do my best to point out interesting and useful things along the way, it is probably best that you be familiar with the official Django tutorials. Now would also probably be a good time to mention that this tutorial works for me using MySQL and revision 524. Django is under constant development, so things may change. I’ll do my best to keep up with changes.

    Getting Started

    As with all Django projects, the best place to start is to start with django-admin.py startproject todo. Make sure that the directory you created your project in is in your PYTHONPATH, then edit todo/settings/main.py to point to the database of your choice. Now would be a good time to set your DJANGO_SETTINGS_MODULE to "todo.settings.main". Next move to your apps/ dir and create a new application: django-admin.py startapp tasks and django-admin.py init. The initial setup process is covered in much more detail in tutorial 1.

    The Model

    Now that we have the project set up, let’s take a look at our rather simple model (todo/apps/tasks/models/tasks.py):

    from django.core import meta
    
    # Create your models here.
    
    class Task(meta.Model):
      fields = (
        meta.CharField('title', maxlength=200),
        meta.TextField('description'),
        meta.DateTimeField('create_date', 'date created'),
        meta.DateTimeField('due_date', 'date due'),
        meta.BooleanField('done'),
      )
    
      admin = meta.Admin(
        list_display = ( 'title', 'description', 'create_date', 'due_date', 'done' ),
        search_fields = ['title', 'description'],
        date_hierarchy = 'due_date',
      )
    
      def __repr__(self):
        return self.title

    The model is short and sweet, storing a title, description, two dates, and if the task is done or not. To play with your model in the admin, add the following to INSTALLD_APPS in todo/settings/main.py: 'todo.apps.tasks',

    Feel free to play around with your model using the admin site. For details, see tutorial 2.

    URL Configuration

    Now let’s configure our URLs. We’ll fill in the code behind these URLs as we go. I edited todo/settings/urls/main.py directly, but you’re probably best off decoupling your URLs to your specific app as mentiond in tutorial 3.

    from django.conf.urls.defaults import *
    
    info_dict = {
      'app_label': 'tasks',
      'module_name': 'tasks',
    }
    
    urlpatterns = patterns('',
      (r'^tasks/?$', 'todo.apps.tasks.views.tasks.index'),
      (r'^tasks/create/?$', 'django.views.generic.create_update.create_object',
      dict(info_dict, post_save_redirect="/tasks/") ),
      (r'^tasks/update/(?P<object_id>\d+)/?$',
      'django.views.generic.create_update.update_object', info_dict),
      (r'^tasks/delete/(?P<object_id>\d+)/?$',
      'django.views.generic.create_update.delete_object',
      dict(info_dict, post_delete_redirect="/tasks/new/") ),
      (r'^tasks/complete/(?P<object_id>\d+)/?$',
      'todo.apps.tasks.views.tasks.complete'),
    )

    Note: I had to alter the formatting of the urlpatterns in order to make them fit. It looks a lot better in its original formatting.

    We use the info_dict to pass information about our application and module to the generic view handlers . The CRUD generic views need only provide these two pieces of information, but some generic views need more. See the generic views documentation for an explanation.

    Let’s look at each of these URLs one at a time, along with the code behind them.

    Index

    (r'^tasks/?$', 'todo.apps.tasks.views.tasks.index'),

    This points to our index view, which is an index function in todo/apps/tasks/views/tasks.py:

    from django.core import template_loader
    from django.core.extensions import DjangoContext as Context
    from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
    from django.models.tasks import tasks
    from django.core.exceptions import Http404
    
    def index(request):
      notdone_task_list = tasks.get_list(order_by=['-due_date'], done__exact=False)
      done_task_list = tasks.get_list(order_by=['-due_date'], done__exact=True)
      t = template_loader.get_template('tasks/index')
      c = Context(request, {
        'notdone_tasks_list': notdone_task_list,
        'done_tasks_list': notdone_task_list,
      })
      return HttpResponse(t.render(c))

    This view creates two lists for us to work with in our template, notdone_tasks_list is (not suprisingly) a list of tasks that are not done yet. Similarly, done_tasks_list contains a list of tasks that have been completed. We will use the template tasks/index.html to render this view.

    Make sure that you have a template directory defined in todo.settings.main (this refers to todo/settings/main.py). Here’s mine:

    TEMPLATE_DIRS = (
     "/home/mcroydon/django/todo/templates",
    )

    Now let’s take a look at the template that I’m using for the index:

    {% if notdone_tasks_list %}
        <p>Pending Tasks:</p>
        <ul>
        {% for task in notdone_tasks_list %}
            <li>{{ task.title }}: {{ task.description }} <br/> 
              Due {{ task.due_date }} <br/> 
              <a href="/tasks/update/{{ task.id }}/">Update</a> 
              <a href="/tasks/complete/{{ task.id }}/">Complete</a>
            </li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No tasks pending.</p>
    {% endif %}
        <p>Completed Tasks:</p>
        <ul>
    {% if done_tasks_list %}
        {% for task in done_tasks_list %}
            <li>{{ task.title }}: {{ task.description }} <br/> 
              <a href="/tasks/delete/{{ task.id }}/">Delete</a>
            </li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No completed pending.</p>
    {% endif %}
    <p><a href="/tasks/create/">Add a task</a></p>

    Don’t let this index scare you, it’s just a little bit of logic, a little looping, and some links to other parts of the application. See the template authoring guide if you have questions. Here’s a picture to give you a better idea as to how the above barebones template renders in Firefox:

    Tasks thumbnail

    Create Generic View

    Now let’s take a look at the following URL pattern:

    (r'^tasks/create/?$', 'django.views.generic.create_update.create_object', dict(info_dict, post_save_redirect="/tasks/") ),

    There’s a lot of magic going on here that’s going to make your life really easy. First off, we’re going to call the create_object generic view every time we visit /tasks/create/. If we arrive there with a GET request, the generic view displays a form. Specifically it’s looking for module_name_form.html. In our case it will be looking for tasks_form. It knows what model to look for because of the information we gave it in info_dict. If however we reach this URL via a POST, the create_object generic view will create a new object for us and then redirect us to the URL of our choice (as long as we give it a post_save_redirect).

    Here’s the template that I am using for tasks_form.html:

    {% block content %}
    
    {% if object %}
    <h1>Update task:</h1>
    {% else %}
    <h1>Create a Task</h1>
    {% endif %}
    
    {% if form.has_errors %}
    <h2>Please correct the following error{{ form.errors|pluralize }}:</h2>
    {% endif %}
    
    <form method="post" action=".">
    <p><label for="id_title">Title:</label> {{ form.title }}
    {% if form.title.errors %}*** {{ form.title.errors|join:", " }}{% endif %}</p>
    <p><label for="id_description">Description:</label> {{ form.description }}
    {% if form.description.errors %}*** {{ form.description.errors|join:", " }}{% endif %}</p>
    <p><label for="id_create_date_date">Create Date:</label> {{ form.create_date_date }}
    {% if form.create_date_date.errors %}*** {{ form.create_date_date.errors|join:", " }}{% endif %}</p>
    <p><label for="id_create_date_time">Create Time:</label> {{ form.create_date_time }}
    {% if form.create_date_time.errors %}*** {{ form.create_date_time.errors|join:", " }}{% endif %}</p>
    <p><label for="id_due_date_date">Due Date:</label> {{ form.due_date_date }}
    {% if form.due_date_date.errors %}*** {{ form.due_date_date.errors|join:", " }}{% endif %}</p>
    <p><label for="id_due_date_time">Due Time:</label> {{ form.due_date_time }}
    {% if form.due_date_time.errors %}*** {{ form.due_date_time.errors|join:", " }}{% endif %}</p>
    <p><label for="id_done">Done:</label> {{ form.done }}
    {% if form.done.errors %}*** {{ form.done.errors|join:", " }}{% endif %}</p>
    <input type="submit" />
    </form>
    <!--
    This is a lifesaver when debugging!
    <p> {{ form.error_dict }} </p>
    -->
    
    {% endblock %}

    Here’s what the create template looks like rendered:

    Create Tasks

    If we fill out the form without the proper (or correctly formatted) information, we’ll get an error:

    Create Tasks

    Update

    (r'^tasks/update/(?P<object_id>\d+)/?$', 'django.views.generic.create_update.update_object', info_dict),

    This URL pattern handles updates. The beautiful thing is it sends requests to tasks_form.html, so with a little logic, 90% of the form can be exactly the same as the create form. If we go to /tasks/create/, we get the blank form. If we visit /tasks/update/1/ we will go to the same form but it will be prepopulated with the data from the task with the ID of 1. Here’s the logic that I used to change the header:

    {% if object %}
    <h1>Update task:</h1>
    {% else %}
    <h1>Create a Task</h1>
    {% endif %}

    So if there’s no object present, we’re creating. If there’s an object present, we’re updating. Same form. Pretty cool.

    Warning: It looks like form.create_date_date and form.create_date_time have broken between the time I wrote this and wrote it up. This form will not prepopulate the form with the stored information. There’s a ticket for this, and I’ll update as neccesary when it has been fixed.

    Delete

    Here’s the URL pattern to delte a task:

    (r'^tasks/delete/(?P<object_id>\d+)/?$', 'django.views.generic.create_update.delete_object', dict(info_dict, post_delete_redirect="/tasks/new/") ),

    There’s another little Django gem in the delete function. If we end up at /tasks/delete/1 using a GET request, Django will automatically send us to the tasks_form_delete.html template. This allows us to make sure that the user really wanted to delete the task. here’s my very simple tasks_form_delete.html template:

    <form method="post" action=".">
    <p>Are you sure?</p>
    <input type="submit" />
    </form>

    Once this form is submitted, the actual delete takes place and we are redirected to the main index page (because we set post_delete_redirect.

    CRUD Generic Views And the Rest of Your Application

    That pretty much covers the basics of the CRUD generic views. The great thing about generic views is that you can use them along side your custom views. There’s no need to do a ton of custom programming for list/detail, date-based, or CRUD since those generic views are available to you. Because we set up our URL patterns, we can make sure that we craft URLs that look pretty and make sense.

    For my sample tasks application I decided that I wanted to create links that would immediately set a task as complete and then redirect to the index. This is pretty much trivial and can be accomplished by adding the following URL pattern and backing it up with the appropriate view. Here’s the pattern:

    (r'^tasks/complete/(?P<object_id>\d+)/?$', 'todo.apps.tasks.views.tasks.complete'),

    And here’s the view that goes along with it (from todo/apps/tasks/models/tasks.py):

    def complete(request, object_id):
      try:
        t = tasks.get_object(pk=object_id)
      except:
        # do something better than this
        raise Http404
      try:
        t.done=True
        t.save()
        return HttpResponseRedirect('/tasks/')
      except:
        # do something better than this
        raise Http404

    I do plan to actually handle errors and respond accordingly, but it was late last night and I just wanted to see it work (and it does).

    Conclusion

    Django rocks. Generic views rock. The framework and specifically the generic views make your life easy. My little tasks app took a few hours to put together, but a significant portion of that was reading up on the documentation, trying to figure out generic views using the existing docs and reading the source, and of course pestering the DjangoMasters about generic views and other stuff on #django (thanks all).

    I hope this overview of CRUD generic views helps, but if anything confuses you, don’t hesitate to comment or get in touch with me (matt at ooiio dot com). Also expect to see updates to this tutorial as APIs change and I get a little more time to clean up my code.

    Feel free to download and play with my little todo app: todo-tutorial.tar.gz or todo-tutorial.zip. Consider them released under a BSD-style license. Above all, don’t sue me.

  • KJS Java Script Engine for Symbian

    I don’t know how I missed this the other day, but in today’s Forum Nokia newsletter (among other things) there is a link to the KJS Java Script Engine for Symbian OS which is based on the JS component of Apple‘s open source WebCore. It’s definitely hardcore, pretty much a source drop, but it’s proof that we’re well on our way to having WebCore in our pockets.

  • Rob Curley Blew My Mind

    A few days back I listened to this IMA Keynote by Rob Curley on IT Conversations. It blew my mind. I started out looking for some insight in to the team behind Django, The Lawrence Journal-World, lawrence.com, and so on. I got a whole lot more than I had bargained for.

    If you’re involved in old media, new media, any media, or just want a glimpse of the future (I hope), please have a listen. It will be very interesting to see what happens in the next few months as Django takes flight, Rob heads to Naples (with technical backup from Eric) and Adrian telecommutes to The Washington Post