Busy making things: tinycast, github, links, photos, @mc.

Darwin Calendar Server

Posted: August 9th, 2006 | Author: | Filed under: Apple, Open Source, Python, Web Services | 73 Comments »

As soon as Gruber pointed out Darwin Calendar Server I felt like I had to check it out. I’ve played with Darwin Streaming Server in the past and love me some Webkit. I was pleasantly suprised to find that Darwin Calendar Server runs on top of Python and Twisted.

So away I went. I checked out the source and began to poke around. I managed to check out the source before the README was added so I did a fair amount of head scratching and wheel spinning, but it turns out that getting up and running is pretty easy: ./run -s

That sets up the server, downloading and building some prereqs as it goes. I already had some prereqs installed system wide so I can’t guarantee that this works, but I’m pretty sure that it has worked for others. I should take a second to qualify that I’m running OS X 10.4 with Python 2.4 installed. From there I copied over the sample config file (cp ./conf/repository-static.xml ./conf/repository-dev.xml) and immediately started troubleshooting SSL errors. First I installed PyOpenSSL and created a self-signed certificate. That yielded a brand new error: OpenSSL.SSL.Error: [('PEM routines', 'PEM_read_bio', 'no start line'), ('SSL routines', 'SSL_CTX_use_PrivateKey_file', 'PEM lib')]

After doing that and getting some guidance from the folks in #collaboration on freenode I decided to hack away at the plist and disable SSL for now (change SSLEnable to false instead of true). From there I could run the server (./run) and bring up a directory listing my pointing to 127.0.0.1:8008.

Darwin Calendar Server Chandler Setup

From there I subscribed to the example payday calendar and the holiday calendar. It appears that iCal won’t do two-way CalDAV until Leopard, but in the meantime I was able to successfully set up and test Chandler.

This is some absolutely amazing tech in its infancy. I can’t wait to see where this goes and I’m excited that it’s built with tools that I’m familiar with (Python, Twisted, SQLite, iCal). It seems to me like this open source app is but the tip of the iceberg of collaboration features that will be baked in to OS X 10.5 desktop and server.  I would also kill for a mobile device that spoke CalDAV natively so that I can replace my duct taped google calendar to iCal to iSync to 6682 workflow.


Acknowledging the Mobile Web with Django

Posted: July 26th, 2006 | Author: | Filed under: Django, Mobile, Python | 11 Comments »

KTKA breaking news homepageI was reading up on HowToProvideAlternateViewsForMobileDevices on the Rails wiki this morning and couldn’t help but notice how much easier it is to set up a mobile version of a Django site. At World Online we have stripped-down barebones no frills “all we want are the facts ma’am” versions of all of our sites. They prove extremely useful during KU basketball games or when you’re in downtown lawrence and want to know what restaurants are open. Since our mobile sites are just alternate templates on the same views, setup goes something like this:

In main_site.settings:

TEMPLATE_DIRS = (
'/path/to/templates/mainsite.com/',
'/path/to/templates/default/',
)

In mobile_site.settings:

from main_site.settings import *
TEMPLATE_DIRS = (
'/path/to/templates/mobile.mainsite.com/',
'/path/to/templates/default/'
) 

The first line imports all of the settings from your main site. We then overwrite the TEMPLATE_DIRS setting to point to the mobile version of our templates (and fall back to default templates if there isn’t a mobile specific version). Because every app that we write also gets a default template we can have a complete mobile site up and running by creating just one or two mobile base templates.

While Django can’t help you debate internally the “one web” versus “two webs” philosophies, it can definitely help you produce lightweight mobile-friendly content with minimum effort.


PyS60 1.3.8 Released

Posted: July 7th, 2006 | Author: | Filed under: Mobile, Open Source, Python | 7 Comments »

Python for S60 version 1.3.8, released specifically for S60 3rd Edition is now available for download. See the release notes for more information. Special thanks to Jukka and everyone else for pushing this release out the door just before Finland shuts down for the summer.


Backing Up Flickr Photos with Amazon S3

Posted: March 22nd, 2006 | Author: | Filed under: Open Source, Projects, Python, Web Services | 711 Comments »

I love that I now have an Amazon S3 billing page that reads like a really cheap phone or water bill. I think that they’re silently changing the game (again) without telling anyone else. I really like the implications of this magepiebrain post and decided to start using S3 “for real” myself last night.

The first ingredient was James Clarke’s flickr.py. Getting a list of my photos is pretty simple:

import flickr
me = flickr.people_findByUsername("postneo")
photos = flickr.people_getPublicPhotos(me.id)

The second ingredient for getting the job done was a pythonic wrapper around the Amazon example python libraries by Mitch Garnaat called BitBucket. Because it builds on the example libraries, there’s very little error checking, so be careful. Check out Mitch’s site for some example BitBucket usage, it’s pretty slick.

Once I was familiar with both libraries, I put together a little script that finds all of my photos and uploads the original quality image to S3, using the flickr photo ID as the key. Here’s the complete code for flickrbackup.py, all 25 lines of it.

After uploading 160 or so photos to Amazon, I owe them about a penny.

Getting photos back out is really easy too:

>>> import BitBucket
>>> bucket = BitBucket.BitBucket("postneo-flickr")
>>> bucket.keys()
>>> bits = bucket[u'116201243']
>>> bits.to_file("photo.jpg")


Clickable Bylines

Posted: February 18th, 2006 | Author: | Filed under: Django, Journalism, Python | 49 Comments »

Clickable bylines are the new black in online journalism according to this post and related comments at Poynter. I have to admit that I thought that this was the norm rather than the exception, since this had been the case at the Journal-World long before I arrived in Lawrence.

A few days ago Dan asked me how long it would take to whip up per-writer RSS feeds. Thanks to django’s syndication framework the answer was no time at all. Over the next couple of days and with the direction of Dan and David, we tweaked the feeds to include both per-writer and per-photographer feeds. David made it easy to set up search alerts for every time a staff member posted a story. We also updated the staff bio pages to make all of this information easier to get to.

Here is an example from a recent story by Joel Mathis:

in-story byline

If you click on Joel’s name, you’ll be taken to his bio page. If you click on Contact, you’ll be taken directly to his contact form. There’s nothing new there (for us anyway). The new stuff happens on the bio page:

Joel's bio page

The very top of every bio page contains more metainformation than you can shake a stick at. First and foremost is Joel’s number and contact form. After that we have an RSS feed of his latest stories. Following that is the search alert form that allows you to be notified every time Joel posts a story. Since Joel is such a converged guy and takes pictures too, you can check out the latest photos he has taken or subscribe to an RSS feed of those photos. You can also subscribe to that feed as a photocast in the latest iPhoto.

Joel’s bio page also also contains a short biography that makes me want to head up the street to Rudy’s every time I read it. Below the bio and mugshot is a list of recent stories by Joel and a form that lets you quickly search his stories.

I think these tools go well beyond what other news organizations are just beginning to do. At the same time there is always room for improvement, so don’t be suprised if more information is added to these pages.


mobile.kusports.com

Posted: January 28th, 2006 | Author: | Filed under: Django, Mobile, Python | 48 Comments »

Over the past few days I’ve been spending some free time and downtime tweaking mobile.kusports.com and adding a couple of really cool (IMHO) features. We send out a ton of cel phone updates during each game, but I really wanted to bring our awesome live stats to mobile devices.

The first order of business was to see how the low-tech live stats view looked on the mobile site. Since pretty much all of our templates extend a base template, it looked pretty darn good out of the box (thanks to template inheritance and some great default templates by Wilson and David). Most of the time spent on this template was to condense the stats a little bit to require less scrolling on small devices. Here is the live stats view using Opera Mini on my 6682:

mobile.kusports.com live stats

Having the live stats accessable from mobile devices is great, but it’s important to make it extremely easy for someone to get to them if they come to mobile.kusports.com while a game is in progress. The solution was to present the current score on the home page if and only if a game is in progress. This was accomplished by writing a custom template tag (which took literally a few minutes) and a tweak to the home page template:

KUSports mobile home page

Now the score and the stats are first and foremost while there’s a game on, but nowhere to be found if there isn’t. The last thing I wanted to do was to make sure that the current score (and a link to the live stats) was available from other interior pages in an unobtrusive way. I figure that if someone is reading a story or viewing photos while a game is on, they probably wouldn’t mind knowing the current score either. I decided to present the information without announcing itself quite as loudly as the score on the home page:

KUSports mobile interior page

I hope to do some more tweaking of our mobile sites here and there as free time permits, and I’ll do my best to highlight the changes here if I think they are particularly clever.


We’re Moving to Kansas!

Posted: October 31st, 2005 | Author: | Filed under: Django, Open Source, Projects, Python, Weblogs | 37 Comments »

No really, we’re moving to Kansas. I’ve accepted a position at World Online, the online division of the Lawrence Journal-World. I’ll be working on some award winning sites including LJWorld.com, lawrence.com, KUsports.com using my favorite web framework: Django.

I’m really excited about working with an awesome team of people doing some really cool stuff. And of course I’m completely stoked about working with Django on a daily basis. I’ll talk about what I’m up to when I can but there will be times when I have to keep my lips zipped. I guess now might be a good time to mention that this is my personal weblog and that views/opinions/etc expressed here are mine and do not necessarily reflect those of my employer.

Needless to say I’ve been a bit busy with getting up to speed at work and planning the move. I’ve been meaning to write this post for some time now and had to delete a completely out of date post that I had half written while in Lawrence. Blogging will probably be light until things settle out, but in the meantime keep an eye on my del.icio.us links.

Strap in, Toto!


IT Conversations Seeks Python Developers

Posted: October 10th, 2005 | Author: | Filed under: Open Source, Python, Weblogs | 10 Comments »

Doug Kaye is looking for help in putting together an open source podcast uploader program:

This is an open-source project to create a client-side application for encoding, normalizing and uploading audio files to the Conversations Network content-management system’s FTP server.

See the requirements doc for more information. Can anyone lend a hand?


PyCon 2006 Call for Proposals

Posted: September 11th, 2005 | Author: | Filed under: Python, Web Services | 2 Comments »

The PyCon 2006 Call for Proposals is out. In addition to the regular sessions and less formal lightning talks, PyCon 2006 will feature longer tutorial sessions on February 23. I’m not sure if I’m going to be able to make it this year, but PyCon is a total blast. Last year it was great to map faces to names and to learn about so many new developments and projects. It’s time to get writing so that there is an excellent and diverse selection of talks again this year!


The Emerging Django Job Market

Posted: August 31st, 2005 | Author: | Filed under: Django, Python | 11 Comments »

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.


Django: Big Integer Fields

Posted: August 26th, 2005 | Author: | Filed under: Django, MySQL, Projects, Python | 6 Comments »

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

Posted: August 25th, 2005 | Author: | Filed under: Django, Python | 3 Comments »

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


Django Generic Views: CRUD

Posted: August 17th, 2005 | Author: | Filed under: Django, Projects, Python | 100 Comments »

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.


Rob Curley Blew My Mind

Posted: August 14th, 2005 | Author: | Filed under: Django, Python, Weblogs | 75 Comments »

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


Django Markup Template Tags

Posted: August 10th, 2005 | Author: | Filed under: Django, Python | 13 Comments »

Changeset 467 contains template tags for renderingTextile (using PyTextile), Markdown (using python-markdown), and ReStructured Text (using docutils). You can find usage examples and some basic tests in markup.py.

You need to make sure that you have the correct modules installed to do the heavy lifting, but the tags are extremely easy to use:

  • {{ textile_content|textile }}
  • {{ markdown_content|markdown }}
  • {{ rest_content|restructuredtext }}

PyCon 2006

Posted: August 10th, 2005 | Author: | Filed under: Python | 1 Comment »

AMK confirms that PyCon 2006 will be held in Dallas on Feb 23-26. I’m sad to see it leave DC, but it sounds like they’re going to get an amazing bang for the buck thanks to the hard work by the D/FW PUG. With the community agog over Django and my continuing interest in Python for Series 60, I’m going to do my best to make it there. It’ll be harder though since I can’t just hop on the metro and head downtown.


Django: Tutorial 4 and RSS

Posted: August 8th, 2005 | Author: | Filed under: Django, Python | 11 Comments »

Today Adrian posted Django Tutorial 4 which covers form processing and generic views. Everyone dive in!

In other news, if you’re looking to generate RSS using Django “the easy way,” look no further than this #django log and associated pastebin.

As always, svn up if you haven’t in the last few hours.

Update: The pastebin dissapeared, so here’s a transcribed version of what was in there. Typos are mine and not Adrian’s of course.


HTTP Headers and Django

Posted: August 8th, 2005 | Author: | Filed under: Django, Python | 8 Comments »

Andrew Brehaut has a hot tip about HTTP headers and Django:

HttpResponse objects contain a headers dictionary, that you can easily add and remove headers from using dictionary notation

You can read more about it and see usage examples in his post.


Migrating Your App to Django

Posted: August 5th, 2005 | Author: | Filed under: Django, Python | 99 Comments »

A few days ago I mentioned Changeset 384 which included a new command, django-admin.py inspectdb <dbname>. It has also been tweaked and improved since it was initially committed. The other day I tried it out on a simple database structure, but I decided to throw a more complex example at it.

I decided to take the final depot application from the excellent Agile Web Development with Rails book. Beta books rule by the way. I executed the SQL in rails-code/depot_final/db/create.sql from the the code tarball to set up the database structure. I then created a new project with django-admin startproject and edited settings/main.py to tell Django how to log in to my mysql database. After exporting the correct DJANGO_SETTINGS_MODULE I ran django-admin.py inspectdb depot_rails which gave me the following model:

# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#     * Rearrange models' order
#     * Add primary_key=True to one field in each model.
# Feel free to rename the models, but don't rename 
# db_table values or field names.
#
# Also note: You'll have to insert the output of 
# 'django-admin.py sqlinitialdata [appname]'
# into your database.

from django.core import meta

class LineItem(meta.Model):
    db_table = 'line_items'
    fields = (
        meta.IntegerField('id'),
        meta.IntegerField('product_id'),
        meta.IntegerField('order_id'),
        meta.IntegerField('quantity'),
        meta.FloatField('unit_price'),
    )

class Order(meta.Model):
    db_table = 'orders'
    fields = (
        meta.IntegerField('id'),
        meta.CharField('name', maxlength=100),
        meta.CharField('email', maxlength=255),
        meta.TextField('address'),
        meta.CharField('pay_type', maxlength=10),
        meta.DateTimeField('shipped_at'),
    )

class Product(meta.Model):
    db_table = 'products'
    fields = (
        meta.IntegerField('id'),
        meta.CharField('title', maxlength=100),
        meta.TextField('description'),
        meta.CharField('image_url', maxlength=200),
        meta.FloatField('price'),
        meta.DateTimeField('date_available'),
    )

class User(meta.Model):
    db_table = 'users'
    fields = (
        meta.IntegerField('id'),
        meta.CharField('name', maxlength=100),
        meta.CharField('hashed_password', maxlength=40),
    )

Per the comment block at the top, you’re not home free yet, but at lot of tedious work has been done for you. This should definitely jumpstart the porting of existing applications to the Django platform.


Geolocation Information Gaps

Posted: August 5th, 2005 | Author: | Filed under: Projects, Python | 96 Comments »

I’ve been seeking out interesting data sources to plot in Google Earth after learning the basics of KML. I’ve been wanting to do something cool with NOAA’s XML weather feeds since I heard about them, so I thought I would download the 700kb list of stations serving up XML and spit out some KML from that data as a “neat” first step.

I’ll probably still do that, but after parsing the data, I’m a bit dissapointed. As always there are huge gaps in geolocation information. In order to get my hands on the data I turned to xmltramp which is an awesome library for accessing simple XML documents in a pythonic way. I then whipped up a few lines of Python to walk through the data:

import xmltramp # http://www.aaronsw.com/2002/xmltramp/

f=open('stations.xml', 'r')
doc=xmltramp.parse(f.read())
count = 0
total = 0
for station in doc['station':]:
  total = total + 1
  sid = str(station['station_id'])
  lat = str(station['latitude'])
  lon = str(station['longitude'])
  if (lat != 'NA') and (lon != 'NA'):
    print "Station ID: " + sid + \
    " (" + lat + "," + lon + ")"
    count = count + 1
print str(count) + " out of " + str(total) + \
" stations are geolocated."

Here’s the output of the above code:

mcroydon@mobilematt:~/py/kmlist$ python kmlist.py
Station ID: PAGM (63.46N,171.44W)
[... snip ...]
Station ID: KSHR (44.46.10N,106.58.08W)
422 out of 1775 stations are geolocated.

Well that’s a bummer. 422 out of 1775, or less than 25% of all stations are geolocated. While that’s still 422 more stations than I knew about previously, it’s a far cry from a majority of weather stations across the United States.

Another thing you will notice is that some stations appear to be expressed in degrees in decimal form (63.46N) while others appear to use Degrees/Minutes/Seconds (44.46.10N).

It’s gaps like these that can make working with “found” geolocation data frustrating.