Irrational Exuberance!

Dynamic Ad Targeting With django-monetize

August 29, 2008. Filed under djangodjango_monetize

This is probably the most exciting Django project I have worked on, and I'm glad that it has gotten to sufficient usable point to share. It is my pleasure to present django-monetize.

django-monetize is a pluggable Django app which aims to make it trivial to intelligently monetize your Django projects. It was designed with two guiding principles:

  1. There are many forms of monetization, and generally a mix-and-match approach will outperform a single monetization solution (for example, a thoughtful combination of donations, Amazon Affiliates and AdSense will perform better than AdSense alone). Switching between and experimenting with different forms and combinations of monetization is a key part of success.

  2. You can greatly enhance monetization by effectively targeting subgroups of your readers. Your monetization will be more effective if you have the ability to display different ads to users coming from search engines (perhaps AdSense would perform better) or Reddit (perhaps... silly tshirts would sell?), using different browsers (wait, I'm marketing my OSX app to internet explorer users?), and viewing different kinds of materials (someone reading an indepth tutorial would be more likely to donate than someone reading your rant about licorice jellybeans).

Supported Forms of Monetization

So far, django-monetize has built in support for Amazon Affiliates' Custom Links, Amazon Affiliates' Omakase, Amazon Affiliates' Search Links, Amazon Honor System donations, Dreamhost referrals, Google Adsense ad units, Paypal donations, and SliceHost referrals.

Its trivial (and encouraged) to add more of your own or customize the existng ones to be more appropriate for your website. It is easy to create your own custom adverts (perhaps an e-book you wrote, linking to a popular blog entry you put together, or advertising your new startup).

Installation

Install django_monetize like you would any other application. Check it out from its repository, add the django_monetize directory to your Python site-packages folder so that:

import django_monetize

works without throwing an error.

Then simply add django_monetize to the INSTALLED_APPS setting in your settings.py file and you're good to go. django_monetize doesn't contain any models (or use the database in any way), so you don't need to perform a sync.

Using django-monetize

The real magic in django-monetize is the level of customization and control that it allows. First lets examine usage at the template level. We'll start with the simplest examples and get more complex as we explore the flexibility.

{% load monetize %}

{% monetize_slot %}
<p> Welcome to my site! </p>
{% monetize_slot %}

That will display two slots, both using the default monetization option (we'll look at setting up the targetting and default logic after we look at template configuration).

{% load monetize %}

{% monetize_slot "header" %}
<p> Welcome to my site! </p>
{% monetize_slot "footer" %}

Now we are labeling the slots. We can use labeling to display different types of monetization options in different locations. This is the simplest concept of targeting.

{% load monetize %}

{% monetize_slot "header" request.META.HTTP_REFERER %}
<p> Welcome to my site! </p>
{% monetize_slot "footer" request.META.HTTP_USER_AGENT %}

Now we're labeling slots and passing them them the referer and user agent respectively. django-monetize will check against our targeting configuration and see if the current referer/user_agent is something we have specified targeting for, and will use the targeted advertisement if it exists, but will otherwise revert to the default advertisement.

{% load monetize %}

{% monetize_slot "header" object.title object.series object.tags %}
<p> Welcome to my site! </p>
{% monetize_slot "footer" %}

Here for the top slot we are beginning to see the flexibility of the targeting mechanism. It will first check if we have a targeted value for the object's title, will then check if we have targeted for any of the values in the object.series list, and will finally check if we have targeted any of the values in the object.tags list.

It will target on the first targeted value it reaches, and won't process the remaining parameters.

Finally, note that once you have found the targeted term, you are also able to specify the type of monetization for that targeted term in different slots. Thus you can display different ads in differently named slots even if you target on something as simple as this (which might be sufficient for mostly static content):

{% load monetize %}

{% monetize_slot "header" "django" %}
<p> Welcome to my site! </p>
{% monetize_slot "footer" "django" %}

Pretty flexible, eh?

Valid Parameters for monetize_slot template tag

We were using monetize_slot in a variety of different ways in the above examples, and it behoves us to clarify the arguments that it can accept.

  1. It can be called with zero arguments, in which case it uses the default monetization option.

  2. It can be called with one argument, which must be the name of the slot. In that case it will use the default monetization option's targeted value for that slot--if such a targeting exists--and will otherwise resort to using the default option.

  3. It can be called with two or more arguments. The first argument must be a string representing the slot's name. Subsequent arguments may be: strings (they may be strings stored in a context variable, or may be absolute strings declared in the template like{% monetize_slot "header" "django" %}), dictionaries (whose values are processed using iteritems()), or iterable items (defined as non-dictionary-non-string objects which have the __iter__ method). If it runs into a type of object which is not one of the above types (for example, when it is handling the contents of a queryset of your tags) it will convert the object into a string by calling unicode(obj). This means you'll target a specific object by using the string returned by its __unicode__ method.

Configuration

First, django-monetizemakes an important distinction between None and False. None indicates 'use the default monetization method', whereas False indicates 'do not use a monetization method.' Acknowledging that this is an arbitrary and confusing decision, thus far it seems like a reasonable solution.

Configuration of ad targeting is done within your project's settings.py file. There is an at least somewhat similar Django project, django-ads, which stores its configuration within the database. While that allows for a more dynamically modifiable system, django-ads doesn't address the issue of ad targeting and customization to the extent that django-monetize does, and I didn't see a reasonable way to store the configuration within the database (short of storing strings of Python code that were then evaluated, which strikes me as far from an acceptable solution).

There are three values used in the settings.py file: MONETIZE_TARGET, MONETIZE_DEFAULT, and MONETIZE_CONFIG. Lets start with a complex example an break it down.

MONETIZE_DEFAULT = (
    'django_monetize/amazon_search.html',
    ('amazon_search_terms','Django book'),
    ('amazon_search_title','Search for Django books!')
)

MONETIZE_TARGET = {
    'django':'django_monetize/paypal_donate.html',
    'Author (Will Larson)':'django_monetize/amazon_honor.html',
    'Author (Joe Somebody)':(
        'django_monetize/amazon_honor.html',
        ('amazon_paypage','Joe Somebodys Amazon Honor Paypage url'),
    ),
    'tutorial':{
        'header':'django_monetize/paypal_donate.html',
        'footer':'django_monetize/amazon_omakase.html',
        None:(
            'django_monetize/amazon_search.html',
            ('amazon_search_terms','JQuery'),
            ('amazon_search_title','Buy books on JQuery!'),
        ),
    },
}

MONETIZE_CONTEXT = {
    'amazon_affiliates_id':'your affiliates tracking id',
    'amazon_paypage':'default amazon paypages url',
    'paypal_business':'paypal accounts email address',
    'paypal_item_name':'My website',
    'paypal_currency_code':'USD',
    'paypal_tax':'0',
    'paypal_lc':'US',
    'paypal_bn':'PP-DonationsBF',
    'paypal_image':'http://www.paypal.com/en_US/i/btn/btn_donate_LG.gif',
}

Okay. So the first thing to realize is that monetization options have three possible appearances:

  1. They can be a string that is the name of a template. In that case the template will be rendered using the context in MONETIZE_CONTEXT.

  2. They can be a tuple or list whose first value is a template name, and other values are tuples/lists in the form (key,value) which are used to override the default values in MONETIZE_CONTEXT when rendering that monetization option.

  3. They can be a dictionary whose keys are ad slots (header, footer, or whatever you name them) and their values are either form #1 or form #2. There is also a magic value, None which is used to specify its default value if it doesn't define its value for all slots.

Next, MONETIZE_DEFAULT is where you establish the default monetization option, which is one of the three appearances above (string, list with string and overrides, dictionary with slot names that each have a string or list of string and overrides).

Finally, MONETIZE_TARGET is a dictionary of terms to target on, where each target has one of the three monetization appearances.

Up and At 'Em

Right now the repository is stored at GitHub, which I realize isn't Google Code and thus will be problematic for some Django users. I am moving up to New Jersey this weekend, and won't have time to setup the Google Code repository until next week.

In that same vein, I haven't had the time to integrate it into this site yet, and thus it hasn't received heavy testing. Forking it on GitHub and making your changes followed by a push request would be the easiest way to contribute, but mailing patches to me at lethain@gmail.com would work as well.

The README file in the repository should be helpful, but at the present time you should consider this blog entry to be a more authoritative resource until I vet README for changes as I refined things (I tried practicing Documentation Driven Development, meaning that I wrote the documentation before I wrote the code, and it worked out pretty well for me, but I imagine it has introduced a few inconsistencies).