De base, Django vous simplifie la vie et vous propose de réserver une partie de votre projet pour consulter et éditer les entrées de données. Il s'agit d'une application implémentée de base dans votre projet que vous pouvez désactiver bien évidemment quand bon vous le semble.
L'interface d'administration est accessible en pointant vers /admin :
Quel Username attend Django? Si vous n'avez pas encore crée d'utilisateur, il y a obligatoirement présence d'un superutilisateur dans votre projet lorsque vous lancé la commande suivante:
python manage.py syncdb
(Voir le chapitre sur l' ORM Django )
Une interface en français
Par défaut la langue est en anglais, mais vous pouvez définir le français par defaut en changer la variable LANGUAGE_CODE du fichier settings.py
LANGUAGE_CODE = 'fr-fr'
Consulter l'interface admin
Entrez dans l'interface admin et vous devriez voir cela:
La première chose que l'on remarque c'est que notre modèle Product n'est pas présent. Pour l'ajouter il vous faudra éditer le fichier admin.py de votre application:
backoffice/admin.py
from django.contrib import admin from models import * # Register your models here. admin.site.register(Product)
Puis réactualisez votre interface d'administration:
Créer une entrée avec l'interface d'administration
Cliquons sur Product :
On remarque la présence d'un bouton Add product , ce qui permet d'ajouter une entrée dans la base pour ce modèle. Cliquons dessus
Remplissons les champs que nous avons définis dans notre modèle puis cliquons sur save :
Voila nous avons crée un produit en quelques clics sans créer aucune interface.
Modifier une entrée dans l'interface administration Django
Vous pouvez bien évidemment modifier l'item que vous voulez. Cliquons sur une entrée:
Modifions le code du produit puis validons la sauvegarde en restant sur la fiche produit "Save and continue editing". Ensuite cliquez sur le bouton history
On y voit un historique complet sur les modifications de l'entrée.
CRUD
L' interface d'administration Django est CRUD (Create Read Update et Delete), c'est à dire qu'elle est capable de faire les opérations de bases.
/admin/<app>/ => liste les modèles de l'application /admin/<app>/<model> => liste les objets du modèle /admin/<app>/<model>/add => crée un nouveau objet /admin/<app>/<model>/<id>/ => consulter un objet en particulier /admin/<app>/<model>/<id>/delete => supprime un objet en particulier
Personnaliser son interface d'administration
Il existe une batterie d'options qui vous permettront de mettre en avant les informations qui vous intéresse.
Par exemple ici nous avons la liste des items Product via deux colonnes: name et code :
backoffice/admin.py
from django.contrib import admin from models import * # Register your models here. class ProductAdmin(admin.ModelAdmin): list_display = ('name', 'code') admin.site.register(Product, ProductAdmin) admin.site.register(ProductItem)
Les options de ModelAdmin
action | une liste d'actions sur la page des listing |
action_on_top | l'emplacement des actions |
action_on_bottom | idem |
action_selection_counter | affiche un compteur à côté des action (True par défaut) |
date_hierarchy | listing des items avec éclatement des dates |
exclude | champs à exclure |
fields | champs à afficher |
fieldset | créer un fieldset |
filter_horizontal | filtre horizontal pour le many-to-many |
filter_vertical | filtre vertical pour le many-to-many |
form | associer un formulaire |
formfield_overrides | permet d'écraser des champs |
inlines | liste d'autre(s) ModelAdmin associé à celui-ci |
list_display | champs affiché par défaut |
list_display_links | champs qui pointent vers la fiche de l'item |
list_editable | champs modifiable depuis la page de listing |
list_filter | champs que l'on peut filtrer dans la page listing |
list_max_show_all | nombre d'items affichables lors de l'action "Show All" (200 par défaut) |
list_per_page | nombre d'items par page |
list_select_related | Dire à Django d'utiliser selected_related() pour les champs indiqués |
ordering | trier en fonction des champs indiqués |
paginator | par défaut django.core.paginator.Paginator est utilisé |
prepopulated_fields | rempli un champs en même temps qu'un autre (utilisé par exemple par un slug) |
preserve_filters | conserve les filtres en mémoire |
radio_fields | Remplace le select par des boutons radio |
raw_id_fields | Remplace le select par un input texte |
readonly_fields | Un champ en mode lecture seule |
save_as | Lors de la sauvegarde d'un objet, au lieu d'une modification il y a création |
search_fields | Créer un search box dans la page de listing |
view_on_site | Permet de créer un lien de l'objet vers le front |
add_form_template | path du template d'ajout |
change_form_template | path du template de modification |
change_list_template | path du template de listing |
delete_confirmation_template | path du template de confirmation de suppression |
delete_selected_confirmation_template | path du template suppression des éléments sélectionnés |
object_history_template | path de l'historique de l'objet |
Les méthodes de ModelAdmin
save_model(request, obj, form, change) | permet de créer des actions pre et post operation |
delete_model(request, obj) | permet de créer des actions pre et post delete operation |
(request, form, formset, change) | permet de créer des actions pre et post operation pour le formset |
get_ordering(request) | permet de créer un ordre d'affichage |
get_search_results(request, queryset, search_term) | créer la queryset de recherche |
save_related(request, form, formsets, change) | permet de créer des actions pre / post operation pour les objets associés au parent |
get_readonly_fields(request, obejct=None) | retourne une liste des champs non éditable |
get_prepopulated_fields(request, None) | retourne une liste des champs prérempli |
get_list_display(request) | retourne les champs affichés par défaut |
get_list_display_links(request, list_display) | retourne les champs qui pointent vers la fiche de l'item |
get_fields(request, obj=None) | retourne tous les champs |
get_fieldsets(request, obj=None) | retourne les fieldset |
get_list_filter(request) | retourne la même séquence que l'attribut list_filter |
get_search_fields(request) | retourne la même séquence que l'attribut search_fields |
get_inline_instances(request, obj=None) | retourne la même séquence que l'attirbut inlines |
get_urls() | retourne l'URL utilisé par le ModelAdmin |
get_form(request, obj=None, **kwargs) | retourne un ModelForm utilisé par le ModelAdmin |
get_formsets(request, obj=None) | yield InlineModelAdmins pour la vue change et add |
get_formsets_with_inlines(request, obj=None) | yield (Formset, InlineModelAdmin) pour la vue change et add |
formfield_for_foreignkey(db_field, request, **kwargs) | écrase le formfield par défaut pour une clé étrangère |
formfield_for_manytomany(db_field, request, **kwargs) | écrase le formfield par défaut pour un m2m |
formfield_for_choice_field(db_field, request, **kwargs) | écrase le formfield par défaut pour les choix multiple |
get_changelist(request, **kwargs) | retourne la class ChangeList utilisé pour le listing |
get_changelist_form(request, **kwargs) | retourne une classe ModelForm pour le Formset dans la page de modification |
get_changelist_formset(request, **kwargs) | retourne une classe ModelFormSet pour la page de modification |
has_add_permission(request) | retourne True si l'ajout d'un objet est autorisée |
has_change_permission(request, obj=None) | retourne True si la modification d'un objet est autorisée |
has_delete_permission(request, obj=None) | retourne True si la supression d'un objet est autorisée |
get_queryset(request) | retourne un queryset de toutes les instances de modèle qui peuvent être édités |
message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False) | envoie un messager à l'utilisateur |
get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) | retourne le paginator utilisé |
response_add(request, obj, post_url_continue=None) | Détermine le HttpResponse pour le vue add |
response_change(request, obj) | détermine le HttpResponse de la vue change |
response_delete(request, obj_display) | détermine le HttpResponse de la vue delete |
get_changeform_initial_data(request) | hook pour les données initiales du form change |
add_view(request, form_url='', extra_context=None) | la vue Django pour la page ADD |
change_view(request, object_id, form_url='', extra_context=None) | la vue Django pour la page CHANGE |
changelist_view(request, extra_context=None) | la vue Django pour la page CHANGELIST |
delete_view(request, object_id, extra_context=None) | la vue Django pour la page DELETE |
history_view(request, object_id, extra_context=None) | la vue Django pour la page HISTORY |
InlineModelAdmin options
Les modèles Inline peuvent récupérer ces options vu précédemment:
form fieldsets fields formfield_overrides exclude filter_horizontal filter_vertical ordering prepopulated_fields get_queryset() radio_fields readonly_fields raw_id_fields formfield_for_choice_field() formfield_for_foreignkey() formfield_for_manytomany() has_add_permission() has_change_permission() has_delete_permission()
Mais possèdent ces options spécifiques:
model | le modèle associé |
fk_name | le nom de la clé étrangère du modèle |
formset | par défaut BaseInlineFormSet |
form | le formulaire associé. Il sera passé par inlineformset_factory() |
extra | le nombre de lignes extra pour créer des nouvelles entrées |
max_num | le nombre maximum de form à afficher dans le inline |
min_num | le nombre minimum de form à afficher dans le inline |
raw_id_fields | remplace le select des clés étrangères par un input |
template | le template utilisé |
verbose_name | un nom verbeux pour le modèle (à mettre dans le class Meta) |
verbose_name_plural | le nom verbeux utilisé au pluriel |
can_delete | indique si l'objet inline est supprimable |
get_formset(request, obj=None, **kwargs) | retourne une classe BaseInlineFormSet pour l'utilisation des vues vue ADD/CHANGE. |
get_extra(request, obj=None, **kwargs) | retourne le nombre d'extra |
get_max_num(request, obj=None, **kwargs) | retourne le nombre maximum de form |
get_min_num(request, obj=None, **kwargs) | retourne le nombre minimum de form |
Remplacer les templates par défaut
Les templates par défaut se trouvent dans le dossier contrib/admin/templates/admin
Pour écraser ces templates vous devez créer un dossier admin dans le dossier template de votre projet .
Par exemple si vous voulez changer le template qui modifie les objets, vous devez créer le fichier suivant:
template/admin/backoffice/change_list.html
Tous les templates ne sont pas écrasables, voici ceux qui le sont:
app_index.html change_form.html change_list.html delete_confirmation.html object_history.html
Un site d'administration Django est représenté par une instance de django.contrib.admin.sites.AdminSite Vous pouvez personnaliser votre administration avec les options suivantes:
AdminSite.site_header | Texte renseigné dans la balise h1 en haut de page. Par défaut la valeur est Django Administration |
AdminSite.site_title | Texte renseigné à la fin de la balise title de chaque page. Par défaut la valeur est Django site admin |
AdminSite.index_title | Le texte renseigné au haut de la page d'index de l'administration. Par défaut: Site administration |
index_template | path vers un template personnalisé qui sera utilisé par la vue principale de l'administration |
app_index_template | path vers un template personnalisé qui sera utilisé par la vue principale de l'application |
login_template | path vers un template pour le login |
login_form | sous-classe de AuthenticationForm qui sera utilisée par la vue du login d'administration |
logout_template | path vers le template de logout |
password_change_template | template pour le changement de mot de passe |
password_change_done_template | template pour la confirmation du changement de mot de passe |
Exemples
Passons maintenant à la pratique avec les modèles suivants:
backoffice/models.py
# coding: utf-8 from django.db import models from django.utils import timezone PRODUCT_STATUS = ( (0, 'Offline'), (1, 'Online'), (2, 'Out of stock') ) 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") status = models.SmallIntegerField(choices=PRODUCT_STATUS, default=0) date_creation = models.DateTimeField(default=timezone.now(),blank=True, verbose_name="Date création") 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', related_name="product_item") 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)
Inlines
Nous voulons pouvoir créer des déclinaisons de produits directement sur la fiche produit:
backoffice/admin.py
from django.contrib import admin from models import * class ProductItemAdmin(admin.TabularInline): model = ProductItem class ProductAdmin(admin.ModelAdmin): model = Product inlines = [ProductItemAdmin,] admin.site.register(Product, ProductAdmin)
Résulat:
http://localhost:8000/admin/backoffice/product/1/
Voila notre fiche produit avec possibilité de créer des déclinaisons très simplement. La première chose que l'on remarque c'est la facilité du code pour créer une interface complète avec ajout / modification / suppression / controles de données . Il nous aura fallu 10 lignes de code, sans compter les modèles.
La seconde chose que l'on remarque c'est le select multiple pas du tout user-friendly pour le champ m2m attributes dans le cas où les attributs dépassent la centaine d'items. Il est possible de changer ce widget avec l'option filter_vertical :
class ProductItemAdmin(admin.TabularInline): model = ProductItem filter_vertical = ("attributes",) class ProductAdmin(admin.ModelAdmin): model = Product inlines = [ProductItemAdmin,] admin.site.register(Product, ProductAdmin)
Cela permet de passer de ce widget:
à celui la
Alors évidemment aucun widget n'est meilleur que l'autre mais l'un peut être meilleur que l'autre dans des situations différentes.
Editez les items directement dans le listing
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product inlines = [ProductItemAdmin,] list_display = ["id", "name", "price_ht", "price_ttc", "code"] list_editable = ["name", "price_ht", "price_ttc"]
http://localhost:8000/admin/backoffice/product/
Remplacer le select par un input
Dans certain cas il peut être intéressant de ne pas afficher un select mais plutôt un input simple . Dans cet input il faudra indiquer la clé de l'item que l'on désire associer au lieu de le sélectionner manuellement dans une liste. Le cas le plus évident sera les cas où les items seraient si nombreux que le chargement de la page serait trop lent. On utilisera donc une solution Ajax. Cette option permet donc de répondre à ce genre de besoin.
class ProductItemAdmin(admin.TabularInline): model = ProductItem raw_id_fields = ["attributes"]
http://localhost:8000/admin/backoffice/product/1/
Bouton radio au lieu d'un select
Vous pouvez remplacer un select par des boutons radio comme ceci:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product radio_fields = {"status": admin.VERTICAL}
http://localhost:8000/admin/backoffice/product/1/
Ajouter une searchbox
Il est possbile d'ajouter une searchbox qui effectue des recherches sur plusieurs champs:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status') search_fields = ('name', 'status')
http://localhost:8000/admin/backoffice/product/
Dans notre exemple nous recherchons tous les produits où status est égal à 1 , donc online
Créer un filtre
Notre exemple de recherche précédent ressemble assez à du bricolage. Comment l'utilisateur lambda peu-il savoir que le "online" est définit par la valeur 1? Il existe une autre option qui vous permet de filtrer les données plus proprement pour ce genre de cas:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status') list_filter = ('status', 'date_creation')
http://localhost:8000/admin/backoffice/product/
Vous pouvez affiner votre filtre en utilisant la classe admin.SimpleListFilter:
backoffice/admin.py
class ProductFilter(admin.SimpleListFilter): title = 'filtre produit' parameter_name = 'custom_status' def lookups(self, request, model_admin): return ( ('online', 'En ligne'), ('offline', 'Hors ligne'), ) def queryset(self, request, queryset): if self.value() == 'online': return queryset.filter(status=1) if self.value() == 'offline': return queryset.filter(status=0) class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status') list_filter = (ProductFilter,)
Option hiérarchie par date
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status') date_hierarchy = 'date_creation'
http://localhost:8000/admin/backoffice/product/
Vous remarquerez la présence d'un lien "Septembre 2019" au dessus des actions qui vous permet de naviguer / filtrer par des intervalles de dates
Trier par défaut
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status') ordering = ('-date_creation',)
http://localhost:8000/admin/backoffice/product/
Utiliser le signe - pour indiquer un ordre décroissant
Créer une action pour une sélection
Vous pouvez ajouter une action dans la liste des actions très simplement avec le module admin. Par exemple nous voulons changer le status des produits sélectionnés en "online":
backoffice/admin.py
def set_product_online(modeladmin, request, queryset): queryset.update(status=1) set_product_online.short_description = "Mettre en ligne" class ProductAdmin(admin.ModelAdmin): model = Product actions = [set_product_online]
http://localhost:8000/admin/backoffice/product/
Je sélectionne l'action puis je clique sur envoyer. Pour les produits sélectionnés, le status aura la valeur 1 comme il est définit dans la fonction set_product_online que j'ai crée.
Créer une colonne personnalisée
Vous pouvez créer une colonne personnalisée et y mettre les informations que vous voulez:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ('id', 'name', 'date_creation', 'status', 'tax') def tax(self, instance): return instance.price_ttc - instance.price_ht tax.short_description = "Taxes"
A noter que la colonne n'est pas triable.
Les paramètres de la colonne personnalisée
Nous avons vu dans l'exemple précédent que nous pouvions ajouter une colonne dans le listing en créant une fonction. Il existe des paramètres pour ces fonctions:
short_description = "xxx" → courte description allow_tags = True → autorise les balises HTML boolean = True → est un booléen admin_order_field = "xxx" → colonne triable
Modifier le queryset de l'admin
Il est possible de modifier le queryset par défaut. Prenons un exemple d'optimisation pour comprendre l'intérêt d'une telle option:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product list_display = ("code", "name", "items_code") def items_code(self, instance): string = "" for product_item in instance.product_item.all(): string+=product_item.code return string items_code.admin_order_field = 'product_item' def queryset(self, request): qs = super(ProductAdmin, self).queryset(request) qs = qs.prefetch_related("product_item") return qs
On passe de 100 requètes SQL à 5, vous pouvez voir le résultat dans votre debugger type " django-debug-toolbar "
Le paramètre items_code.admin_order_field = 'product_item' permet d'indiquer à Django qu'on aimerait rendre cette colonne triable.
Un autre exemple pour calculer le nombre de déclinaisons avec la méthode annotate :
backoffice/admin.py
from django.db.models import Count class ProductAdmin(admin.ModelAdmin): model = Product list_display = ("code", "name", "product_item_count") def product_item_count(self, obj): return obj.product_item_count def queryset(self, request): qs = super(ProductAdmin, self).queryset(request) return qs.annotate(product_item_count=Count('product_item'))
prepopulated_fields facilite vos slug
Il est possible de proposer à l'utilisateur un service qui au moment où celui-ci rempli un champ d'autres champs se remplissent en même temps. Souvent utilisé lors de l'écriture d'un titre, son slug se crée en temps réel. Prenoms l'exemple d'un nom pour créer un code:
backoffice/admin.py
class ProductAdmin(admin.ModelAdmin): model = Product prepopulated_fields = {"code": ("name", )}
http://localhost:8000/admin/backoffice/product/add/