Xadmin Django thème bootstrap twitter

On ne va pas se mentir, de ce que nous avons vu de la partie admin ce n'est pas l'extase visuel. Il existe des projets -sur github notamment- qui proposent d'améliorer le design de votre projet Django . Le module que j'utilise le plus souvent est xadmin puisqu'en plus d'être sympatique visuellement (basé sur bootstrap twitter ) il propose quelques plugins très utiles, comme les exports en CSV , XML et JSON mais également des filtres très bien pensés.

Démo de xadmin

Arrêtons les éloges, vous pouvez visiter le site de démonstration pour vous faire votre propre idée: Demo xadmin .
Les identifiants sont admin / admin .

Installer xadmin

Un petit pip pour installer xadmin :

pip install django-xadmin

Le projet est sur Github: xAdmin

Implémenter xadmin

Il vous faudra indiquer la présence de xadmin et crispy_form dans le partie settings:

eboutique/settigns.py

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'backoffice',
    'crispy_forms',
    'xadmin',
)

Puis configurez le fichier urls.py

eboutique/urls.py

from django.conf.urls import patterns, include, url
import xadmin

xadmin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'eboutique.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    #url(r'^admin/', include(admin.site.urls)),

    url(r'^xadmin/', include(xadmin.site.urls)),
)

Nous avons défini que le module xadmin est accessible via l'uri /xadmin/

Premières captures

http://localhost:8000/xadmin

Vous pouvez vos connecter à l' interface xadmin en indiquant les identifiants que vous avez renseigné lors de votre syncb .

Responsive design

Format grand écran:

Format tablette:

Format smartphone:

Afficher ses modèles

Dans nos exemples nous utiliserons les modèles suivants:

backoffice/models.py

# coding: utf-8

from django.db import models

class Product(models.Model):
    """
    Produit : prix, code, etc.
    """
    
    class Meta:
        verbose_name = "Produit"
        
    name       = models.CharField(max_length=100)
    code       = models.CharField(max_length=10, null=True, blank=True, unique=True)
    price_ht   = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Prix unitaire HT")
    price_ttc  = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Prix unitaire TTC")
    
    def __unicode__(self):
        return u"{0} [{1}]".format(self.name, self.code)

class ProductItem(models.Model):
    """
    Déclinaison de produit déterminée par des attributs comme la couleur, etc.
    """
    
    class Meta:
        verbose_name = "Déclinaison Produit"
        
    product     = models.ForeignKey('Product')
    code        = models.CharField(max_length=10, null=True, blank=True, unique=True)
    code_ean13  = models.CharField(max_length=13)
    attributes  = models.ManyToManyField("ProductAttributeValue", related_name="product_item", null=True, blank=True)
       
    def __unicode__(self):
        return u"{0} [{1}]".format(self.product.name, self.code)
    
class ProductAttribute(models.Model):
    """
    Attributs produit
    """
    
    class Meta:
        verbose_name = "Attribut"
        
    name =  models.CharField(max_length=100)
    
    def __unicode__(self):
        return self.name
    
class ProductAttributeValue(models.Model):
    """
    Valeurs des attributs
    """
    
    class Meta:
        verbose_name = "Valeur attribut"
        ordering = ['position']
        
    value              = models.CharField(max_length=100)
    product_attribute  = models.ForeignKey('ProductAttribute', verbose_name="Unité")
    position           = models.PositiveSmallIntegerField("Position", null=True, blank=True)
     
    def __unicode__(self):
        return u"{0} [{1}]".format(self.value, self.product_attribute)

Comme pour la partie admin de Django , il est nécessaire d'indiquer à xadmin quel modèle nous voulons afficher dans l'interface. Créons un fichier adminx.py dans notre application:

backoffice/adminx.py

# coding: utf-8

import xadmin
from models import Product

xadmin.site.register(Product)

Vous pouvez maintenant visualiser votre vue Product :

http://localhost:8000/xadmin

Ajoutons un produit:

http://localhost:8000/xadmin/backoffice/product/add/

Ne remplissons pas les champs obligatoires pour voir la réaction de Django:

http://localhost:8000/xadmin/backoffice/product/add/

Validons notre produit correctement

http://localhost:8000/xadmin/backoffice/product/add/

Exporter les données

xadmin vous apporte des options en plus comme la possibilité d'exporter votre liste de données en CSV, XML ou JSON:

Inlines

Comme nous l'avons vu dans le chapitre admin , nous pouvons mettre à la suite de notre formulaire principal un formulaire pour les clés étrangères à l'aide du paramètre inlines :

backoffice/adminx.py

# coding: utf-8

import xadmin
from models import *

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 1
    
class ProductAdmin(object):
    model = Product
    inlines = [ProductItemAdminInline]

xadmin.site.register(Product, ProductAdmin)

Résultat de notre fiche produit:

Par défaut les items se succèdent les uns à la suite des autres, ce qui en terme d'ergonomie peut dans certains cas être génant, par exemple si les données inlines sont nombreuses. Il existe des options pour changer l'affichage:

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 3
    style = 'tab'

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 3
    style = 'table'

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 3
    style = 'accordion'

Créer une page personnalisée

Un backoffice n'est pas composé que de pages d'édition ou de listing de modèles, vous pouvez par exemple créer une page statistiques:

backoffice/adminx.py

from xadmin import views

class StatsView(views.CommAdminView):

    def get(self, request, *args, **kwargs):
        return self.template_response('xadmin/stats.html', self.get_context())

xadmin.site.register_view(r'stats/$', StatsView, name='stats')

CSS

Amusez-vous à modifier le CSS pour personnaliser votre interface

textarea:focus,input:focus{outline:none!important}
:focus{outline:none!important}
:active{outline:none!important}
.form-control:focus{border-color:#ededed;-webkit-box-shadow:none;box-shadow:none}
.form-horizontal .control-label{margin-top:8px}
.controls{padding:1px!important;margin:0}
.form-horizontal .control-label{padding-top:0!important}
.form-horizontal .controls{border-left:none}
.form-control{display:block;width:100%;padding:2px;font-size:1em;line-height:1.428571429;
color:#555;vertical-align:middle;background-color:#fff;background-image:none;
border:1px solid #ededed;border-radius:0;-webkit-box-shadow:none;box-shadow:none;
-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out .15s;
transition:border-color ease-in-out 0.15s,box-shadow ease-in-out .15s;margin-top:1px}
ul.nav-sitemenu{padding:0}
#left-side{padding:1px;margin:0}
#left-side a{background-color:#fff;color:#333}
#left-side .active a{background-color:#3276b1;color:#333;font-weight:700;color:#fff}
body{background-color:#eee}
#content-block{padding:0 20px!important}
.form-group{background-color:none;border-bottom:0}
.panel-body{margin:10px 1px}
input[type="file"]{max-width:300px;border:1px solid #efefef}
input[type="number"]{max-width:100px;border:1px solid #efefef;text-align:center}
input[type="email"]{max-width:300px;border:1px solid #efefef}
input[type="text"]{width:100%;border:1px solid #efefef}
input{font-weight:700}
.text-field{max-width:none;height:30px}
.panel .btn{padding:4px}
.panel .input-group-addon{padding:2px 5px}
.input-group-addon,.input-group-btn{width:0}
.textinput{max-width:500px;width:100%}
.text-field{width:70%!important}
.selectize-input{min-height:15px;line-height:13px}
input[data-upper]{text-transform:uppercase}
input[type="text"],input[type="number"],input[type="email"],input[type="file"]
{padding:0;padding-left:5px;line-height:1em;height:30px}
.navbar .navbar-brand{max-width:none}
.select2-container{width:99%}
.select2-choices{max-width:100%}

Les templates de xadmin

Vous trouverez ci-dessous tous les templates du projet xadmin . Pour écraser un template de base , il vous suffit de le recréer dans votre dossier template de votre application avec le même nom.

tree /usr/local/lib/python2.7/dist-packages/xadmin/templates/xadmin/

├── 404.html
├── 500.html
├── auth
│   ├── password_reset
│   │   ├── complete.html
│   │   ├── confirm.html
│   │   ├── done.html
│   │   ├── email.html
│   │   └── form.html
│   └── user
│       ├── add_form.html
│       └── change_password.html
├── base.html
├── base_site.html
├── blocks
│   ├── comm.top.setlang.html
│   ├── comm.top.theme.html
│   ├── comm.top.topnav.html
│   ├── modal_list.left_navbar.quickfilter.html
│   ├── model_form.before_fieldsets.wizard.html
│   ├── model_form.submit_line.wizard.html
│   ├── model_list.nav_form.search_form.html
│   ├── model_list.nav_menu.bookmarks.html
│   ├── model_list.nav_menu.filters.html
│   ├── model_list.results_bottom.actions.html
│   ├── model_list.results_top.charts.html
│   ├── model_list.results_top.date_hierarchy.html
│   ├── model_list.top_toolbar.exports.html
│   ├── model_list.top_toolbar.layouts.html
│   └── model_list.top_toolbar.refresh.html
├── edit_inline
│   ├── accordion.html
│   ├── base.html
│   ├── blank.html
│   ├── one.html
│   ├── stacked.html
│   ├── tab.html
│   └── tabular.html
├── filters
│   ├── char.html
│   ├── checklist.html
│   ├── date.html
│   ├── fk_search.html
│   ├── list.html
│   ├── number.html
│   ├── quickfilter.html
│   └── rel.html
├── forms
│   └── transfer.html
├── grids
│   └── thumbnails.html
├── includes
│   ├── box.html
│   ├── pagination.html
│   ├── sitemenu_accordion.html
│   ├── sitemenu_default.html
│   ├── submit_line.html
│   ├── toggle_back.html
│   └── toggle_menu.html
├── layout
│   ├── fieldset.html
│   ├── field_value.html
│   ├── field_value_td.html
│   ├── input_group.html
│   └── td-field.html
├── views
│   ├── app_index.html
│   ├── batch_change_form.html
│   ├── dashboard.html
│   ├── form.html
│   ├── invalid_setup.html
│   ├── logged_out.html
│   ├── login.html
│   ├── model_dashboard.html
│   ├── model_delete_confirm.html
│   ├── model_delete_selected_confirm.html
│   ├── model_detail.html
│   ├── model_form.html
│   ├── model_history.html
│   ├── model_list.html
│   ├── quick_detail.html
│   ├── quick_form.html
│   ├── recover_form.html
│   ├── recover_list.html
│   ├── revision_diff.html
│   └── revision_form.html
└── widgets
    ├── addform.html
    ├── base.html
    ├── chart.html
    ├── list.html
    └── qbutton.html

Intégrer font awesome

La version font awesome intégrée ne propose pas toutes les icones. Vous pouvez importer le projet font awesome en modifiant le fichier base.html

backoffice/template/xadmin/base.html

<link rel="stylesheet" type="text/css" href="{% static "css/font-awesome.min.css" %}" />

N'oubliez pas d'ajouter le projet font-awesome dans votre dossier static .

Ajouter une ligne dans votre menu

Les menus sont construit dans le template sitemenu_default.html

backoffice/templates/xadmin/includes/sitemenu_default.html

<li class="nav-header">Actions</li>
<li class="{% if request.path == "/xadmin/stats/" %}active{% endif %}">
<a href="{% url 'xadmin:stats' %}"><i class="fa-fw fa fa-line-chart"></i> Statistique</a>
</li>

À noter que request.path nécessite une action de votre part expliquée dans le chapitre context processor .

Résultat:

Associer une icone à une vue

Les icones cercle par défaut sont assez fades, vous pouvez ajouter votre propres icones font awsome en renseignant l'attribut model_icon :

backoffice/adminx.py

class ProductAdmin(object):
    model = Product
    model_icon = "fa fa-barcode"

class ProductAttributeAdmin(object):
    model= ProductAttribute
    model_icon = "fa fa-list-ol"

Résultat:

Manipuler depuis les valeurs POST envoyées

Vous pouvez redéfinir la méthode post pour ajouter des contrôles ou modifier des données lors de la soumission du formulaire.

backoffice/adminx.py

class ProductAdmin(object):
    model = Product 
  
    def post(self, request, *args, **kwargs):
        try:
            self.instance_forms()
            self.setup_forms()
            
            if self.valid_forms():
                self.message_user("Attention ceci et cela bla bla", 'warning')
                return self.get_response()      
        except:
            pass

        return super(TicketAdmin, self).post(request, *args, **kwargs)

Pour info vous pouvez récupérer les données soumis par le formulaire via l' objet request .

Voici un exemple qui récupère le nombre d'items de product

nb = int(request.POST.get("productitem_set-TOTAL_FORMS", "0"))