Techniques for modifying Mezzanine

A question that often comes up on the Mezzanine mailing list is "how can I make Mezzanine do x". Modifying Mezzanine's code directly is an option but that will make upgrading Mezzanine painful. Below I discuss a number of techniques that can be used to modify Mezzanine without actually touching Mezzanine's codebase.

Monkey Patching

In case you are unfamiliar, Wikipedia defines a monkey patch as

"a way to extend or modify the run-time code of dynamic languages without altering the original source code."

Here's an example:

original_get_absolute_url = deepcopy(Page.get_absolute_url) 
def menu_item_get_absolute_url(self):
    """
    Returns a menuitem's href if this is a menuitem, otherwise, returns the
    original Page get_absolute_url
    """
    if self.content_model == "menuitem":
        return self.get_content_model().get_absolute_url()
    return original_get_absolute_url(self)
Page.get_absolute_url = menu_item_get_absolute_url

In the previous example Page's get_absolute_url was monkey patched to provide custom functionality if the Page was of the type "menuitem." A good place to put code like this is in a models.py file.

Field Injection

The field injection provided by Mezzanine is useful if you want to add additional fields to a Django model. Here is an example of adding a featured image to Pages:

EXTRA_MODEL_FIELDS = (
    (
        "mezzanine.pages.models.Page.featured_image",
        "mezzanine.core.fields.FileField",
        ("Header image",),
        {"blank": True,
         "null": True,
         "upload_to": "page_header",
         "format": "Image",
         "max_length": 255},
    ),
)

For the featured image to show up in the admin you would also need to register and unregister the Page admin classes, including featured_image in their fieldsets. Here is an example admin.py that would do this:

from copy import deepcopy

from django.contrib import admin

from mezzanine.forms.admin import FormAdmin
from mezzanine.forms.models import Form
from mezzanine.galleries.admin import GalleryAdmin
from mezzanine.galleries.models import Gallery
from mezzanine.pages.admin import PageAdmin
from mezzanine.pages.models import RichTextPage


# add the featured image to page subclasses in the admin
page_fieldsets = deepcopy(PageAdmin.fieldsets)
page_fieldsets[0][1]["fields"] += ("featured_image",)
PageAdmin.fieldsets = page_fieldsets
GalleryAdmin.fieldsets = page_fieldsets

form_fieldsets = deepcopy(FormAdmin.fieldsets)
form_fieldsets[0][1]["fields"] += ("featured_image",)
FormAdmin.fieldsets = form_fieldsets

admin.site.unregister(Form)
admin.site.register(Form, FormAdmin)
admin.site.unregister(Gallery)
admin.site.register(Gallery, GalleryAdmin)
admin.site.unregister(RichTextPage)
admin.site.register(RichTextPage, PageAdmin)

Python Mixins

Mixins are a feature of Python that can be used to dynamically add methods to a Python class. In particular we will make use of the fact that mixins can be added to a class without modifying its source code directly. This technique could be used to add arbitrary methods to Pages, BlogPosts, Cartridge Categories or any other class. Here is an example that adds a Hello World method to Pages.

from mezzanine.pages.models import Page


class PageExtend:
    def hello_world(self):
        return 'Hello World from %s' % self.title
Page.__bases__ += (PageExtend,)

This code could also live in a models.py although anywhere that get's imported should work.

There are many other ways that Mezzanine (and more generally Django) could be extended. Know of any, or see something I did wrong, let's hear it in the comments below.

Comments