Django Suit documentation

Django Suit - Modern theme for Django admin interface.

About

Django Suit is alternative theme/skin/extension for Django admin app (administration interface).

Licence

Resources

Preview

Click on screenshot for live demo:

Django Suit Preview

Getting Started

Getting Started

Installation

  1. You can get stable version of Django Suit by using pip or easy_install:

    pip install django-suit==0.2.25
    
  2. You will need to add the 'suit' application to the INSTALLED_APPS setting of your Django project settings.py file.:

    INSTALLED_APPS = (
        ...
        'suit',
        'django.contrib.admin',
    )
    

Important

'suit' must be added before 'django.contrib.admin' and if you are using third-party apps with special admin support (like django-cms) you also need to add 'suit' before 'cms'.

  1. For Django < 1.9: You need to add 'django.core.context_processors.request' to TEMPLATE_CONTEXT_PROCESSORS setting in your Django project settings.py file.:

    from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
    
    TEMPLATE_CONTEXT_PROCESSORS = TCP + (
        'django.core.context_processors.request',
    )
    

For Django >= 1.9 or with new Django ``TEMPLATES`` setting: Make sure you have django.template.context_processors.request in your TEMPLATES OPTIONS context_processors setting in your Django project settings.py file.:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request', # Make sure you have this line
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Note: This is required to handle left side menu. If by some reason you removed original Django Suit menu.html, you can skip this.

Deployment

Deployment with Django Suit should not be different than any other Django application. If you have problems with deployment on production, read Django docs on wsgi first.

Note

If you deploy your project with Apache or Debug=False don’t forget to run ./manage.py collectstatic

Develop branch

Develop branch is considered as release candidate version. Check commits and changelog of develop branch first, before installing develop version. It is quite stable and always tested, but can contain some flaws or behaviour changes too. To install latest develop version use:

pip uninstall django-suit
pip install https://github.com/darklow/django-suit/tarball/develop

v2-dev branch

v2-dev branch is a complete rewrite using Bootstrap v4. It is still in development and this documentation doesn’t match v2-dev. Read more about v2 here. To install latest v2 version use:

pip uninstall django-suit
pip install https://github.com/darklow/django-suit/tarball/v2

Configuration

Templates

You must extend base_site.html template to customize footer links, copyright text or to add extra JS/CSS files. Example file is available on github.

Copy customized base_site.html template file to your project’s main application templates/admin/ directory and un-comment and edit the blocks you would like to extend.

Alternatively you can copy base_site.html to any of template directories, which are defined in TEMPLATE_DIRS setting (if any). By default Django looks in every registered application templates/ dir.

In the same way you can override any of Django Suit admin templates. More about customizing project’s templates, you can read in Django Admin Tutorial

Customization

You can customize Django Suit behaviour by adding SUIT_CONFIG configuration variable to your Django project settings.py file.

SUIT_CONFIG = {
‘PARAM’: VALUE, ‘PARAM2’: VALUE2 …

}

Default values are the ones specified in examples.

Full example

Configuration sample you can use as a start:

# Django Suit configuration example
SUIT_CONFIG = {
    # header
    # 'ADMIN_NAME': 'Django Suit',
    # 'HEADER_DATE_FORMAT': 'l, j. F Y',
    # 'HEADER_TIME_FORMAT': 'H:i',

    # forms
    # 'SHOW_REQUIRED_ASTERISK': True,  # Default True
    # 'CONFIRM_UNSAVED_CHANGES': True, # Default True

    # menu
    # 'SEARCH_URL': '/admin/auth/user/',
    # 'MENU_ICONS': {
    #    'sites': 'icon-leaf',
    #    'auth': 'icon-lock',
    # },
    # 'MENU_OPEN_FIRST_CHILD': True, # Default True
    # 'MENU_EXCLUDE': ('auth.group',),
    # 'MENU': (
    #     'sites',
    #     {'app': 'auth', 'icon':'icon-lock', 'models': ('user', 'group')},
    #     {'label': 'Settings', 'icon':'icon-cog', 'models': ('auth.user', 'auth.group')},
    #     {'label': 'Support', 'icon':'icon-question-sign', 'url': '/support/'},
    # ),

    # misc
    # 'LIST_PER_PAGE': 15
}

Forms

SHOW_REQUIRED_ASTERISK

Automatically adds asterisk symbol * to the end of every required field label:

SUIT_CONFIG = {
    'SHOW_REQUIRED_ASTERISK': True
}

CONFIRM_UNSAVED_CHANGES

Alert will be shown, when you’ll try to leave page, without saving changed form first:

SUIT_CONFIG = {
    'CONFIRM_UNSAVED_CHANGES': True
}

List

LIST_PER_PAGE

Set change_list view list_per_page parameter globally for whole admin. You can still override this parameter in any ModelAdmin class:

SUIT_CONFIG = {
    'LIST_PER_PAGE': 20
}

Features

Widgets

There are handy widgets included in Django Suit.

Widgets

Easiest way to change widget for specific fields is define your own ModelForm class with Meta class and widgets. Following this logic, you can override almost any widget in your form.

For example if you want to add some additional CSS class to input you can do following:

from django.forms import ModelForm, TextInput
from django.contrib.admin import ModelAdmin
from .models import Country

class CountryForm(ModelForm):
    class Meta:
        widgets = {
            'name': TextInput(attrs={'class': 'input-mini'})
        }

class CountryAdmin(ModelAdmin):
    form = CountryForm
    ...

admin.site.register(Country, CountryAdmin)

Note

If you define some custom fields for your form, you must specify model = YourModel parameter for class Meta.

NumberInput

HTML5 number input type="number":

from django.forms import ModelForm
from suit.widgets import NumberInput

class OrderForm(ModelForm):
    class Meta:
        widgets = {
            'count': NumberInput,

            # Optionally you specify attrs too
            'count': NumberInput(attrs={'class': 'input-mini'})

        }

Note

The same result you can achieve with HTML5Input(input_type='number') widget, this is just a shortcut.

HTML5Input

Specify input_type and use any of HTML5 input types. You can see some HTML5 input examples in Django Suit Demo app in KitchenSink forms:

from django.forms import ModelForm
from suit.widgets import HTML5Input

class ProductForm(ModelForm):
    class Meta:
        widgets = {
            'color': HTML5Input(input_type='color'),
        }

Note

Not all major browsers support all the new input types. However, you can already start using them; If they are not supported, they will behave as regular text fields. Also not all types are supported by Twitter Bootstrap.

EnclosedInput

Supports Twitter Bootstrap prepended/appended form inputs. EnclosedInput widget accepts two arguments prepend= and append=. Values can be text, icon-class or html (starts with html tag). You can also use both prepend= and append= together:

from django.forms import ModelForm
from suit.widgets import EnclosedInput

class ProductForm(ModelForm):
    class Meta:
        widgets = {

            # Appended by text
            'discount': EnclosedInput(append='%'),
            'size': EnclosedInput(append='m<sup>2</sup>'),

            # By icons
            'email': EnclosedInput(append='icon-envelope'),
            'user': EnclosedInput(prepend='icon-user'),

            # You can also use prepended and appended together
            'price': EnclosedInput(prepend='$', append='.00'),

            # Use HTML for append/prepend (See Twitter Bootstrap docs of supported tags)
            'url': EnclosedInput(prepend='icon-home', append='<input type="button" class="btn"  value="Open link">'),

        }

Preview:

_images/enclosed_input.png

AutosizedTextarea

AutosizedTextarea enables automatic height based on user input for textarea elements. Uses jQuery Autosize plugin. All the required javascript will be automatically attached. Usage:

from django.forms import ModelForm
from suit.widgets import AutosizedTextarea

class ProductForm(ModelForm):
    class Meta:
        widgets = {
            'description': AutosizedTextarea,

            # You can also specify html attributes
            'description': AutosizedTextarea(attrs={'rows': 3, 'class': 'input-xlarge'}),
        }

For demo - see countries description field or kitchensink tabluar inlines

Note

If you want to change height, when vertical scrollbar appears, use CSS max-height attribute. By default: max-height: 150px

Date/Time widgets

SuitDateWidget, SuitTimeWidget and SuitSplitDateTimeWidget extends original admin widgets by adding some additional output styling only. Widgets still uses same original JavaScript for calendar and time. You can see example in Demo app: User changeform:

from django.forms import ModelForm
from suit.widgets import SuitDateWidget, SuitTimeWidget, SuitSplitDateTimeWidget

class UserChangeForm(ModelForm):
    class Meta:
        model = User
        widgets = {
            'last_login': SuitSplitDateTimeWidget,
            'date_joined': SuitSplitDateTimeWidget,
        }

Preview:

_images/dates.png

LinkedSelect

Adds link to related item right after select for foreign key fields:

from django.forms import ModelForm
from suit.widgets import LinkedSelect

class CountryForm(UserChangeForm):
    class Meta:
        widgets = {
            'continent': LinkedSelect
        }

Preview:

_images/linked_select.png

Sortables

Sortables are handy admin tools for ordering different lists.

Sortables

Currently Django Suit supports these types of sortables:

  1. Sortable for change list
  2. Sortable for django-mptt tree list
  3. Sortable for Tabular, Stacked, GenericTabular, GenericStacked inlines

Limitations

Since sortables are based on JavaScript solution, there are known limitations:

  1. They don’t work with pagination.
  2. You won’t be able to use different list order other than by sortable parameter.

Under the hood

Widgets add sortable parameter to list_editable fields as simple number inputs. Afterwards JavaScript utils replaces these editable inputs with arrows. Order is not saved instantly, but only when user saves form, which is very handy - user can do sorting first and afterwards save it or cancel if changed his mind.

Change list sortable

To use change list sortable you must do following:

  1. In your models.py file add integer property for sortable to you model:

    from django.db import models
    
    class Continent(models.Model):
        ...
        order = models.PositiveIntegerField()
    
  2. In your in your admin.py extend SortableModelAdmin class and specify sortable name:

    from suit.admin import SortableModelAdmin
    
    class ContinentAdmin(SortableModelAdmin):
        ...
        sortable = 'order'
    

That’s it, you should see similar picture to example below in your admin now.

Note

If you want sortable arrows to appear in different column than last, you can do this by adding sortable field to list_editable in desired order, for example: list_editable=('name', 'order', 'something'). If you set arrows as first column, you must also define list_display_links - because arrows can’t be displayed also as links.

Example
_images/changelist_sortable.png

django-mptt tree sortable

To use sortable on djang-mptt tree, you must follow the same instructions as for change list sortable. Combining with documentation on django-mptt, final code should look like this:

  1. Prepare your model in models.py

    from django.db import models
    from mptt.fields import TreeForeignKey
    from mptt.models import MPTTModel
    
    class Category(MPTTModel):
        name = models.CharField(max_length=64)
        parent = TreeForeignKey('self', null=True, blank=True,
                                related_name='children')
    
        # Sortable property
        order = models.PositiveIntegerField()
    
        class MPTTMeta:
            order_insertion_by = ['order']
    
        # It is required to rebuild tree after save, when using order for mptt-tree
        def save(self, *args, **kwargs):
            super(Category, self).save(*args, **kwargs)
            Category.objects.rebuild()
    
        def __unicode__(self):
            return self.name
    
  2. Prepare admin class in admin.py:

    from suit.admin import SortableModelAdmin
    from mptt.admin import MPTTModelAdmin
    from .models import Category
    
    class CategoryAdmin(MPTTModelAdmin, SortableModelAdmin):
        mptt_level_indent = 20
        list_display = ('name', 'slug', 'is_active')
        list_editable = ('is_active',)
    
        # Specify name of sortable property
        sortable = 'order'
    
    admin.site.register(Category, CategoryAdmin)
    

Note

MPTTModelAdmin must be specified “before” SortableModelAdmin in extend syntax as shown in example.

Example
_images/mptt_sortable.png

Tabular inlines sortable

  1. In models.py your model for inlines, should have integer property for sortable, same way as described in all previous sortable examples:

    from django.db import models
    
    class Country(models.Model):
        ...
        order = models.PositiveIntegerField()
    
  2. In admin.py inline class must extend SortableModelAdmin class and specify sortable name:

    from django.contrib.admin import ModelAdmin
    from suit.admin import SortableTabularInline
    
    class CountryInline(SortableTabularInline):
        model = Country
        sortable = 'order'
    
    class ContinentAdmin(ModelAdmin):
        inlines = (CountryInline,)
    

That’s it, you should see similar picture to example below in your admin now.

Example
_images/tabular_inline_sortable.png

Stacked and Generic inlines sortable

Implementation of sortables for Stacked and Generic inlines is the same as mentioned above for Tabular inlines. You just have to use appropriate base class instead of SortableTabularInline:

# For Stacked inlines
from suit.admin import SortableStackedInline

# For Generic inlines
from suit.admin import SortableTabularStackedInline
from suit.admin import SortableGenericStackedInline
Example
_images/stacked_inline_sortable.png

Form tabs

Form tabs help you organize form fieldsets and inlines into tabs.

Form tabs

Form tabs help you organize form fieldsets and inlines into tabs. Before reading further, take a look on the tabs in the demo app.

Under the hood: Tabs are based on mostly CSS/JS solution, therefore integration of tabs is simple and non intrusive - all your form handling will work the same as before.

To organize form into tabs you must:

  1. Add suit_form_tabs parameter to your ModelAdmin class:

    # (TAB_NAME, TAB_TITLE)
    suit_form_tabs = (('general', 'General'), ('advanced', 'Advanced Settings'))
    
  2. Add 'suit-tab suit-tab-TAB_NAME' to fieldset classes, where TAB_NAME matches tab name you want to show fieldset in.

  3. To use with inlines, specify same css classes in suit_classes parameter for inline classes

Example

from django.contrib import admin
from .models import Country


class CityInline(admin.TabularInline):
    model = City
    suit_classes = 'suit-tab suit-tab-cities'


class CountryAdmin(admin.ModelAdmin):
    inlines = (CityInline,)

    fieldsets = [
        (None, {
            'classes': ('suit-tab', 'suit-tab-general',),
            'fields': ['name', 'continent',]
        }),
        ('Statistics', {
            'classes': ('suit-tab', 'suit-tab-general',),
            'fields': ['area', 'population']}),
        ('Architecture', {
            'classes': ('suit-tab', 'suit-tab-cities',),
            'fields': ['architecture']}),
    ]

    suit_form_tabs = (('general', 'General'), ('cities', 'Cities'),
                 ('info', 'Info on tabs'))

    # Read about form includes in next section
    suit_form_includes = (
        ('admin/examples/country/custom_include.html', 'middle', 'cities'),
        ('admin/examples/country/tab_info.html', '', 'info'),
    )


admin.site.register(Country, CountryAdmin)

Same way you can organize any HTML into tabs, just wrap it in previously mentioned CSS classes:

<div class="suit-tab suit-tab-TAB_NAME">...</div>

Preview

_images/form_tabs.png

Form Includes

Django Suit provides handy shortcut to include templates into forms.

See Form Includes

Form includes

Django Suit provides handy shortcut to include templates into forms.

Form Includes

Django Suit provides handy shortcut to include templates into forms for several positions (top, middle and bottom).

Under the hood: Suit includes are nothing but a shortcut. The same can be achieved by extending change_form.html and hooking into particular blocks. Suit includes can be used in combination with or without Form tabs.

Each suit_form_includes item can contain 3 parameters:

  1. Path to template (Required)
  2. Position: (Optional)
  • top - above fieldsets:
  • middle - between fieldsets and inlines
  • bottom - after inlines (Default)
  1. Specify TAB_NAME if using in combination with Form tabs (Optional)

Example

from django.contrib import admin
from .models import Country

class CountryAdmin(admin.ModelAdmin):
    ...
    suit_form_includes = (
        ('admin/examples/country/custom_include.html', 'middle', 'cities'),
        ('admin/examples/country/tab_info.html', '', 'info'),
        ('admin/examples/country/disclaimer.html'),
    )

Preview

_images/form_includes.png

List attributes

Using few callable helpers you can customize change list row and cell attributes based on object instance variables.

List attributes

Using few callable helpers you can customize change list row and cell attributes based on object instance variables.

List header columns

As soon as you add suit to your apps, all your changelist table header columns will have specific field CSS class present.

For example for column country list header tag will look like this <th class="country-column">. Which means you can change it’s appearance using CSS.

.country-column .text, .country-column a {
    text-align: center;
}

List row attributes

To add html attributes like class or data to list rows, you must define suit_row_attributes callable (function). Callable receives object instance and the request as arguments and must return dict with attributes for current row.

Example:

from django.contrib.admin import ModelAdmin

class CountryAdmin(ModelAdmin):
    ...

    def suit_row_attributes(self, obj, request):
        return {'class': 'type-%s' % obj.type}

    # Or bit more advanced example
    def suit_row_attributes(self, obj, request):
        css_class = {
            1: 'success',
            0: 'warning',
            -1: 'error',
        }.get(obj.status)
        if css_class:
            return {'class': css_class, 'data': obj.name}

Note

Twitter bootstrap already provides handy CSS classes for table row styling: error, warning, info and success

Preview:

_images/list_attributes.png

List cell attributes

To add html attributes like class or data to list cells, you must define suit_cell_attributes callable (function). Callable receives object instance and column name as arguments and must return dict with attributes for current cell.

Example:

from django.contrib.admin import ModelAdmin

class CountryAdmin(ModelAdmin):
    ...

    def suit_cell_attributes(self, obj, column):
        if column == 'countries':
            return {'class': 'text-center'}
        elif column == 'name' and obj.status == -1:
            return {'class': 'text-error'}

Note

Twitter bootstrap already provides handy CSS classes for table cell alignment: text-left, text-center, text-right

Wysiwyg editors

How to use wysiwyg editors in Django Suit.

WYSIWYG editors

If you use third party wysiwyg editor app, you should follow its manual.

If you would like to use save horizontal space, you should use full-width fieldset class with your wysiwyg editor.

We tested many existing wysiwyg apps, but many of them were missing tests, had implementation or other flaws, so we decided to create our own. We are maintaining two simple wysiwyg apps for Imperavi Redactor and CKEditor.

Right now they doesn’t support any uploads or anything with Django urls related, however it is planned to improve and update these packages in future.

These packages are independent and Django Suit isn’t requirement.

Imperavi Redactor

Install:

  1. pip install django-suit-redactor
  2. Add suit_redactor to INSTALLED_APPS
  3. Editor options for editor_options parameter can be found in Redactor Docs

Usage:

from django.forms import ModelForm
from django.contrib.admin import ModelAdmin
from suit_redactor.widgets import RedactorWidget

class PageForm(ModelForm):
    class Meta:
        widgets = {
            'name': RedactorWidget(editor_options={'lang': 'en'})
        }

class PageAdmin(ModelAdmin):
    form = PageForm
    fieldsets = [
      ('Body', {'classes': ('full-width',), 'fields': ('body',)})
    ]
    ...

admin.site.register(Page, PageAdmin)

Preview:

_images/full-width.png

CKEditor 4.x

Install:

  1. pip install django-suit-ckeditor
  2. Add suit_ckeditor to INSTALLED_APPS
  3. Editor options for editor_options parameter can be found in CKEditor Docs

Usage for CKEditor is the same as for Imperavi Redactor, just change RedactorWidget to CKEditorWidget:

from django.forms import ModelForm
from django.contrib.admin import ModelAdmin
from suit_ckeditor.widgets import CKEditorWidget

class PageForm(ModelForm):
    class Meta:
        widgets = {
            'name': CKEditorWidget(editor_options={'startupFocus': True})
        }

class PageAdmin(ModelAdmin):
    form = PageForm
    fieldsets = [
      ('Body', {'classes': ('full-width',), 'fields': ('body',)})
    ]
    ...

admin.site.register(Page, PageAdmin)

Preview:

_images/ckeditor.png

Other wysiwyg apps

Also you can integrate WYSIWYG editor using any other third party apps.

Few third party apps we tested for Django Suit:

JavaScript and CSS

JavaScript & CSS

JavaScript goodies

Inlines hook

When working with Django inlines and JavaScript, very often we want to hook/attach to event - when new inline row is added. Django Suit gives us such chance.

Use JavaScript Suit.after_inline.register to register/attach your function to new inline addition event.

$(function () {
    Suit.after_inline.register('my_unique_func_name', function(inline_prefix, row){
        // Your code here
        console.info(inline_prefix)
        console.info(row)
    });
});
jQuery

Django Suit provides jQuery (currently v1.8.3) and it is using custom namespace to avoid collisions between different apps which also provide jQuery.

To use jQuery from Django Suit package:

// Use Suit.$ instead of $
Suit.$('.my-selector').addClass('my-class');

// Or for larger code you can wrap it in following way:
(function ($) {
    // Here you can use regular $ sign
    $('.my-selector').addClass('my-class');
}(Suit.$));

// On document ready example:
(function ($) {
  $(function () {
      $('.my-selector').addClass('my-class');
  });
}(Suit.$));

CSS goodies

Original collapse and wide fieldset classes are also supported by Django Suit. Usage:

from django.contrib.admin import ModelAdmin

class CountryAdmin(admin.ModelAdmin):
    ...
    fieldsets = [
        (None, {'fields': ['name', 'description']}),

        ('Advanced settings', {
            'classes': ('collapse',),  # Specify fieldset classes here
            'fields': ['hidden_checkbox', 'hidden_choice']}),
    ]
  • collapse CSS class makes fieldset collapsable:

    collapse

  • wide CSS class makes fieldset labels wider

  • full-width CSS class hides field label and makes field controls in full width (useful for wysiwyg editors). Because label will be hidden, this class is intended to use one field per fieldset and fieldset title will be used as field title.

    _images/full-width.png

Support

Examples

Besides documentation examples, Django Suit demo application source code is also available on separate github repository: django-suit-examples. If you see anything in demo application, you can always go to this repository and see implementation and code in one of models.py or admin.py files

Supported apps

Besides Django admin, Django Suit supports following third-party apps:

Important

If you are using third-party apps with special admin support (like django-cms) you also need to add 'suit' before 'cms' in the list of `INSTALLED_APPS` in your `settings.py` file.

Contributing

Contributing

To contribute to Django Suit:

# Clone forked Django Suit repo
git clone git@github.com:YOUR_USERNAME/django-suit.git suit

# Install Django Suit from local fork in editable mode
pip install -e suit

If you wish to participate in development discussions, join our IRC channel #django-suit on irc.freenode.net

Tests

When contributing don’t forget to test your code by running:

./manage.py test suit

CSS/LESS

Contributing on specifically UI/CSS features/fixes have more requirements:

  • lessc compiler - http://lesscss.org/
  • watchdog package - pip install watchdog
  • django-suit-examples - it may be a good idea to add examples app to your project

While editing .less files, run following script, which automatically watches .less files for changes and compiles them to suit.css:

python suit/watch_less.py suit/static/suit/less/suit.less