I just found this in my django-ego-feed: 23 excuses: Simple Django View for Dynamic Text Replacement
I’ve been using something similar to generate the titles for the site (look at the title above for an example), so I’m pretty familiar with the technique.
Andrew’s code over there is pretty good, but I’ve got a few improvements he and you might be interested in:
The business to writing to a temp file is ugly and will break as soon as you get two simultaneous requests. The right way to do this is by remembering that an HTTPResponse exposes a file-like interface, so you can write the image directly to it:
response = HTTPResponse(mimetype="image/png") im.save(response, "PNG")You’ll save on bandwidth and processor time if you add in E-tag support. This’ll let browsers cache your image locally and save you from generating and sending it each time.
This is actually pretty easy to do. At the start of the view, calculate the E-tag and check it against the If-None-Match header:
etag = md5.new(header + fontalias).hexdigest() if request.META.get("HTTP_IF_NONE_MATCH") == etag: return HttpResponseNotModified()Then, just before you return the response, insert the E-tag header:
response["e-tag"] = etagAs long as we’re being pedantic about HTTP, let’s return a 403 Forbidden for a bad font file (instead of a 200 OK)
Here’s the complete view_header() code with my modifications:
import md5
from django.conf import settings
from django.http import (HttpResponse,
HttpResponseNotModified,
HTTPResponseForbidden)
import Image, ImageFont, ImageDraw
def view_header(request, fontalias):
try:
fontfile = settings.DYNAMIC_FONT_ALIASES[fontalias]
except:
return HttpResponseForbidden("font alias not supported")
if request.GET.has_key('text'):
header = request.GET['text']
else:
header = 'Hello world'
etag = md5.new(header + fontalias).hexdigest()
if request.META.get("HTTP_IF_NONE_MATCH") == etag:
return HttpResponseNotModified()
imf = ImageFont.truetype(fontfile, 20)
size = imf.getsize(header)
im = Image.new("RGB", size)
draw = ImageDraw.Draw(im)
draw.text((0, 0), header, font=imf)
response = HTTPResponse(mimetype="image/png")
im.save(response, "PNG")
response["e-tag"] = etag
return response
Enjoy!
Andrew
June 30th, 2006
2:35 p.m.
Excellent, I knew someone would come rescue the crap that I posted. :)
antrix
June 30th, 2006
10:48 p.m.
Hah! Just last week, I put up a stand alone web script that does just this: fetches text as image files based on request parameters.
Check out: http://antrix.net/text/
Paul
July 1st, 2006
6:31 a.m.
Why not use sifr? it has so many advantages over image replacement :
clientside, text is selectable and .....
http://www.mikeindustries.com/sifr/
Jacob Kaplan-Moss
July 1st, 2006
7:25 a.m.
Meh, too heavyweight for me. Plus there's the whole trusting Macrodobe to do the right thing wrt Flash, and I'll bet you can guess how I feel about that.
Jeff Croft
July 1st, 2006
1:25 p.m.
I've used sIFR quite a bit myself, with both Inman and Mike being friends of mine. They'll be the first to admit, though, that it's not a long-term solution. It was more designed as a way to tell the font foundries and W3C "Us web designers are here, and we're going to find a way to use your fonts on the web whether you sanction it or not." Hopefully they'll come up with a good long-term solution. In the interim, both sIFR and server-generated image replacement are good solutions.
sIFR has the positive of being scaleable -- but how much does that really matter when you're dealing with larger headers anyway (which is sIFR's intended purpose). Selectable is not an advantage -- image replacement is selectable too, when done with CSS instead of HTML img elements. Being clientside could be an advantage -- although you'll find as many people that disagree with that as agree with it. To me, the one real advantage of sIFIR is that it's in Flash, which is a comfortable place for designer to work -- most of us designers would rather not have to write view code in order to get pretty type.
Image replacement has the advantage of being much better-looking (Flash's antialiasing, even in the latest version, just isn't to Photoshop-like levels) and not requiring Flash (although I question that advantage a bit, as Flash is so ubiquitous these days). And, like Jacob pointed out, not relying on Flash means not relying on a giant corporation to do the right thing.
I think both are good interim solutions over all, and it makes perfect sense that programmers would flock to image replacement and designers would prefer Flash.
Either way, this Django view is a clever solution. Way to go, guys. :)
jim
July 2nd, 2006
10:37 a.m.
Being a 64bit linux user I have to encourage you not to use Flash :P
Btw, using your django solution, won't it return the same etag if you change font/etag (i.e. won't people have to use ctrl+f5 to see any changes)? I was wondering if you should use a version number and add it to the etag or something so that won't happen.
jim
July 2nd, 2006
10:39 a.m.
that was supposed to say font/style
Jacob
July 2nd, 2006
8:36 p.m.
Jim -- good eyes! I've updated the view to hash the font name along with the string.
Thanks!
Andrew
July 4th, 2006
11:17 p.m.
I hate SIFR, I've had way too many problems with it. A few clients looked for _exact_ dimensions and line height, spacing and it was impossible to do reliably cross browser. It's unfortunate, because it's a decent idea, but replacing with pngs is much more friendly to all users. Unfortunately PIL doesn't have very good font support, so it's not easy (if even possible) to customize line height, and letter spacing, and therefore sifr is still being used on that site.
Jeremy Walker
July 15th, 2006
9:29 p.m.
In the line:
from django.http import (HttpResponse,
HttpResponseNotModified,
HTTPResponseForbidden)
Shouldn't "HTTPResponseForbidden" be "HttpResponseForbidden"?
Jeremy Walker
July 16th, 2006
4:53 p.m.
Similarly, shouldn't this line:
response = HTTPResponse(mimetype="image/png")
use "HttpResponse" instead?
Shev
March 11th, 2007
11:23 p.m.
I got this method,
def silent_update():
# Do secret stuff
return HttpResponseNotModified()
---------------------------------------------------
But i got this error:
Traceback (most recent call last):
File "C:\Python24\lib\site-packages\django\core\servers\basehttp.py", line 272, in run
self.result = application(self.environ, self.start_response)
File "C:\Python24\lib\site-packages\django\core\servers\basehttp.py", line 614, in __call__
return self.application(environ, start_response)
File "C:\Python24\lib\site-packages\django\core\handlers\wsgi.py", line 193, in __call__
response = middleware_method(request, response)
File "C:\Python24\lib\site-packages\django\contrib\sessions\middleware.py", line 74, in process_response
patch_vary_headers(response, ('Cookie',))
File "C:\Python24\lib\site-packages\django\utils\cache.py", line 104, in patch_vary_headers
if response.has_header('Vary'):
TypeError: unbound method has_header() must be called with HttpResponseNotModified instance as first argument (got str instance instead)
I do not want the view to refresh my html page.
Jacob
March 12th, 2007
12:14 a.m.
Yeah, I don't really want my blog to be a clearing-house for Django questions. Please ask this on django-users or some other appropriate forum.
Tom W. Most
July 22nd, 2007
5:03 p.m.
Um, the ETag header is spelled without the hyphen--see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 It won't do you much good like that, will it?