952 lines
16 KiB
Markdown
952 lines
16 KiB
Markdown
# Django Initiation
|
||
|
||
par
|
||
|
||
Julien Palard <julien@palard.fr>
|
||
|
||
https://mdk.fr
|
||
|
||
notes:
|
||
|
||
Introduce yourself!
|
||
|
||
|
||
# Django
|
||
|
||
Django est une infrastructure d’applications web populaire et robuste.
|
||
|
||
> The web framework for perfectionists with deadlines.
|
||
|
||
|
||
## Django : qui l’utilise ?
|
||
|
||
- Instagram
|
||
- Pineterest
|
||
- Mozilla
|
||
- National Geographic
|
||
- [Washington Post](https://www.djangoproject.com/weblog/2005/dec/08/congvotes/)
|
||
- ...
|
||
|
||
|
||
## Vocabulaire
|
||
|
||
Dans Django on va avoir principalement des `models`, des `vues`, des
|
||
`templates`, et des `urls`.
|
||
|
||
|
||
# Les bonnes bases
|
||
|
||
On travaillera toujours dans un `venv` :
|
||
|
||
```bash
|
||
python -m pip install django
|
||
```
|
||
|
||
# La théorie — Projet
|
||
|
||
Pour démarrer un projet, une commande :
|
||
|
||
```bash
|
||
django-admin startproject project
|
||
```
|
||
|
||
## La théorie — App
|
||
|
||
Une fois dans le projet, pour créer une application, une commande :
|
||
|
||
```bash
|
||
python manage.py startapp watch
|
||
```
|
||
|
||
notes:
|
||
|
||
(et ajout dans `settings.py`)
|
||
|
||
|
||
## La théorie — Modèle
|
||
|
||
Un « modèle » est la description d’une table.
|
||
|
||
Ça rappelle un ORM, mais ça permet beaucoup plus de choses en Django.
|
||
|
||
notes:
|
||
|
||
- admin
|
||
- forms
|
||
- serializers (API)
|
||
- class based views
|
||
|
||
|
||
## La théorie — Modèle
|
||
|
||
Par exemple :
|
||
|
||
```python
|
||
class Website(models.Model):
|
||
host = models.CharField(max_length=512)
|
||
is_up = models.BooleanField(null=True, blank=True)
|
||
last_check = models.DateTimeField(auto_now_add=True)
|
||
```
|
||
|
||
notes:
|
||
|
||
Prendre le temps d’expliquer les fields (leur relation avec la DB), et
|
||
les differents endroits ou Django peut reutiliser cette information
|
||
(widgets, validation, ...).
|
||
|
||
|
||
## Première interface d’admin
|
||
|
||
En une ligne, pourquoi pas :
|
||
|
||
```python
|
||
admin.site.register(Website)
|
||
```
|
||
|
||
## La théorie — la DB
|
||
|
||
- PostgreSQL
|
||
- MySQL
|
||
- sqlite
|
||
- ...
|
||
|
||
notes:
|
||
|
||
Leur faire croire 2 secondes qu’on va devoir s’installer et se
|
||
configurer un serveur de base de donnée :D
|
||
|
||
Leur expliquer que sqlite est utilisé dans les applications : pas
|
||
besoin d’installer un postgresql pour utiliser Firefox, pourtant
|
||
Firefox a besoin d’une base de donnée.
|
||
|
||
|
||
## La théorie — La DB
|
||
|
||
```bash
|
||
python manage.py makemigrations
|
||
python manage.py migrate
|
||
```
|
||
|
||
notes:
|
||
|
||
On expliquera plus tard, leur dire que ça crée la DB et que le but
|
||
maintenant c’est surtout d’aller tester ça :)
|
||
|
||
|
||
## L’interface d’administration
|
||
|
||
On a une DB, mais pas encore d’utilisateur admin dedans :
|
||
|
||
```bash
|
||
python manage.py createsuperuser
|
||
```
|
||
|
||
## Terminé
|
||
|
||
On a terminé, on peut démarrer le serveur :
|
||
|
||
```bash
|
||
python manage.py runserver
|
||
```
|
||
|
||
# La pratique
|
||
|
||
```bash
|
||
django-admin startproject project
|
||
cd project
|
||
python manage.py startapp watch
|
||
```
|
||
|
||
notes:
|
||
|
||
Biiien prendre le temps d’expliquer l’arborescence, de se promener,
|
||
d’y lire les commentaires.
|
||
|
||
|
||
## La pratique
|
||
|
||
Ajout de l’app `watch` dans `project/settings.py` :
|
||
|
||
```python
|
||
INSTALLED_APPS = [
|
||
"watch",
|
||
...,
|
||
...,
|
||
]
|
||
```
|
||
|
||
|
||
## Les modèles
|
||
|
||
On va mettre celui-ci dans `watch/models.py`.
|
||
|
||
```python
|
||
class Website(models.Model):
|
||
host = models.CharField(max_length=512)
|
||
is_up = models.BooleanField(null=True, blank=True)
|
||
last_check = models.DateTimeField(auto_now_add=True)
|
||
```
|
||
|
||
|
||
## Première interface d’admin
|
||
|
||
Et ça dans `watch/admin.py`.
|
||
|
||
```python
|
||
from watch.models import Website
|
||
|
||
|
||
admin.site.register(Website)
|
||
```
|
||
|
||
|
||
## Création de la DB
|
||
|
||
```bash
|
||
python manage.py makemigrations
|
||
python manage.py migrate
|
||
```
|
||
|
||
notes:
|
||
|
||
Expliquer les deux étapes.
|
||
|
||
|
||
## Les modèles
|
||
|
||
Désambiguons `makemigrations` et `migrate` d’abord.
|
||
|
||
|
||
## L’interface d’administration
|
||
|
||
On a une DB, mais pas encore d’utilisateur admin dedans :
|
||
|
||
```bash
|
||
python manage.py createsuperuser
|
||
```
|
||
|
||
## Terminé
|
||
|
||
On a terminé, on peut essayer maintenant ?
|
||
|
||
```bash
|
||
python manage.py runserver
|
||
```
|
||
notes:
|
||
|
||
Leur faire faire ça dans un **autre** shell.
|
||
|
||
puis les laisser jouer avec l’interface d’admin, créer quelques sites...
|
||
|
||
|
||
## L’interface d’administration
|
||
|
||
Les modèles, leurs `fields` ne servent donc pas qu’a l’ORM, cette
|
||
interface d’admin nous a demandé une ligne de code.
|
||
|
||
notes:
|
||
|
||
Si ce n’est pas déjà fait, leur faire ajouter des `__str__`.
|
||
|
||
|
||
## Astuce
|
||
|
||
On peut passer beaucoup de temps à peaufiner l’interface d’admin,
|
||
repoussez ça après avoir livré une première version.
|
||
|
||
|
||
# Manipulation des modèles
|
||
|
||
## Mise en pratique
|
||
|
||
Créez le modèle `Check` avec les champs `is_up`, `date`, `website`, et
|
||
`message`.
|
||
|
||
notes:
|
||
|
||
Pour le champ `website` vous aurez besoin d’un `models.ForeignKey`, RTFM.
|
||
|
||
|
||
## L’admin
|
||
|
||
Ajoutez une interface d’admin pour ce modèle, et ajoutez à la main quelques « *checks* ».
|
||
|
||
notes:
|
||
|
||
Les faire tester ça.
|
||
|
||
|
||
## Personalisons
|
||
|
||
Dans chaque modèle, un `__str__` aide l’admin à être lisible.
|
||
|
||
|
||
## Personalisons
|
||
|
||
Dans `admin.py` on peut préciser les colonnes qu’on veut afficher :
|
||
|
||
```python
|
||
@admin.register(Website)
|
||
class WebsiteAdmin(admin.ModelAdmin):
|
||
list_display = ("host", "is_up", "last_check")
|
||
|
||
|
||
@admin.register(Check)
|
||
class CheckAdmin(admin.ModelAdmin):
|
||
list_display = ("website", "date", "is_up", "message")
|
||
```
|
||
|
||
|
||
# Les URLs & les vues
|
||
|
||
Changons complètement de sujet : les URLs et des vues.
|
||
|
||
|
||
## Les URLs
|
||
|
||
Dans `project/urls.py` on va se rajouter une URL pour la page d’accueil :
|
||
|
||
```python
|
||
from watch import views
|
||
|
||
urlpatterns = [
|
||
...,
|
||
...,
|
||
path("", views.index, name="index"),
|
||
]
|
||
```
|
||
|
||
notes:
|
||
|
||
C’est un `path`, un chemin, c’est le chemin vide.
|
||
|
||
|
||
## Les URLs
|
||
|
||
On aurait pu rajouter :
|
||
|
||
```python
|
||
path("about", views.about, name="about"),
|
||
path("help", views.help, name="help"),
|
||
...
|
||
```
|
||
|
||
## include
|
||
|
||
Petite parenthèse, on aurait pu mettre un `urlpatterns` dans
|
||
`watch/urls.py`, et les inclure dans `project/urls.py` en utilisant :
|
||
|
||
```python
|
||
from django.urls import include
|
||
|
||
[...]
|
||
|
||
path("", include("watch.urls")),
|
||
```
|
||
|
||
notes:
|
||
|
||
C’est pratique pour « ancrer » un ensemble de chemin sous un autre
|
||
chemin : pour se faire une hierarchie.
|
||
|
||
|
||
## namespaces
|
||
|
||
Les espaces de nommage permettent de désambiguer les urls nommées :
|
||
|
||
`index` est le nom de la page d’accueil de l’interface d’admin ou de la
|
||
page d’accueil de votre application ?
|
||
|
||
|
||
## namespaces
|
||
|
||
Avec les espaces de nommage, on a donc :
|
||
|
||
- `admin:index`
|
||
- `watch:index`
|
||
|
||
sans ambiguité.
|
||
|
||
notes:
|
||
|
||
Utiliez-en, c’est bien.
|
||
|
||
|
||
## Les vues
|
||
|
||
Dans `watch/views.py` :
|
||
|
||
```python
|
||
from django.http import HttpResponse
|
||
|
||
|
||
def index(request):
|
||
html = "<html><body><h1>Website Watcher</h1></body></html>"
|
||
return HttpResponse(html)
|
||
```
|
||
|
||
|
||
notes:
|
||
|
||
C’est bien mais écrire du HTML dans du Python c’est pas élégant.
|
||
|
||
|
||
## Les vues
|
||
|
||
Mieux :
|
||
|
||
```python
|
||
from django.http import HttpResponse
|
||
from django.shortcuts import render
|
||
|
||
|
||
def index(request):
|
||
return render(request, "watch/index.html")
|
||
```
|
||
|
||
|
||
## Les templates
|
||
|
||
Django va chercher `watch/index.html` dans tous les dossiers de
|
||
templates, dont `watch/templates/`.
|
||
|
||
|
||
## Les templates
|
||
|
||
Donc dans `watch/templates/watch/index.html` :
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Website Watch</title>
|
||
</head>
|
||
<body>
|
||
<h1>Website Watch</h1>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
notes:
|
||
|
||
La création du dossier `templates/` est typiquement quelque chose que
|
||
`runserver` ne voit pas, il faut le redémarrer.
|
||
|
||
|
||
## Les vues
|
||
|
||
Et si on ajoutait de la donnée provenant de la DB dans le template ?
|
||
|
||
|
||
## Les vues
|
||
|
||
```python
|
||
...
|
||
from watch.models import Website
|
||
|
||
def index(request):
|
||
return render(request, "watch/index.html",
|
||
{"websites": Website.objects.all()})
|
||
```
|
||
|
||
notes:
|
||
|
||
Ne pas oublier les imports…
|
||
|
||
Premier apperçu de l’ORM en passant.
|
||
|
||
|
||
## Les vues
|
||
|
||
Digression :
|
||
|
||
Il existe aussi des vues basées sur des classes, pouvant s’appuyer sur
|
||
des modèles.
|
||
|
||
|
||
## Les vues
|
||
|
||
```python
|
||
class WebsiteListView(ListView):
|
||
model = Website
|
||
```
|
||
|
||
## Les templates
|
||
|
||
```html
|
||
<body>
|
||
<h1>Website Watch</h1>
|
||
<ul>
|
||
{% for website in websites %}
|
||
<li>{{ website.host }} {% if website.is_up %}✓{% else %}✗{% endif %}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</body>
|
||
```
|
||
|
||
|
||
## Les templates
|
||
|
||
Ça fonctionne, mais on ne veut pas répéter l’entête HTML à chaque
|
||
page…
|
||
|
||
|
||
## Les templates
|
||
|
||
En utilisant `extends`, on peut réutiliser des templates.
|
||
|
||
|
||
## La Debug Toolbar
|
||
|
||
![](https://raw.githubusercontent.com/django-debug-toolbar/django-debug-toolbar/master/example/django-debug-toolbar.png)
|
||
|
||
|
||
## La Debug Toolbar
|
||
|
||
```bash
|
||
python -m pip install django-debug-toolbar
|
||
```
|
||
|
||
L’ajouter dans `settings.py` et `urls.py`.
|
||
|
||
|
||
# L’ORM
|
||
|
||
## L’ORM
|
||
|
||
C’est l’occasion de sortir un `python manage.py shell`.
|
||
|
||
```pycon
|
||
>>> from watch.models import Website
|
||
>>> Website.objects.all()
|
||
```
|
||
|
||
Essayer `.all`, `.filter`, `.get`, `.order_by`, et les slices.
|
||
|
||
|
||
## Les *Managers*
|
||
|
||
Les *managers* représentent une table, ils sont accessibles via
|
||
l’attribut `objects` d’un modèle.
|
||
|
||
Ses opérations (des méthodes) renvoient des `queryset`s.
|
||
|
||
```pycon
|
||
In [2]: Website.objects
|
||
Out[2]: <django.db.models.manager.Manager at 0x7fa77a9a1500>
|
||
```
|
||
|
||
|
||
## Les instances de modèles
|
||
|
||
Les instances de modèles représentent une ligne de la table.
|
||
|
||
|
||
## Les *Queryset*
|
||
|
||
Représentent un ensemble de lignes de la base de donnée. Ils ont les
|
||
mêmes méthodes que les *managers* :
|
||
|
||
- filter
|
||
- get
|
||
- order_by
|
||
- ...
|
||
|
||
|
||
## Les *Queryset*
|
||
|
||
```pycon
|
||
In [3]: Website.objects.all()
|
||
Out[3]: <QuerySet [<Website: mdk.fr>]>
|
||
```
|
||
|
||
|
||
## Les *Queryset*
|
||
|
||
Pour ceux qui ont fait du SQL c’est un "lazy select" : c’est un
|
||
`SELECT` qui ne s’éxécutera que si nécessaire.
|
||
|
||
|
||
## Les *RelatedManager*
|
||
|
||
Sont des managers, mais sur les relations :
|
||
|
||
```
|
||
In [4]: Website.objects.get(host="mdk.fr").check_set.all()
|
||
Out[4]: <QuerySet [<Check: mdk.fr is up>, <Check: mdk.fr is up>]>
|
||
```
|
||
|
||
|
||
# Forms
|
||
|
||
Comme pour l’admin, Django peut utiliser les informations des modèles
|
||
pour vous aider à générer des formulaires.
|
||
|
||
|
||
## Forms fields
|
||
|
||
Créer un formulaire ressemble à créer un modèle, on décrit les champs :
|
||
|
||
```python
|
||
class WebsiteForm(forms.Form):
|
||
host = forms.CharField(label="Website hostname",
|
||
max_length=512)
|
||
```
|
||
|
||
|
||
## Forms
|
||
|
||
On le donne au template :
|
||
|
||
```python
|
||
return render(
|
||
request,
|
||
"watch/index.html",
|
||
{
|
||
"websites": Website.objects.all(),
|
||
"form": WebsiteForm,
|
||
},
|
||
)
|
||
```
|
||
|
||
|
||
## Forms
|
||
|
||
On l’affiche dans le template :
|
||
|
||
```html
|
||
<form action="/" method="post">
|
||
{% csrf_token %}
|
||
{{ form }}
|
||
<input type="submit" value="Add">
|
||
</form>
|
||
```
|
||
|
||
## Forms
|
||
|
||
Mais là on a pas utilisé les informations données par les modèles…
|
||
|
||
|
||
## ModelForm
|
||
|
||
Avec un `ModelForm` on ne répète pas les attributs fields :
|
||
|
||
```python
|
||
class WebsiteForm(forms.ModelForm):
|
||
class Meta:
|
||
model = Website
|
||
fields = ("host",)
|
||
```
|
||
|
||
|
||
## Widgets
|
||
|
||
Le rendu HTML d’un champ de formulaire est appelé un `Widget`, il est
|
||
possible de le changer, par exemple si vous préférez un `<input` à un
|
||
`<textarea` ou vice-versa.
|
||
|
||
|
||
## Validation
|
||
|
||
Une instance de formulaire a une méthode `is_valid` :
|
||
|
||
```python
|
||
if request.method == "POST":
|
||
form = WebsiteForm(request.POST)
|
||
if form.is_valid():
|
||
Website.objects.create(host=form.cleaned_data["host"])
|
||
```
|
||
|
||
notes:
|
||
|
||
Django peut donc vérifier que les champs non-empty sont bien pas
|
||
vides, que les longuers sont respectées, etc…
|
||
|
||
|
||
# Les tests
|
||
|
||
Tester c’est douter.
|
||
|
||
notes:
|
||
|
||
Ou pas. Avoir les tests qui passent avant de pousser, avant de merger
|
||
une PR, avant de mettre en prod c’est un véritable confort.
|
||
|
||
|
||
## Les fixtures
|
||
|
||
Pour tester on va avoir besoin de données de test.
|
||
|
||
On appelera ça des « fixtures ».
|
||
|
||
|
||
## Les fixtures
|
||
|
||
Le moyen simple de créer des fixtures est d’utiliser les données que
|
||
vous avez crée via l’admin :
|
||
|
||
```bash
|
||
./manage.py dumpdata -o watch/fixtures/initial.json
|
||
```
|
||
|
||
notes:
|
||
|
||
Créez le dossier d’abord ;)
|
||
|
||
|
||
## Les fixtures
|
||
|
||
Profitez-en pour indiquer aux collègues dans le README qu’ils peuvent
|
||
les charger aussi :
|
||
|
||
```bash
|
||
git clone …
|
||
cd project
|
||
./manage.py migrate
|
||
./manage.py loaddata initial
|
||
```
|
||
|
||
|
||
## Les tests
|
||
|
||
Par défaut Django utilise la bibliothèque native `unittest`, on
|
||
placera les tests de nos applications dans le dossier `tests` ou le
|
||
fichier `tests.py` des applications.
|
||
|
||
notes:
|
||
|
||
Habituellement on dit que le nom du fichier n’a aucune importance, que
|
||
seul le succès de l’import compte. Ici c’est le contraire, le nom du
|
||
fichier doit commencer par `test` pour être trouvé.
|
||
|
||
|
||
## Les tests
|
||
|
||
Les tests s’exécutent via :
|
||
|
||
```bash
|
||
python manage.py test
|
||
```
|
||
|
||
## Les tests
|
||
|
||
Tous nos tests seront des instances de `TestCase` :
|
||
|
||
```python
|
||
from django.test import TestCase
|
||
|
||
class WatchTestCase(TestCase):
|
||
fixtures = ["initial"]
|
||
|
||
def test_can_access_home(self):
|
||
...
|
||
```
|
||
|
||
|
||
## Les tests
|
||
|
||
Pour vérifier que tout se passe bien `unittest` et `Django` nous
|
||
fournissent une collection d’assertions :
|
||
|
||
- assertTemplateUsed
|
||
- assertRedirects
|
||
- assertHTMLEqual
|
||
- assertInHTML
|
||
- ...
|
||
|
||
|
||
## Les tests
|
||
|
||
Personnellement habitué à pytest j’abuse de `assert`. Attention, bien
|
||
que ça fonctionne, les messages ne sont pas aussi lisibles.
|
||
|
||
|
||
# Les relations entres modèles
|
||
|
||
On a déjà fait une `ForeignKey`, mais il existe aussi :
|
||
|
||
- OneToOneField
|
||
- ManyToManyField
|
||
|
||
|
||
notes:
|
||
|
||
Parler des cas d’usage, et parler de `thrue`.
|
||
|
||
|
||
# Users
|
||
|
||
- Groups
|
||
- Permissions
|
||
- Authentication
|
||
|
||
# Static assets
|
||
|
||
Chaque application peut déposer des fichiers dans son dossier `/static/`.
|
||
|
||
|
||
## Static assets
|
||
|
||
En dev ils seront tous accessibles via l’URL `/static/`.
|
||
|
||
|
||
## Static assets
|
||
|
||
En prod cependant il y a beaucoup plus efficace pour les servir :
|
||
|
||
```bash
|
||
./manage.py collectstatic
|
||
```
|
||
|
||
Puis configurez votre serveur HTTP (`nginx`/`apache2`) pour servir le
|
||
dossier généré sans passer par Python.
|
||
|
||
|
||
# Deployment
|
||
|
||
En parlant de prod, comment mettre un Django en prod ?
|
||
|
||
|
||
## Deployment
|
||
|
||
Django utilise les protocoles `wsgi` (synchrone) et `asgi`
|
||
(asynchrone), des classiques en Python.
|
||
|
||
|
||
## Deployment
|
||
|
||
Attention, `runserver` c’est bien en dev, mais ça n’est pas fait pour la prod.
|
||
|
||
notes:
|
||
|
||
Sérieusement.
|
||
|
||
|
||
## Deployment
|
||
|
||
`runserver` c’est un peu comme un groupe éléctrogène :
|
||
|
||
- C’est vrai que ça fonctionne.
|
||
- Ça dépanne quand on est seul dessus, chez soi.
|
||
- Mais on alimente pas un quartier ou une ville avec.
|
||
|
||
notes:
|
||
|
||
Trouver mieux :D
|
||
|
||
|
||
## Deployment
|
||
|
||
Nginx et Apache2 gèrent `wsgi`, d’autres serveurs aussi, probablement.
|
||
|
||
|
||
## Deployment
|
||
|
||
Mais en production on ne veut pas juste une instance de Django, on en
|
||
veut plusieurs, pour traiter plus de requêtes, pour ça on peut
|
||
utiliser `gunicorn`, `uwsgi`, …
|
||
|
||
|
||
## Deployment
|
||
|
||
`gunicorn` est un bon point de départ :
|
||
|
||
```bash
|
||
pip install gunicorn
|
||
gunicorn -w 16 project.wsgi
|
||
```
|
||
|
||
## Deployment
|
||
|
||
`gunicorn` est bien derrière un `nginx` qui va s’occuper, entre autres,
|
||
de la décapsulation HTTPS, ou de délivrer vos fichiers statiques sans
|
||
passer par Python.
|
||
|
||
|
||
## Deployment
|
||
|
||
```nginx
|
||
server {
|
||
listen 443 http2 ssl;
|
||
server_name example.com;
|
||
|
||
include snippets/letsencrypt-example.com.conf;
|
||
|
||
location /static {
|
||
alias /opt/example.com/static/;
|
||
}
|
||
|
||
location / {
|
||
proxy_pass http://unix:/run/example.com/wsgi.sock;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-Protocol $scheme;
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
# Bonnes pratiques
|
||
|
||
Pas de `order_by` / `fiter` / … dans les vues, rangez ça dans les
|
||
modèles (dans des *managers* personalisés).
|
||
|
||
Ça permet de nommer et de réutiliser.
|
||
|
||
|
||
## Bonnes pratiques
|
||
|
||
La gestion des dépendances avec `pip-compile`.
|
||
|
||
|
||
## Bonnes pratiques
|
||
|
||
Versionnez !!
|
||
|
||
|
||
## Bonnes pratiques
|
||
|
||
Prennez le temps de poser un `.gitignore`.
|
||
|
||
|
||
## Bonnes pratiques
|
||
|
||
On en mettra le moins possible dans le dossier du projet, on
|
||
utilisera des applications pour le reste du code.
|
||
|
||
|
||
## Bonnes pratiques
|
||
|
||
On surcharge l’objet `User`, même si on pense ne pas en avoir besoin :
|
||
|
||
```python
|
||
class User(django.contrib.auth.models.AbstractUser):
|
||
...
|
||
```
|
||
|
||
et :
|
||
|
||
```python
|
||
AUTH_USER_MODEL = ...
|
||
```
|
||
|
||
notes:
|
||
|
||
Enfin, si on est **100% sûrs** que notre application ne sera pas
|
||
réutilisée dans un contexte où une autre app redéfinit l'object
|
||
`User`…
|
||
|
||
|
||
## La configuration de Django
|
||
|
||
- `include local_settings.py`
|
||
- `django-configurations`
|
||
- `django-environ`
|
||
|
||
|
||
## Resources
|
||
|
||
- La doc officielle.
|
||
- Pour Django : https://ccbv.co.uk/ (Memo : « Classy Class-Based-View »).
|
||
- Pour Django Rest Framework : https://www.cdrf.co (Memo : « Classy Django REST Framework »).
|