Django, Flatpages, Markdown, and Syntax Highlighting

For what should have been an easy task, this turned out to be extraordinarily difficult.

I assume you have Django already installed. If not, the tutorials on djangoproject.com aren’t too terrible, provided you’re not on a shared server (that’s another story!). I’m using Flatpages, but you can use whatever you want to follow along with this tutorial.

1. Install Markdown and Pygments for Python if you haven’t already. If you’re on Ubuntu, you can `sudo apt-get install python-markdown` or `easy_install Markdown` and `sudo apt-get install python-pygments`.

2. Go into your Django app and `mkdir templatetags`. This should be at the same level as `manage.py`, `settings.py` and `urls.py`. Then `cd` into that directory, and `touch __init__.py` (so that it becomes a module) and then add another file called something like `tags.py` (don’t name it “markdown.py” — that’ll bite you later). Paste this code in there:

from django import template
from django.template.defaultfilters import stringfilter

from django.conf import settings

register = template.Library()

@register.filter(name='markdown')
@stringfilter
def markdown(value, arg=''):
    """
    Filter to create HTML out of Markdown, using custom extensions.

    The diffrence between this filter and the django-internal markdown
    filter (located in ``django/contrib/markup/templatetags/markup.py``)
    is that this filter enables extensions to be load.

    Usage::

        {{ object.text|markdown }}
        {{ object.text|markdown:"save" }}
        {{ object.text|markdown:"codehilite" }}
        {{ object.text|markdown:"save,codehilite" }}

    This code is taken from
    http://www.freewisdom.org/projects/python-markdown/Django
    """

    try:
        import markdown
    except ImportError:
        if settings.DEBUG:
            raise (template.TemplateSyntaxError,
                   "Error in {% markdown %} filter: "
                   + "The markdown library isn't installed.")
        else :
            from django.utils.html import escape, linebreaks
            return linebreaks(escape(value))
    else:
        extensions=arg.split(",")
        if len(extensions) > 0 and extensions[0] == "safe" :
            extensions = extensions[1:]
            safe_mode = True
        else :
            safe_mode = False
        return markdown.markdown(value, extensions, safe_mode=safe_mode)

Yes, I’m leaving the link in there; I didn’t write that code.

3. Go into `settings.py` and add your app to the `INSTALLED_APPS`. You actually have to `import` your own app too; this baffled me for about an hour. My app is called `tech_eval` so my `INSTALLED_APPS` looks like this:

import tech_eval # you can put this line at the top of settings.py if you want

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'django.contrib.flatpages',
    'tech_eval',
)

4. Like I said, I’m using Flatpages, so my template looks like this (I saved it to djangoproject/app/templates/flatpages/default.html).

{% load tags %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>{{ flatpage.title }}</title>
    <link rel="stylesheet" type="text/css" href="/tech_eval/media/css/code_style.css" />
</head>
<body>
    <h1>{{ flatpage.title }}</h1>
    {{ flatpage.content|markdown:"codehilite"|safe}}
</body>
</html>

Notice how we filter the content with markdown, make sure the codehilite extension is used, and flag it as `safe` afterwords (otherwise Django will automatically character-encode everything).

5. Hopefully this is already working for you at this point, but you probably want to add some color to your code. You can generate the appropriate css file with this:

pygmentize -f html -S default -a .codehilite >> code_style.css

Some different styles you can use (at least on my system) are:

['manni', 'colorful', 'murphy', 'autumn', 'bw', 'pastie', 'native', 'perldoc', 'borland', 'trac', 'default', 'fruity', 'emacs', 'friendly']

(stolen from here).

Or just copy and paste this CSS file if you’re lazy:

.codehilite .c { color: #408080; font-style: italic } /* Comment */
.codehilite .err { border: 1px solid #FF0000 } /* Error */
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
.codehilite .o { color: #666666 } /* Operator */
.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
.codehilite .ge { font-style: italic } /* Generic.Emph */
.codehilite .gr { color: #FF0000 } /* Generic.Error */
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
.codehilite .go { color: #808080 } /* Generic.Output */
.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.codehilite .gs { font-weight: bold } /* Generic.Strong */
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.codehilite .gt { color: #0040D0 } /* Generic.Traceback */
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.codehilite .kt { color: #B00040 } /* Keyword.Type */
.codehilite .m { color: #666666 } /* Literal.Number */
.codehilite .s { color: #BA2121 } /* Literal.String */
.codehilite .na { color: #7D9029 } /* Name.Attribute */
.codehilite .nb { color: #008000 } /* Name.Builtin */
.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
.codehilite .no { color: #880000 } /* Name.Constant */
.codehilite .nd { color: #AA22FF } /* Name.Decorator */
.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.codehilite .nf { color: #0000FF } /* Name.Function */
.codehilite .nl { color: #A0A000 } /* Name.Label */
.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
.codehilite .nv { color: #19177C } /* Name.Variable */
.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
.codehilite .mf { color: #666666 } /* Literal.Number.Float */
.codehilite .mh { color: #666666 } /* Literal.Number.Hex */
.codehilite .mi { color: #666666 } /* Literal.Number.Integer */
.codehilite .mo { color: #666666 } /* Literal.Number.Oct */
.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.codehilite .sx { color: #008000 } /* Literal.String.Other */
.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
.codehilite .vc { color: #19177C } /* Name.Variable.Class */
.codehilite .vg { color: #19177C } /* Name.Variable.Global */
.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
.codehilitetable { border: 1px solid #666; width: 100%; background-color: #666; }
.codehilitetable .linenos { background-color: #666; padding: 0 5px; width: 20px; }
.codehilitetable .code { padding: 0 15px; background-color: #fff; }

And that should pretty much be it. Let me know if I missed anything or this was too difficult to follow along.

Qt + OpenGL Code Example

This article is from August 2009; please see the updated and more detailed version here.

I wanted to develop a game using OpenGL but I was having trouble deciding on a windowing library. Somebody suggested I try Qt, so I decided to give it another shot. I heard it was good, but the editor it came with was so foreign and unintuitive. Unfortunately, this overshadowed what a beautiful library Qt really is! It takes a little getting used to, but it really is well designed.
Read more Qt + OpenGL Code Example

Posted in

array_map_recursive

Not much needs to be said here.

function array_map_recursive($callback, $arr) {
    $ret = array();
    foreach($arr as $key => $val) {
        if(is_array($val)) $ret[$key] = array_map_recursive($callback, $val);
        else $ret[$key] = $callback($val);
    }
    return $ret;
}

I wrote this so that I could do this…

$filters = array('htmlspecialchars', 'nl2br');
foreach($filters as $filter) $view_vars = array_map_recursive($filter, $view_vars);

Which just sanitizes my variables before I print them.

Posted in

jQuery Select Table Column or Row

Table rows are pretty easy to work as all the td elements are contained within one tr, but how would you grab all the tds in a single column? Why, by using these selectors of course!

$.fn.row = function(i) {
    return $('tr:nth-child('+(i+1)+') td', this);
}
$.fn.column = function(i) {
    return $('tr td:nth-child('+(i+1)+')', this);
}

// example - this will make the first column of every table red
$(document).ready(function() {
    $('table').column(0).css('background-color','red');
});

I like my selectors zero-based, but you can get rid of the +1 if you don’t like it.

SQL Injection Safe Queries Redux

function mysql_safe_string($value) {
    if(is_numeric($value))      return $value;
    elseif(empty($value))       return 'NULL';
    elseif(is_string($value))   return '\''.mysql_real_escape_string($value).'\'';
    elseif(is_array($value))    return implode(',',array_map('mysql_safe_string',$value));
}

function mysql_safe_query($format) {
    $args = array_slice(func_get_args(),1);
    $args = array_map('mysql_safe_string',$args);
    $query = vsprintf($format,$args);
    $result = mysql_query($query);
    if($result === false) echo '<div class="mysql-error"><strong>Error: </strong>',mysql_error(),'<br/><strong>Query: </strong>',$query,'</div>';
    return $result;
}

// example
$result = mysql_safe_query('SELECT * FROM users WHERE username=%s', $username);

Just use mysql_safe_query in place of mysql_query and you should be safe from SQL injection attacks. Use %s in place of any variables, and append them as arguments. Don’t quote your strings, it’ll be done for you automatically. Arrays will be flattened for you automatically and concatenated with commas. You can delete the error-echoing line if you want, but I find it useful for development.

Posted in

Facebook PHP API: Get the names of all your friends

If you didn’t already know, Facebook has an API that exposes quite a darn bit information. You can easily query this data using their API, but each request takes a fair bit of time. Typically, to get the names of all your friends, first you have to grab a list of the user ids of all your friends, and then query each and every single one to get their names. For me, that’s about 300 cross-server requests, which will almost certainly cause my server to time out, and probably force Facebook to reject me. Fortunately, Facebook has also created their own MySQL-like language, FQL (I pronounce it feequel), which lets you do some of these “complicated” queries in a single call. Here’s a simple example I wrote:

<?php
require_once 'facebook-platform/php/facebook.php';

$appapikey = 'yourapikeyhere';
$appsecret = 'yoursecretkey';
$facebook = new Facebook($appapikey, $appsecret);
$user_id = $facebook->require_login();

$result = fql_query('SELECT name FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=%s)', $user_id);
pr($result);


function fql_query($query) {
    global $facebook;
    $args = array_slice(func_get_args(), 1);
    return $facebook->api_client->fql_query(vsprintf($query, $args));
}

function pr($arr) {
    echo '<pre>';
    print_r($arr);
    echo '</pre>';
}

You’ll notice I’ve included two of my favorite wrapper functions. You can unroll them if you want. Anyway, just thought I’d share 🙂 I prefer writing FQL than trying to remember all their API calls anyway.

Oh… and just FYI, this prints out something like this:

Array
(
    [0] => Array
        (
            [name] => Mark Zuckerberg
        )

    [1] => Array
        (
            [name] => Tom Riddle
        )

I hate it when people leave it a mystery what exactly their example is doing!

Posted in

Get Domain & Subdomain from URL

preg_match('/^(?:www\.)?(?:(.+)\.)?(.+\..+)$/i', $_SERVER['HTTP_HOST'], $matches);

define('PROTOCOL', strtolower(substr($_SERVER['SERVER_PROTOCOL'],0,strpos($_SERVER['SERVER_PROTOCOL'],'/'))).'://');
define('SUBDOMAIN', $matches[1]);
define('DOMAIN', $matches[2]);
define('HERE', $_SERVER['REQUEST_URI']);

If you’re at http://www.sub.domain.com/page, then:

PROTOCOL = http://
SUBDOMAIN = sub
DOMAIN = domain.com
HERE = /page

Posted in

Insert into MySQL datetime column from PHP

You can use this simple function to convert a unix timestamp (like the one obtained from time()) to MySQL datetime format:

function mysql_datetime($timestamp = null) {
    if(!isset($timestamp)) $timestamp = time();
    return date('Y-m-d H:i:s', $timestamp);
}

I’m using this instead of MySQL’s NOW() function because my MySQL server isn’t localized to my timezone, but my PHP is by usage of date_default_timezone_set.

Edit: And you can use this function to convert back the other way (you can do this directly in the SQL too, but just in case)

function datetime_to_unix($datetime) {
    list($date, $time) = explode(' ', $datetime);
    list($year, $month, $day) = explode('-', $date);
    list($hour, $minute, $second) = explode(':', $time);
    return mktime($hour, $minute, $second, $month, $day, $year);
}

PHP code for handling recursive categories/hierarchical data

I’m just going to paste the code I wrote here… you can read some other tutorial to understand what it’s doing, but for some reason they have really incomplete examples, so here’s the whole shabang. It will even keep your nodes in alphabetical order. It has a function for displaying a drop down list too; wrap it in <select> tags.

<?php
class Category {
    function getParent($id) {
        return mysql_safe_query('SELECT parent.* FROM categories AS child, categories AS parent WHERE parent.id = child.parent_id AND child.id = %s LIMIT 1', $id);
    }

    function getChildren($id, $direct = false) {
        if($direct) {
            return mysql_safe_query('SELECT * FROM categories WHERE parent_id = %s ORDER BY left_val ASC', $id);
        } else {
            return mysql_safe_query('SELECT child.* FROM categories AS child, categories AS parent WHERE child.left_val >= parent.left_val AND child.right_val <= parent.right_val AND parent.id = %s ORDER BY child.left_val ASC', $id);
        }
    }

    function insert($name, $parent_id) {
        $result = mysql_safe_query('SELECT * FROM categories WHERE parent_id <=> %s AND name < %s ORDER BY name DESC LIMIT 1', $parent_id, $name);
        if(mysql_num_rows($result) > 0) {
            // insert between children
            $row = mysql_fetch_assoc($result);
            mysql_safe_query('UPDATE categories SET left_val = left_val + 2 WHERE left_val > %s', $row['right_val']);
            mysql_safe_query('UPDATE categories SET right_val = right_val + 2 WHERE right_val > %s', $row['right_val']);
            mysql_safe_query('INSERT INTO categories (parent_id, left_val, right_val, name) VALUES (%s, %s, %s, %s)',
                $parent_id, $row['right_val'] + 1, $row['right_val'] + 2, $name);
        } else {
            $result = mysql_safe_query('SELECT * FROM categories WHERE id <=> %s LIMIT 1', $parent_id);
            if(mysql_num_rows($result) > 0) {
                // insert first child
                $row = mysql_fetch_assoc($result);
                mysql_safe_query('UPDATE categories SET left_val = left_val + 2 WHERE left_val > %s', $row['left_val']);
                mysql_safe_query('UPDATE categories SET right_val = right_val + 2 WHERE right_val > %s', $row['left_val']);
                mysql_safe_query('INSERT INTO categories (parent_id, left_val, right_val, name) VALUES (%s, %s, %s, %s)',
                    $parent_id, $row['left_val'] + 1, $row['left_val'] + 2, $name);
            } else {
                // insert at beginning of tree
                mysql_safe_query('UPDATE categories SET left_val = left_val + 2, right_val = right_val + 2');
                mysql_safe_query('INSERT INTO categories (parent_id, left_val, right_val, name) VALUES (%s, %s, %s, %s)',
                    null, 1, 2, $name);
            }
        }
    }

    function delete($id) {
        $result = mysql_safe_query('SELECT * FROM categories WHERE id = %s LIMIT 1', $id);
        $row = mysql_fetch_assoc($result);
        mysql_safe_query('DELETE FROM categories WHERE id = %s LIMIT 1', $id);
        mysql_safe_query('UPDATE categories SET parent_id = %s WHERE categories.parent_id = %s', $row['parent_id'], $id); // relink parent
        mysql_safe_query('UPDATE categories SET left_val = left_val - 1, right_val = right_val - 1 WHERE left_val > %s AND right_val < %s', $row['left_val'], $row['right_val']); // update children
        mysql_safe_query('UPDATE categories SET left_val = left_val - 2 WHERE left_val > %s', $row['right_val']); // update left values of nodes to right
        mysql_safe_query('UPDATE categories SET right_val = right_val - 2 WHERE right_val > %s', $row['right_val']); // update right values of encompassing nodes and nodes to right
    }

    function incPostCount($id, $amount=1) {
        $result = mysql_safe_query('SELECT * FROM categories WHERE id = %s', $id);
        $row = mysql_fetch_assoc($result);
        return mysql_safe_query('UPDATE categories SET num_posts = num_posts + %s WHERE left_val <= %s AND right_val >= %s',
            $amount, $row['left_val'], $row['right_val']);
    }

    function decPostCount($id, $amount=1) {
        return self::incPostCount($id, -$amount);
    }

    function getPath($id) {
        $result = mysql_safe_query('SELECT * FROM categories WHERE id = %s', $id);
        $row = mysql_fetch_assoc($result);
        return mysql_safe_query('SELECT * FROM categories WHERE left_val <= %s AND right_val >= %s ORDER BY left_val ASC',
            $row['left_val'], $row['right_val']);
    }

    function printOptions($selected=null, $format=null, $mult=null, $add=null) {
        if(!isset($format)) $format = '<option value="%s" style="padding-left:%spx"%s>%s</option>';
        if(!isset($mult))   $mult = 15;
        if(!isset($add))    $add = 3;
        $depth = 0;
        $result = mysql_safe_query('SELECT * FROM categories ORDER BY categories.left_val ASC');
        while($row = mysql_fetch_assoc($result)) {
            if(isset($last)) {
                if($row['left_val'] == $last['left_val'] + 1) {
                    ++$depth;
                    $next_right = $last_right + 1;
                } elseif($row['left_val'] > $last['right_val'] + 1) {
                    $levels = $row['left_val'] - $last['right_val'] - 1;
                    $depth -= $levels;
                }
            }
            $last = $row;
            if($row['id'] == $selected) $selected_str = ' selected="selected"';
            else $selected_str = '';
            echo sprintf($format, $row['id'], $depth*$mult+$add, $selected_str, $row['name'], $row['num_posts'], $depth);
        }
    }

    function printTree($format=null) {
        if(!isset($format)) $format = '<li><a href="index.php?cat=%s">%s</a> (%s)';
        $depth = 0;
        $result = mysql_safe_query('SELECT * FROM categories ORDER BY categories.left_val ASC');
        echo '<ul>';
        while($row = mysql_fetch_assoc($result)) {
            if(isset($last)) {
                if($row['left_val'] == $last['left_val'] + 1) {
                    echo '<ul>';
                    ++$depth;
                    $next_right = $last_right + 1;
                } elseif($row['left_val'] > $last['right_val'] + 1) {
                    $levels = $row['left_val'] - $last['right_val'] - 1;
                    echo str_repeat('</li></ul></li>', $levels);
                    $depth -= $levels;
                } else {
                    echo '</li>';
                }
            }
            $last = $row;
            echo sprintf($format, $row['id'], $row['name'], $row['num_posts'], $depth);
        }
        echo str_repeat('</li></ul>', $depth+1);
    }

    function printAdminTree() {
        self::printTree('<li><a href="index.php?cat=%s">%s</a> (<a href="category_delete.php?id=%1$s">Delete</a>)');
    }
}

Please leave a comment if you found this useful!

Posted in