Experts en logiciels libres, cartographie et analyse de données, nous concevons des applications métiers innovantes.
Nos valeurs :
"%s : un•e expert•e, fort de %s ans d'expérience à votre écoute" % (name, xp)
La Plateforme de développement Web pour les perfectionnistes sous pression.
— www.django-fr.org
Simplicity should be a key goal in design and unnecessary complexity should be avoided.
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
La documentation précise certaines conventions de codage spécifiques à Django. La PEP 8 fait référence pour le reste.
L'architecture de Django s'inspire du principe MVC (Model, View, Controller) ou plutôt MTV (Model, Template, View) :
La fonction controller est gérée par l'URL dispatcher qui permet de faire correspondre des URLs sous forme d'expressions régulières à des vues.
Python parcours sys.path pour chercher les modules à importer
/usr/lib/python
,
/usr/local/lib/python
, ~/.local/lib/python
ainsi que le répertoire courant en général$ virtualenv env # crée l'environnement
$ ./env/bin/python # lance le python de l'environnement virtuel
(env) $ source env/bin/activate # ajoute ./env/bin en tête du PATH
(env) $ python # lance le python de l'environnement virtuel
(env) $ deactivate # rétablit le path
$ python # lance le python du système
Cela permet ainsi de créer plusieurs environnement avec différentes version de python, de Django, etc.
Quelques alternatives/extensions : virtualenvwrapper, anaconda, pyenv.
Proposez vos idées pour un tutorial original !
Sur les slides, des exemples basés sur une bibliothèque seront utilisés
À défaut d'idées dans l'audience, nous créerons une application de gestion de Todo lists :
$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install django
(venv) $ django-admin startproject library
(venv) $ cd library
(venv) $ ./manage.py runserver
Django vient avec ce serveur HTTP de développement (à ne surtout pas utiliser en production pour des raisons de performances et de sécurité)
settings.py
)└── library
├── library
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
library
: conteneur du projet (le nom est sans importance)library/manage.py
: utilitaire en ligne de commande permettant différentes actions sur le projetlibrary/library
: paquet Python effectif du projetlibrary/library/settings.py
: réglages et configuration du projetlibrary/library/urls.py
: déclaration des URLs du projetlibrary/library/wsgi.py
: point d'entrée pour déployer le projet avec WSGIIl est important de différencier la notion de projet et d'application.
Une application est une application Web qui fait quelque chose – par exemple un système de blog, une base de données publique ou une application de sondage
Un projet est un ensemble de réglages et d’applications pour un site Web particulier.
Un projet peut contenir plusieurs applications. Une application peut apparaître dans plusieurs projets.
— docs.djangoproject.com
pip
)manage.py startapp
crée automatiquement un patron d'app dans un nouveau
répertoireINSTALLED_APPS = [...]
) Django "impose" une organisation du code (noms et emplacements des fichiers)
views.py
dans le répertoire de l'app (ou dans le package views
de l'app)urls.py
) from django.conf.urls import url, include
urlpatterns = [
# soit on définit des couples url-vue
url(r'^$', une_vue),
# ... ou on inclut les urls depuis une autre app
url(r'', include('books.urls')),
]
$ ./manage.py startapp books
├── books/
│ ├── __init__.py
│ ├── admin.py
| ├── apps.py
│ ├── migrations/__init__.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py
models.py
: déclaration des modèles de l'applicationviews.py
: écriture des vues de l'applicationadmin.py
: comportement de l'application dans l'interface d'administrationtests.py
: Il. Faut. Tester.migrations
: modifications successives du schéma de la base de donnéesLa commande devra être lancée avec le bon nom de module (todo).
# settings.py
INSTALLED_APPS = (
'django.contrib.admin',
...
'books',
)
Django propose une configuration par défaut pour une base SQLite (cf : settings.py
).
Voici un exemple de configuration pour une base Postgresql :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'library_db',
'USER': 'library_user',
'PASSWORD': 'Cx12%a03oa',
'HOST': 'localhost'
}
}
$ ./manage.py migrate
Créer le projet (ex: formation)
Puis une application (ex: todo)
Enfin activer l'application
HttpRequest
et renvoie un objet HttpResponse
HttpRequest
correspondant à la requête du clientHttpRequest
en paramètreHttpResponse
en retour de la fonction ou de la classeHttpRequest
Permet d'accéder à de nombreux attributs tels que
Peut être lu comme un flux
cf. https://docs.djangoproject.com/fr/stable/ref/request-response/#httprequest-objects
HttpResponse
Permet de régler de nombreux attributs tels que
Peut être instancié directement avec le contenu comme paramètre
response = HttpResponse("foobar")
Peut être écrit comme un flux
request.write()
Est dérivé en sous-classes (ex. HttpResponseRedirect)
cf. https://docs.djangoproject.com/fr/stable/ref/request-response/#httpresponse-objects
En somme, une vue se résume à déclarer une url :
# books/urls.py
from django.conf.urls import patterns, include, url
import books.views
urlpatterns = [
url(r'^ma-vue$', book.views.ma_vue),
]
et retourner un contenu en fonction d'une requête
# books/views.py
from django.http import HttpResponse
def ma_vue(request):
return HttpResponse("mon contenu")
Créer une vue affichant "Bienvenue dans la Todo List"
Ce sera notre page d'accueil, elle sera accessible sur http://127.0.0.1:8000/
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
release = models.DateField(blank=True, null=True)
borrowed = models.BooleanField(default=False)
def __str__(self):
return self.title
Ces 3 types de champs suffisent pour l'appli todo
Pour python2, utiliser @python2_unicode_compatible
L'ajout de la classe Meta
dans un modèle permet de déclarer des options de métadonnées sur le modèle. Exemple :
class Book(models.Model):
...
class Meta:
db_table = 'book'
verbose_name = 'Book'
verbose_name_plural = 'Books'
ordering = ('-released', )
D'autres options permettent par exemple de :
Documentation : https://docs.djangoproject.com/fr/stable/ref/models/
Ici on utilisera uniquement verbose_name et ordering
Mentionner le fait que les noms de modele sont declinés de leur nom système
CharField
(une ligne avec longueur max)TextField
(multiligne)EmailField
(vérifie la syntaxe de l'adresse)IntegerField
et PositiveIntegerField
FloatField
DecimalField
(précision fixe, non soumis aux arrondis) AutoField
(IntegerField
incrémenté automatiquement)BooleanField
et NullBooleandField
DateField
, TimeField
et DateTimeField
DurationField
FileField
et ImageField
FilePathField
Chaque type de champs possède ses propres propriétés. Cependant, certaines sont communes et souvent utilisées comme :
verbose_name
: label du champnull
: valeur NULL autorisée ou non en base de donnéesblank
: valeur vide autorisée lors de la validation du champ dans un formulairedefault
: valeur par défaut pour une nouvelle instanceeditable
: le champ doit-il apparaître automatiquement dans les formulaireschoices
permet d'expliciter la liste de valeurs possiblesprimary_key
est la clé primaire (remplace id)unique
ajoute une contrainte d'unicitévalidators
permet d'ajouter des contraintes de validation au niveau du modèleDocumentation https://docs.djangoproject.com/fr/stable/ref/models/fields/#field-options
migrations/
. Il est conseillé d'enregistrer les migrations avec le code(venv) $ ./manage.py makemigrations
(venv) $ ./manage.py migrate
# admin.py
from django.contrib import admin
from books.models import Book
admin.site.register(Book)
L'interface d'administration est le "back-office" automatique" de Django qui liste les instances et par introspection des modèles, créer les formulaire de création/modification correspondants.
Elle est personnalisable et permet de modifier :
Documentation : https://docs.djangoproject.com/fr/stable/ref/contrib/admin/
Par défaut: Créer le modèle tâche ayant notamment les champs :
Pour accèder à l'admin de Django, vous aurez besoin d'un superutilisateur :
(venv) $ ./manage.py createsuperuser
But: Le modèle doit apparaitre dans l'interface d'administration avec les bons champs
# book/views.py
from django.shortcuts import render
from books.models import Book
def book_list(request):
books = Book.objects.all()
context = {
'books': books
}
return render(request, 'books/book_list.html', context)
Ce style de vue est dit "Function-based" (par opposition à "Class-based").
{# books/templates/books/book_list.html #}
<h1>Liste des livres</h1>
{% if books %}
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
{% else %}
<p>Aucun livre !</p>
{% endif %}
Documentation: https://docs.djangoproject.com/fr/stable/topics/templates/
Routeur basé sur des regex, avec un préfixe par application. Ce préfixe n'est pas obligatoire mais permet de classer les vues afin d'éviter les conflits, par exemple :
book/list
et book/add
movie/list
et movie/add
# books/urls.py
from django.conf.urls import patterns, include, url
urlpatterns = [
url(r'^book_list$', books.views.book_list, name='book_list'),
]
# library/urls.py
...
urlpatterns = [
...
url(r'^books/', include('books.urls', namespace="books")),
]
Une vue basée sur une fonction Django est simplement une fonction Python qui prend en entrée une requête HTTP et retourne une réponse HTTP.
Cette réponse peut être une page HTML, un document XML, une redirection, une erreur 404, ...
Ces vues sont généralement écrites dans le fichier views.py
de l'application.
# some_app/views.py
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
Une vue basée sur une classe Django est simplement une classe Python préformatée qui prend en entrée une requête HTTP et retourne une réponse HTTP.
# some_app/views.py
from django.http import HttpResponse
from django.views.generic import View
class CurrentDatetimeView(View):
def get(self, request, * args, ** kwargs):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
C'est un simple fichier texte qui peut générer n'importe quel format de texte (HTML, XML, CSV, ...).
Un template a accès à des variables qui lui auront été passées via un contexte par la vue.
Par défaut, Django fournit sa propre syntaxe de template mais il est possible de la remplacer par un autre moteur comme Jinja2.
Pour retrouver les templates d'un projet, Django se base sur le réglage TEMPLATES
. Le plus souvent on stocke les templates :
<app>/templates/<app>
.
Ils seront retrouvés grâce au loader activé par défaut quand la clé APP_DIRS
vaut True
.templates/
à la racine du projet qu'il faudra déclarer dans la clé DIRS
.Dans ce mécanisme de découverte, l'ordre importe : cela permet de surcharger les templates d'autres applications.
{{ ma_variable }}
Il est possible de modifier l'affichage d'une variable en appliquant des filtres. Un filtre peut prendre (ou non) un argument. Les filtres peuvent être appliqués en cascade. Quelques exemples :
{{ name|lower }}
{{ text|linebreaksbr }}
{{ current_time|time:"H:i" }}
{{ weight|floatformat:2|default_if_none:0 }}
Django fournit nativement une liste de filtres assez intéressante et il est possible d'écrire des filtres personnalisés facilement.
Les tags sont plus complexes que les variables, ils peuvent créer du texte ou de la logique (boucle, condition, ...) dans la tempate.
{% if condition %} .. {% else %} .. {% endif %}
{% for item in list %} .. {% endfor %}
<a href="{% url 'books:book_detail' book.pk %}">Django book</a>
Django fournit aussi plusieurs tags nativement et il est possible d'écrire ses propres tags.
L'intérêt de l'héritage de template est par exemple de pouvoir créer un squelette HTML contenant tous les éléments communs du site et définir des blocs que chaque template pourra surcharger.
Dans une template parent, la balise {% block %}
permet de définir les blocs surchargeables.
Dans une template enfant, la balise {% extends %}
permet de préciser de quel template celui-ci doit hériter.
{# templates/base.html #}
<html>
<head>
<title>
{% block title %}
...
{% endblock %}
</title>
<link href="styles.css" rel="stylesheet" />
</head>
<body>
<header>Entête commune à tout le site</header>
<section>
{% block content %}
...
{% endblock %}
</section>
<footer>Pied de page commun à tout le site</footer>
</body>
</html>
{# books/templates/books/book_list.html #}
{% extends "base.html" %}
{% block title %}
Liste des livres
{% endblock %}
{% block content %}
{% if books %}
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
{% else %}
<p>Aucun livre !</p>
{% endif %}
{% endblock %}
L'intérêt de l'inclusion de template est de pouvoir factoriser du code de template :
Cela peut être utile dans différents cas :
{# templates/base.html #}
<html>
<head>
<title>
{% block title %}
...
{% endblock %}
</title>
<link href="styles.css" rel="stylesheet" />
</head>
<body>
{% include 'templates/header.html' %}
<section>
{% block content %}
...
{% endblock %}
</section>
{% include 'templates/footer.html' %}
</body>
</html>
ROOT_URLCONF
dans les settings), souvent projet/urls.py
urlpatterns
urls.py
de chaque application.HttpRequest
) puis toutes les valeurs capturées dans la regex.Le module URLconf est un fichier urls.py
contenant une variable urlpatterns
:
# library/urls.py
from django.conf.urls import patterns, url
urlpatterns = [
url(r'^myview$', myapp.views.my_view, name='my_view'),
...
]
À chaque vue peut être associé un nom système qui pourra servir lors de l'inversion d'une url.
Souvent, l'URLconf racine inclura les modules URLconf de chaque application :
# urls.py
from django.conf.urls import patterns, url
urlpatterns = [
url(r'^myapp/', include('myapp.urls', namespace='myapp')),
...
]
À chaque application peut être associé un namespace qui pourra servir lors de l'inversion d'une url.
url(r'^myview$', my_view, name='my_view')
La vue aura en argument seulement l'objet HttpRequest
.
url(r'^myview_by_month/(?P<year>\d{4})/(?P<month>\d{2})/$',
MyViewByMonth.as_view(),
name='myview_by_month'),
La vue aura en argument l'objet HttpRequest
, puis les valeurs trouvées dans l'expression régulière (ex: request, year=2014, month=12
).
La résolution d'une url consiste à partir d'une URL et trouver la regex correspondant ainsi que ses paramètres.
La résolution inversée part d'un nom système de vue et de paramètres pour arriver à une URL.
Ceci permet de modifier les motifs sans avoir à retoucher toutes les fois où
l'url est appelée (par exemple dans un <a href>
).
# project/urls.py
urlpatterns = [
url(r'^book/', include('book.urls', namespace='book')),
]
# book/urls.py
urlpatterns = [
url(r'^list$', book.views.list, name='list'),
url(r'^edit/(?P<pk>\d+)$', book.views.edit, name='edit'),
]
Dans un template:
<a href="{% url 'book:list' %}">Liste des livres</a>
Dans du code python :
from django.urls import reverse
return HttpResponseRedirect(reverse('book:edit', pk=12))
Créer la vue liste des tâches et détail d'une tâche
Créer auparavant quelques tâches via l'interface d'administration
Pour la liste de toutes les tâches : Task.objects.all()
et pour le détail Task.objects.get(pk=<pk>)
django.forms
Django possède une bibliothèque assez complète de gestion de formulaires : django.forms
.
Les concepts principaux sont les suivants:
Widget
: permet de gérer et faire le rendu d'un widget HTML (ex: un champ <input>
, <textarea>
, ...)Field
: permet de gérer l'initialisation et la validation d'un champ de formulaireForm
: permet de gérer un ensemble de champs de formulaires, ainsi que l'initialisation, le rendu et la validation du formulaire globalModelForm
: permet de gérer des formulaires basés sur des modèles (création / modification d'une instance du modèle)# contact/forms.py
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
__init__
: permet de personnaliser l'intialisation du formulaire (par exemple : pré-remplir le champ sender
par l'email de l'utilisateur connecté)clean
: permet de personnaliser la validation du formulaire (par exemple : vérifier que sender
a bien été fourni si cc_myself
a été coché)La bibliothèque django.forms
fournit plus de 20 types de champs différents, dont voici les principaux :
CharField
, SlugField
, RegexField
, EmailField
, UrlField
FloatField
, IntegerField
BooleanField
, NullBooleandField
ChoiceField
, MultipleChoiceField
DateField
, DateTimeField
, TimeField
, DurationField
FileField
, FilePathField
, ImageField
Certains modules annexes fournissent leurs propres champs et il est possible d'écrire des champs personnalisés.
Rappel : ces champs gérent les données, ce sont les widgets qui gèrent la manière
dont ils sont saisis. Par exemple, un CharField
gérera du texte et aura par défaut
un widget TextInput
, mais il est possible de spécifier un widget un widget Textarea
.
from django.shortcuts import render
from django.http import HttpResponseRedirect
from myapp.forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Traite les données dans form.cleaned_data puis redirige
# ...
return HttpResponseRedirect('/thanks/')
else:
form = ContactForm()
# Affiche le formulaire
return render(request, 'contact.html', {'form': form})
def contact(request):
form = ContactForm(request.POST or None)
if form.is_valid():
# ...
return HttpResponseRedirect('/thanks/')
return render(request, 'contact.html', {'form': form})
<form action="/contact/" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
L'utilisation du tag {% csrf_token %}
est importante car elle permet de protéger le formulaire des attaques de type CSRF (Cross Site Request Forgeries).
Un formulaire peut être rendu de différentes manières :
Des packages de la communauté (cripsy_forms, floppyforms, material) permettent d'afficher le bon markup pour un framework HTML/CSS.
La classe ModelForm
permet de créer automatiquement des formulaires basés sur des modèles.
Le fonctionnement est assez semblable à celui des formulaires classiques à quelques différences près :
Meta
est nécessaire pour préciser sur quel modèle doit se baser le formulaire__init__
prend en argument l'instance du modèle à modifier (ou None
dans le cas d'une création)save
qui permet d'enregistrer l'instance éditée via le formulaireeditable=False
# book/models.py
class Book(models.Model):
title = models.CharField(max_length=100)
release = models.DateField()
borrowed = models.BooleanField(default=False)
# book/forms.py
class AddBookForm(forms.ModelForm):
class Meta:
model = Book
fields = ('title', 'release')
# book/views.py
def add_book(request):
form = AddBookForm(request.POST or None)
if form.is_valid():
form.save()
return HttpResponseRedirect('/books/')
return render(request, 'add_book.html', {'form': form})
Créer les vues d'ajout et modification d'une tâche
Afficher également des liens depuis la liste de tâches
La bibliothèque django.models
fournit différents champs spécifiques pour représenter les relations entre modèles.
models.ForeignKey
: représente une relation de type 1-Nmodels.ManyToManyField
: représente une relation de type N-Nmodels.OneToOneField
: représente une relation de type 1-1Le champ ForeignKey
doit être déclaré avec comme premier argument le modèle auquel il est lié par cette relation 1-N. L'argument optionnel related_name
permet de nommer la relation inverse à partir de ce modèle lié.
La représentation de ce champ en base de données est une contrainte de type clé étrangère.
Un livre est associé à un auteur, un auteur peut avoir écrit plusieurs livres.
# book/models.py
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, related_name='books')
Le champ ManyToManyField
doit être déclaré de la même manière que le champ ForeignKey
.
La représentation de ce champ en base de données est une table contenant deux clés étrangères vers les deux tables des modèles liés.
Un livre est associé à plusieurs catégories, plusieurs livres peuvent appartenir à une même catégorie.
# book/models.py
class Category(models.Model):
label = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=100)
categories = models.ManyToManyField(Category, related_name='books')
La déclaration du OneToOneField
est similaire.
La représentation de ce champ en base de données est une clé étrangère possédant une contrainte d'unicité.
Un livre est associé à un seul code barre, un code barre correspond à un seul livre.
# book/models.py
class BarCode(models.Model):
code = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=100)
barcode = models.OneToOneField(BarCode, related_name='book')
Mettre en place une modélisation gérant des listes de tâches partagées entre utilisateurs
Ici l'attendu est
django.db.backends.postgresql
django.db.backends.mysql
django.db.backends.oracle
django.db.backends.sqlite3
DateRangeField
, JSONField
, etc)settings.py
(variable DATABASES
), ainsi que la configuration du nom de la base, du serveur, et de l'authentificationDocumentation https://docs.djangoproject.com/fr/stable/ref/databases/
Pour créer une instance, il suffit de l'instancier en passant en argument les noms des attributs du modèle. L'instance dispose ensuite d'une méthode save
qui permet de l'enregistrer en base de données.
>>> b = Book(name='Two scoops of django',
release=date(2013, 08, 31))
>>> b.save()
La même méthode save
est utilisée pour enregistrer en base de données des modifications sur l'instance.
>>> b.name ='Two scoops of django - Best practices'
>>> b.save()
Pour supprimer une instance, il suffit d'appeler la méthode delete()
qui permet de supprimer directement la ligne en base de données.
>>> b = Book(name='Two scoops of django',
release=date(2013, 08, 31))
# Création en BDD
>>> b.save()
# Suppression
>>> b.delete()
Manager
& Queryset
Pour récupérer une ou plusieurs instances, il faut construire un Queryset
via un Manager
associé au modèle.
Manager
?Un Manager
est l'interface à travers laquelle les opérations de requêtage en
base de données sont mises à disposition d'un modèle Django. Chaque modèle
possède un Manager
par défaut accessible via la propriété objects
.
Queryset
?Un Queryset
représente une collection d'objets provenant de la base de
données. Cette collection peut être filtrée, limitée, ordonnée, ... grâce à
des méthodes qui correspondent à des clauses SQL.
A partir d'un queryset il est possible d'obtenir un autre queryset plus spécialisé. Un queryset est paresseux (la requête SQL n'est faite que lorsqu'il n'est plus possible de la retarder).
>>> Book.objects.all()
Les méthodes de filtrage principalement utilisées sont filter
et exclude
. Il est possible de les chaîner.
>>> Book.objects.filter(
release__gte=date(2013, 01, 01)
).exclude(
borrowed=True
)
>>> Book.objects.exclude(borrowed=True).order_by('title')
__iexact
pour une recherche insensible à la casse__contains
pour chercher à l'intérieur__lt
, __lte
, __gt
,__gte
pour les inégalitésDocumentation : https://docs.djangoproject.com/fr/stable/ref/models/lookups/
books = Book.objects.filter(title__startswith="Le")
books = Book.objects.filter(release__year__lt=1950) \
.exclude(title__icontains="fleurs")
Pour l'opérateur OU ou des requêtes plus complexes, utiliser django.db.models.F
et django.db.models.Q
, qui permettent des combinaisons avant exécution.
La méthode get
permet de récupérer une instance particulière.
>>> Book.objects.get(pk=12)
La méthode ne peut retourner qu'une instance précise, il faut donc que le filtre fourni ne soit pas ambigu. Il faut veiller à filtrer sur un champ unique
(ou un ensemble de champs uniques ensemble).
Book.DoesNotExist
sera levée (de manière générique : <Model>.DoesNotExist
).Book.MultipleObjectsReturned
(<Model>.MultipleObjectsReturned
).Pour les relations entre instances (ForeignKey
, ManyToManyField
), Django fournit un Manager
spécifique nommé RelatedManager
. Il permet notamment de :
ForeignKey
vers une instance donnéeManyToManyField
Retrouver les livres disponibles d'un auteur :
>>> author = Author.objects.get(pk=25)
>>> author.books.filter(borrowed=False)
Ajouter un livre à une catégorie :
>>> category = Category.objects.get(pk=5)
>>> book = Book.objects.get(pk=12)
>>> category.books.add(book)
Supprimer l'association de livres à une catégorie :
>>> category = Category.objects.get(pk=5)
>>> category.books.clear()
Mettre en place un formulaire de filtrage de tâches :
django_extensions
: plusieurs extensions et outils d'administration très pratiquesdjango_debug_toolbar
: une barre latérale permettant de faire du debug et du profiling page par pagedjango_hijack
: permet de se connecter avec un autre utilisateur sans se déconnecterdjango_extra_views
: apporte d'autres CBV pour des formulaires et vues toujours plus rapidesdjango_braces
: apporte des mixins pour vos CBVfactory_boy
: création de grappes de données pour les testsdjango_jenkins
: intégration à Jenkinsdjango_compressor
: compression des fichiers statiquesdjango_pagination
: affichage de listes paginéesdjango_sorting
: affichage de tableaux triablesdjango_filters
: création de liste filtréesdjango_crispy_forms
: affichage de forms avec Bootstrap/Foundation/Uniformdjango_breadcrumbs
: création de fil d'arianedjango_xworkflows
: gestion de workflowsdjango_modeltranslation
: gestion de modèles multilingueseasy_thumbnails
ou versatileimagefield
: gestion de miniatures pour les imagesdjango_tinymce
: intégration d'un widget TinyMCETable of contents | t |
---|---|
Exposé | ESC |
Autoscale | e |
Full screen slides | f |
Presenter view | p |
Source files | s |
Slide numbers | n |
Blank screen | b |
Notes | 2 |
Help | h |