How to Add Locations to Python Path for Reusable Django Apps

posted on April 10th, 2009 by Greg Allard in Greg's Posts on Code Spatter

In my previous post I talk about reusable apps, but I don’t really explain it that much. If you have an app that might be useful in another project, it’s best to not refer to the project name in the application so you don’t have to search and remove it when adding to another project. To never refer to your project name in your app’s code, you will need to put your app on the python path. I usually do project_folder/apps/app_folder so apps will need to be a location that python is checking when you are importing so that importing looks like the following:

from appname.filename import foo

There are a few places you might need to add an apps folder to the pythonpath.

Add to settings.py

This will add the apps directory in your project directory to the beginning of the path list. This will allow manage.py syncdb and manage.py runserver to know that the apps folder should be added.

import os
import sys
PROJECT_ROOT = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(PROJECT_ROOT, "apps"))

That should be all you need to do to get most django projects working with your reusable apps, but if for any reason you need add to the path with mod python or mod wsgi, the following should work.

Apache mod_python

In the setting-up-everything post I show an example httpd.conf file. In your apache configuration you will probably see something similar to what is below. To add the location /var/www/myproject/apps to the PythonPath I added it in the list.

SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE myproject.settings
PythonOption django.root /myproject
PythonDebug On
PythonPath "['/var/www','/var/www/myproject/apps'] + sys.path"

Apache mod_wsgi

If you use mod wsgi instead of mod python, your apache config will be loading a wsgi file with a line like this WSGIScriptAlias /var/www/myproject/myproject.wsgi. You will need to edit that file to add to the path (django’s site has an example file).

sys.path.append('/var/www/myproject/apps')

You never know when you might want to use an app in another project, so always try to keep from mentioning the project name anywhere in the applications.

Related posts:

  1. Python Projects in Users’ Home Directories with wsgi Letting users put static files and php files in a...
  2. How to Write Reusable Apps for Pinax and Django Pinax is a collection of reusable django apps that...
  3. Getting Basecamp API Working with Python I found one library that was linked everywhere, but it...

Getting Basecamp API Working with Python

posted on April 1st, 2009 by Greg Allard in Greg's Posts on Code Spatter

I found one library that was linked everywhere, but it wasn’t working for me. I was always getting 400 Bad Request when using it. Chris Conover was able to get the following code working.

import urllib2
 
protocol = 'https://'
url = 'example.com'
command = '/projects.xml'
headers = {'Accept' : 'application/xml', 
'Content-type' : 'applications/xml'}
username = 'x'
password = 'y'
 
# Setup password stuff
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
authhandler = urllib2.HTTPBasicAuthHandler(passman)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)
 
# Send the request and get response
req = urllib2.Request(protocol + url + command, None, headers)
response = urllib2.urlopen(req)
results = response.read()
 
print results

I thought it was a problem with how the authorization was formed so based on the above code I modified the old basecamp.py file and I was able to get a response. The following is what I changed.

Around line 64

    def __init__(self, username, password, protocol, url):
        self.baseURL = protocol+url
        if self.baseURL[-1] == '/':
            self.baseURL = self.baseURL[:-1]
 
        passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
        passman.add_password(None, url, username, password)
        authhandler = urllib2.HTTPBasicAuthHandler(passman)
 
        self.opener = urllib2.build_opener(authhandler)

And around line 142

path = '/projects.xml'

With that I was able to use basecamp.py to retrieve a list of projects. Other modifications may be needed for other features, but that was all I planned on using.

Here is an example of using ElementTree to parse the XML response to get the names of all of the projects returned from basecamp.

import elementtree.ElementTree as ET
from basecamp import Basecamp
 
protocol = 'https://'
url = 'example.com'
username = 'x'
password = 'y'
 
bc = Basecamp(username, password, protocol, url)
projects = bc.projects()
tree = ET.fromstring(projects)
tags = tree.getiterator(tag='project')
 
for t in tags:
    project_name = t.findtext('name')

Related posts:

  1. Python Projects in Users’ Home Directories with wsgi Letting users put static files and php files in a...
  2. How to Add Locations to Python Path for Reusable Django Apps In my previous post I talk about reusable apps, but...
  3. Setting up Apache2, mod_python, MySQL, and Django on Debian Lenny or Ubuntu Hardy Heron Both Debian and Ubuntu make it really simple to get...

How to Write Django Template Tags

posted on January 22nd, 2009 by Greg Allard in Greg's Posts on Code Spatter

Template tags can be useful for making your applications more reusable by other projects. For this example I will be adding to the books project that I started in a previous post. Also, I’ve bundled the example files into a google code project.

Start off by creating a folder called templatetags in your app directory and create two files in it. The first one named __init__.py and the second book_tags.py. There’s 3 things that we need to accomplish with our template tags. The first is to create a tag that will output the url for the action of the form. For example, {% get_book_form_url foo_object %}Next we need to get the form and assign it to a template variable that can be specified by the template variable. For example, {% book_form as bar_var %}. And the third template tag will get the books for an object and place in a template variable. For example, {% books_for_object foo_object as bar_var %}.

from django.template import Library, Node, TemplateSyntaxError
from django.template import Variable, resolve_variable
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from books.models import Book
 
register = Library()
 
def get_contenttype_kwargs(content_object):
    """
    Gets the basic kwargs necessary for form submission url
    """
    kwargs = {'content_type_id':
        ContentType.objects.get_for_model(content_object).id,
    'object_id':
        getattr(content_object, 'pk',
            getattr(content_object, 'id')),
    }
    return kwargs
 
def get_book_form_url(content_object):
    """
    prints url for form action
    """
    kwargs = get_contenttype_kwargs(content_object)
    return reverse('new_book', kwargs=kwargs)
 
class BooksForObjectsNode(Node):
    """
    Get the books and add to the context
    """
    def __init__(self, obj, context_var):
        self.obj = Variable(obj)
        self.context_var = context_var
 
    def render(self, context):
        content_type = ContentType.objects.get_for_model(
            self.obj.resolve(context))
        # create the template var by adding to context
        context[self.context_var] = \
            Book.objects.filter( # find all books for object
                content_type__pk = content_type.id,
                object_id = self.obj.resolve(context).id
            )
        return ''
 
def books_for_object(parser, token):
    """
    Retrieves a list of books for given object
    {% books_for_object foo_object as book_list %}
    """
    try:
        bits = token.split_contents()
    except ValueError:
        raise TemplateSyntaxError(
            _('tag requires exactly two arguments')
    if len(bits) != 4:
        raise TemplateSyntaxError(
            _('tag requires exactly three arguments')
    if bits[2] != 'as':
        raise TemplateSyntaxError(
            _("second argument to tag must be 'as'")
    return BooksForObjectsNode(bits[1], bits[3])
 
def book_form(parser, token):
    """
    Adds a form to the context as given variable
    {% book_form as form %}
    """
    # take steps to ensure template var was formatted properly
    try:
        bits = token.split_contents()
    except ValueError:
        raise TemplateSyntaxError(
            _('tag requires exactly two arguments')
    if bits[1] != 'as':
        raise TemplateSyntaxError(
            _("second argument to tag must be 'as'")
    if len(bits) != 3:
        raise TemplateSyntaxError(
            _('tag requires exactly two arguments')
    # get the form
    return BookFormNode(bits[2])
 
class BookFormNode(Node):
    """
    Get the form and add it to the context
    """
    def __init__(self, context_name):
        self.context_name = context_name
    def render(self, context):
        from books.forms import NewBookForm
        form = NewBookForm()
        # create the template var by adding to context
        context[self.context_name] = form
        return ''
 
# register these tags for use in template files
register.tag('books_for_object', books_for_object)
register.tag('book_form', book_form)
register.simple_tag(get_book_form_url)

Add this to your template

To start adding books to an object, add this code to your template and change my_awesome_object_here to the template variable name of your object.

<h2>Books</h2>
{% load book_tags %}
 
{% books_for_object my_awesome_object_here as books %}
{% for book in books %}
 
<a href="{{ book.get_absolute_url }}">{{ book }}</a> -
        {{ book.description }}
 
{% endfor %}
<h2>Add a book</h2>
<form action="{% get_book_form_url my_awesome_object_here %}" method="post">
{% book_form as form %}
{{ form }}
<input type="submit" value="Go" />
</form>

You can get the template tags source code and the code from the previous post at the google code project page or by doing

svn co http://django-books.googlecode.com/svn/trunk books

in a directory on the python path.

Related posts:

  1. How to Write Reusable Apps for Pinax and Django Pinax is a collection of reusable django apps that...
  2. Django RequestContext Example Browsing other peoples’ code is a great way to learn...
  3. Quick Thumbnails in Django I normally like to write code myself instead of installing...

Django RequestContext Example

posted on December 22nd, 2008 by Greg Allard in Greg's Posts on Code Spatter

Browsing other peoples’ code is a great way to learn new things about a language or framework. I never made it to the Django docs about Contexts, but the Pinax developers apparently did and I got a chance to learn this from them. This is a few sections of their code and how they use RequestContext in their apps.

If you are looking at the source of some of their views you might see how they are using it. Here is what it looks like in friends_app.friends

    return render_to_response(template_name, {
        "join_request_form": join_request_form,
        "invites_received": invites_received,
        "invites_sent": invites_sent,
        "joins_sent": joins_sent,
    }, context_instance=RequestContext(request))

So what extactly does context_instance=RequestContext(request) do? I took a look at the django documentation to find out more. And that led me to check the settings file and I found that there were quite a few things listed in TEMPLATE_CONTEXT_PROCESSORS.

TEMPLATE_CONTEXT_PROCESSORS = (
    "django.core.context_processors.auth",
    "django.core.context_processors.debug",
    "django.core.context_processors.i18n",
    "django.core.context_processors.media",
    "django.core.context_processors.request",
 
    "notification.context_processors.notification",
    "announcements.context_processors.site_wide_announcements",
    "account.context_processors.openid",
    "account.context_processors.account",
    "misc.context_processors.contact_email",
    "misc.context_processors.site_name",
    "messages.context_processors.inbox",
    "friends_app.context_processors.invitations",
    "misc.context_processors.combined_inbox_count",
)

I opened up friends_app.context_processors to see a bit more and it looked like this

from friends.models import FriendshipInvitation
 
def invitations(request):
    if request.user.is_authenticated():
        return {
            'invitations_count':
            FriendshipInvitation.objects.filter(
            to_user=request.user, status="2").count(),
        }
    else:
        return {}

This means that every view that has context_instance=RequestContext(request) in it will call the above function since it is listed in settings.py and it will provide the template variable, {{ invitations_count }}.

Using RequestContext makes it easy to have the common template variables available on every page and I will have to start using it more in my apps. So make sure you have from django.shortcuts import render_to_response and from django.template import RequestContext in your file and add your context processor to the settings file and you should be ready to add template vars to your contexts.

Related posts:

  1. How to Write Reusable Apps for Pinax and Django Pinax is
  2. How to Write Django Template Tags Template t
  3. Quick Thumbnails in Django I normally