-
Here’s a PDF of the slides and notes from my talk at Clepy: Ingredients for Building a DSL in Python. Most of the content is in the notes, so zoom out if you don’t see them. A few slides have accompanying code files, which I’ll get online later tonight.
-
Sounds like a bigger problem for you than me…
-
Python talk at October Clepy meeting →
This Monday (October 6th) at Clepy, I’ll be giving a talk about the ingredients for creating DSLs in Python. These ingredients include deferred expressions, the generative/builder pattern, metaclasses, and descriptors. See the meeting announcement for details.
-
Distributing media with Django apps
Reusable Django apps have a sore spot right now: media.
Introduction
Django takes a hands-off approach to your media files. Your project has two settings,
MEDIA_URL(the base URL where your media is located) andMEDIA_ROOT(the filesystem path where media files are stored). The only thing it does with these is putFileFieldfiles underMEDIA_ROOT. The rest — collecting the required files, serving them up somehow, and ensuring that they’re loaded fromMEDIA_URLin templates — is completely up to the developer. This mostly works out great, since many sites will have a dedicated media server, and developers probably know better than Django which media files should be loaded where. It isn’t perfect, however, because there is one thing Django could help with: collecting the required files.The problem
Some Django apps — batchadmin, for example — distribute media (CSS, images, JavaScript) that is necessary (or recommended) for the app to work. Doing this is currently ad hoc and annoying for both the app developer and the app user.
Since there is only one
MEDIA_ROOT, the app’s files have to get in there somehow. How do they get there? Who knows, but the person installing the app has to do it manually. Okay, now the app should be able to access its media, right? Maybe. It depends, where did the user copy/link/move those files to, anyway? The app must now define its own settings so the user can tell it where they put the app’s media files inMEDIA_ROOT.The solution
The approach I propose is hopefully the simplest thing that could possibly work. I think that approach is to just find all media files in the project’s
INSTALLED_APPSand put them inMEDIA_ROOT. This idea has resulted in a collectmedia management command that can be run once by the user at installation or deployment time. This way, if you have apps laid out like:app1/media/ app1/media/app1/ app1/media/app1/css/style.css app1/media/app1/js/forms.js app2/media/ app2/media/app2/ app2/media/app2/css/fonts.css app2/media/app2/img/icon.png…then running
collectmediawill let the apps reference their media like so (in a template, for example):{{ MEDIA_URL }}app1/css/style.css {{ MEDIA_URL }}app2/css/fonts.cssJust like with reusable app templates, it should be best practice to make a subdirectory under
mediawith the same name as the app. This way, app2 can override app1’s style by including a file with the pathapp2/media/app1/css/style.css. And just like with templates, when multiple apps provide a file with the same relative path, the app listed inINSTALLED_APPSfirst is selected.This approach avoids having Django try to do any sort of dynamic dispatching of media files, because that would eliminate the advantage of using a media server completely independent of Django.
Speak up
If you have ideas or opinions on this matter, and especially if you want to see something like this included in Django, check out the relevant thread on the django-developers mailing list.
-
django-batchadmin: Batch actions in the admin change list
I’m releasing a new project tonight: django-batchadmin. It looks like this:

Plenty of people have done this before with recipes or patches. But this is a project, and it’s distributed as a Django app, not with copy & paste or a diff. Also, since it’s being released post-1.0, it uses the latest and greatest in
newforms-adminfeatures.The app is very minimal but customizable. There’s only one action included in the app: delete. Refer to the Getting Started page to add your own actions.
All functionality is added with a
ModelAdminsubclass calledBatchModelAdmin. This simply puts a checkbox in each row, specifies a change list template that wraps a form around the list, and makes the view accept POST requests, which it dispatches to the action code.Stay tuned for some custom actions.
-
Texts Rasterization Exposures →
As the guy who notices when a single pixel is off, I love this article about font rasterization. I’ve had it bookmarked for at least a year and sometimes read it just for fun.
-
The New Yorker: I have to pay a lot of taxes living in New York City. What about you, are you a taxes guy?
Chris Onstad: I pay almost all the major taxes, and I’m also part of a local program in my town where we pay experimental taxes to see if we get mad.
-
Dumb little obscure languages →
The level of complete misunderstanding here is absolutely hilarious.
-
Menu buttons in 20 lines of jQuery
This is my approach to menus using jQuery. I’ll go through it line by line to serve as a mini introduction to events in jQuery.
Here’s a preview of what we’re making. The desired behavior:
- Clicking on a button opens its menu
- Clicking anywhere except inside the menu closes it
I use two elements per menu: a
buttonand adiv. Clicking thebuttonmakes thedivappear. The button’snameattribute marks theidof thedivto open:<button name="the-menu" type="button" class="menu">...</button> <div id="the-menu" class="menu">...</div>Now the JavaScript to make it work. Each line is explained after the code:
jQuery(function($) { $('button.menu').one('click', openMenu); function openMenu(e) { var button = $(this).addClass('active'); var menu = $('#' + button.attr('name')); var offset = button.offset(); var h = (button.outerHeight) ? button.outerHeight() : button.height(); menu.addClass('active').css({ 'top': offset.top + h, 'left': offset.left }).click(function(e) { e.stopPropagation(); }).show(200, function() { $(document).one('click', {button: button, menu: menu}, closeMenu); }); } function closeMenu(e) { e.data.menu.removeClass('active').hide(100, function() { e.data.button.removeClass('active'); }); e.data.button.one('click', openMenu); } });Here we go:
jQuery(function($) {jQueryis an alias for$(document).ready. The callback is passed the jQuery object as an argument, so you can name it whatever you want. This is good practice for getting along with other JavaScript libraries. Name it $ here and it’ll work in the callback even if another library uses $.$('button.menu').one('click', openMenu);Attach the click event handler
openMenuto all buttons with class “menu”.oneis used to only respond to this event for one click. Why? Because the next click should close the menu, not open it. Why not usetoggle? Because the menu should close when clicking on the document too, not just on the button, and this could put the toggle status out of sync.function openMenu(e) { var button = $(this).addClass('active');Define the
openMenucallback, get the clicked button, and add a class for styling purposes.var menu = $('#' + button.attr('name'));Retrieve the
divwith the sameidas the button’sname.var offset = button.offset(); var h = (button.outerHeight) ? button.outerHeight() : button.height();Get the button’s position and size, because the menu will appear under it.
outerHeightis used if available, provided by the Dimensions plugin, to account for borders and such.menu.addClass('active').css({ 'top': offset.top + h, 'left': offset.leftAdd a class to the menu for styling purposes, then align it to the left and bottom of the button.
}).click(function(e) { e.stopPropagation(); }).show(200, function() { $(document).one('click', {button: button, menu: menu}, closeMenu); });Set a click handler on the menu that stops the click event from bubbling up to the document. Why? Because after the menu appears using the
showmethod, we attach a click event to the whole document that closes the menu. If the event were allowed to propagate, clicking within the menu would close it, something you don’t necessarily always want. Notice that the call toonepasses an object along with the event handler. This will be available ase.dataincloseMenu.} function closeMenu(e) { e.data.menu.removeClass('active').hide(100, function() { e.data.button.removeClass('active'); });Now define the
closeMenucallback. It removes the “active” class from the menu, hides it, and also removes the “active” class from the button.e.data.button.one('click', openMenu); } });Finally, attach the
openMenucallback to the button’s click event handler again. Remember,oneis only good for one occurrence of the event.That’s it! Here’s the final result again. See menu.css for the necessary bits of CSS and how I made everything pretty.
-
Rendering search results in Django
This weekend I wrote a Django tag & filter module for rendering search results. The primary feature is truncating a body of text to show the search terms in context, just like Google shows you an excerpt. A decent text indexer will normally provide this, but most of my projects aren’t big enough to justify setting one up, so I make do with normal database queries.
Examples and documentation are on the snippet page, but here’s how I actually use it in my latest project. I have some flatpages storing documentation and other help for the web site, and a search form just for these pages. The following code can practically be dropped into any Django project using flatpages.
The view looks like this:
from django.contrib.flatpages.models import FlatPage from django.db.models import Q def help_search(request): query_string = request.GET.get('q', "") search_terms = query_string.split() results = FlatPage.objects.filter(url__startswith="/help/") if search_terms: query = Q() for term in search_terms: query &= Q(content__icontains=term) | Q(title__icontains=term) results = results.filter(query) return render_to_response("flatpages/search.html", {'query': query_string, 'terms': search_terms, 'results': results} )And the relevant portion of
flatpages/search.htmllooks like this:{% load search %} <h1>Search Results</h1> <h2> {% with results|length as result_count %} {{ result_count|default:"No" }} page{{ result_count|pluralize }} found {% if terms %}for “{{ query }}”{% endif %} {% endwith %} </h2> <ul id="results"> {% for page in results %} <li> {% searchexcerpt terms 6 as content %} {# set `content` object #} {{ page.content|striptags }} {% endsearchexcerpt %} {% highlight terms as title %} {# set `title` object #} {{ page.title }} {% endhighlight %} <h3> <a href="{{ page.get_absolute_url }}">{{ title.highlighted }}</a> {% if terms %}{% with content.hits|add:title.hits as hits %} <span class="count">{{ hits }} hit{{ hits|pluralize }}</span> {% endwith %}{% endif %} </h3> <p class="context">{{ content.excerpt|highlight:terms }}</p> </li> {% endfor %} </ul>Finally, here are some screenshots of the results:


