Select2 et Django

Avoir une interface admin qui répond à tous les besoins de base c'est bien, mais pouvoir personnaliser nos besoins en ajax c'est mieux! Il existe un plugin django django_select2 qui reprend le projet select2 pour le mettre à la sauce Django. L'idée est de modifier les select de Django par défaut en barre de recherche Ajax pour associer des items entre eux.

Installer django_select2

Installez la librairie avec pip:

sudo pip install django_select2

Puis indiquez sa présence dans votre fichier de configuration:

eboutique/settings.py

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

Et ajoutons select2 à la gestion des url:

eboutique/urls.py

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)),
    url(r'^select2/', include('django_select2.urls')),
)

Exemple django_select2

Nous utiliserons les mêmes modèles que dans les chapitres précédents:

eboutique/models.py

#!/usr/bin/env python
#-*- 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)

Et notre interface xadmin:

eboutique/adminx.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-

import xadmin
from models import *
import forms

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 3
    style = 'table'
    
class ProductAdmin(object):
    model = Product
    inlines = [ProductItemAdminInline]
    form = forms.ProductForm

class ProductAttributeValueAdminInline(object):
    model = ProductAttributeValue
    extra = 3
    style = 'table'
    
class ProductAttributeAdmin(object):
    model= ProductAttribute
    inlines = [ProductAttributeValueAdminInline]


xadmin.site.register(Product, ProductAdmin)
xadmin.site.register(ProductAttribute, ProductAttributeAdmin)

Voici à quoi ressemble notre ManyToMany de base pour la partie productitem:

On remarque que niveau érgonomie / performence en cas de présence de milliers d'attributs, l'interface n'est pas top.

Tout d'abord, nous allons créer un champ select2 dans notre fichier formulaire:

backoffice/forms.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from django import forms
from backoffice.models import *
import django_select2

class MultiAttribute(django_select2.AutoModelSelect2MultipleField):

    queryset = ProductAttributeValue.objects
    search_fields = ['value__icontains', ]
    
class ProductItemForm(forms.ModelForm):
    
    attributes  = MultiAttribute(required=False)

Dans notre fichier adminx.py nous allons associer ce formulaire à ProductAdmin:

backoffice/adminx.py

class ProductItemAdminInline(object):
    model = ProductItem
    extra = 3
    style = 'tab'
    form = forms.ProductItemForm