Customization

While Grappelli is mainly about the look & feel of the admin interface, it also adds some features.

Available Settings

GRAPPELLI_ADMIN_TITLE
The Site Title of your admin interface. Change this instead of changing index.html.
GRAPPELLI_AUTOCOMPLETE_LIMIT
Number of items to show with autocomplete drop–downs.
GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS
A dictionary containing search patterns for models you cannot (or should not) alter.
GRAPPELLI_SWITCH_USER
Set to True if you want to activate the switch user functionality.
GRAPPELLI_SWITCH_USER_ORIGINAL
A function which defines if a User is able to switch to another User (returns either True or False). Defaults to all superusers.
GRAPPELLI_SWITCH_USER_TARGET
A function which defines if a User is a valid switch target (returns either True or False). Defaults to all staff users, excluding superusers.
GRAPPELLI_CLEAN_INPUT_TYPES
Replaces HTML5 input types (search, email, url, tel, number, range, date, month, week, time, datetime, datetime-local, color) due to browser inconsistencies. Set to False in order to not replace the mentioned input types.

Collapsibles

Use the classes property in order to define collapsibles for a ModelAdmin or an InlineModelAdmin. Possible values are grp-collapse grp-open and grp-collapse grp-closed:

class ModelOptions(admin.ModelAdmin):
    fieldsets = (
        ('', {
            'fields': ('title', 'subtitle', 'slug', 'pub_date', 'status',),
        }),
        ('Flags', {
            'classes': ('grp-collapse grp-closed',),
            'fields' : ('flag_front', 'flag_sticky', 'flag_allow_comments', 'flag_comments_closed',),
        }),
        ('Tags', {
            'classes': ('grp-collapse grp-open',),
            'fields' : ('tags',),
        }),
    )

class StackedItemInline(admin.StackedInline):
    classes = ('grp-collapse grp-open',)

class TabularItemInline(admin.TabularInline):
    classes = ('grp-collapse grp-open',)

With StackedInlines, an additional property inline_classes is available to define the default collapsible state of inline items (as opposed to the inline group):

class StackedItemInline(admin.StackedInline):
    classes = ('grp-collapse grp-open',)
    inline_classes = ('grp-collapse grp-open',)

Inline Sortables

For using drag/drop with inlines, you need to add a PositiveIntegerField to your Model:

class MyInlineModel(models.Model):
    mymodel = models.ForeignKey(MyModel)
    # position field
    position = models.PositiveSmallIntegerField("Position", null=True)
    class Meta:
        ordering = ['position']

Now, define the sortable_field_name with your InlineModelAdmin:

class MyInlineModelOptions(admin.TabularInline):
    fields = (... , "position",)
    # define the sortable
    sortable_field_name = "position"

The inline rows are reordered based on the sortable field (with a templatetag formsetsort). When submitting a form, the values of the sortable field are reindexed according to the position of each row. We loop through each field of each row and check if the field has a value. If at least one value is given for a row, the sortable field is being updated. In order to exclude specific fields from this behaviour, use Sortable Excludes.

In case of errors (somewhere within the form), the position of inline rows is preserved. This also applies to rows prepared for deletion while empty rows are being moved to the end of the formset.

Besides using the drag/drop-handler, you are also able to manually update the position values. This is especially useful with lots of inlines. Just change the number within the position field and the row is automatically moved to the new position. Each row is being reindexed with submitting the form.

GrappelliSortableHiddenMixin

There is also GrappelliSortableHiddenMixin, which is a Mixin in order to hide the PositionField. Please note that this Mixin works with a default sortable_field_name = "position". Therefore, you only need to explicitly define the sortable_field_name if it’s named differently.

from grappelli.forms import GrappelliSortableHiddenMixin

class MyInlineModelOptions(GrappelliSortableHiddenMixin, admin.TabularInline):
    fields = (... , "position",)

# explicitly defining the sortable is only necessary
# if the sortable field name is not 'position'
class MyCustomInlineModelOptions(GrappelliSortableHiddenMixin, admin.TabularInline):
    fields = (... , "customposition",)
    sortable_field_name = "customposition"

Sortable Excludes

You may want to define sortable_excludes (either list or tuple) in order to exclude certain fields from having an effect on the position field. With the example below, the fields field_1 and field_2 have default values (so they are not empty with a new inline row). If we do not exclude this fields, the position field is updated for empty rows:

class MyInlineModelOptions(admin.TabularInline):
    fields = (... , "position",)
    # define the sortable
    sortable_field_name = "position"
    # define sortable_excludes
    sortable_excludes = ("field_1", "field_2",)

Rearrange Inlines

Sometimes it might make sense to not show inlines at the bottom of the page/form, but somewhere in–between. In order to achieve this, you need to define a placeholder with your fields/fieldsets in admin.py:

("Some Fieldset", {
    "classes": ("grp-collapse grp-open",),
    "fields": ("whatever",)
}),
("Image Inlines", {"classes": ("placeholder images-group",), "fields" : ()}),
("Another Fieldset", {
    "classes": ("grp-collapse grp-open",),
    "fields": ("whatever",)
}),

inlines = [ImageInlines]

The two classes for the placeholder are important. First, you need a class placeholder. The second class has to match the id of the inline–group.

Autocomplete Lookups

Autocomplete lookups are an alternative to related lookups (for foreign keys, many–to-many relations and generic relations).

Add the staticmethod autocomplete_search_fields to all models you want to search for:

class MyModel(models.Model):
    name = models.CharField(u"Name", max_length=50)

    @staticmethod
    def autocomplete_search_fields():
        return ("id__iexact", "name__icontains",)

If the staticmethod is not given, GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS will be used if the app/model is defined:

GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS = {
    "myapp": {
        "mymodel": ("id__iexact", "name__icontains",)
    }
}

Defining autocomplete lookups is very similar to related lookups:

class MyModel(models.Model):
    related_fk = models.ForeignKey(RelatedModel, verbose_name=u"Related Lookup (FK)")
    related_m2m = models.ManyToManyField(RelatedModel, verbose_name=u"Related Lookup (M2M)")

class MyModelOptions(admin.ModelAdmin):
    # define the raw_id_fields
    raw_id_fields = ('related_fk','related_m2m',)
    # define the autocomplete_lookup_fields
    autocomplete_lookup_fields = {
        'fk': ['related_fk'],
        'm2m': ['related_m2m'],
    }

This also works with generic relations:

from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models

class MyModel(models.Model):
    # first generic relation
    content_type = models.ForeignKey(ContentType, blank=True, null=True, related_name="content_type")
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = generic.GenericForeignKey("content_type", "object_id")
    # second generic relation
    relation_type = models.ForeignKey(ContentType, blank=True, null=True, related_name="relation_type")
    relation_id = models.PositiveIntegerField(blank=True, null=True)
    relation_object = generic.GenericForeignKey("relation_type", "relation_id")

class MyModelOptions(admin.ModelAdmin):
    # define the autocomplete_lookup_fields
    autocomplete_lookup_fields = {
        'generic': [['content_type', 'object_id'], ['relation_type', 'relation_id']],
    }

If your generic relation points to a model using a custom primary key, you need to add a property id:

class RelationModel(models.Model):
    cpk  = models.IntegerField(primary_key=True, unique=True, editable=False)

    @property
    def id(self):
        return self.cpk

If the human-readable value of a field you are searching on is too large to be indexed (e.g. long text as SHA key) or is saved in a different format (e.g. date as integer timestamp), add a staticmethod autocomplete_term_adjust to the corresponding model with the appropriate transformation and perform the lookup on the indexed field:

class MyModel(models.Model):
    text = models.TextField(u"Long text")
    text_hash = models.CharField(u"Text hash", max_length=40, unique=True)

    @staticmethod
    def autocomplete_term_adjust(term):
        return hashlib.sha1(term).hexdigest()

    @staticmethod
    def autocomplete_search_fields():
        return ("text_hash__iexact",)

For the representation of an object, we first check for a callable related_label. If not given, __unicode__ is being usedin Python 2.x or __str__ in Python 3.x.

Example in Python 2:

def __unicode__(self):
    return u"%s" % self.name

def related_label(self):
    return u"%s (%s)" % (self.name, self.id)

Example in Python 3:

def __str__(self):
    return "%s" % self.name

def related_label(self):
    return "%s (%s)" % (self.name, self.id)

Note

In order to use autocompletes, you need to register both ends (models) of the relationship with your admin.site.

Using TinyMCE

Grappelli already comes with TinyMCE and a minimal theme as well. In order to use TinyMCE, copy tinymce_setup.js to your static directory, adjust the setup (see TinyMCE Configuration) and add the necessary javascripts to your ModelAdmin definition (see ModelAdmin asset definitions):

class Media:
    js = [
        '/static/grappelli/tinymce/jscripts/tiny_mce/tiny_mce.js',
        '/static/path/to/your/tinymce_setup.js',
    ]

Using TinyMCE with inlines is a bit more tricky because of the hidden extra inline. You need to write a custom template and use the inline callbacks to

  • onInit: remove TinyMCE instances from the empty form.
  • onAfterAdded: initialize TinyMCE instance(s) from the form.
  • onBeforeRemoved: remove TinyMCE instance(s) from the form.

Note

TinyMCE with inlines is not supported by default.

If our version of TinyMCE does not fit your needs, add a different version to your static directory and change the above mentioned ModelAdmin setup (paths to js–files).

Warning

TinyMCE will be removed with version 3.0 of Grappelli, because TinyMCE version 4.x comes with a decent skin.

Changelist Templates

Grappelli comes with different change–list templates. To use the alternative templates, you need to add change_list_template to your ModelAdmin definition.

Filters as drop-down, automatically applied (default template)

The default template shows filters as a drop–down, selecting a single filter immediately applies the filter. This template supports use cases where single filters should be quickly applicable.

class MyModelOptions(admin.ModelAdmin):
    change_list_template = "admin/change_list.html"

Filters as drop-down, manually applied

This template shows filters as a drop-down, selected filters have to be applied manually by clicking an “Apply” button. This template supports use cases where multiple filters have to be selected and applied at the same time.

class MyModelOptions(admin.ModelAdmin):
    change_list_template = "admin/change_list_filter_confirm.html"

Filters in a sidebar, automatically applied

This template shows filters in a sidebar, selecting a single filter immediately applies the filter. This template supports use cases where single filters should be quickly applicable.

class MyModelOptions(admin.ModelAdmin):
    change_list_template = "admin/change_list_filter_sidebar.html"

Filters in a sidebar, manually applied

This template shows filters in a sidebar, selected filters have to be applied manually by clicking an “Apply” button. This template supports use cases where multiple filters have to be selected and applied at the same time.

class MyModelOptions(admin.ModelAdmin):
    change_list_template = "admin/change_list_filter_confirm_sidebar.html"

Changelist Filters

Grappelli comes with 2 different change–list filters. The standard filters are selects, the alternative filters are list of options (similar to djangos admin interface). To use the alternative filters, you need to add change_list_filter_template to your ModelAdmin definition:

class MyModelOptions(admin.ModelAdmin):
    change_list_filter_template = "admin/filter_listing.html"

Switch User

You sometimes might need to see the admin interface as a different user (e.g. in order to verify if permissions are set correctly or to follow an editors explanation). If you set GRAPPELLI_SWITCH_USER to True, you’ll get additional users with your user dropdown. Moreover, you can easily switch back to the original User.

Note

This functionality might change with future releases.

Warning

If you are using a custom user model and want to turn this feature on, pay attention to the following topics:

  • if is_superuser is neither a field nor a property of your user model, you will have to set both GRAPPELLI_SWITCH_USER_ORIGINAL and GRAPPELLI_SWITCH_USER_TARGET to functions; failing to do so will break the admin area. If you followed the instructions in the Django docs, is_superuser won’t be a field nor a property of your user model. If you define is_superuser as a property of your model, the admin area will get back to work.
  • if is_staff is not a field, and/or is_superuser is neither a field nor a property of your user model, the Grappelli tests will be broken (because e.g. of some user.is_staff = True instructions). This -again- is your case if you followed the Django docs on customizing user model, where is_staff is defined as a property (as opposite to a field).

Clean input types

With setting GRAPPELLI_CLEAN_INPUT_TYPES to True, Grappelli automatically replaces all HTML5 input types (search, email, url, tel, number, range, date month, week, time, datetime, datetime-local, color) with type="text". This is useful if you want to avoid browser inconsistencies with the admin interface. Moreover, you remove frontend form validation and thereby ensure a consistent user experience.

Note

This functionality might change with future releases.