YUI Rich Text Editor in Django Admin

This ain't exactly rocket science, but it took me an embarrassing amount of time to get there, so I'm posting the code for next time. This will turn a given textarea in your admin area into a WYSIWYG. It's got a fairly small feature set, but that's only because I've stripped most of them out. You can add them back in by taking a look at the documentation. Per the Django docs, create an admin folder under one of your templates directories, then add subfolders for the app and model (though you can do just one if you want it to apply to all forms in the app or do neither to apply to all apps and models in your site) and add this as "change_form.html" (it took me an extra 10 minutes to get this done because I was sure it should be named "change_form.py" in spite of copious amounts of documentation that said otherwise):

  2. {% extends "admin/change_form.html" %}
  4. {% block extrahead %}{{ block.super }}
  5. <!-- Skin CSS file -->
  6. <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/assets/skins/sam/skin.css">
  7. <!-- Utility Dependencies -->
  8. <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
  9. <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/element/element-beta-min.js"></script>
  10. <!-- Needed for Menus, Buttons and Overlays used in the Toolbar -->
  11. <script src="http://yui.yahooapis.com/2.5.1/build/container/container_core-min.js"></script>
  12. <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/menu/menu-min.js"></script>
  13. <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/button/button-min.js"></script>
  14. <!-- Source file for Rich Text Editor-->
  15. <script src="http://yui.yahooapis.com/2.5.1/build/editor/editor-beta-min.js"></script>
  16. <script type="text/javascript">
  17. // YUI editor
  18. var editor = new YAHOO.widget.Editor("id_content", {
  19. handleSubmit: true,
  20. toolbar: {
  21. buttonType: 'advanced',
  22. buttons: [
  23. { group: 'fontstyle', label: 'Font',
  24. buttons: [
  25. { type: 'select', label: 'Arial', value: 'fontname',
  26. disabled: true,
  27. menu: [
  28. { text: 'Arial', checked: true },
  29. { text: 'Verdana' }
  30. ]
  31. },
  32. { type: 'spin', label: '10', value: 'fontsize', range: [
  33. 10, 16 ], disabled: true },
  34. { type: 'color', label: 'Font Color', value: 'forecolor', disabled: true }
  35. ]
  36. },
  37. { type: 'separator' },
  38. { group: 'textstyle', label: 'Font Style',
  39. buttons: [
  40. { type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
  41. { type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
  42. { type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' }
  43. ]
  44. },
  45. { type: 'separator' },
  46. { group: 'indentlist', label: 'Indenting and Lists',
  47. buttons: [
  48. { type: 'push', label: 'Indent', value: 'indent',
  49. disabled: true },
  50. { type: 'push', label: 'Outdent', value: 'outdent',
  51. disabled: true },
  52. { type: 'push', label: 'Create a Bulleted List', value:
  53. 'insertunorderedlist' },
  54. { type: 'push', label: 'Create a Numbered List', value:
  55. 'insertorderedlist' }
  56. ]
  57. },
  58. { type: 'separator' },
  59. { group: 'insertitem', label: 'Link',
  60. buttons: [
  61. { type: 'push', label: 'HTML Link CTRL + SHIFT + L',
  62. value: 'createlink', disabled: true }
  63. ]
  64. }
  65. ]
  66. }
  67. }
  68. );
  69. editor._defaultToolbar.buttonType = 'advanced';
  70. editor.render();
  71. </script>
  72. {% endblock %}
  74. {% block bodyclass %}{{ block.super }} yui-skin-sam{% endblock %}

Like I said, not rocket science. It adds some CSS & JavaScript includes (which are remotely-hosted, so you don't even have to worry about media roots or how it works locally vs. live) and then a bit to add a class to the body tag for the YUI skin.


My Name is Crew

I am posting this because I need to keep track of these things. Dreamt last night Michelle and I put on a community event centered around the retirement of some guy that had been a social worker all his life. After the dance crew came off the stage, there was a PowerPoint presentation of his life that I put together (each slide featured an allegorical photo of a raven) and a country music song. All I remember is:

My name is Crew
My name is Crew
Saving kids is kinda what I do
[a capella]Leading them away from a path of self-destruction . . .

And so on. The ravens were a result of watching a David Attenborough documentary last night and Michelle points out "kinda what I do" is a phrase that Bill Burr repeated in the stand-up show we watched again last night. So that explains a bit of it, but I still don't get where these dreams with original music come from. I must be choking off my creative brain during waking hours. Earlier this week I'd dreamt my friend had walked into a convenience store and declaimed a filthy sonnet in perfect ABAB rhyme scheme explaining why he needed to buy the New York Times Sunday Magazine and not the whole paper.


Expression Engine if Clauses

This is the kind of thing that's not worth a blog post except some day it might save one person hours of frustration. Expression Engine apparently doesn't like it when if statements either span multiple lines or when the trailing curly brace is pushed to a new line. I can't quite run down which it is, but it's not all that important: if your if clause isn't behaving as expected, make sure it's all on one line without any extraneous whitespace.

Dreams They Complicate My Life

Had one of those constant dreams nights which I take as indicative of good sleep, though I was awoken by my iPod once. I've been listening to the over-my-head In Our Time podcast because, whether it's terribly interesting or terribly boring, it makes me terribly sleepy. It was off-putting to wake up hearing a stranger in a dark room speaking of the Fall of Carthage. The highlights:

  1. Falls into what I would call the "Tetris Dream" category, when you've become a little too interested in something: had a dream where I was in a field hospital somewhere in the jungle watching a TV report, hosted by @leolaporte about Canadian Twitterers, whom he referred to as "C-itters". I'd really hoped we were beyond that kind of prejudice in 2009.
  2. I was a amateur anti-mob sniper, recruited for the job by a rogue government agent who's ex-girlfriend I was dating (she was also recruited for the cause; for the record, I'm pretty sure it was this woman from Numb3rs). I was up in our hotel room[1] with my assignment in my sights when I noticed the Mob's snipers hanging out of the hotel window right next to me. Jumping back out of the window, I went to inventory the guns and ammunition I'd been left when the treachery became clear: the case had almost no bullets (but plenty AA batteries, the guns being electric). To heighten the stress of the moment, the cleaning crew started coming around the halls.
  3. To cap the evening, I got another go at my newest recurring dream. Since my Mom died, I've been having this strange cartographic dream where I try to map out the city of Newport[2] from a boat. It never works, the boat sinks and whatever I'm looking for goes unfound. I could probably save this dream-self a lot of time if he'd give me a whack at his map.

[1] We'd gotten to the point in our relationship where we were not only comfortable sharing a hotel room, we could interrupt a vacation to gun down mobsters without any negative effect on our relationship
[2] More accurately, the whole of Aquidneck Island, as I think last time I wound up in Portsmouth under the Mount Hope Bridge.


PHP Excel Exporter

A few times a year a client needs to export something from a database table to Excel. There's a simple hack to do it in most any language. There are actually a few, but having come up as a web developer, my preferred trick is to just build an HTML table and serve it as Excel by setting the mime type header. Having done this dozens of times, I finally formalized this into a simple PHP class tonight to save myself some time and figured I might as well share it.

The bad news: because I am lazy, it relies on an old data connection class I wrote years ago when I was even less bright than I am now. The thing's so ugly I posted it somewhere else because I am too ashamed to host it here. You can rip that out and use whatever you prefer by just changing the logic in _get_table() below. If you do choose to use my old data-class.php, be aware it expects 4 constants, DB_SERVER, DB_USER, DB_USER_PASS, DB_NAME to create a connection to the database.

Here's the exporter code itself Update: I moved the code to snipplr because this WordPress plugin doesn't handle newline characters very well.

The simplest use is to instantiate an object, tell the exporter what you want to appear in the header row in the spreadsheet (by setting column_heads to an array of values) and then calling export(), passing it the SQL query that gets the data. If the number of fields in your query doesn't match the number of heads in column_heads, the resulting HTML will be a mess. You will understand if the code assumes you never make such mistakes. Here's a code example:

  2. $e = new ExcelExporter();
  3. $e->column_heads = array("First Name", "Last Name");
  4. echo $e->export("SELECT first_name, last_name FROM table");

Quick notes:

  • Control the Excel filename in my example by setting $e->filename("something-else.xls")
  • Add a timestamp to every file (useful for making sure the filename is always unique) by setting $e->timestamp_file = true
  • When you're trying to implement this and it's not working and having to say yes to the popup and let the file open in Excel is driving you crazy, set $e->debug = true and it will skip the Excel headers, sending the output to the browser

The big gotcha that works well for me but might not for you: there's a hook in the code that passes every data column through _format_field(). In my current class, this looks for any field with "_date" in the column name, assumes that field is a Unix timestamp and transforms the value into a m/d/y date. If you live in the other 99% of the world where people format their dates un-Americanly, well, you can do that like this: $e->date_format("d/m/y") or whatever other crazy date/ time format you like.

If you think that behavior stinks, rip it out. Alternatively, you can modify it or subclass this code (like "client-xyz-exporter extends ExcelExporter" for every client who lives in Excel) and change _format_field() to do whatever you want in a one-off sort of way. This is not high art, it's just a faster way of making someone happy (if you can imagine the kind of person whose life is improved by additional spreadsheets).

Windows GPG Front-End

I'm doing some work with GPG encryption and I always like to have a visual/ gui front-end to use to make sure I haven't screwed something up in my command line adventures. I came across Cryptophane today and it seems like a nice way to keep track of my particular Alice and Bob. The only problem I ran across was that my GPG install was in a non-standard place and Cryptophane doesn't look in the registry (I'm pretty sure GPG writes to it). The error wasn't immediately clear and there's no online help (though a .chm is provided), so I thought I'd post this for anyone else who runs into a similar problem. My shortcut target now looks like this:
"G:\Program Files\Cryptophane\Cryptophane.exe" --gpg-path "G:\Program Files\GNU\GnuPG\gpg.exe"

Your paths may vary, etc.

Morning Stress

I want English Muffin insurance, for those times when you tear the thing all wrong and one side is basically not there and the thing toasts completely unevenly because of the difference in girth. And the pressure's on then, because the damn things come in six-packs, which makes no sense. It doesn't correlate with anything: there are five weekdays, seven days in a week if you're eating them every day (and it's 10/14 if there's two of you in the house). Every once in a while I'll come upon a four-pack and think, "Two of those might work better", but then I get up close and realize it's one of those massive "SANDWICH SIZE!" affairs and I can't do it. Sure I've eaten sandwiches made from English Muffins, even hamburgers when we ran out of buns, but there's something depressing about intentionally buying those. And if you try to eat one for breakfast, you're going to feel like a real fatty. So that doubles the pressure and I wind up in front of the toaster like some bomb disposal technician whose decided on which wire to cut and now it's just a question of death or glory. This is why there are Eggos.

Nothing's Coming Out!

You want to know why recycling isn't keeping pace, why the universe is going to suffer heat death and run out of energy a few billion years from now? I just opened a new container of bay leaves-- if you don't know what those are, they're pretty much what you thought they were. The salient point is their size; go ahead and look, I'll wait.

The container had a shaker top on it. You know, the kind of thing you'd expect if the contents were ground up seeds and not say, vegetation off a tree. So I have to break a goddamn thumbnail because some halfwit in Sandusky, Ohio can't figure out whether or not to slap a shaker lid (with two options, not that either one would work) on top without calling someone back at HQ. Can't you see him starting to take out that 25' yellow Stanley tape he's carried around all his life for no good reason, thinking, "This is finally it" when he realizes everyone's staring a little more than normal? Isn't there something on the shop wall next to the OSHA posters, some suggested guideline on when not to include the shaker cap? "If it's bigger than the tip of your pinkie," with a red crossout circle.

Of course not, because the job's long since been turned over to the one reliable employee, some robot who happily slaps caps all day. When Skylab comes on line and the Terminators get rid of us, it won't be because of world wars or violence or inhumanity, it'll be because some computer figured out it just spent the last 10 years making the world worse off.

Anatomy of a Late Night

New Year's Eve has totally thrown us off schedule. For some time (the formula for which is: NOW - # of years we've had a dog) we've been consistently in bed by 11, 12 on weekend nights, with 1am being a notably late night. Head hit the pillow at 3 again last night and I'm delineating Why for myself so I can find the error

  • 8pm: Watched Tropic Thunder which turned out to be exactly what I thought it was, a decent three-and-a-half star comedy, something that's not so easy to find nowadays. Perhaps the comedy palate has grown more sophisticated since the days of Meatballs, though the trailer we caught beforehand, Van Wilder: Freshman Year, suggests otherwise. Amazing the amount of work shitty comedy writers ("And then her tits pop out!") can still get while more talented writers ("It was a surprise for all when her breasts sprang free of the bespoke bustier she was wearing.") go hungry. Movie opens well with fake trailers, then nose dives for a bit until it sets up the story. Would be worth sitting through for Robert Downey, Jr. alone. Tom Cruise was better than I want to admit.
  • 10pm: Rock Band. As my poor X-Box can attest, I'd jumped off the Rock Band train last fall. I'd gotten good enough to play some on Expert but was still way overmatched by certain songs on Hard (and always songs like "Foreplay/ Longtime" that take half a goddamn hour to point out you're going to fail for the fiftieth time, never some Ramones ditty that's over in 1:58). A light clicked on and I realized "playing" a "game" doesn't involve nearly popping a vessel in your head and throwing things after the age of 5 or so. I was fine with no more Rock Band, there was no hole in my life.

    Then I heard The Gaslight Anthem's new album. After playing it non-stop for a week, the damn drummer(1) hooked me back into Rock Band. Thankfully things have gone a lot better since. Even managed to skid through "Don't Fear the Reaper" last night, failing just late enough to make it to the end.
  • 1am: Bill Burr, Why Do I Do This? via Netflix. Discovered him last week on Comedy Central, causing another late night. The set was so good I was ready to sit through the repeat airing until smarter heads prevailed. I've been obsessivley listening to his old podcast episodes this week. Can't believe he can spitball an hour worth of funny on a weekly basis.
  • 2:30am: This is where things really fell off the track. It started to go wrong in the 1am hour when we re-watched something we'd seen a week before and wound up cracking the emergency PBR 12 pack in the frige, but I could have just shut down the X-Box, finished the beer and gone to bed. Instead I have to page through the Netflix Instant Queue, even though if anything in there was worthy of watching at 3am I'd have watched it already. Nope, a half hour later it's 3am, we're only halfway through the pilot episode of Macgyver, the beer's all gone and I realize the level of tension wasn't high enough to keep anyone but me awake. The possible rocket explosion will have to wait until we have more beer. Actually, to honor the spirit of the show, I should really brew some Pruno in the toilet. How Richard Dean Anderson isn't starring in anything more than my dreams nowadays is a mystery.

Postscript, 8am: dog gets up in bed, throws an absolute shitfit, leaving me wide awake with the better part of 12 beers still coursing through my veins. I think it's some sort of Temperance thing with her, trying to get us off the Devil's Brew.

1. Brush with fame: the drummer went to elementary school with a friend of mine. The amazing part is my friend went to elementary school in New Jersey and still wound up bright.

"Life's Good", Indeed

I am providing this post-mortem both as a public service and so I don't punch in the screen of my brand-new LG monitor. I picked up a new 22" monitor at Best Buy yesterday after losing a monitor to the power surges from the ice storm (what I ought to have done is replace the APC unit that's now cost me an external drive enclosure and a monitor, but that's another gripe). Almost 24 hours later, the monitor is actually running as expected. Let me preface this by saying I appreciate my setup might be a little different from what the folks at LG bothered to test on:

  • Mac hardware running Windows
  • it's a second monitor
  • NVidia and UltraMon are both fighting for control of the setup

All the same, this was a more painful hardware process than I remember going through. It's a goddamn display, not an ultrasound machine. Nothing should be more plug-and-play. And yet, when I plugged it all in with the existing VGA connection from the old monitor, none of the native resolutions were available. I installed the software and drivers, but it still couldn't figure itself out. The LG software could identify my primary monitor, but the software would not allow me to use any of the features because it could only run on an LG device. Even when it figured out there was an LG display somewhere, no dice. I'm assuming it has to be the primary display. Also, for a company that's done such a good job of becoming an international player, it's disappointing the English was as broken as the software displaying it.

I gave up on LG and updated the NVidia Control Panel, hoping newer versions would have the widescreen resolutions. Playing with the new control panel only blew things up worse: reversed the primary and the secondary and then screwed up Ultramon so badly the taskbar was displaying a box for every background process running on the box under my user account. It also saw fit to snap the resolution on both monitors to something fun. So, reboot.

My last gasp, and it included a fair bit of gasping as I've been screwing off work due to a wrenched back, was to find a DVI cable and try that. Nothing. No second monitor, no avowed knowledge of a second monitor in the control panel. For no good reason except I'm my own IT department, rebooted. Success! Sort of. Both monitors are working during bootup, both are correctly identified in the control panel, both have their proper native resolutions dialed in and programs are being popped up on the LG. Just one issue: it's power light is in orange standby mode, the screen is off and no amount of pressing the power button will convince it to turn on or off. This is when one has to say, "I can tell a convincing lie to the returns desk if need be" and show inanimate objects just who the hell runs the show around here. Out comes the power cable. The funny little capacitor manages to keep the light orange for five brave seconds before giving up the ghost. I yell, "Clear", jam the power cable back in as painfully as possible and here we are, all of us with a better understanding of the pecking order. And then I removed the startup item LG installed ("forte display manager") to keep it from doing it's infinite loop dance with NVidia for control of the display.

So, the public service announcement: if you get a new wide-screen LG monitor, get a DVI cable with it. Hook them up. Don't install the LG software (though you'll most likely need the drivers). I suppose if you just have one monitor, you'll be fine either way, but you'll only find this post if something goes wrong.