Magazine High tech

5 astuces pour améliorer le site d'admin de Django

Publié le 13 juillet 2010 par Luc

Une des grandes forces de Django est le site d'administration automatiquement généré depuis le modèle. Celui-ci est largement configurable pour en faire un vrai outil de back-office en très peu de temps. Je vous propose dans cet article quelques astuces pour aller encore plus loin dans la personnalisation de ce site. Des astuces faciles à implémenter et qui vous rendront peut-être de bons services.

1. Définir le format d'un champ date

Par défaut les champs de type date sont sous un format anglophone 'yyyy-mm-dd' alors que nous autres francophones préféront faire les choses en sens inverse et sommes habitués à un format de type 'dd/mm/yyyy'. Heureusement Django permet de définir les formats acceptés via le paramètre input_formats de DateField. Il est donc très facile d'ajouter notre format de date préféré. Attention, en redéfinissant le DateField à ne pas écraser le widget spécifique du site d'administration qui propose un petit bouton affichant un calendrier (qui lui retournera la date au format anglophone qu'il faut donc continuer d'accepter).

Quelques lignes de code valent mieux que des longs discours. Ajouter le code suivant dans votre admin.py:

#les imports a rajouter
from django.forms.models import ModelForm
from django.contrib.admin.widgets import AdminDateWidget
from django.forms.fields import DateField
#definir un formulaire
class MyModelForm(ModelForm):
    #on definit les formats de date acceptes
    my_date = DateField(input_formats=['%d/%m/%Y','%Y-%m-%d'],

# et on affecte le widget de date du site d'administration

widget=AdminDateWidget(format='%d/%m/%Y'))
    class Meta:
        model = models.MyModel
class MyModelAdmin(admin.ModelAdmin):    
#le site d'admin utilisera notre formulaire

    form = MyModelForm
    ...

Le site d'administration devrait à présent accepter les dates selon les 2 formats définis et faciliter la saisie aux francophones. A noter que cette techinque fonctionne aussi pour les champs 'inline'.

2. Visualiser l'image d'un champ ImageField

Les champs de type ImageField permettent d'uploader une image dans le répertoire MEDIA_ROOT et de stocker en base l'accès vers ce fichier. Ils sont très utiles pour une application, un CMS par exemple, qui gère des images. Malheureusement, le site d'administration ne permet d'afficher que le chemin du fichier. On est parfois frustré de ne pas pouvoir visualiser cette image directement. Heureusement la aussi avec quelques lignes de code dans le admin.py, nous pouvons ajouter cette fonctionnalité:

Il faut pour cela tout d'abord surchager le widget AdminFileWidget afin de lui faire afficher l'image

from django.utils.safestring import mark_safe
from django.contrib.admin.widgets import AdminFileWidget

class AdminImageWidget(AdminFileWidget):
    """subclass the AdminFileWidget in order to display the image"""
    def render(self, name, value, attrs=None):
        output = []
        if value:
            output.append(u'<div>{0}</div>'.format(value))
        output.append(super(AdminFileWidget, self).render(name, value, attrs))
        if value and getattr(value, "url", None):
#l'image mais on pourrait aussi mettre un lien
img =
u'<div><img src="{0}" height="128px"/></div>'.format(value.url)
            output.append(img)
        return mark_safe(u''.join(output))

Et ensuite de surcharger la méthode formfield_for_dbfield qui permet de faire correspondre un contrôle à un champ du modèle.

class MyModelAdmin(admin.ModelAdmin):
    ...
    def formfield_for_dbfield(self, db_field, **kwargs):
       
if db_field.name == 'image': #on suppose que le modele a un champ image
    kwargs['widget'] = AdminImageWidget
       
kwargs.pop('request', None) #erreur sinon
        return db_field.formfield(**kwargs)
    return super(MyModelAdmin,self).formfield_for_dbfield(db_field, **kwargs)

Cette technique pourraît être utilisée pour enrichir d'autres types de contrôles (UrlField par exemple). C'est d'ailleurs ce que nous ferons par la suite.

Merci au passage à Psychic Origami pour la forte inspiration.

3. Inclure l'éditeur Tiny_MCE là où vous le voulez

Tiny_MCE est un éditeur HTML écrit en javascript qui peut s'intégrer très facilement au site d'administration de Django. Il suffit pour cela d'inclure le code suivant en faisant pointer vers les chemins de son code et d'un fichier javascript de configuration (textareas.js dans ce cas).

class EmailCfgAdmin(admin.ModelAdmin):
    class Media:
        """Use Tiny_MCE as content editor"""
        js = ('js/tiny_mce/tiny_mce.js', 'flatcms/js/textareas.js',)

L'inconvénient est qu'ainsi tous les contôles de type TextArea (TextWidget de Django) deviennent éditables avec Tiny_MCE. Imaginons le cas d'un éditeur d'e-mail ou l'on souhaiterait un TextArea éditable en HTML pour le corps du message et un second simplement en texte pour le corps de message alternatif pour les destinataires ne pouvant pas lire du HTML.

La encore la solution tient en quelques lignes.

Tout d'abord en définissant un "déselecteur" dans le textareas.js

tinyMCE.init({
    ...
    editor_deselector : "nomce",
    ...
});

Ensuite, en ajoutant la classe correspondante sur les contrôles à ne pas rendre éditable par Tiny_MCE:

class EmailCfgAdmin(admin.ModelAdmin):
    class Media:
        """Use Tiny_MCE as content editor"""
        js = ('js/tiny_mce/tiny_mce.js', 'flatcms/js/textareas.js',)
    fieldsets = (
        ...
        (_('Content'), {'fields': ('html_body', 'text_body')}),
    )
   
    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'text_body':
            kwargs['widget'] = AdminTextareaWidget(attrs={'class':'nomce'})                                    
        return super(EmailCfgAdmin,self).formfield_for_dbfield(db_field,**kwargs)

4. Faire afficher les valeurs d'un ManyToManyField en mode raw_id

Django propose un mode raw_id pour les ForeignKeyField et les ManyToManyField permettant de remplacer la boîte de sélection standard par un champ texte affichant les identifiants des objets de la relation.

Ceci est très pratique lorsque le nombre de possibilités dans la relation devient très grand et que l'on ne s'y retrouve plus dans la boîte de sélection. Le bouton loupe du raw_id va alors offrir un moyen de sélection bien pensé.

Malheureusement contraitrement au ForeignKeyField, le ManyToManyField ne permet pas d'afficher à droite du champ la valeur de la sélection. La lecture des identifiants de base étant difficile, je vous propose de pallier à ce problème avec le contrôle suivant:

Django ManyToManyField RawId

from django.contrib.admin.widgets import ManyToManyRawIdWidget
import unicodedata
from django.utils.html import escape

class VerboseManyToManyRawIdWidget(ManyToManyRawIdWidget):
    """
    A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
    in a <select multiple> box.
Display user-friendly value like the ForeignKeyRawId widget
    """

    def __init__(self, rel, attrs=None):
        self._rel = rel
        super(VerboseManyToManyRawIdWidget, self).__init__(rel, attrs)
    def label_for_value(self, value):
        values = value.split(',')
        str_values = []
        key = self.rel.get_related_field().name
        for v in values:
            try:
                obj = self.rel.to._default_manager.using(self.db).get(**{key: v})
                # gere les erreurs unicode
x = unicodedata.normalize('NFKD', u'%s' % obj).encode('ascii','ignore')
               
# supprime le code HTML
str_values += [escape(x)]
            except self.rel.to.DoesNotExist:
                str_values += [u'???']
        return u'&nbsp;<strong>%s</strong>' % (u',&nbsp;'.join(str_values))

De la même manière, on surcharge la méthode formfield_for_dbfield qui pour activer le contrôle:

class MyAdmin(admin.ModelAdmin):
...
def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name in ('groups',):
            kwargs.pop('request', None)
            kwargs['widget'] = VerboseManyToManyRawIdWidget(db_field.rel)
            return db_field.formfield(**kwargs)
        return super(MyAdmin,self).formfield_for_dbfield(db_field,**kwargs)

5. Ecraser le fichier lors de la mise à jour d'un FileField

Vous avez peut-être remarqué que lors de la mise à jour d'un champ de type FileField (ou ImageField qui en dérive), si le fichier uploadé existe déjà celui-ci est renommé. Ce comportement peut paraître plus sûr et éviter de supprimer des données nécessaires au site. Toutefois, cela cause aussi sur le serveur l'existence d'un grand nombre de fichiers obsolètes et force à renommer le fichier avec un nom différent de celui d'origine. Il est donc parfois préférable de demander l'écrasement du fichier existant avec le code suivant:

Ici tout se passe au niveau du modèle et non pas de l'admin. On définit tout d'abord un nouveau "Storage" qui définit comment les données sont stockées sur le serveur. On vérifie simplement si le fichier existe et si c'est le cas on le supprime.

from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name):

if self.exists(name):

self.delete(name)
return name

Puis on définit ce "Storage" comme mode de sauvegarde pour notre champ FileField

class MyModel(models.Model):
    file = models.FileField(upload_to='my_folder', storage=OverwriteStorage())

Avant chaque mise à jour, un éventuel fichier de même nom sera supprimé afin d'éviter de renommer celui qui est téléchargé.

Voilà ces quelques astuces qui me sont utiles assez régulièrement. N'hésitez pas à commenter si vous avez des propositions d'améliorations. J'espère que cela vous aidera à personnaliser le site d'administration de Django que personnellement j'aime beaucoup utiliser.


Retour à La Une de Logo Paperblog

A propos de l’auteur


Luc 11 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte

Dossier Paperblog