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:
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
# 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 "{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 "{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 "{0} [{1}]".format(self.value, self.product_attribute)
Et notre interface xadmin:
eboutique/adminx.py
# 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
# 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