Django and Taskpaper
I’ve been trying out Taskpaper for the last week, and it’s pretty cool. It’s very simple and easy to work with. It adds a bit of structure to the way I’ve been managing my tasks with Smultron: just a couple of headings and numbered tasks. The nice thing about Taskpaper is that it takes that style of project management and runs with it. So I’m still just typing out headlines and marking tasks beneath them, but now I get to hit a little radio button to draw a line through the task when I’ve completed it. I can tag the tasks and, when I feel like doing a little programming, pull down all of the programming tasks throughout my project list.
It sounds silly, but I think I’ve gotten more done this last week than the month before that.
So I think I’ll be buying Taskpaper. I have three main concerns, but judging from what the author (Jesse Grosjean) has said on the Taskpaper forums, they’re going to be addressed. I’d like the system to keep track of when a task is completed, I’d like it to be scriptable, and notes need to be attached to their task. Jesse already has a means of keeping track of completion dates planned out. Tags can be followed by parenthetical information. Archived tasks, for example, are “@project(project-name)”. It looks like done items will be “@done(date-completed)”. Tags can have parenthetical information… remember that, there’s going to be a test later.
One of the things I finally got done this last week was moving another of my FileMaker 6 databases to SQLite via Django. This database is a database of recurring tasks, probably my most-used database. One of the things that keeps me from getting those recurring tasks done is that I have to remember to pull up the database. Moving it to SQLite makes it easier to write a reminder script, but I still had no idea what the reminder script was going to actually do. E-mail was the obvious choice, but I hate it when reminder e-mails build up in my inbox.
So I thought, why not combine it with Taskpaper? Taskpaper can remember multiple windows and open them automatically. And it has a simple text format that would be easy to use with Django’s template language. So, under database conversion, I added:
- autogenerate recurring task file by day for TaskPaper
- check for completed tasks before rewriting task file
Autogenerate recurring task file by day for TaskPaper
This part was simple. If you’ve used Django before, you probably already know how simple. I already had the model, so I made the template. A Taskpaper file is just a collection of projects and tasks. A project is any line that ends with a colon; a task is any line that begins with a dash and a space. Tags are handled by putting a space and an @ symbol in front of a word.
The template
- {% for task in tasks %}{% ifchanged %}{{ task.dueDate|date:"l, F jS" }}:
- {% endifchanged %} - {{ task.title }}{% if task.category %} @{{ task.category.key|slugify }}{% endif %}
- {% endfor %}
Because a Taskpaper file is basically a straight text file, white space does matter for display purposes, which makes the template a little uglier than its HTML counterpart would be. But the first line creates the headline, one for each day (“ifchanged” means, only output this part of the template if it has changed since the last iteration).
The second line creates the task, and adds a category tag to the end. (The third line just ends the “for” loop.)
I saved this in my Django templates folder, as “personal/taskpaper.html”.
The script
The script is also fairly simple. If you’ve used Python you should be able to follow it pretty easily.
- #!/usr/bin/env python
- import os, sys, datetime
- #set up the environment
- DOCS = os.path.join(os.environ['HOME'], 'Documents')
- sys.path.append(os.path.join(DOCS, 'CMS'))
- os.environ['DJANGO_SETTINGS_MODULE'] = 'CMS.settings'
- #import necessary Django libraries
- from django.template import Template, Context
- from django.template.loader import get_template
- #import the recurrers model; each Item is a recurring task
- from CMS.recurrers.models import Item
- #rewrite the task file from currently due tasks
- taskfile = os.path.join(DOCS, 'Tasks', 'Recurrers.taskpaper')
- #all items due on or before today
- tasks = Item.objects.filter(dueDate__lte=datetime.date.today()).order_by('dueDate', 'rank')
- #render the template
- rcontext = Context()
- rcontext['tasks'] = tasks
- rtemplate = get_template('personal/taskpaper.html')
- #and write it out to the task file
- newtasks = open(taskfile, 'w')
- newtasks.write(rtemplate.render(rcontext))
- newtasks.close()
That’s really it. Half of the script is setting up the connection to Django. Then there’s one line to get the tasks, three lines to prepare the template, and four lines to write it out to the task file.
check for completed tasks before rewriting task file
This makes it easier for me to remember to check my recurring tasks. But I’d still have to go into the database to mark them as having been completed so that they stop appearing in my task file and get marked for their next recurrence. It would be nice, since Taskpaper has a means of marking an item as done, if the script could pay attention to that for me.
In order to do that, I need to match a task back to its record. Each of the records has a unique ID. Tags can hold information. So if I can add an ID tag to each task I can put the actual ID in it. Something like “@ID(97)”.
Add “ @ID()” right after “”. Don’t leave the space out in front of the @ symbol. The template should now be these three lines:
- {% for task in tasks %}{% ifchanged %}{{ task.dueDate|date:"l, F jS" }}:
- {% endifchanged %}- {{ task.title }} @ID({{ task.id }}){% if task.category %} @{{ task.category.key|slugify }}{% endif %}
- {% endfor %}
In order to grab the ID out of the task, I’m going to need to use a regular expression, so I add “re” to the “import” line:
- import os, sys, datetime, re
And, before writing out the tasks, I check to see if a task file already exists. After the “taskfile =” line, and before the “tasks =” line, I add:
[toggle code]
- #if the task file is already there, check it for done items
-
if os.path.exists(taskfile):
- #eventually use @done(date) rather than when the task file was last saved
- taskdate = datetime.date.fromtimestamp(os.stat(taskfile).st_mtime)
- oldtasks = open(taskfile)
-
for task in oldtasks:
- #tasks begin with a dash and a space
- #done tasks contain @done text
-
if task.startswith(' - ') and task.find(' @done') > 0:
- #get the task ID
- match = re.search('@ID\(([1-9][0-9]*)\)', task)
-
if match:
- taskID = match.group(1)
- doneTask = Item.objects.get(pk=taskID)
- #don't mark it as done if it has already been rescheduled
-
if doneTask.dueDate <= taskdate:
- doneTask.reschedule(completionDate=taskdate)
- oldtasks.close()
The only odd thing here is that I’m using the task file’s modification date as the date that the task was completed. Hopefully, the @done tag will contain the completion date automatically in a later version of Taskpaper.
So this section of the script:
- goes through each line
- recognizes a task as a line starting with a dash and a space
- recognizes a completed task as a line containing “ @done”
- grabs the task’s SQLite ID number from the @ID tag in the line, via a regular expression
- picks the item by ID from the SQLite database via a Django call
- checks that the item hasn’t been updated already from somewhere else
- and then calls the item’s reschedule method to reschedule it for its next recurrence
Which is pretty cool. The final step is to add this script to my crontab file and have it run a few times every day so that when I open Taskpaper, any recurring tasks are waiting for me.
- Django
- “Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.” Oh, the sweet smell of pragmatism.
- Fraise: Jean-François Moy and Peter Borg
- Fraise is the successor to the great text editor Smultron. It’s an easy-to-use, powerful, free, text editor with tabs, split windows, syntax coloring, and more.
- Taskpaper
- At first glance it doesn’t seem like you’d want to pay $20 for a task manager this simple—but a lot of people are. Sometimes, simplicity is worth paying for.
More Django
- Converting an existing Django model to Django-MPTT
- Using a SQL database to mimic a filesystem will, eventually, create bottlenecks when it comes to traversing the filesystem. One solution is modified preordered tree traversal, which saves the tree structure in an easily-used manner inside the model.
- Two search bookmarklets for Django
- Bookmarklets—JavaScript code in a bookmark—can make working with big Django databases much easier.
- Fixing Django’s feed generator without hacking Django
- It looks like it’s going to be a while before the RSS feed generator in Django is going to get fixed, so I looked into subclassing as a way of getting a working guid in my Django RSS feeds.
- ModelForms and FormViews
- This is just a notice because when I did a search, nothing came up. Don’t use ModelForm with FormView, use UpdateView instead.
- Django: fix_ampersands and abbreviations
- The fix_ampersands filter will miss some cases where ampersands need to be replaced.
- 29 more pages with the topic Django, and other related pages
More TaskPaper
- GeekTool, TaskPaper, and XML
- A script to convert a TaskPaper file to XML so as to filter it for specific tags and display the results on the Desktop.
- SilverService and Taskpaper
- SilverService is a great little app if you commonly need to repetitiously modify text. Any application that supports services will support running selected text through command-line scripts via SilverService.
- Web display of Taskpaper file
- It is easy to use PHP to convert a Taskpaper task file into simple HTML conducive to styling via CSS.
March 10 2010: Updated the Django template file to reflect the new Taskpaper format. It meant adding a tab in front of the dash for each day’s tasks.