Friday, December 18, 2009

Where Have All The Good DatePickers Gone?

Is it just me, or is it hard to find a good jQuery-based date picker? Or maybe I'm just picky.

The widget needs to support selecting a date range (there goes most of them). And it needs to be able to support configuring a starting and ending time. And it has to support Themeroller (there goes whatever few remained).

So as it seems is starting to become a norm for me, I'm working on a custom jQuery widget. It's been provisionally named "LoneRangeSelector". It's for a custom web-app-calendar sort of thing.

Provisionally, here's my progress thus far...


The main goal is to make an interface that's quick and easy to setup the date-related parameters of an event, without having to enter 4 different fields (start date, start time, end date, end time).

The secondary goal is that as a byproduct of having a single widget, there is quite a bit less error checking that needs to be done (ie. it's impossible to type in an ending date that occurs before the starting date).

I actually re-created the calendar code instead of trying to modify jQuery UI's datepicker, though it does implement identical css classes so the Themeroller appearance is the same.

The lower portion has been one of the more challenging. I think the time slider needs to have some type of time indication on it, because just at first glance there's no way you konw that it spans midnight to midnight.

Concept:

Friday, December 11, 2009

Using Google's jQuery CDN with Django when not in development

I wanted to transition the Django site I work with primarily to use Google's jQuery CDN. However, when developing locally, it's often faster to just use a local copy. What I wanted was a way to toggle which copy of jQuery was being used based on the environment.

Environment Detection
Before we can toggle the jQuery location, we need to have a way to detect which environment we're running in.I previously blogged about how I have my settings.py configured. There are different ways to do this. One is to use local_settings.py, the other is to have conditional code in your main settings.py which determines values. Either way works.

As for my setting, on the webserver is a folder structure that resembles...

    \webroot
\django_devel
\django_prod
My webserver pulls double duty, hosting both a production version ("prod") and a staging/testing version ("devel"). In my settings.py file I'm using this to determine which environment Django is running in...
import os
DJANGO_ROOT = os.path.abspath(os.path.dirname(__file__))

#
# Globals for determining settings
#
STAGING = PRODUCTION = DEVELOPMENT = False

if 'django_devel' in DJANGO_ROOT:
STAGING = True
elif 'django_prod' in DJANGO_ROOT:
PRODUCTION = True
else:
DEVELOPMENT = True

Most likely, you'll have some other what you're determining your environment. In that case just substitute that.

{% jquery template tag %}
Now that we have the required support code to detect what environment we're running in, putting together the actual implementation is simple.

I decided to go with a template tag. It's simple and easy.
import settings
from django import template
from django.conf import settings

register = template.Library()


# -----------------------------------------------------------------------------
# jQuery
# -----------------------------------------------------------------------------
@register.tag
def jquery(parser, token):
return JQueryNode()

class JQueryNode(template.Node):
def render(self, context):
jquery = 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'
jquery_ui = 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js'
if getattr(settings, 'DEVELOPMENT', True):
media_url = getattr(settings, 'MEDIA_URL', '/media/')
jquery = '%sjs/jquery-1.3.2.min.js' % media_url
jquery_ui = '%sjs/jquery-ui-1.7.2.custom.min.js' % media_url
return '<script type="text/javascript" src="%s"></script><script type="text/javascript" src="%s"></script>' % (jquery, jquery_ui)

There are a couple of things to note here.

First, there's a hard-coded path in this code. Because I plan to always use this template tag every time I need jQuery, I felt confident doing so as it leaves only one place to edit. However, if you won't be following that rigid of an implementation pulling the locations of jQuery out and into a settings.py file would probably be a smart idea.

Second, I'm implementing my type of detection for the environment. This probably differs from the majority of Django users.

When all is said and done, all that needs to remain is to simply drop the tag into base.html and we're good to go.
{% load jquery %}{% jquery %}

Thursday, December 10, 2009

How can I create a page that regularly updates itself with Django?

It seems this question has been coming up a lot on Stack Overflow, and I wrote a pretty lengthy response which I'll include here. The original question is #1883266.

Q: I'm building a web application that needs to have content on the page updated in real time without a page refresh (like a chat or messaging application). How do I do this with Django? In other words, how do I use AJAX with Django?

A: There's a lot going on in order to make this process work...

  • The client regularly polls the server for new chat entries
  • The server checks for and only replies with the newest
  • The client receives the newest entries and appends them to the DOM

This can be confusing when you're first starting because it's not always clear what the client does and what the server does, but if the large problem is broken down I think you'll find it's a simple process.

If the client is going to regularly poll the server for new chat entries, then the server (django) needs to have some type of API to do so. Your biggest decision will be what data type the server returns. You can choose from: rendered HTML, XML, YAML, or JSON. The lightest weight is JSON, and it's supported by most of the major javascript frameworks (and django includes a JSON serializer since it's that awesome).

# (models.py)
# Your model I'm assuming is something to the effect of...
class ChatLine(models.Model):
screenname = model.CharField(max_length=40)
value = models.CharField(max_length=100)
created = models.DateTimeField(default=datetime.now())

# (urls.py)
# A url pattern to match our API...
url(r'^api/latest-chat/(?P<seconds_old>\d+)/$',get_latest_chat),

# (views.py)
# A view to answer that URL
def get_latest_chat(request, seconds_old):
# Query comments since the past X seconds
chat_since = datetime.datetime.now() - datetime.timedelta(seconds=seconds_old)
chat = Chat.objects.filter(created__gte=comments_since)

# Return serialized data or whatever you're doing with it
return HttpResponse(simplejson.dumps(chat),mimetype='application/json')

So whenever we poll our API, we should get back something like this (JSON format)...
[
{
'value':'Hello World',
'created':'2009-12-10 14:56:11',
'screenname':'tstone'
},
{
'value':'And more cool Django-ness',
'created':'2009-12-10 14:58:49',
'screenname':'leethax0r1337'
},
]

On our actual page, we have a <div> tag which we'll call <div id="chatbox"> which will hold whatever the incoming chat messages are. Our javascript simple needs to poll the server API that we created, check if there is a response, and then if there are items, append them to the chat box.

<!-- I'm assuming you're using jQuery -->
<script type="text/javascript">

LATEST_CHAT_URL = '{% url get_latest_chat 5 %}';

// On page start...
$(function() {
// Start a timer that will call our API at regular intervals
// The 2nd value is the time in milliseconds, so 5000 = 5 seconds
setTimeout(updateChat, 5000)
});

function updateChat() {
$.getJSON(LATEST_CHAT_URL, function(data){
// Enumerate JSON objects
$.each(data.items, function(i,item){
var newChatLine = $('<span class="chat"></span>');
newChatLine.append('<span class="user">' + item.screenname + '</span>');
newChatLine.append('<span class="text">' + item.text + '</span>');
$('#chatbox').append(newChatLine);
});
});
}

</script>

<div id="chatbox">
</div>

Tuesday, December 8, 2009

Efficiently View Apache Log Files on Windows

One of the annoying things about using multiple platforms is when one platform has a useful utility (no matter how small) and the other platform doesn't. Have you ever needed to regularly check an Apache log file on your Windows development machine? The shell user inside me says "just tail it"... but this is Windows.

However, I just found a really amazing tool called BareTail.



BareTail "connects" to a log file, and shows you an automatically updated (live) tail of that file. It basically allows this situation to happen:

I have a window up on my developer machine (in the 2nd monitor) which is the tail of the Apache error log. Every time something is written to that log, BareTail pushes it into the window on my screen. I see log entries in real time.

Let me tell you, it makes debugging Apache error log issues much more efficient.

Time for Python Neenjah!

I happened upon an administrative assistant today who was renaming files using Windows Explorer. She had a folder with about 50 or so sub-folders, many of which contained sub-folders of their own. Every file needed to be renamed with the company name prefixing it. So for example, the file "january-charts.pdf" needed to be renamed to "Company - january-charts.pdf".

"You know", I said, "I could help that go a little faster if you're interested." I happened to know that these files needed to be sent as of yesterday, so I figured she wouldn't mind me helping to trim an hour or two off them getting out. That must mean it's time for Python Neenjah! (In case you missed it, that was a somewhat veiled reference to xkcd 'Regular Expressions')

A month or two ago I had put together a re-usable python module to allow easy recursively searching a directory. I know, I know, python already includes similar functionality. But it was weird to me, and I wanted a simpler and more flexible format. The module I built, inspire by some ideas I found around the internet, allows a callback to be specified whenever a file is found. It means you can do virtually anything from that directory search, and never really be concerned about how it does it.

from dirsearch import DirSearch

def search_callback(file):
print file

dir = DirSearch('C:\Path\Whatever', show_output=True)
dir.search(search_callback)

The source for dirsearch.py is included below.

Well with my module it only took a few lines of code to put together a command to complete the task at hand.

DirSearch.py


rrename.py

Friday, December 4, 2009

MarkEdit in Django

Being primarily a Django developer, it was only a matter of time until MarkEdit came with Django integration.

I added to the GitHub repo today a simple app which provides a widget that renders MarkEdit through Django. It's probably a bit more difficult to use than the jQuery plugin because it makes some assumptions about your Django configuration and usage (as do many Django apps). In any case, an initiated Django developer should be able to figure it out just fine.

In addition to the source update, the wiki has also been updated with documentation for the Django integration.

An of course, here's a screenshot of MarkEdit in the admin (running under Grappelli):

Thursday, December 3, 2009

MarkEdit Moved to GitHub

I have relocated the MarkEdit repository to GitHub to give it more community accessibility. The GitHub repo now also includes an improved wiki.


MarkEdit now has it's own project page, also hosted by GitHub: http://tstone.github.com/jquery-markedit/

The project page finally puts a working demo online (along with 4 other advanced demos, showing some of MarkEdit's flexibility).

Wednesday, December 2, 2009

IE TextRanges, Selections, and Carriage Returns

I spent several hours this week trying to track down a bug with programmatically selecting text via javascript.


if (textarea.setSelectionRange) {
// Set selection for Mozilla-ish browsers
textarea.setSelectionRange(start, end);
}
else {
// Set selection for IE
var range = textarea.createTextRange();
range.collapse(true);
range.moveEnd('character', start);
range.moveStart('character', end);
range.select();
}
Assuming that 'textarea' is the element of the textarea, and 'start' and 'end' are Number values that indicate the start and end of the selection, respectively.

I was managing this in MarkEdit by keeping a state object that recorded the "Before Select" (everything before the selection), the selection text, and the "After Select" (everything after the selection).

So it seems easy to implement right?
start = beforeSelect.length;
end = start + select.length;
That doesn't always work!

It works sometimes, but occasionally the selection will be the proper length, but randomly offset by 3-7 characters. It took quite a bit to figure out (that's an understatement). Here's what a lot of debugging and experimentation came up with:

When Internet Explorer creates a Range or a TextRange object, it converts all line returns to Carriage Return/Line Feed format; basically [0A] -> [0D 0A]. Easy to counter? Just determine all the instances of [0D] and [0A], subtract the two, then you have the offset? That's what I thought too, but even with the offset, the discrepancy persisted.

So it was time for a new approach. What if we converted all of the [0A] characters in the textarea to [0D 0A]'s ? Again, the offset of the selection persisted.

Here's what I finally narrowed it down to be: When you create a TextRange, IE doesn't actually re-render the text internally, converting all [0A]'s. However, when you request the text (TextRange.text) it converts it at that time. This means if you were to get the length of the selected text, TextRange.text.length, it would be longer than what the TextRange is seeing internally! When using .moveEnd on the TextRange, the value counts the position from the internal text, not from the text it gives you.

That is reeeeeeediculously wrong. The final solution is to "sanitize" the text TextRange gives you by removing all [0D] characters from it. From there you can accurately calculate the selection position and give the TextRange the proper coordinates.

Whew... that was several hours needlessly wasted by IE (again).



Monday, November 30, 2009

MarkEdit Documentation

For those interested, I finished the preliminary documentation and sample code for MarkEdit. It can be viewed via the Bitbucket Wiki.

The documentation includes...

  • Installation
  • Sample code
  • Complete API reference
  • Complete configuration option reference

Saturday, November 28, 2009

Developer Blog Entries

I haven't done any development in the past few days thanks to this holiday weekend, however I did have a chance to catch up on my blog subscriptions (which were really beginning to pile up). My wife just peeked over my should and said I was also spending quality time with her.

  • Ryan Tomayko on Unicorn (ruby), "because it's Unix." A neat post on using some of the unixy functionalities of ruby to develop a network service. I'd really like to try it, but I don't have a POSIX computer up and running. Hmmm... I should fix that.
  • Simon Willison talks about Node.js, a server-side javascript framework which looks ridiculously cool. Having been spending a lot of time working on MarkEdit, I can certainly appreciate some of the non-blocking/callback ideas that it implements. However, again with the lack of POSIX. It doesn't yet run on Windows. I really need to remedy that.
  • Again from Simon Willison he mentions flXHR -- A replacement for XmlHttpRequest, but implemented by an invisible flash (.swf) wrapper layer. It's a really interesting concept, though completely hacky. Why bother with this? 1.) get around cross-browser issues, and 2.) get around browser security issues (like making an AJAX request to API on another domain for example).

Monday, November 23, 2009

WMD Editor & jQuery

I've been using Dana Robinson's WMD Editor fork for a couple of months now. It was a great place to start. The problem is, it's becoming a real pain to customize. Small things were taking huge amounts of time to accomplish so I had a tough decision to make: Continue to use WMD Editor and have to spend hours for customization that should only take a minute or two -OR- re-write the UI myself on top of the existing Markdown rendering component?

Introducing: MarkEdit -- the Javascript MarkDown editor built on jQuery (to replace WMD Editor). It's currently being hosted on BitBucket.

  • Built On jQuery/UI -- Lighter weight code and less cross-browser issues
  • Themeroller Support -- Use existing or custom jQuery UI Themeroller themes
  • International -- Supports the loading of an alternate language
  • Sensible Defaults -- You should be able to get up and running with 1 line of javascript
  • Configurable -- Almost all defaults can be overridden very easily upon initialization
  • Public API -- Interact with whatever part you're interested in
  • Documented

Since everybody loves screenshots, here's the editor running under a jQuery theme and using the Chinese language translation.





And again here's a translated popup asking for the URL to insert an image...




And here's preview mode using a different theme but still in Chinese...




So far I've invested about 15 hours into the project with it being probably around 95% complete for initial coding. There is still a TON of testing to be done. I haven't even tried to fire it up in any of the fussy browsers yet.

I actually wrote a whole lot about it so far -- it's all up on the Wiki. There is still lots more documentation to come.

Friday, November 20, 2009

Using jQuery's Dialog Function to Get User Input

The jQuery UI library provides a really useful method: dialog. It allows any tag to be rendered as a fake "window" in the browser in either a modal or non-modal way. It's easily a natural choice for any time we need a dialog to fetch input from a user.

What makes it somewhat difficult for some programmers to use is that it's an asynchronous function. It opens the dialog then keeps on executing code. Here's why this can be confusing.

Let's say for example we're building a web-based instant messaging client. A feature of this client would be the ability for the user to change their nickname, even after they've already logged in (similar to how MSN works). We've decided to implement this functionality by adding a button on the IM toolbar "Change Nickname". When the user clicks it, the dialog should popup, ask them for their new nickname, and then perform whatever action should be taken to update the nickname.

The Natural-But-Doesn't-Work Method
The natural way (using synchronous code) to achieve this would be something like the following:

(Javascript code is included above. I'm not sure if this renders via RSS)

But as you can see from the code, this won't work. Why? Dialog is an asynchronous method. It's easy to get comfortable with synchronous dialogs, after all, Javascript has a built in one: input(); However in this case we need to develop a new strategy for working with an asynchronous dialog.

Now lest you be tempted to do something like while(!closed) { // do nothing }; let me just ask you now: stop!. That's fighting the system. That will most likely lock up the browser, and it's an absolute waste of resources.

Using Callbacks to Trigger Behavior
Stepping back from the problem, what we really want is that we have some code, and we want it to run only when the user has finished using the dialog boxes. Being asynchronous, the dialog function actually provides a measure for doing so: Callbacks. When we defined the OK and Cancel buttons, we specified an anonymous function() that would be called whenever that button was clicked. This is a call back. We can use this exact same mechanic to signal our other code when it should run.

But how? Here's some updated code...

(Javascript code is included above. I'm not sure if this renders via RSS)

To accomplish this would could create a "gating" function -- a function which be responsible for dispatching either a call to our dialog, or a call to the code which should be run when we have a value. This is implemented in the example code as getAndSetNewNickname. In addition to the new method we've also added a parameter to the getNewNick method: callback. This parameter will allow us to pass a function that will be called whenever the "Ok" button is clicked. In our implementation, this function happens to be the same function that's calling it. The situation we end up with is a recursive callback which will call our function a 2nd time when it has a value.

The end result? Our setNewNickname method only gets called when we actually have a value. You've successfully gotten user input, and didn't have to restore to timeouts or a weird while loop solution.

Happy jQuery'ing!

Tuesday, November 17, 2009

A Truly Modular Web Framework

Web development is weird. It's weird because the rendering of the UI (in HTML) feels so incredibly disconnected from the logic and objects that are represented by the UI. It's weird because the UI is usually rendered by a server-side template facility, but is occasionally rendered by cilent-side javascript.


Here's an example. I find the decorator pattern really cool. Let's say I'm writing a blog. Actually, everyone writes blogs. Let's say I'm writing a Stack Overflow clone. In Stack Overflow users can ask questions. Other users can answer them. Users can also comment on a question (different from an answer), or comment on an answer made by another user. The shared functionality is placing comments on an object.

The mechanics of a comment aren't particularly unique that they need to be bound to a specific object. A UI is presented for a user to type in a message. The server should store the messages and associate them with a specific object.

So let's say in this example the comment functionality is implemented as a decorator generically which can be applied to any object. So we have the question functionality, implemented perhaps as a class, and we can decorate it with the comment functionality. If later we decided to implement a "News" section, and need each news post to also have comments, we could also decorate the news posts using the same decorator as well. The comment functionality at this point is a self-contained decorator.

Programatically this doesn't seem like a difficult suggestion (maybe), but what happens when we need to render this out to HTML? Even worse, let's say the comment functionality requires javascript to fetch comments via AJAX -- then what?

I think the problem here is that whatever is doing the rendering doesn't know what the object Question or Answer looks like. It also doesn't know what the decorator Comments looks like either. If it did, it could render them intelligently. I'm thinking of decorated rendering here.

Functionality similar to what I'm describing can be achieved in current web frameworks, but usually through things like template tags, framework widgets, or partially rendered views. But that's not quite what I'm talking about. It still requires a lot of repetition and isn't really modular. Template tags still need to be coded very specifically.

Part of the issue is that in web development frameworks we are using HTML to describe too many things. Take Django's template system, which despite many people's complaints, is quite functional and I actually kind of like. Django templates provide the facilities of context (passing variable data to the template), template tags (custom bits of code that can be arbitrarily executed) and filters (helpers to modify data to conform to a particular output).

But for each page template that is created, the following things are being handled in that one template:
  1. How a given object should be rendered
  2. How the page is layed out
  3. What media files need to be linked/included in order for this page to rendered properly (js/css/etc.)'
I'm thinking out loud here, but what would a rendering system (and supporting framework) look like which separated what the template system did into separate pieces?
  • Each object has it's own multiple HTML "interpretations", outfits if you will for the class. Decorators can call the decorated class's outfits if needed.
  • Page templates become more semantic and less about display -- they're interested in where pieces go on the page, not necessarily the markup to render it to appear a certain way. More of an outline than a sea of div tags. It seems very often the task of laying out what goes on a page, and implementing the UI are two completely separate tasks, and yet most template systems I've used force you to do them together.
  • Media files are handled programatically in code, somewhere, somehow. An object outfit can for example say "I need this javascript file" and the template engine will react accordingly. This prevents <script> tags and @import statements being strewn all over, and allows the framework to pass those media files off to a handler that decide if something should be done with them (ie automatically put <script> at bottom and <link> at the top, compress the JS, etc.). Disclaimer: This is probably a harder problem than I'm thinking it is, especially when AJAX gets involved.
More to come on this. I might have to do some experimental code. Should template engines help you by handling some pieces of the Javascript on the client? Hmm...

Friday, November 6, 2009

Django South Fork

I really like South (the database migration app for Django). I even wrote about it a little bit on Stack Overflow (#1590944). In short, South lets you create python-based migrations to change the database schema. The translation is that you can have a Django model, make a change to it, and then have south change the database schema for you. It's generally awesome.

But there's this one catch to it: It doesn't seem to be able to order migrations on a project-wide basis. This is a biggie for me.

Consider this scenario:
app1.model has a foreign key to app2.model. App2 is a 3rd party app and you decide to extend it. So you create app3.model which inherits from app2.model. Then, you update the foreign key on app1.model to point to app3.model. In order for the app1 migration to run successfully, the app3 migration that creates the table app3_model needs to have run first. How do you control this?

So I forked South. Yeah, bitbucket and all.

My fork works by creating a manifest file (in JSON format for easy human and VCS editing). Each time a 'startmigration' is created, a new entry is added to the manifest. The manifest entries can be re-ordered whenever without any real consequence. When a 'manage.py migrate' is run, if no app or migration number are given, the migrate command will default to running the migrations in the order of the manifest. If an app or a migration number are given, 'migrate' will run as it normally does.

I also added another command 'manage.py create_manifest' if, for whatever reason, you need to manually create a new manifest file.

For the most part my fork is a drop-in replacement. You can use it, gaining the manifest functionality, and not have to modify any of the way you work.

The only caveat I've found so far is in creating a new manifest file. When a few manifest is created, it generates its list from the applied manifests in the south_migrationhistory table. What it doesn't do is add manifests that are waiting to be applied to the new list. It's a limitation. At some point I'll probably develop a fix for it, but in the mean time you can add entries to the manifest file manually if they need to be applied (but have already been generated by 'startmigration').

Happy migrating.

Thursday, November 5, 2009

Komodo Edit Theme: Candybar Ver 1

Well what's the fun of a new editor without a new color scheme to go along with it? Here's my latest Komodo Edit color scheme. The colors were inspired by Mozilla Bespin, but I've certainly taken things in my own direction.

Python


HTML



Instead of uploading the file somewhere (which would create a broken link a year from now), I'm going to insert the text right here.

To use this theme copy all of the text and save it as "Candybar ver 1.ksf". Save the file to C:\Documents and Settings\[username]\Application Data\ActiveState\KomodoEdit\[version]\schemes. Under Windows Vista and 7 Save to the "Users" folder instead of "Documents and Settings".

This is my first release of this theme. Expect minor tweaks in the future.


Version = 4

Booleans = {'caretLineVisible': False, 'preferFixed': True, 'useSelFore': False}

CommonStyles = {'attribute name': {'fore': 8904824},
'attribute value': {'fore': 13676752},
'bracebad': {'back': 2501686, 'bold': 1, 'fore': 128},
'bracehighlight': {'back': 2501686, 'bold': 1, 'fore': 2721475},
'classes': {'back': 2501686, 'bold': True, 'fore': 5546676},
'comments': {'back': 2501686, 'fore': 9411235, 'italic': 1},
'control characters': {'back': 2501686, 'fore': 7897997},
'default_fixed': {'back': 2501686,
'bold': False,
'eolfilled': 0,
'face': 'Monaco',
'fore': 11581373,
'hotspot': 0,
'italic': 0,
'size': 10,
'useFixed': 1},
'default_proportional': {'back': 2501686,
'bold': 0,
'eolfilled': 0,
'face': 'Verdana',
'fore': 11581373,
'hotspot': 0,
'italic': 0,
'size': 10,
'useFixed': 0},
'fold markers': {'back': 2501686, 'fore': 7897997},
'functions': {'fore': 7897997},
'identifiers': {'fore': 7897997},
'indent guides': {'back': 6710886, 'fore': 7897997},
'keywords': {'fore': 5546676},
'keywords2': {'fore': 8904824},
'linenumbers': {'fore': 7897997},
'numbers': {'fore': 12688752},
'operators': {'fore': 4222857},
'preprocessor': {'fore': 7897997},
'regex': {'fore': 16751415},
'stderr': {'fore': 7897997, 'italic': 1},
'stdin': {'fore': 7897997},
'stdout': {'fore': 7897997},
'stringeol': {'back': 6697728, 'eolfilled': 1, 'fore': 7897997},
'strings': {'fore': 12688752},
'tags': {'fore': 52735},
'variables': {'fore': 0}}

LanguageStyles = {'C#': {'UUIDs': {'fore': 0},
'commentdockeyword': {'fore': 0},
'commentdockeyworderror': {'fore': 2293759},
'globalclass': {'fore': 7632127},
'verbatim': {'fore': 0}},
'C++': {'UUIDs': {'fore': 0},
'commentdockeyword': {'fore': 0},
'commentdockeyworderror': {'fore': 2293759},
'globalclass': {'fore': 7632127},
'verbatim': {'fore': 0}},
'CSS': {'classes': {'fore': 12629703},
'identifiers': {'fore': 16634024},
'ids': {'fore': 7986943},
'operators': {'fore': 11599871},
'tags': {'fore': 3977471},
'values': {'fore': 14155775}},
'Diff': {'additionline': {'fore': 7667711},
'chunkheader': {'fore': 7632127},
'deletionline': {'fore': 16777076},
'diffline': {'fore': 9868950, 'italic': 1},
'fileline': {'fore': 8904824, 'italic': 1}},
'Errors': {'Error lines': {'fore': 16777113, 'hotspot': 1, 'italic': 1}},
'HTML': {'attribute name': {'fore': 16428914},
'attributes': {'fore': 13369361},
'cdata': {'fore': 7667711},
'default': {'fore': 11581373},
'entity references': {'fore': 11581373},
'keywords': {'fore': 10091996},
'numbers': {'fore': 13625852},
'operators': {'fore': 7897997},
'strings': {'fore': 5546676},
'tags': {'fore': 7897997},
'variables': {'fore': 2721475}},
'IDL': {'UUIDs': {'fore': 0},
'commentdockeyword': {'fore': 0},
'commentdockeyworderror': {'fore': 2293759},
'globalclass': {'fore': 7632127},
'verbatim': {'fore': 0}},
'Java': {'UUIDs': {'fore': 0},
'commentdockeyword': {'fore': 0},
'commentdockeyworderror': {'fore': 2293759},
'globalclass': {'fore': 7632127},
'verbatim': {'fore': 0}},
'JavaScript': {'UUIDs': {'fore': 0},
'commentdockeyword': {'fore': 0},
'commentdockeyworderror': {'fore': 2293759},
'globalclass': {'fore': 7632127},
'verbatim': {'fore': 0}},
'PHP': {'attribute name': {'fore': 16428914},
'attribute value': {'fore': 13676752},
'bracehighlight': {'back': 5197568},
'identifiers': {'fore': 10091996},
'keywords': {'fore': 11140834},
'operators': {'fore': 39423},
'strings': {'fore': 15523820},
'tags': {'fore': 39423},
'variables': {'fore': 15838576}},
'Perl': {'here documents': {'bold': 1, 'fore': 8183004}},
'Python': {'identifiers': {'fore': 7897997}, 'keywords2': {'fore': 11581373}},
'Regex': {'charclass': {'fore': 10092543, 'italic': 1},
'charescape': {'fore': 12303104, 'italic': 1},
'charset_operator': {'fore': 8372175, 'size': 12},
'comment': {'fore': 12303291, 'italic': 1},
'default': {},
'eol': {},
'groupref': {'fore': 10092543, 'italic': 1},
'grouptag': {'fore': 8372175, 'size': 8},
'match_highlight': {'back': 6908265},
'operator': {'fore': 8372175, 'size': 12},
'quantifier': {'bold': 1, 'fore': 10092543, 'size': 12},
'special': {'bold': 1, 'fore': 10092543},
'text': {}},
'XML': {'cdata': {'fore': 7667711},
'cdata content': {'fore': 7632127},
'cdata tags': {'fore': 7667711},
'data': {'fore': 14474353},
'declarations': {'fore': 13418403},
'entity references': {'fore': 14474353},
'pi content': {'fore': 7632127},
'pi tags': {'fore': 7667711},
'prolog': {'fore': 0},
'xpath attributes': {'fore': 7667711},
'xpath content': {'fore': 16741120},
'xpath tags': {'fore': 16777204}}}

MiscLanguageSettings = {}

Colors = {'activeBreakpointColor': 526573,
'bookmarkColor': 13434879,
'callingLineColor': 16776960,
'caretFore': 16777215,
'caretLineBack': 3355443,
'currentLineColor': 16777011,
'edgeColor': 3355443,
'foldMarginColor': None,
'pendingBreakpointColor': 569096,
'selBack': 6238471,
'selFore': 65535,
'whitespaceColor': 16777215}

Indicators = {'find_highlighting': {'alpha': 100,
'color': 1110271,
'draw_underneath': True,
'style': 7},
'linter_error': {'alpha': 100,
'color': 255,
'draw_underneath': True,
'style': 1},
'linter_warning': {'alpha': 100,
'color': 32768,
'draw_underneath': True,
'style': 1},
'soft_characters': {'alpha': 100,
'color': 13209,
'draw_underneath': False,
'style': 6},
'tabstop_current': {'alpha': 100,
'color': 3355647,
'draw_underneath': True,
'style': 7},
'tabstop_pending': {'alpha': 100,
'color': 16751001,
'draw_underneath': True,
'style': 6},
'tag_matching': {'alpha': 100,
'color': 33023,
'draw_underneath': True,
'style': 7}}

Komodo Edit

I've been using Netbeans for Python for several releases now. Don't get me wrong, it's not a bad editor. The problem is that I've used Visual Studio which includes Intellisense. In Episode 50 of the Stack Overflow podcast Joel refereed to Intellisense as "crack" -- once developers try it they're hooked. After coding in Visual Basic, VB.NET, and then C# I can say it's a very nice feature.

But I'm writing python now (and playing house with Ruby). Python is a dynamic language. I threw all hope of code completion and automatically displaying method tooltips away.

Then I found Komodo Edit.

It isn't perfect, but it's good enough™, perhaps even the best there is for Python at the moment (though what's the rumor I hear about Visual Studio 2010 supporting python?).

Aside from the code completion support, Komodo Edit has some neat features...

The Toolbox
That's cool! Komodo Edit (KE from this point on) offers a Toolbox running vertically on the right part of the interface. IT allows you to create items in a tree-view format which can do things. Lots of things. Execute a shell script: Check! Open a URL: Check! Insert a custom code snippet: Check! I guarantee you'll find like 10 things to stick there the first time you use it.

For Django development for example I've added items to open...
http://localhost:8000
http://localhost:8000/admin
The project issue tracker
The mercurial repository page

And code snippets to...
Skeleton Admin model
mode_save method
Skeleton Model

Ctrl+R
I know this is available in other places, but it's amazing to see it as a default in KE. Ctrl+R will launch the command line. I probably find this particularly notable since back in 2001 I released a Windows utility "CmdHotKey" which did pretty much the exact same thing (except it was Windows-wide).

Project File Tree View
I was really surprised to see that KE shows a tree view of the folder structure of your project. I assumed that would only be available in the paid-for version (Komodo IDE).

Color Schemes
What's an editor that forces you to look at a white background and black text?

KE also supports other neat stuff that I didn't mention like recordable macros.

All-in-all it's a very feature-packed editor and for free.

But it has some quirks...

Sadly, it's not perfect.

If one is using the tree view showing the project folders, right-clicking and choosing "Add >" gives several options including:

  • New Folder...
  • New Live Folder...
This is really stupid. New Folder creates a project-only organizational unit which from what I can tell serves no actual purpose. New Live Folder creates an actual disk directory like you'd expect.

Another thing -- If you move or delete a file, perhaps with mercurial so the move changes are in the repository, KE will show the old and new file, but the old file will be marked read and you'll need to remove it form the project manually. It would be nicer if since it detected the change it would just remove it from the project.

Monday, November 2, 2009

Mercurial hgwebdir under Apache using WSGI

The doc for setting up HgWebDirStepByStep sure make it seem harder than it really is. Maybe it is hard. Maybe it's just that I spent so much time trying to understand the doc, that when I actually just tried it myself it wasn't that bad.

I've been thinking of moving over from SVN to Mercurial (hg) but wasn't sure if it was worth the fuss. I moved. It seems nice. Here's a guide, including tutorials for both the server and client, to switch to hgwebdir (server) /TortoiseHg (client).

Let's get started.

Setting up Mercurial (Part 1)
Before we configure the server, the following must already be installed and running:

I'm going to be demonstrating the setup on Windows 2000 Server as the host for hgwebdir/apache, and Windows Xp as the developer machine.

1. Setup the Directory Structure
Create the following structure:
C:\Mercurial
\src
\hgwebdir
\repositories

We'll be putting files into these folders shortly.

2. Download mercurial source

Whatever you do, don't install TortoiseHg on the server. To run hgwebdir you're going to want to have compiled mercurial yourself.

Download Mercuria sourcel (Be sure to not get the binary)

After download, extract the contents to C:\Mercurial\src, or wherever you created your 'src' folder in the directory structure from above.

3. Build Mercurial
(Instructions taken mostly from WindowsInstall wiki page)
Open up the command prompt...
C:\>
C:\> cd mercurial\src
C:\Mercurial\src> python setup.py --pure build_py -c -d . build_ext -i build_mo --force
C:\Mercurial\src> python setup.py --pure install --force--force
Mercurial will now be built and installed into your python site-packages folder.

4. Setup a 3-way Diff tool
Download KDiff3 from SourceForge. Install using the included windows setup binary.

5. Configure Environment Variables

Add "C:\Python26\Scripts" and "C:\Program Files\KDiff3" to your PATH environment variable.

You will need to be logged into an account that has admin right on the machine to setup this next part. Right-click on "My Computer" and choose "Properties". Choose "Advanced", then click the button "Environment Variables." In the list at the bottom, "System variables", scroll down and find PATH. Click "Edit". At the end of the textbox add a semi-colon, then type in the path from above. Your PATH variable should then resemble...
...;C:\Program Files\KDiff3;C:\Program Files\KDiff3;
6. Create a local Mercurial configuration file
Windows Explorer has a limitation that won't let you create a new file that starts with a period. The way around this is to use notepad on the command line to type in the name of a file with a period.

Mercurial uses a local file named ".hgrc" to configure itself about which 3-way merge tool it should use and similar things to that nature.

Follow these commands to create the .hgrc file for the server:
C:\>
C:\> cd Python26\Scripts
C:\Python26\Scripts> notepad .hgrc
This will cause notepad to pop up and ask if you want to create a new file named .hgrc. You do. Here's the basic configuration file for mercurial. Edit these settings to whatever you need to be.
[ui]
editor = Notepad
username = hgadmin <webmaster@yourdomain.com>

[merge-tools]
kdiff3.priority=-1
kdiff3.args=-L1 base --L2 local --L3 other $base $local $other -o $output
kdiff3.regkey=Software\KDiff3
kdiff3.regappend=\kdiff3.exe
kdiff3.fixeol=True
kdiff3.gui=True

You've got Mercurial installed now! If you open a command prompt and type 'hg' it should print out the command's help.

Setting up Apache/hgwebdir
Hmmm... ok maybe that is a lot of work. It didn't seem like it at the time.

Going into this, I'm assuming you've already got Apache with mod_wsgi up and running. I should point out that you can technically run hgwebdir under CGI or mod_python, but as WSGI is the hotness now days probably a lot of people have already started using that.

Good news is, I think this part is a lot easier.

On my setup I've only got 1 server, and so I tend to run things as Virtual Hosts. If you're already running Apache I'm assuming you've got the smarts to change this config if you like a different setup, but for the example httpd.conf below, I'll be doing a virtual host.

1. Create our WSGI handler file
The WSGI file is really simple. We basically need to 1.) import the necessary modules, then 2.) pass a new instance of hgwebdir with the configuration file path back to WSGI handler.

Create this script and save it as "hgwebdir.wsgi" into the C:\Mercurial\hgwebdir folder.

2. WSGI app config file
You probably noticed in the code above that we're referencing a config file that doesn't yet exist. Let's go ahead and create that.
[web]
style = coal

[paths]
/ = C:/Mercurial/repositories/**
Save this file as "hgweb.config" into the C:\Mercurial\hgwebdir folder.

3. Configure Apache
So we've got our WSGI script and config file setup. All that's left now is to configure Apache.

We need to have a few things in our Apache conf in order to get everything working: 1.) A new virtual host to hold all of our Mercurial settings, 2.) a WSGIScriptAlias to make sure our hgwebdir.wsgi file handles web request, 3.) authentication (skip this if you want a public repository), and 4.) SSL.

All of the above goes in httpd.conf.

There's lots of stuff going on in there. Let's look at a bit of it.

Even though we've configured hg.yourdomain.com, unless you have actually setup an A record of "hg" for yourdomain.com, that address won't actually resolve (ie. it won't work).

The WSGIScriptAliasMatch is actually what routes our incoming Mercurial requests to the hgwebdir.wsgi file for handling. In order for this directive to work, you'll need to have mod_wsgi enabled in your httpd.conf file. (this is outside the scope of this post)

Just copy and paste the SSL configuration from elsewhere in your config file if you're already using it. If you don't have a cert or don't want to use SSL then delete all of that section and change the ports at the top back to 80.

Lastly, did you notice the auth part of the configuration? We're using AuthType Basic and we have a user file "C:\Mercurial\accounts". We haven't created that yet, so let's go ahead and do so.

In Part 2 when we look at setting up the client, I'll show you how to configure TortoiseHg to save the user/pass that will be authenticated here by Apache.

4. Setting up Mercurial Users
It's important to note at this point that the authentication is being handled by Apache and not Mercurial. In my setup we're just using HTTP Basic, however if your Apache install is already using something better (LDAP, DBM, whatever), then by all means use that instead.

If you've never made an Apache passwd file, it's an easy process. Apache gives you a tool "htpasswd" which allows you to create a new passwd file or to add new users to an existing file.

* Important: I don't know where your Apache is installed, so you'll need to interpret some of these paths for your specific machine.

htpasswd is located in the Apache bin folder. We're talking something to the effect of C:\Apache2.2\bin\htpasswd.exe. Figure that out, and then use the command line as indicated below to create our accounts file for Mercurial.
C:\>
C:\> cd Apache2.2\bin
C:\Apache2.2\bin> htpasswd "C:\Mercurial\accounts" testuser
This will create the file "accounts" in C:\Mercurial, add a new user "testuser" and then prompt you for a password for the new user. You can repeat this command later on to add new users.

5. Bask in how fashionable you are using Mercurial
If everything went well up to this point, you should be able to start Apache and try out your new hgwebdir install.

But upon arrival you'll probably notice that... it's empty. Let's create a new repository.

6. Adding repositories
Remember the folder we made C:\Mercurial\repositories? That's where all the repos will be living. Create a new repository like you would locally...
C:\>
C:\> cd Mercurial\repositories
C:\Mercurial\repositories\> mkdir testrepo
C:\Mercurial\repositories\> cd testrepo
C:\Mercurial\repositories\testrepo\> hg init
Refreshing your the URL of your hgwebdir install should now show a new read-only repository. By default new repositories are not push-able.

7. Create a repository configuration file
We can enable features and also fill out the details about repository by create a configuration file. Assuming the repository you created was "testrepo", create a new file named "hgrc" in C:\Mecurial\repositories\testrepo\.hg\ folder.
[web]
contact = Your Name
name = Test Repository
description = A repository that I can show how cool I am using hg
allow_push = *
allow_archive = gz zip
Most of this is self-explanatory. The "allow_push" directive enables files to be synchronized from the client's repository up to the repository on the server. The "allow_archives" will enable a snapshot of a given revision to be downloaded in those formats.

Save this file, refresh and there you go.

Next, we'll look at setting up TortoiseHg on a Windows XP developer box to connect to our new hgwebdir instance.

Sunday, November 1, 2009

Javascript: The Good Parts (the Video)

Kudos to Google and MIT for uploading such great quality stuff to YouTube (if you've been scared off by the drones of webcam-yourself-complaining-about-current-events then give it another try).


I stumbled upon Doug Crockford at google speaking on "Javascript: The Good Parts". He has a book by the same title as well. In his presentation he also mentions JSLint, a tool he wrote to parse Javascript and act as a syntax checker. I'm wondering if it could be rolled into my Django workflow or automated testing.

Here's the video:

FizzBuzz Ruby

Well since I was in the mood writing FizzBuzz and since I've been working on learning Ruby, I figured, why not do recursive fizzbuzz in ruby?


Ruby is quite an expressive language. It feels like it comes with more built-in keywords and default methods than python, so I'm sensing it is a bit more complex to learn. However, it has some neat stuff. I'm thinking of writing FizzBuzz using some of the different methods it has, like .net and the '..' range. I've also seen a built in function something to the effect of '.upto' which might be fun to play with as well.

Toshiba T135-1309 13.3" Laptop Review

It's funny how times changes. 9 years ago I purchased a Toshiba laptop for college. It set me back $1800 and came "loaded" with a Pentium 3 @ 600Mhz and 64MB RAM. I promptly maxed out the RAM to 192MB. I ended up hating it and vowed never to buy a laptop again.


Fast forward now a decade into the future. Things have changed. I'm married, have a full time job, and laptop hardware doesn't suck now. Neither does battery life. Windows 7 comes out. A netbook looks tempting. But do I really want a machine with such a tiny keyboard and screen? The shopping begins.

My search end when I decided on a Toshiba T135-1309 laptop. For the price, the specs are quite impressive:

  • Intel Pentium SU4100
  • 3GB RAM (DDR3 1066)
  • 13.3" LCD (16:9)
  • 3.88 lbs.
  • Windows 7, 32-bit Premium
  • 320 GB Hard Drive
  • ~8hrs battery life
  • Built in ethernet/wifi-N
  • Built in webcam
  • Built in memory card reader
Cost at Best Buy: $549

So far there are some things I really like about this laptop:
  • Affordable
  • Small and lightweight
  • Seems to get really good battery life
  • Comes with windows 7 premium
Overall I've been rather happy with this machine. There are 2 small things I'm not so pleased with, but they're very minor and defiantly not deal breakers.
  • The speakers are quiet; a lot more quiet than you'd expect. There's no external sound control either.
  • The whole laptop is rather glossy. I wish they had toned that down a bit.
At the time of purchase, I also picked up a Logitech M305 wireless mouse. Let me tell you -- it's worth the money. The mouse comes with a dongle that is ridiculously small. You plug it in and basically leave it there. There isn't a huge thumb-drive sized thing hanging out the side of your laptop. It's smaller in size, but not uncomfortably so.

The M305 actually comes in 3 'fashionable' designs and of course boring gray. I went with a designed model that was a bit more subtle than the other 2. It fits with the laptop.'


Wednesday, October 28, 2009

Structure and Interpretation, LISP and Python

I'm a "city boy" when it comes to programming languages. 90% of my experience in programming is in ridiculously high level languages like VB.NET, C#, and Python. I haven't "roughed it out" much in the wilderness of some low level language.


I had heard talk about MIT's Open Courseware, but wasn't sure what the hoopla was all about. Based on a reference in someone's blog post, I happened upon a 1986 recording of Structure and Interpretation of Computer Programs (YouTube). I watched the whole thing. I am thoroughly blown away.

I'll admit, the first 20 minutes were dizzyingly abstract, but once some code samples started to show up, it began to make sense. What I was floored the most about was seeing how much an influence LISP had over the language I've been working in the most lately -- Python. All of the "funky things" that I wasn't used too from C#, inner methods, using "def" instead of "function", etc. etc. were all elements of LISP.

So having FizzBuzz on the mind, I decided to give it a shot.... in LISP (and then after recursively in Python). The course professor noted that LISP had no for loops. "A challenge" I thought to myself. (Long Side Tangent: I feel at the moment as if I'm creating a programmer's Fight Club where I mentally abuse the comfortable high-level language life I once knew to get down and dirty fighting with the bare essentials of computational logic. [end sensationalistic, metaphoric movie reference])

In case you're not familiar with FizzBuzz, here's the problem:
Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".
I "made up" a function definition for print since the details of printing to the screen weren't really my concern.


The odd part for me was after working through the mental process of how the solution would work recursively in LISP, writing it again in Python felt ridiculously easy...



Look mom, no loops!

Monday, October 26, 2009

Notating Environment in Django's settings.py

I answered a question (#1626326) on Stack Overflow recently about Django setttings.py files which reminded me I'm still undecided on what the best way to handle this issue.

The issue is -- How do you handle Django settings which change based on which environment the web app is running in? The Django documentation recommends using something to this effect.

if DEBUG:
VALUE = 'something'
else:
VALUE = 'something else'
I used this approach for a while, but found that it was becoming limiting. The reason why is that it doesn't actually address the issue of environment. Where the "if DEBUG" trick is used, we're really only looking to see if we're in DEBUG mode, not if we're running in a development or production environment.

When I was first setting up django-compressor (which by the way is teh hotness) I wanted to sets the setup in a development environment with DEBUG=False. The "if DEBUG" failed miserably here.

settings.py Version 2
So my next attempt was to attempt to determine the machine the settings.py was being evaluated on, and from there set what the environment was.
PRODUCTION_SERVERS = ['WEBSERVER1','WEBSERVER2',]
if os.environ['COMPUTERNAME'] in PRODUCTION_SERVERS:
PRODUCTION = True
else:
PRODUCTION = False

DEBUG = not PRODUCTION
TEMPLATE_DEBUG = DEBUG

# ...

if PRODUCTION:
VALUE = 'something'
else:
VALUE = 'something else'
This is easily an improvement over "if DEBUG". At least now we have some control over values based on environment. I used this for a while and then realized...

What if I have two installations of Django running on the same physical machine but which should be running in different environments?

This is a conceivable situation. Let's say in a smaller company like the one I work in we only have 1 webserver. Departments request features from the web team, which then get implemented by developer(s). However the features need to be tested by the web team and evaluated by the department before deployed to the production site.

So, the webserver gets configured to run 2 virtual hosts: One for "testing.domain.com" and another for "www.domain.com." BAM! The "if PRODUCTION" method just failed. Why? Because we technically now have 3 environments (development, testing, production), 2 of which run on the same physical server.

Imagine this bit of settings.py...
if PRODUCTION:
DATABASE_HOST = '192.168.1.1' # Production MySQL
else:
DATABASE_HOST = 'localhost'
Bad things would follow. The testing copy of the web app (testing.domain.com) would load up, and mark PRODUCTION=True as it is technically on the production server. It then uses the production MySQL database. Fail.

To overcome this, our settings.py file really needs a way to distinguish what environment it is regardless of which physical machine it's located on.

settings.py Version 3
Some ideas I've had to address this would to set the environment based on...
  • The folder path the app is currently located in
    The code for this could get ugly easily.

  • The complete hostname the server is running under
    This might be win.

  • Something else?
I'll post some code when I find something that works well that I like.

On a related note, I was reading the Sinatra (ruby) documentation this week, and I noticed that it automatically sets the environment based on the values set by the RACK_ENV variable. This would be the equivalent of adjusting the settings.py based on the presence of a WSGI or FCGI (or whatever) variable. It seems like a really neat idea, however it's somewhat dependent on the way Django is being loaded, and given that there are so many possibilities, I'm not sure if it'd work out as well for Django as it does for Sinatra.

Friday, October 16, 2009

Neat Django Admin Tricks (Part 1)

About django admin in a sec. First a bit of setup:

As I've been organizing our office into using an issue tracker for keeping track of our Django app, I haven't really found one that 'fit' yet. The issue trackers I looked at had 1 of 2 problems:

  1. They were massive, huge, enterprise-level monsters. I need something for 3-4 people.
  2. They required a bunch of extra setup. I already have a Django app running, why load the server with extra software?
So I spent a few hours after work putting a simple issue tracker together. To really improve the usability and get what I wanted out of the app, I needed to control how the django admin was presenting things. Some neat django admin tricks were in order.

Making An Admin list_display Field Show In Color
By default, all values in the Django admin model list view are in black.

Photobucket

Boring black. If you have an issue tracker it should show in color, right? Here's what my issue admin was looking like:
class IssueAdmin(admin.ModelAdmin):
list_display = ('title', 'priority', 'completed', 'assigned_to', 'last_modified')
The easiest way to get the 'priority' column in color would be to render it as HTML. If you were to try this, you'd find that the Django admin does not escape that value, and you'd end up with < >'s showing. However, there is an API to get around that. In Python all functions are objects. Because of this, we can add a property '.allow_tags' at runtime to signal to Django that the value contains tags.
# models.py
def priority_html(self):
return u'<span >%s</span>' % self.get_priority_display()
priority_html.allow_tags = True
Our value now shows in color, but the column header renders as "Priority html". We can add one more property 'short_description' which would allow us to specify the equivalent of 'verbose_name' for a field.

Here's the (almost) complete code to achieve a custom HTML value in a list_display entry:
# models.py
class Issue(models.Model):

PRIORITY_CHOICES = (
(1, 'Low'),
(2, 'Normal'),
(3, 'High'),
)

priority = models.IntegerField(choices=PRIORITY_CHOICES, help_text='Default: Normal Priority', default=2)

def priority_html(self):
if self.priority == 3:
color = "652D90"
elif self.priority == 2:
color = "37B34A"
else:
color = "26A9E0"
return u'<span style="color:#%s">%s</span>' % (color, self.get_priority_display())
priority_html.allow_tags = True
priority_html.short_description = 'priority'

# admin.py
class IssueAdmin(admin.ModelAdmin):
list_display = ('title', 'priority_html', 'completed', 'assigned_to', 'last_modified')


Photobucket

Given the code above, I bet you can guess how the (Email) link was achieved.

Friday, October 2, 2009

Managing Django Dependencies via SVN

One of the things that's been somewhat of a small hassle in working with Django/Python is managing dependencies across multiple developer machines, especially through an SVN managed project. Whenever a new dependency is needed, we've ended up doing 1 of 2 things:

  • Adding the name of the package to a text file which can be "easy_install "
  • Copying the zip/bz/exe to a folder "dependencies"
In both cases, what normally happens is Developer X will add a dependency to the repo. Developer Y at some point does an SVN update, and pulls down the code Developer X committed. Developer Y runs his runserver and BAM! not found! So then Developer Y has to go and look in the "dependencies" folder and try and figure out if he needs to run an easy_install or run an exe or whatever.

Obviously a rather inefficient task. I had heard about systems that will do automatic dependency installing, but the problem I had with that is that sometimes you have dependencies that are just a single file, and other times a whole package, others are in Pypi, etc.. The packages come in a huge crazy mess of ways. I wanted something consistent.

My (somewhat hacky) Soultion
Create a 'dependencies' folder that lives inside the project root (mine is named "_dependencies"). This folder will hold everything needed by Django (except Django itself) that would normally go in 'site-packages'.

When the Django app starts, have this path added to the Python path, so that libraries can be installed to it, instead of ..\Python26\Lib\site-packages. This allows dependencies to be setup in a folder that everyone pulls down via SVN. Bingo!

There are mainly 2 times when we need to make sure that dependency path is available:
  1. When we're doing a management task (including runserver)
  2. When the production site actually handles a task via Apache or whatever
To accomplish both, I came up with the following code:

# manage.py (before Django loads itself)

import os
import sys

def set_dependency_path():
dep_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '_dependencies')
if not dep_root in sys.path:
sys.path.append(dep_root)

set_dependency_path()


# __init__.py (the main, root init)

from manage import set_dependency_path
set_dependency_path()

  © Blogger template 'Minimalist G' by Ourblogtemplates.com 2008

Back to TOP