While Grappelli is mainly about the look & feel of the admin interface, it also adds some features.
New in version 2.4.6: Added setting GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS
New in version 2.4.1: Added setting GRAPPELLI_AUTOCOMPLETE_LIMIT
Changed in version 2.4.0: Added namespace grp-.
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.
A ModelAdmin example:
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',),
}),
)
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 NavigationItemInline(admin.StackedInline):
classes = ('grp-collapse grp-open',)
inline_classes = ('grp-collapse grp-open',)
New in version 2.3.
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")
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. 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.
New in version 2.4.
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. This is especially useful if a field has a default value:
class MyInlineModelOptions(admin.TabularInline):
fields = (... , "position",)
# define the sortable
sortable_field_name = "position"
# define sortable_excludes
sortable_excludes = ("field_1", "field_2",)
New in version 2.4.6.
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 needs to match the id of the inline–group.
New in version 2.4.7: staticmethod autocomplete_term_adjust for better search optimization.
Changed in version 2.4.6: staticmethod autocomplete_search_fields is optional if GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS is being used.
Changed in version 2.3.5: staticmethod autocomplete_search_fields is required, related_autocomplete_lookup has been removed.
New in version 2.3.4: autocomplete_lookup_fields.
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.
Changed in version 2.4: The admin media URLs has been changed to use static URLs in compliance with Django 1.4
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 Media 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
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 2.5 of Grappelli, because TinyMCE version 4.x comes with a decent skin.
New in version 2.4.2.
Grappelli comes with 2 different change–list templates. The standard template shows filters with a drop–down, the alternative template shows filters on the right hand side of the results (similar to djangos admin interface).
To use the alternative template, you need to add change_list_template to your ModelAdmin definition:
class MyModelOptions(admin.ModelAdmin):
change_list_template = "admin/change_list_filter_sidebar.html"
New in version 2.4.2.
Grappelli comes with 2 different change–list filters. The standard filters are drop–downs, 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"