Extending Django Media Tree

There are several ways in which you may want to add functionality to Media Tree. Suppose you need to add support for a specific image format, or you need custom maintenance actions in the admin, or you might need to add some Javascript or CSS code to the FileNode form. For each of these cases, there is a so-called extender class.

Overview

The extender base classes provided by Media Tree are ModelExtender, AdminExtender and FormExtender, and by subclassing them you create your custom extenders. The structure of extender classes is similar to that of a regular Django Model, ModelAdmin, or Form class, respectively, meaning that you can define several attributes such as model Fields or form Media, and they will be added to Media Tree during runtime.

Note

You can package and install your extender classes as a regular Django application module, and have Media Tree auto-discover installed extensions by providing a media_extension.py module. An application containing one or more extenders and a media_extension.py that registers them is what is called a Media Tree extension.

Media Tree already comes with some exemplary extensions in its contrib.media_extensions module. You should inspect these examples in order to get an idea of how to build an extension. There is also a tutorial below that should help you with creating your own extension.

Extender bases

An extender is created by subclassing one of the following base classes:

Extending the FileNode model

Extending the FileNode admin

Extending forms

Registering and installing Media Tree extensions

Each extension module is a regular Django application that is installed by putting the application in the INSTALLED_APPS setting.

An extension needs to contain a media_extension.py module that registers all extenders that the extension module contains:

Example of an extension.py file:

from media_tree import extension

class SomeModelExtender(extension.ModelExtender):
    """Example extender"""
    pass

extension.register(SomeModelExtender)

Notice that on the last line the extender is registered by calling the function media_tree.extension.register().

Tutorial extension: Geotagging Photos

Assume you are using landscape photographs on your website, and in the FileNode admin you would like to be able to enter the latitude and longitude of the place where they were taken. This is called geotagging.

Getting started

The first step is to create a Django application that serves as the container for our new extender classes. You can do this as usual on the command line from your project folder:

django-admin startapp media_tree_geotagging
cd media_tree_geotagging
touch media_extension.py

Notice that on the last line we created a file called media_extension.py. Media Tree will scan all INSTALLED_APPS for such a file, so that all installed extensions will be auto-disovered.

We can delete most of the other files that the startapp command created, such as models.py, as we are probably not going to need them.

Extending the Model

Now you can create the model extender in the file media_extension.py, subclassing the parent class provided by Media Tree:

from media_tree import extension
from django.db import models

class GeotaggingModelExtender(extension.ModelExtender):
    lat = models.FloatField('latitude', null=True, blank=True)
    lng = models.FloatField('longitude', null=True, blank=True)

extension.register(GeotaggingModelExtender)

This class looks similar to a regular Model, but it does not have its own database table – instead, its fields are added to the FileNode class when you restart the development server.

Note

This extension adds the fields lat and lng to the FileNode model. You are going to have to add these fields to the database table yourself by modifying the media_tree_filenode table with a database client, unless you installed it before running syncdb).

Extending the form

Of course we want to be able to edit our two new fields in the admin, so we need to create a form extender and add a new fieldset. We do this by adding a new class to media_extension.py:

class GeotaggingFormExtender(extension.FormExtender):

    class Meta:
        fieldsets = [
            ('Geotagging', {
                'fields': ['lat', 'lng'],
                'classes': ['collapse']
            })
        ]

extension.register(GeotaggingFormExtender)

Installing the extension

After you have created the database fields, you can install the extension by adding it to the INSTALLED_APPS in your project’s settings file:

INSTALLED_APPS = (
    # ... your apps here ...
    'media_tree',
    'media_tree_geotagging'
)

Adding an Admin Action

Let’s assume you have a content editor on staff, and this person’s job is to check if photographs were geotagged, and to notify the photographer of the ones that aren’t. We can simplify this task by adding an admin action to the FileNode admin.

With this extender, the editor will be able to check the checkboxes next to image files, have them checked automatically to see if they are not yet geotagged, and email the photographer the admin links to those FileNode objects.

As you may be assuming by now, we create an admin extender in media_extension.py:

from django.core.mail import send_mail

class GeotaggingAdminExtender(extension.AdminExtender):

    def notify_of_non_geotagged(modeladmin, request, queryset):
        non_geotagged_links = []
        for node in queryset:
            # Check if node is JPG and not geotagged:
            if node.extension == 'jpg':
                if not node.lat or not node.lng:
                    non_geotagged_links.append(node.get_admin_url())
        # Send email with admin links for these nodes, and message
        # current user about status of the action.
        if len(non_geotagged_links):
            message = '\n'.join(non_geotagged_links) + '\n\nThanks!'
            send_mail('Please geotag these files', message,
                'from@example.com', ['to@example.com'])
            modeladmin.message_user(request, 'Notification sent for'  \
                + ' %i non-geotagged JPGs.' % len(non_geotagged_links))
        else:
            modeladmin.message_user(request, 'All selected images appear'  \
                +' to be OK.')
    notify_of_non_geotagged.short_description =  \
        'Notify photographer if selected JPGs are not geotagged'

    actions = [notify_of_non_geotagged]

This last example is a bit more verbose, but you will notice that it just contains one method with the exact same signature like a regular Django admin action, and on the last line we are specifying the list of actions that this extender will contribute to the FileNode admin. Also, we are giving the method a short_description that will appear in the drop-down menu above the list displaying all of our FileNodes.

And that’s it! We are now able to geotag images in the Django admin.

Adding Form Media

Of course it would be great if we had a map widget in the form where we can just drop a pin on the location of the photograph. Creating such a widget is beyond the scope of this tutorial, but if we had created a Javascript containing the code that implements such a widget, we could easily add this file by adding a Media class to our form extender:

class GeotaggingFormExtender(extension.AdminExtender):

    class Media:
        js = (
            'map_widget.js',
        )

    # ...

This Media definition is merged with the default media loaded for the FileNode form, and we can use it to load any code or CSS files required by our hypothetical map widget.

Conclusion

Using this extension system, you can change many aspects of how Media Tree behaves. There are more attributes and also signals that you can define in your extenders than the ones described in this tutorial. Code away and, please, share your extensions with the Interested Public!

Tutorial extension: Creating an icon set

Icon sets are also packaged as Django applications, and creating a custom set is rather easy. Basically, an icon set is a Python module containing nothing but an empty __init__.py and a static folder with the respective image files. Here’s an example of how that could look like:

my_custom_audio_icon_set
    __init__.py
    static
        audio_icons
            audio.png
            ogg.png
            mp3.png

Note that this package contains three icons: One for generic audio files and one for either OGG or MP3 files.

Note

When displaying a file icon, Media Tree will scan all installed icon sets for an icon that is named like the media file’s extension (e.g. mp3.png), then for one named like its mimetype (e.g. audio/x-mpeg.png), then for the mime supertype (e.g. audio.png). Icon discovery is handled by a class called MimetypeStaticIconFileFinder, which by default only finds PNG files.

To install this icon set, simply add my_custom_audio_icon_set to your INSTALLED_APPS, collect its static files, and configure the new icon folder using the MEDIA_TREE_ICON_DIRS setting. See Installing icon sets (optional) for more detailed instructions.