Add documentation and source
Added documentation, source and extra files.
1
.gitignore
vendored
@ -163,3 +163,4 @@ cython_debug/
|
|||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
/documentation/slides/
|
/documentation/slides/
|
||||||
|
.idea
|
||||||
|
135
documentation/00-course-plan.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Programme de Django Introduction
|
||||||
|
|
||||||
|
TODO: Remplacer les images de code par des sections Markdown
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Découvrir Django
|
||||||
|
|
||||||
|
- Design Pattern MVC : structure, utilisation
|
||||||
|
- Présentation de Django : paradigme, versions, documentation
|
||||||
|
- Autres frameworks web Python existants : Flask, Pylons, Pyramid etc.
|
||||||
|
- Environnements de développement intégrés
|
||||||
|
- Environnements de développement console
|
||||||
|
- Installation de Django et paquets annexes (applications Django)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure générale d'un projet Django
|
||||||
|
|
||||||
|
- Structure générale d'un projet
|
||||||
|
- Structure générale d'une application
|
||||||
|
|
||||||
|
Atelier : Création d'un projet et d'une application Django
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Afficher des pages avec Django
|
||||||
|
|
||||||
|
- Routage d'URL
|
||||||
|
- Écriture de vues
|
||||||
|
- Intrpduction aux Vues basées sur des classes
|
||||||
|
- Introduction au middleware
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templating (gabarits) avec Django
|
||||||
|
|
||||||
|
- Principe du templating général
|
||||||
|
- Principe du templating avec Django
|
||||||
|
- Espaces réservés (interpolation)
|
||||||
|
- Filtres de templates
|
||||||
|
- Balises de templates (tags) et structures de contrôle
|
||||||
|
- Héritage de templates
|
||||||
|
- Balises spéciales : include, load, url et static
|
||||||
|
- Balises et filtres personnalisés
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Ateliers :
|
||||||
|
|
||||||
|
- Créer et afficher un template dans la console
|
||||||
|
- Utiliser un contexte dans un template (+filtres)
|
||||||
|
- Utiliser des structures de contrôle
|
||||||
|
- Utiliser l'héritage et les blocs
|
||||||
|
- Découvrir les balises spéciales
|
||||||
|
- Écrire des filtres personnalisés
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Saisir des données avec les formulaires
|
||||||
|
|
||||||
|
- Définition de formulaires manuels
|
||||||
|
- Utilisation de formulaires
|
||||||
|
- Utilisation des formulaires dans les templates
|
||||||
|
- Valeurs par défaut des formulaires, traitement, sécurité etc.
|
||||||
|
- Validation de la saisie utilisateur
|
||||||
|
- Gestion des téléchargements et fichiers média
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistance des données avec des bases de données (ORM)
|
||||||
|
|
||||||
|
- ORM : intérêt, avantages
|
||||||
|
- ORM Django : configuration, fonctionnalités
|
||||||
|
- Définition des modèles de données
|
||||||
|
- Définition des relations entre tables (clés)
|
||||||
|
- Configuration et classe Meta
|
||||||
|
- Héritage, Proxy et modèles abstraits
|
||||||
|
- Interroger la base de données
|
||||||
|
- Enregistrer et modifier des données dans la base (+ concurrence)
|
||||||
|
- Système de migrations de schéma Django
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utiliser des formulaires avec la base de données
|
||||||
|
|
||||||
|
- Définition de formulaires basés sur les modèles
|
||||||
|
- Utilisation des formulaires et validation
|
||||||
|
- Gestion des téléchargements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interface d'administration de Django
|
||||||
|
|
||||||
|
- Présentation de l'interface d'administration intégrée
|
||||||
|
- Configuration de l'administration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## L'authentification et les utilisateurs
|
||||||
|
|
||||||
|
- L'application d'authentification de Django
|
||||||
|
- S'authentifier programmatiquement dans une vue
|
||||||
|
- S'authentifier avec un formulaire
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Traduction d'un projet Django (I18N)
|
||||||
|
|
||||||
|
- Traduction dans le code Django
|
||||||
|
- Traduction dans les templates
|
||||||
|
|
||||||
|
Ateliers :
|
||||||
|
- Ajout de traduction au code de projet
|
||||||
|
- Ajout de traduction dans les templates
|
||||||
|
|
||||||
|
Atelier : I18N de l'application
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Concepts avancés
|
||||||
|
|
||||||
|
- Envoyer des emails
|
||||||
|
- Créer des vues de téléchargment
|
||||||
|
- Lancement de tests d'un projet Django (unittest) :
|
||||||
|
- Test Runner
|
||||||
|
- Test Client
|
||||||
|
- Déploiement d'un projet Django
|
||||||
|
- Interconnexion avec les réseaux sociaux (django-allauth)
|
||||||
|
- API REST en Django
|
||||||
|
- Interfaçage avec un framework front-end (ex. Vue.js ou Angular)
|
||||||
|
- Interfaçage avec un frawework front-end basé sur le contenu HTML (ex. HTMX)
|
||||||
|
- Intégration avec Git et une usine logicielle
|
||||||
|
|
140
documentation/01-discover.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
title: Découvrir Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Découvrir Django
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Frameworks web
|
||||||
|
|
||||||
|
Pourquoi est-ce que les frameworks web existent ?
|
||||||
|
|
||||||
|
- Avec un langage de programmation, si on a assez d'outils en standard, on pourrait très bien imaginer réinventer la roue;
|
||||||
|
- Or, réinventer la roue pour effectuer des tâches communes à de nombreux projets est une perte de temps et d'énergie;
|
||||||
|
- Écrire une fois le code, le réutiliser sur plusieurs projets semble beaucoup plus intelligent.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Que font les frameworks ?
|
||||||
|
|
||||||
|
- Un framework est une bibliothèque qui propose un plan d'architecture pour réaliser un projet;
|
||||||
|
- Le développeur qui utilise un framework va devoir commencer son projet en utilisant une base de code spécifique au framework (des fichiers Python, des fichiers ini ou autres);
|
||||||
|
- Ensuite, il va étoffer son projet en utilisant généralement d'autres outils fournis par le framework.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Exemples de frameworks Python
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
- **Sites web** : [Flask](https://flask.palletsprojects.com/en/2.2.x/)
|
||||||
|
- **Sites web** : [Web2py](http://www.web2py.com/init/default/index)
|
||||||
|
- **Sites web** : [Turbogears](https://turbogears.org/)
|
||||||
|
- **Sites web** : Pyramid (miniframework)
|
||||||
|
- **Réseau et programmation asynchrone** : [Twisted](https://twisted.org/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Django et Design patterns
|
||||||
|
|
||||||
|
Les **Design Patterns** sont un ensemble "standardisé" de bonnes pratiques liées au développement de projets ou d'algorithmes. Un design pattern connu au niveau projet est nommé **MVC**, ou **Model, View and Controller**.
|
||||||
|
|
||||||
|
Le design pattern MVC consiste à découper le développement d'un projet avec des données et une interface graphique en 3 groupes de fichiers de code distincts…
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### MVC : Modèle
|
||||||
|
|
||||||
|
La partie **Modèle** du MVC consiste à écrire dans des fichiers spécifiques, du code destiné à décrire la base de données et les traitements de base qui peuvent être effectués sur celle-ci. En général, la couche d'abstraction des données est décrite en Python sous la forme de classes nommées modèles.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### MVC : Vue
|
||||||
|
|
||||||
|
La partie **Vue** du MVC consiste, idéalement, à rédiger dans des fichiers spécifiques, le code destiné à afficher du contenu et une interface graphique au client. Autant que possible, dans un univers parfait, on séparerait complètement ce code de celui qui réagit aux interactions de l'utilisateur.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### MVC : Contrôleur
|
||||||
|
|
||||||
|
La partie **Contrôleur** du MVC est la partie la plus intelligente du lot. C'est celle qui s'occupe de l'intelligence spécifique au projet, et pas au métier décrit dans la partie modèle (elle n'est pas censée être réutilisable ailleurs que dans le projet).
|
||||||
|
|
||||||
|
Elle peut décrire ce qui se passe quand l'utilisateur valide des formulaires, et appeler le modèle pour modifier les données.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Présentation de Django
|
||||||
|
|
||||||
|
Django a été créé en 2003, dans les locaux du **Lawrence Journal-World Newspaper**, lorsque deux développeurs du journal en ligne ont commencé à utiliser le langage Python pour effectuer leurs tâches courantes.
|
||||||
|
|
||||||
|
Dès 2005, alors que le développement du framework pour leurs besoins avait suffisamment évolué, le projet a été rendu public sous [licence BSD](https://fr.wikipedia.org/wiki/Licence_BSD).
|
||||||
|
|
||||||
|
À partir de l'année 2009, le framework est devenu un outil de choix pour la création de nombreux projets web. Parmi les acteurs les plus célèbres utilisant Django, on trouve Instagram, Disqus ou encore plusieurs sites de Mozilla.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Arguments de vente
|
||||||
|
|
||||||
|
- Prend en charge Python 3.8 et plus récents
|
||||||
|
- Le code source est libre, sous licence BSD;
|
||||||
|
- Accessible en ligne : [Github Django](https://github.com/django/django)
|
||||||
|
- **Attention** : Le copyright appartient à la Django Software Foundation;
|
||||||
|
|
||||||
|
- Première release en 2005 : [Historique versions](https://en.wikipedia.org/wiki/Django_(web_framework)#Version_history)
|
||||||
|
- Version à jour : _5.1.2_ (Octobre 2024)
|
||||||
|
- Une nouvelle version mineure tous les 9 mois (18 mois de support);
|
||||||
|
- Une nouvelle version majeure toutes les 27 mois;
|
||||||
|
- Une version LTS (3 ans de support) à chaque dernière version mineure.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Environnements de développement intégrés
|
||||||
|
|
||||||
|
Pour travailler avec Django, il existe plusieurs IDE recommandés :
|
||||||
|
|
||||||
|
- [JetBrains® PyCharm](https://www.jetbrains.com/fr-fr/pycharm/) (Community ou Professional)
|
||||||
|
- [Visual Studio Code](https://code.visualstudio.com/) (avec extensions Python + Django)
|
||||||
|
|
||||||
|
(PyCharm Community ne prend pas en charge le Javascript ni les outils spécifiques à Django)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Outils de développement pour la console
|
||||||
|
|
||||||
|
Il est possible de travailler dans une console Python pour tester certains éléments de code (ex. tester le traitement des données en base de données dans notre projet Django). Certains packages externes Python nous offrent des consoles Python avancées, utilisables dans un terminal.
|
||||||
|
|
||||||
|
- [iPython](https://ipython.org/) : console interactive, associée à Jupyter.
|
||||||
|
- Jupyter : console interactive utilisable depuis un navigateur.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Installation de Django et paquets annexes
|
||||||
|
|
||||||
|
Pour installer Django sous Linux, le procédé est assez classique :
|
||||||
|
|
||||||
|
1. Créer son répertoire de projet
|
||||||
|
2. Créer un environnement virtuel Python (via Pycharm)
|
||||||
|
3. Installer Django dans l'environnement virtuel
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Installation de Django
|
||||||
|
|
||||||
|
Installer Django
|
||||||
|
|
||||||
|
```{.bash .numberLines}
|
||||||
|
pip install Django # installe la dernière version
|
||||||
|
```
|
||||||
|
|
||||||
|
. . .
|
||||||
|
|
||||||
|
Créer son projet en utilisant les outils en ligne de commande de Django
|
||||||
|
|
||||||
|
```{.bash .numberLines}
|
||||||
|
django-admin startproject <nom projet> <répertoire du fichier principal>
|
||||||
|
# En général, le répertoire du fichier principal sera ".", le répertoire courant.
|
||||||
|
```
|
276
documentation/02-structure.md
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
---
|
||||||
|
title: Projets et applications
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Structure d'un site web avec Django
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Notion de projet dans Django
|
||||||
|
|
||||||
|
Dans le jargon de [Django]{.naming}, un projet équivaut à un site complet que vous souhaitez créer.
|
||||||
|
Par exemple, si vous souhaitez créer un nouveau site web à déployer en production sur un serveur,
|
||||||
|
vous devez créer un [projet]{.naming} associé à celui-ci.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Créer un nouveau projet
|
||||||
|
|
||||||
|
Puisqu'il vous faut un projet pour votre prochain site web, vous devez le créer.
|
||||||
|
Le framework Django vous impose une structure générale, qui peut être créée en ligne de commande.
|
||||||
|
(les frameworks front-end tels que [vue.js]{.naming} par exemple, utilisent le même système de
|
||||||
|
[bootstrapping]{.naming}).
|
||||||
|
|
||||||
|
Lorsque vous installez Django, le framework vous permet d'utiliser certains commandes dans un terminal.
|
||||||
|
Lesdites commandes vont vous permettre d'exécuter plusieurs tâches d'administration, dont la création de
|
||||||
|
la structure minimale d'un nouveau projet.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
L'outil principal de gestion de projets Django est disponible sous le nom `django-admin`{.bash}.
|
||||||
|
Sa [documentation](https://docs.djangoproject.com/en/dev/ref/django-admin/) indique qu'on peut l'utiliser
|
||||||
|
pour réaliser diverses tâches de base, dont la création d'un nouveau répertoire de projet :
|
||||||
|
|
||||||
|
```bash {.number-lines}
|
||||||
|
django-admin startproject <name> [répertoire de création, ou name par défaut]
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Cette commande est nécessaire et fait partie du processus normal de création de site.
|
||||||
|
En général, si le répertoire contenant le projet à réaliser existe déjà (par exemple lorsqu'on travaille avec PyCharm),
|
||||||
|
il faut porter une attention à ne pas créer une arborescence inutile; il faudra créer le projet Django directement à la
|
||||||
|
racine du répertoire de projet PyCharm :
|
||||||
|
|
||||||
|
```{.bash .numberLines}
|
||||||
|
django-admin startproject <name> .
|
||||||
|
```
|
||||||
|
|
||||||
|
Notez le `.` afin d'indiquer à la commande `startproject` de ne pas créer un sous-répertoire inutile
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Fichiers du package de projet
|
||||||
|
|
||||||
|
La commande `startproject` de l'outil `django-admin` vous crée un répertoire avec le minimum syndical
|
||||||
|
pour démarrer un projet avec des bases fiables (sécurité, fonctionnalités). Les fichiers créés par cette commande
|
||||||
|
sont presque tous nécessaires au bon fonctionnement d'un site web écrit avec Django.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
| | Description |
|
||||||
|
|-------------------|--------------------------------------------------------------------------|
|
||||||
|
| `myproject/` | Répertoire racine du projet contenant les fichiers de configuration. |
|
||||||
|
| ├── `manage.py` | Script de gestion du projet Django (lancer le serveur, migrations). |
|
||||||
|
| └── `myproject/` | Répertoire contenant les paramètres et configurations du projet. |
|
||||||
|
| ├── `__init__.py` | Fichier vide marquant ce répertoire comme un package Python. |
|
||||||
|
| ├── `settings.py` | Fichier de configuration du projet Django (bases de données, etc.). |
|
||||||
|
| ├── `urls.py` | Fichier de routage des URLs pour les vues du projet. |
|
||||||
|
| ├── `asgi.py` | Point d’entrée pour le serveur ASGI (pour les applications asynchrones). |
|
||||||
|
| └── `wsgi.py` | Point d’entrée pour le serveur WSGI (pour les applications synchrones). |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Le fichier `manage.py`
|
||||||
|
|
||||||
|
Ce fichier est un script proposant exactement la même chose que l'outil en ligne de commande `django-admin`,
|
||||||
|
mais en prenant en compte les paramètres spécifiques au projet, tels qu'indiqués dans le fichier `settings.py`.
|
||||||
|
|
||||||
|
Par exemple, il peut être utilisé pour mettre à jour le schéma de la base de données d'un projet Django.
|
||||||
|
Lorsqu'un [projet]{.naming} est configuré pour utiliser des "plugins", ces derniers peuvent proposer de nouvelles commandes
|
||||||
|
utilisables avec l'outil.
|
||||||
|
|
||||||
|
```bash {.numberLines}
|
||||||
|
# Lancer le serveur web de développement pour tester son projet
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Le fichier `settings.py`
|
||||||
|
|
||||||
|
Ce fichier contient, sous la forme de simples constantes (ex. `CONSTANTE = valeur`{.python}),
|
||||||
|
la configuration actuelle de votre projet. Le nom des constantes à définir est documenté dans le détail
|
||||||
|
dans la [documentation officielle](https://docs.djangoproject.com/en/dev/topics/settings/),
|
||||||
|
mais la liste des paramètres disponibles est si longue qu'il faut la consulter uniquement
|
||||||
|
au besoin.
|
||||||
|
|
||||||
|
Parmi les paramètres intéressants, vous pouvez configurer votre accès aux **bases de données**,
|
||||||
|
choisir les répertoires par défaut pour stocker des fichiers, les "plugins" utilisés par votre
|
||||||
|
projet, et globalement tous les éléments de votre site.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Autres fichiers importants
|
||||||
|
|
||||||
|
- `urls.py` : fichiers définissant les URL principales de votre site
|
||||||
|
- `asgi.py` : fichier de serveur asynchrone pour la mise en production
|
||||||
|
- `wsgi.py` : fichier de serveur synchrone pour la mise en production
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Notion d'application dans Django
|
||||||
|
|
||||||
|
Un concept intéressant de Django est que si vous avez un projet,
|
||||||
|
vous avez la possibilité d'y brancher, à l'instar d'un système de plugins,
|
||||||
|
des packages Python réutilisables que l'on appelle [applications]{.naming}.
|
||||||
|
|
||||||
|
Une application Django est un **package Python** possédant une certaine structure (certains modules
|
||||||
|
sont détectés automatiquement par le framework Django).
|
||||||
|
Une application est normalement écrite pour définir un périmètre de fonctionnalités réutilisable
|
||||||
|
entre plusieurs projets. Par exemple, une application de _gestion de blog_, de _gestion d'images_, etc.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Applications fournies par Django
|
||||||
|
|
||||||
|
Parmi les nombreuses applications fournies avec le framework Django, vous trouverez par exemple
|
||||||
|
des applications dédiées à plusieurs sujets :
|
||||||
|
|
||||||
|
| Module | Description |
|
||||||
|
|------------------------------|-----------------------------------------------------------------|
|
||||||
|
| `django.contrib.auth` | Gestion des utilisateurs, groupes et permissions (à venir) |
|
||||||
|
| `django.contrib.admin` | Une interface d'administration avancée (à venir) |
|
||||||
|
| `django.contrib.messages` | Système de messages génériques pour les utilisateurs. |
|
||||||
|
| `django.contrib.staticfiles` | Gestion des fichiers statiques tels que les fichiers CSS et JS. |
|
||||||
|
| `django.contrib.sites` | Gestion des sites multiples dans une seule application Django. |
|
||||||
|
|
||||||
|
|
||||||
|
N'oubliez pas que Django est extensible et que de nombreuses autres applications tierces sont disponibles
|
||||||
|
via le Python Package Index (PyPI) pour répondre à des besoins plus spécifiques.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Créer une nouvelle application
|
||||||
|
|
||||||
|
Pour créer une application, vous pouvez utiliser la commande suivante dans un terminal :
|
||||||
|
|
||||||
|
```bash {.numberLines}
|
||||||
|
django-admin startapp <package>
|
||||||
|
```
|
||||||
|
|
||||||
|
Un package Python avec certains fichiers par défaut sera créé. Si cette application est spécifique à
|
||||||
|
votre projet de site plutôt qu'à une fonctionnalité réutilisable, vous pouvez la créer dans votre répertoire
|
||||||
|
de projet, mais si vous souhaitez travailler sur votre application pour la réutiliser dans plusieurs projets,
|
||||||
|
vous pourriez la créer sans même avoir un projet Django pour la tester.
|
||||||
|
|
||||||
|
Une application générale pour un projet est souvent nommée `core`, `main` ou `project` (si cela n'est pas déjà pris).
|
||||||
|
|
||||||
|
Une application développée pour être réutilisable sera de préférence développée de façon à être installable
|
||||||
|
avec l'outil `pip`, mais ceci est un sujet avancé.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Fichiers créés dans le package d'application
|
||||||
|
|
||||||
|
Lorsque vous créez une application Django pour l'utiliser dans un projet, un package est généré
|
||||||
|
contenant des fichiers de base, mais pas tous fondamentaux.
|
||||||
|
|
||||||
|
| Fichier/Répertoire | Description |
|
||||||
|
|--------------------|-----------------------------------------------------------|
|
||||||
|
| `admin.py` | Module de configuration de l'administration. |
|
||||||
|
| `apps.py` | Module déclarant les informations de l'application. |
|
||||||
|
| `migrations/` | Package contenant les évolutions de la couche **Modèle**. |
|
||||||
|
| `models.py` | Module pour déclarer la couche **Modèle**. |
|
||||||
|
| `tests.py` | Module de tests automatisés (`unittest` par défaut). |
|
||||||
|
| `views.py` | Module pour déclarer la couche **Vue** et **Contrôleur**. |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Fichiers non créés avec l'application
|
||||||
|
|
||||||
|
Certains modules ou répertoires sont souvent présents dans des applications Django,
|
||||||
|
et doivent être créés au besoin, manuellement :
|
||||||
|
|
||||||
|
| Fichier/Répertoire | Description |
|
||||||
|
|---------------------|---------------------------------------------------------------|
|
||||||
|
| `fixtures/` | Répertoire contenant des dumps de données réutilisables. |
|
||||||
|
| `forms.py` | Module pour déclarer des formulaires. |
|
||||||
|
| `urls.py` | Module pour déclarer des routes locales à l'application. |
|
||||||
|
| `locale/` | Répertoire de gestion des catalogues de traduction. |
|
||||||
|
| `static/` | Répertoire contenant les fichiers statiques de l'application. |
|
||||||
|
| `templates/` | Répertoire contenant les templates de l'application. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Associer une application à un projet
|
||||||
|
|
||||||
|
Pour que Django reconnaisse que votre application est activée dans votre projet, ce qui permettra à
|
||||||
|
Django d'y détecter automatiquement certains fichiers (certains modules y seront détectés automatiquement),
|
||||||
|
vous devez préciser dans les paramètres de votre projet quelles applications vous souhaitez y inclure.
|
||||||
|
|
||||||
|
Pour cela, vous devez ajouter le nom qualifié de package de votre application dans le paramètre `INSTALLED_APPS`
|
||||||
|
du module `settings.py` du projet :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"app_package_name", # le nom d'import de votre package
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce paramètre est une liste contenant les applications activées pour votre site. La configuration
|
||||||
|
par défaut est généralement saine et il est désirable de partir de celle-ci.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Fichiers et répertoires standard
|
||||||
|
|
||||||
|
En règle générale, on peut aussi, dans une application, ajouter des répertoires ou fichiers plus
|
||||||
|
ou moins standard, tels que :
|
||||||
|
|
||||||
|
- `templates` : Répertoire de templates (découverts automatiquement par défaut)
|
||||||
|
- `static` : Répertoire de fichiers statiques spécifiques à l'application (ex. JS, CSS, images)
|
||||||
|
|
||||||
|
Lorsque nous ferons notre premier contenu HTML, nous y reviendrons.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Commandes `django-admin`
|
||||||
|
|
||||||
|
La commande `django-admin`, accessible partout, est disponible en ligne de commande dans le terminal et
|
||||||
|
accepte les actions suivantes, entre autres :
|
||||||
|
|
||||||
|
- `startapp` : Créer une nouvelle application
|
||||||
|
- `startproject` : Créer un nouveau projet de site web
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Commandes uniquement pour les projets
|
||||||
|
|
||||||
|
Certaines commandes de l'outil `django-admin` sont dépendantes de la configuration d'un projet,
|
||||||
|
telle que précisée dans le module `settings.py`. C'est le cas par exemple des commandes suivantes :
|
||||||
|
|
||||||
|
- `runserver` : lancer un serveur web de test du projet (utilise les paramètres)
|
||||||
|
- `makemigrations` et `migrate` pour gérer la base de données (voir chapitre **ORM**)
|
||||||
|
- `makemessages` et `compilemessages` pour les traductions (voir chapitre **Traduction**)
|
||||||
|
|
||||||
|
Si votre commande nécessite d'avoir accès aux paramètres d'un projet Django, vous devez, au lieu
|
||||||
|
d'utiliser la commande `django-admin`, exécuter le module `manage.py` :
|
||||||
|
|
||||||
|
```bash {.numberLines}
|
||||||
|
./manage.py runserver # ou encore
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
L'ensemble des commandes disponibles par défaut se trouve dans la [documentation officielle](https://docs.djangoproject.com/en/dev/ref/django-admin/).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Récapitulatif
|
||||||
|
|
||||||
|
Démarrer un projet minimal, c'est simple avec PyCharm (**Community**) :
|
||||||
|
|
||||||
|
1. Créer un nouveau projet Python avec PyCharm
|
||||||
|
2. Installer Django : `pip install django`{.bash}
|
||||||
|
3. Démarrer un projet Django : `django-admin startproject <name> .`{.bash}
|
||||||
|
4. Créer une application Django : `django-admin startapp <name2>`{.bash}
|
||||||
|
5. Ajouter l'application au projet : `INSTALLED_APPS = [..., "name2"]`{.py}
|
565
documentation/03-urls-views.md
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
---
|
||||||
|
title: URLs dans Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Afficher des pages avec Django
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Routage d'URLs
|
||||||
|
|
||||||
|
Tous les serveurs d'application web ont besoin de savoir comment afficher des pages à des URLs distinctes.
|
||||||
|
|
||||||
|
Par exemple, si vous demandez au serveur à l'adresse `https://mon.adresse.com` l'URL dont le nom est `https://mon.adresse.com/resource`, le serveur doit pouvoir définir ce qu'il va faire de la ressource dont le chemin est `/resource`. (Et en général, il doit vous renvoyer un contenu avec le code de statut HTTP 404 s'il ne le peut pas)
|
||||||
|
|
||||||
|
Pour ce faire, la majorité des systèmes de serveurs web mettent en place un système de routage d'URLs où le développeur configure quelles URLs seront acceptées par le serveur et ce qu'elles fournissent comme ressource.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Exemple de routage
|
||||||
|
|
||||||
|
Si du côté du développeur, vous configurez votre site ainsi :
|
||||||
|
|
||||||
|
- Le développeur configure le routage d'URL pour accepter l'URL `/about`;
|
||||||
|
- Le développeur indique que l'URL `/about` pointera vers une fonction qui renvoie le contenu à afficher au navigateur;
|
||||||
|
|
||||||
|
Ce qui se produira lorsqu'une requête HTTP sera envoyée par votre navigateur web vers votre serveur web sera le suivant :
|
||||||
|
|
||||||
|
- Vous demandez une ressource via votre navigateur, `/about`
|
||||||
|
- Le serveur vérifie dans ses configurations d'URL si l'URL demandée correspond à une route configurée
|
||||||
|
- Si **oui**, la fonction associée à l'URL est exécutée et une réponse peut être renvoyée au navigateur
|
||||||
|
- Si **non**, le serveur renverra si possible un contenu de page avec un code HTTP 404 (ressource non trouvée)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Exemple de routage d'URL dans un projet
|
||||||
|
|
||||||
|
Lorsque nous avons un nouveau projet Django dans lequel travailler, nous pouvons déclarer des URLs et rédiger du code afin que lesdites URLs nous affichent un document (HTML ou non).
|
||||||
|
|
||||||
|
La méthode imposée par le framework consiste à définir une variable possédant un nom spécifique, dans un module de votre projet qui par défaut est `urls.py` :
|
||||||
|
|
||||||
|
- La variable qui recense les URLs principales de votre projet est nommée `urlpatterns`, et est une liste de valeurs configurées via des appels d'une fonction `path`;
|
||||||
|
- Le nom du module contenant les URLs principales est configurable via la variable `settings.ROOT_URLCONF`{.python}.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
L'exemple de code suivant est un exemple général de routes principales qu'il est possible de configurer dans Django.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Les URLs de l'application `application` seront accessibles dans l'URL appli/
|
||||||
|
path("appli/", include("application.urls")),
|
||||||
|
# L'URL par défaut du site reçoit un nom user-friendly (voir Cool URLs)
|
||||||
|
path("", view_function, name="<url-friendly-name>"),
|
||||||
|
# Une URL peut indiquer des parties dynamiques acceptées par le routage
|
||||||
|
path("url/<int:dynamique>", view_dynamic, name="<url-friendly-name-2>"), # URL dynamique
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [Documentation officielle](https://docs.djangoproject.com/en/dev/ref/urls/#path) de la fonction `django.urls.path`
|
||||||
|
- [Types d'emplacements dynamiques](https://docs.djangoproject.com/en/5.0/topics/http/urls/#path-converters)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### La fonction `django.urls.path`
|
||||||
|
|
||||||
|
Lorsque vous souhaitez définir des routes dans votre projet (ou votre application Django), vous ajoutez des résultats d'appels de la fonction `path` comme éléments de la liste contenue dans la variable `urlpatterns`.
|
||||||
|
|
||||||
|
La fonction `path` possède la signature suivante :
|
||||||
|
|
||||||
|
- `def path(route: str, view_function: Callable, kwargs: dict, name: str)`{.python}
|
||||||
|
- `route` : URL à configurer. Ne contient pas le `/` de début;
|
||||||
|
- `view_function` : référence de fonction à associer à l'URL lorsqu'on y accède;
|
||||||
|
- `name` : donner un nom à l'URL pour la référencer même si l'URL change de forme.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Écrire une fonction de vue
|
||||||
|
|
||||||
|
Les fonctions de vue que vous associez à vos routes sont de simples fonctions, qui sont toujours appelées par le système de routage de Django de la même manière :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.http import HttpResponse, HttpRequest
|
||||||
|
|
||||||
|
def view_function(request: HttpRequest) -> HttpResponse:
|
||||||
|
return HttpResponse("Page content")
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction **doit** au moins posséder un premier argument positionnel nommé `request`, qui contiendra les données de requête HTTP, et retourner un objet de la classe `HttpResponse`, qui contiendra les informations de réponse HTTP (en-têtes, etc.).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Décrire une simple URL statique
|
||||||
|
|
||||||
|
Pour définir une URL simple à laquelle on renvoie un contenu (page HTML ou autre document), il nous faut 2 choses, à savoir ajouter une route, et ajouter la fonction à associer à cette route :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("chemin", reference_fonction, name="nom-de-reference")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
L'argument `name` de la fonction `path` permet de référencer l'URL a posteriori non pas par son chemin absolu, mais par un nom. Cet argument est recommandé, car coder en dur des URLs partout dans notre code va causer des soucis de maintenabilité dès que vous souhaiterez modifier l'URL.
|
||||||
|
|
||||||
|
```{.python}
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
def reference_fonction(request):
|
||||||
|
return HttpResponse("Document content")
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Inclure les URLs fournies par une application
|
||||||
|
|
||||||
|
Django propose des outils de routage avancés, et parmi ces outils, la réutilisation d'URLs définies dans une application Django.
|
||||||
|
|
||||||
|
Supposons qu'une application Django propose son propre fichier `urls.py`
|
||||||
|
avec ses propres routes. Ces routes peuvent être intégrées à votre projet en les ajoutant comme sous-URLs d'une route définie dans votre projet.
|
||||||
|
|
||||||
|
Si l'application se dédie à la gestion d'utilisateurs et définit trois sous-routes aux URLs :
|
||||||
|
|
||||||
|
- `view/<int:idx>`
|
||||||
|
- `edit/<uuid:uuid>`
|
||||||
|
- `report/<int:idx>`
|
||||||
|
|
||||||
|
Vous pouvez les intégrer à une URL de votre projet, ex. `users/`, et y accéder via les URLs suivantes :
|
||||||
|
|
||||||
|
- `users/view/<int:idx>`
|
||||||
|
- `users/edit/<uuid:uuid>`
|
||||||
|
- `users/report/<int:idx>`
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Pour définir ce type d'URL, il vous faut :
|
||||||
|
|
||||||
|
- Une application Django (à ajouter à `settings.INSTALLED_APPS`)
|
||||||
|
- Un module `urls` dans l'application avec une configuration d'URL
|
||||||
|
- Puis, dans le module `urls` de votre projet :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("application/", include("application.urls", namespace="nom"))
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
[Documentation officielle de la fonction `include`](https://docs.djangoproject.com/en/4.1/ref/urls/#include)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Espace de nom d'inclusion d'URLs
|
||||||
|
|
||||||
|
Lorsque vous utilisez la fonction `include`, vous pouvez donner un nom de "groupe" commun aux URLs incluses.
|
||||||
|
Pour cela, vous devez passer un argument `namespace` qui contiendra le nom de groupe des URLs. Vous devrez utiliser ce nom de groupe lorsque vous souhaiterez référencer ces URLs par la suite.
|
||||||
|
|
||||||
|
**Note** : Lorsque vous précisez un `namespace` dans la fonction `include`, Django refusera votre définition, à moins que vous précisiez quel est le nom de l'application incluse. La façon la plus simple de faire est d'ajouter, dans le fichier `urls.py` de votre application, une variable nommée `app_name` contenant le nom de l'application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Espace de noms d'URLs
|
||||||
|
|
||||||
|
Exemple de code pour les espaces de noms :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path, include
|
||||||
|
urlpatterns = [
|
||||||
|
path("dossier/", include("application.urls", namespace="application"))
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# Module `application.urls`
|
||||||
|
app_name = "application" # À définir pour l'espace de noms
|
||||||
|
urlpatterns = ["…"]
|
||||||
|
```
|
||||||
|
|
||||||
|
[Documentation de include](https://docs.djangoproject.com/fr/3.2/ref/urls/#include)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Éléments dynamiques d'une URL
|
||||||
|
|
||||||
|
On peut, et il vaut mieux, pouvoir définir des URLs avec des portions dynamiques, ex. `users/view/<identifiant>`.
|
||||||
|
Pour faire cela, la fonction `path` nous offre une méthode simple et efficace pour définir lesdits emplacements;
|
||||||
|
il suffit de marquer la portion dynamique entre chevrons et suivre le format suivant :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path
|
||||||
|
from application.views import view_reference
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("view/<type:nom>/", view_reference, name="view-name")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Dans l'exemple précédent, la mention `type` doit être remplacée par l'un des types reconnus par le moteur de routage de Django ([Formats reconnus pour les parties dynamiques d'URL](https://docs.djangoproject.com/fr/3.2/topics/http/urls/#path-converters))
|
||||||
|
|
||||||
|
- `str` : toute chaîne de caractères non vide, sans le slash
|
||||||
|
- `int` : toute chaîne représentant un entier positif
|
||||||
|
- `slug` : uniquement des lettres latines non accentuées, chiffres, `-`, `_` et aucun espace
|
||||||
|
- `uuid` : nombre sur 128 bits représenté en hexadécimal, groupé par 8-4-4-4-12 chiffres séparés par un tiret.
|
||||||
|
- `path` : toute chaîne de caractères non vide, slash inclus
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Fonction de vue et éléments dynamiques
|
||||||
|
|
||||||
|
La fonction de vue ciblée par une définition d'URL contenant une ou plusieurs portions dynamiques devra accepter un argument portant le nom défini dans la définition d'URL. Si votre définition de route est ainsi :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path
|
||||||
|
from application.views import view_reference
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("view/<int:value>/", view_reference, name="view-name")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Alors votre définition de fonction de vue doit suivre la signature suivante :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# Module application.views
|
||||||
|
from django.http import HttpResponse, HttpRequest
|
||||||
|
|
||||||
|
def view_reference(request: HttpRequest, value: int = None):
|
||||||
|
"""
|
||||||
|
Fonction de vue.
|
||||||
|
|
||||||
|
L'argument `value` est converti automatiquement depuis
|
||||||
|
une chaîne (dans l'URL) vers le type à reconnaître.
|
||||||
|
"""
|
||||||
|
return HttpResponse("Texte de la page.")
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Écriture de vues
|
||||||
|
|
||||||
|
Une fois les routes définies, il faut les lier à des vues. Les vues sont de simples fonctions qui prennent au moins un argument positionnel nommé `request`. L'objet `request` contient des données relatives à la requête HTTP entrante, et la vue doit toujours renvoyer un objet de type `HttpResponse`. Il s'agit d'un contenu de réponse HTTP classique renvoyé au navigateur.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def view_example(request):
|
||||||
|
return HttpResponse("Texte à renvoyer.")
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Lorsqu'une vue est appelée via une URL qui accepte des portions dynamiques, la signature de la vue doit accepter des arguments du même nom que les portions dynamiques de l'URL :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.urls import path
|
||||||
|
from somewhere import view_parametrized
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("view/<uuid:uuid_val>/", view_parametrized, name="view-name")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def view_parametrized(request, uuid_val=None):
|
||||||
|
# Récupérer l'objet possédant l'UUID ou renvoyer un
|
||||||
|
return HttpResponse("Page relative à l'UUID demandé.")
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Types de réponses disponibles
|
||||||
|
|
||||||
|
Django offre plusieurs classes de réponses pour les vues. Voici un tableau de différentes classes pour renvoyer une page 404, 403, ou une redirection avec Django :
|
||||||
|
|
||||||
|
| Type de renvoi | Description |
|
||||||
|
|-------------------------|---------------------------------------|
|
||||||
|
| `HttpResponse` | Contenu basique renvoyé au navigateur |
|
||||||
|
| `HttpResponseNotFound` | Page 404 |
|
||||||
|
| `HttpResponseForbidden` | Page 403 |
|
||||||
|
| `HttpResponseRedirect` | Redirection vers une autre URL |
|
||||||
|
|
||||||
|
Il existe toutefois de meilleurs outils pour générer des pages 404, 403 et redirections.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Renvoyer des redirections
|
||||||
|
|
||||||
|
Django offre une fonction de raccourci pour renvoyer des redirections.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
|
def view_example(request):
|
||||||
|
"""Redirection vers une autre vue."""
|
||||||
|
return redirect("url-name", permanent=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction `redirect`{.py} prend en argument le nom de la route vers laquelle rediriger, et accepte facultativement un argument `permanent` qui indique si la redirection est permanente ou non.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### HttpResponse, c'est bien mais léger
|
||||||
|
|
||||||
|
Devoir renvoyer manuellement une instance de `HttpResponse` dans chaque vue peut sembler fastidieux, notamment si l'on souhaite générer des pages complexes et interactives. Naturellement, Django propose des fonctions utilitaires pour renvoyer des contenus qui ne sont pas directement présents dans du code Python, mais dans des fichiers séparés (on parle de [templates]{.naming}).
|
||||||
|
|
||||||
|
Pas de panique, il existe des façons de renvoyer du contenu dans les vues sans créer d'objet `HttpResponse` de cette façon. On peut par exemple renvoyer le contenu d'un fichier de _template_ via des fonctions simples. (voir le chapitre sur les gabarits)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Vues génériques basées sur les classes
|
||||||
|
|
||||||
|
Django 1.3 a introduit un système de vues qui sont basées sur un système de classes.
|
||||||
|
L'objectif était de rendre élégante l'écriture de vues en n'écrivant que des classes où il suffirait de préciser quelques attributs,
|
||||||
|
et ainsi créer facilement des pages intelligentes.
|
||||||
|
|
||||||
|
Dans la pratique, ça n'est pas un si beau gain de temps que prévu :
|
||||||
|
|
||||||
|
- Une vue de type fonction est souvent simple à comprendre, et s'écrit simplement.
|
||||||
|
- Il existe de nombreuses classes à connaître pour écrire des vues diverses.
|
||||||
|
- Ces classes-là possèdent également des méthodes qu'il faut connaître pour aller plus loin.
|
||||||
|
- La documentation sur le sujet est dense mais incomplète (notamment sur le système complexe de mixins)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Utilité des vues basées sur les classes
|
||||||
|
|
||||||
|
Même si le système est trop complexe pour démarrer (il faut se documenter tout le temps), certaines classes de vues
|
||||||
|
sont utiles car assez concises :
|
||||||
|
|
||||||
|
- `TemplateView` : permet de rendre rapidement un template (configurable)
|
||||||
|
- `ListView` : permet d'afficher une liste d'objets
|
||||||
|
- `DetailView` : permet d'afficher la page de détail d'un objet
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Utiliser une classe de vue
|
||||||
|
|
||||||
|
Si l'on définit une classe de vue pour afficher le contenu d'un template :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
class MyView(TemplateView):
|
||||||
|
template_name = "nom du template"
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.urls import path
|
||||||
|
urlpatterns = [
|
||||||
|
path("view/", MyView.as_view(), name="view-name")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Pages spéciales : 404
|
||||||
|
|
||||||
|
Imaginez que vous avez défini une URL dynamique pour afficher une page produit. L'URL existe sous la forme `view/<uuid:reference>`. Supposons qu'un visiteur arrive à une URL correspondant au format défini, mais que vous ne trouvez dans votre base de données aucun produit correspondant à la référence passée dans l'URL.
|
||||||
|
|
||||||
|
Vous souhaiterez renvoyer une page 404 classique sur votre site web. Pour ne pas vous embêter, Django propose un mécanisme simple pour rendre automatiquement une page 404 (personnalisable); il suffit de lever manuellement une exception de type `Http404`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.http import Http404
|
||||||
|
def my_view(request, user_id):
|
||||||
|
user = find_user(user_id)
|
||||||
|
if user is None:
|
||||||
|
raise Http404()
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Le Concept de Middleware dans Django
|
||||||
|
|
||||||
|
Un [middleware]{.naming} dans Django est une couche d'[intergiciel]{.naming} qui permet d'intercepter et de traiter les requêtes et réponses HTTP. Il s'agit d'une série de composants qui s'exécutent pendant le traitement des requêtes, offrant un moyen de modifier l'entrée ou la sortie globale de l'application.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Qu'est-ce qu'un Middleware ?
|
||||||
|
|
||||||
|
Un middleware est une classe qui définit des méthodes pour intervenir à différentes étapes du cycle de traitement des requêtes et des réponses. Les méthodes les plus couramment utilisées sont :
|
||||||
|
|
||||||
|
| Méthode | Description |
|
||||||
|
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `__init__(self, get_response)` | Initialisation du middleware. |
|
||||||
|
| `__call__(self, request)` | Traitement de la requête avant qu'elle n'atteigne la vue, et de la réponse avant qu'elle ne soit renvoyée au client. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Comment Fonctionne le Middleware ?
|
||||||
|
|
||||||
|
Lorsqu'une requête est reçue, Django la passe à travers chaque middleware dans l'ordre défini dans le paramètre `MIDDLEWARE` de votre fichier `settings.py`. Chaque middleware peut :
|
||||||
|
|
||||||
|
- Modifier la requête avant qu'elle n'atteigne la vue.
|
||||||
|
- Modifier la réponse avant qu'elle ne soit renvoyée au client.
|
||||||
|
- Gérer les exceptions.
|
||||||
|
- Traiter les réponses de templates.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Exemple de Middleware Intégré
|
||||||
|
|
||||||
|
Django fournit plusieurs middlewares intégrés, tels que :
|
||||||
|
|
||||||
|
|
||||||
|
| Middleware | Description |
|
||||||
|
|------------------------------|------------------------------------------------------------------------------------------|
|
||||||
|
| `AuthenticationMiddleware` | Gère l'authentification des utilisateurs (ajoute un attribut `user` à la requête, etc.). |
|
||||||
|
| `SessionMiddleware` | Gère les sessions côté serveur. |
|
||||||
|
| `CommonMiddleware` | Ajoute des améliorations utiles, comme la gestion des en-têtes HTTP. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Créer un Middleware Personnalisé
|
||||||
|
|
||||||
|
Voici comment, par exemple, créer un middleware personnalisé qui enregistre le temps pris par une vue pour s'exécuter.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# myapp/middleware.py
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
class TimingMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
# Initialisation du middleware
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
# Code exécuté pour chaque requête avant la vue
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
# Code exécuté pour chaque réponse après la vue
|
||||||
|
duration = time.time() - start_time
|
||||||
|
print(f"La vue a pris {duration:.2f} secondes.")
|
||||||
|
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Enregistrer le Middleware
|
||||||
|
|
||||||
|
Pour que Django utilise votre middleware, vous devez l'ajouter à la liste `MIDDLEWARE` dans `settings.py`.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# settings.py
|
||||||
|
MIDDLEWARE = [
|
||||||
|
# ... autres middlewares ...
|
||||||
|
'myapp.middleware.TimingMiddleware',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Explication du Fonctionnement
|
||||||
|
|
||||||
|
|
||||||
|
| Méthode | Description |
|
||||||
|
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `__init__(self, get_response)` | Django passe la fonction `get_response` au middleware. Cette fonction est appelée pour obtenir la réponse de la vue ou du middleware suivant. |
|
||||||
|
| `__call__(self, request)` | Cette méthode est appelée pour chaque requête. Vous pouvez y ajouter du code qui s'exécute avant et après l'appel de `get_response(request)`. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Middleware avec Méthodes Additionnelles
|
||||||
|
|
||||||
|
Vous pouvez également implémenter des méthodes spécifiques pour intervenir à différentes étapes spécifiques.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
class MyCustomMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
|
# Code exécuté juste avant l'appel de la vue
|
||||||
|
pass
|
||||||
|
|
||||||
|
def process_exception(self, request, exception):
|
||||||
|
# Code exécuté si une exception est levée
|
||||||
|
pass
|
||||||
|
|
||||||
|
def process_template_response(self, request, response):
|
||||||
|
# Code exécuté si la réponse est un TemplateResponse
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Cycle de Traitement des Requêtes
|
||||||
|
|
||||||
|
1. **Requête entrante**: Le middleware peut modifier ou rejeter la requête.
|
||||||
|
2. **Processus de la vue**: La requête est traitée par la vue.
|
||||||
|
3. **Réponse sortante**: Le middleware peut modifier la réponse avant qu'elle ne soit renvoyée.
|
||||||
|
4. **Réponse au client**: La réponse finale est envoyée au client.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Cas d'Utilisation Courants
|
||||||
|
|
||||||
|
- **Authentification et Autorisation**: Contrôle d'accès aux vues.
|
||||||
|
- **Journalisation**: Enregistrement des requêtes et réponses pour le débogage.
|
||||||
|
- **Gestion des Exceptions**: Capture et traitement des erreurs.
|
||||||
|
- **Compression**: Compression des réponses pour réduire la taille des données transférées.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Exemples Pratiques
|
||||||
|
|
||||||
|
Middleware pour Bloquer des IP
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# myapp/middleware.py
|
||||||
|
|
||||||
|
class BlockIPMiddleware:
|
||||||
|
BLOCKED_IPS = ['192.168.1.1']
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
if ip in self.BLOCKED_IPS:
|
||||||
|
return HttpResponseForbidden("Votre IP a été bloquée.")
|
||||||
|
response = self.get_response(request)
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Middleware pour Ajouter des En-têtes HTTP
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# myapp/middleware.py
|
||||||
|
|
||||||
|
class CustomHeaderMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
response['X-Custom-Header'] = 'MaValeurPersonnalisee'
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Divers : Débogage et accès
|
||||||
|
|
||||||
|
Lorsque dans vos paramètres, `settings.DEBUG = False` (ce qui est la seule valeur acceptable en production) Django n'autorise plus l'accès à vos vues par défaut.
|
||||||
|
|
||||||
|
Pour accéder à vos vues, vous devez définir explicitement une liste d'adresses IP qui ont accès à votre site dans `settings.ALLOWED_HOSTS`. Le paramètre accepte des adresses IPv4, IPv6 et noms d'hôte. La valeur `"*"` signifie "toutes adresses".
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
# settings.py
|
||||||
|
ALLOWED_HOSTS = ("127.0.0.1", "machinea", "192.168.1.15")
|
||||||
|
```
|
615
documentation/04-templating.md
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Templates avec Django
|
||||||
|
|
||||||
|
:::notes
|
||||||
|
Préférer commencer par les vues, pour avoir des vues où utiliser des templates...
|
||||||
|
:::
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Principe de templating
|
||||||
|
|
||||||
|
Le concept de [templating]{.naming} (gabarits en français) consiste à utiliser des fichiers contenant du texte
|
||||||
|
avec des emplacements réservés, qui seront remplis à la demande pour afficher des données personnalisées à l'utilisateur
|
||||||
|
(par exemple, le système permet de générer un email de bienvenue commençant par « Bonjour `votre prénom` »).
|
||||||
|
|
||||||
|
Ce concept est fourni par de nombreuses bibliothèques, et ce dans plusieurs langages (PHP, Javascript, C++, Python etc.)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Exemples de systèmes de templating en Python
|
||||||
|
|
||||||
|
Il existe un certain nombre de moteurs de templating assez populaires en Python :
|
||||||
|
|
||||||
|
- [Django](https://docs.djangoproject.com/en/3.2/topics/templates/) : Intégré à Django, propose un bon compromis entre performance et sécurité.
|
||||||
|
- [Jinja 3](https://jinja.palletsprojects.com/en/3.0.x/) : Basé sur Django (et Genshi ?), mais utilisable dans tout projet Python. *Instagram*
|
||||||
|
- [Mako](https://www.makotemplates.org/) : Performant, autorise le code Python brut. *Reddit*
|
||||||
|
- [Genshi](https://genshi.readthedocs.io/en/latest/) : Utilisé dans TurboGears
|
||||||
|
|
||||||
|
Django est à l'origine de deux moteurs de templates populaires, tels que `Jinja` pour Python et `Twig` pour PHP qui en reprennent largement la syntaxe.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Principe de templating dans Django
|
||||||
|
|
||||||
|
Un moteur de templates propose une façon simple et rapide de générer du contenu en rédigeant
|
||||||
|
des gabarits (des squelettes de document), des modèles où il ne reste plus qu'à renseigner des éléments manquants.
|
||||||
|
Il existe plusieurs façons de procéder lorsqu'on conçoit un langage de templates :
|
||||||
|
|
||||||
|
- Un moteur peut offrir la possibilité d'insérer des expressions Python sans limite dans un document de template
|
||||||
|
- Un langage peut permettre d'écrire du Python brut pour générer du contenu
|
||||||
|
- Un langage peut offrir des fonctionnalités limitées pour éviter des injections de code ou l'écriture de code métier en dehors du code principal
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Les considérations suivantes ont mené à la création du moteur de templates de Django :
|
||||||
|
|
||||||
|
- Les templates sont souvent rédigés et modifiés par des designers (HTML notamment)
|
||||||
|
- Écrire du code Python ne devrait se faire que dans des fichiers Python
|
||||||
|
- Exécuter du code Python en dehors de scripts Python normaux rend un programme plus dur à déboguer
|
||||||
|
- Un designer ou intégrateur ne devrait pas être tenu d'apprendre le Python
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Ainsi, le moteur de templates de Django repose sur des paradigmes simples :
|
||||||
|
|
||||||
|
- Une syntaxe peu variée pour simplifier l'apprentissage (interpolation, attributs, filtres et balises)
|
||||||
|
- Pas une ligne de Python autorisée (même si la syntaxe est similaire)
|
||||||
|
- Système extensible, mais uniquement via du code Python
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Le langage de templates de Django
|
||||||
|
|
||||||
|
Toute la section qui va suivre concerne les éléments de langage spécifiques aux templates de Django.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Créer des templates
|
||||||
|
|
||||||
|
Par défaut, Django ne sait trouver et exploiter des documents de templates que grâce à un paramètre `TEMPLATES` dans le fichier `settings` de votre projet :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [], # Répertoires où trouver des templates
|
||||||
|
"APP_DIRS": True, # Utiliser le répertoire `templates` de mes applications
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
- [Documentation sur le paramètre `TEMPLATES`](https://docs.djangoproject.com/en/dev/ref/settings/#templates)
|
||||||
|
- La clé `BACKEND` permet de choisir le moteur de templates de Django (parmi deux)
|
||||||
|
- La clé `APP_DIRS` permet d'indiquer si les répertoires `templates` des applications tierces sont automatiquement utilisés par le moteur.
|
||||||
|
- La clé `DIRS`, elle, permet de spécifier des répertoires supplémentaires dans lesquels le moteur de templates de Django ira automatiquement chercher des fichiers de templates.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Nommage des templates
|
||||||
|
|
||||||
|
Dans Django, lorsque vous souhaitez afficher un template au navigateur, vous avez accès à une fonction de raccourci nommée `django.shortcuts.render(request, template, context)`.
|
||||||
|
|
||||||
|
Si vous appelez cette fonction avec les arguments `render(request, "index.html", {"value": 3.14159})`{.py} :
|
||||||
|
|
||||||
|
- Django va considérer le premier fichier `index.html` trouvé dans les répertoires `templates` des applications tierces (celles indiquées dans `settings.INSTALLED_APPS`)
|
||||||
|
- Le fichier `index.html` sera rendu en ayant accès aux données de `context`
|
||||||
|
- La fonction renverra à la fin un objet `HttpResponse` avec le document rendu.
|
||||||
|
- [Documentation sur la fonction `render`](https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#render)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Exemple de template
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
return render(request, "index.html", {"value": 3.14159})
|
||||||
|
```
|
||||||
|
|
||||||
|
Le template `index.html` peut être trouvé dans le dossier `templates` de votre application :
|
||||||
|
|
||||||
|
```html {.numberLines}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Index</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Index</h1>
|
||||||
|
<p>Value: {{ value }}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Commentaires
|
||||||
|
|
||||||
|
En HTML, la balise de commentaire s'écrit de la façon suivante :
|
||||||
|
|
||||||
|
```html {.numberLines}
|
||||||
|
<!-- Texte de commentaire HTML -->
|
||||||
|
```
|
||||||
|
|
||||||
|
C'est utile pour annoter un document, mais deux choses se produisent :
|
||||||
|
|
||||||
|
- Le commentaire est visible dans le navigateur.
|
||||||
|
- Écrire ce type de commentaire dans un document qui n'est pas du HTML ne fonctionnera pas.
|
||||||
|
|
||||||
|
Avec Django, on peut insérer un vrai commentaire de template via la balise suivante :
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{# Texte de commentaire Django #}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le commentaire ne sera jamais rendu par le moteur, au même titre que les commentaires Python sont ignorés par l'interpréteur. Essayez autant que possible de vous en servir pour plus facilement relire vos templates _a posteriori_.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Interpolation de données
|
||||||
|
|
||||||
|
Lorsque vous effectuez le rendu d'un template, le seul moyen d'y insérer des données est de les passer via un [contexte]{.naming}. Un contexte est un simple **dictionnaire Python**, où les clés sont des noms qui seront utilisables uniquement dans le template. Si en Python, on rend un template :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
def view_func(request):
|
||||||
|
return render(request, "template.html", {"name": "Maurice"})
|
||||||
|
```
|
||||||
|
|
||||||
|
Le template aura uniquement accès à une donnée nommée `name` et dont la valeur sera `"Maurice"`{.py}. (Voir [Documentation sur les modifieurs de contexte](https://docs.djangoproject.com/en/dev/ref/templates/api/#built-in-template-context-processors) pour savoir quels autres noms sont rendus disponibles dans le contexte de template)
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
Bonjour, {{ name }}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Interpoler un attribut d'une variable
|
||||||
|
|
||||||
|
Lorsqu'une des variables du contexte est un objet qui contient des attributs, vous pouvez les interpoler avec la syntaxe suivante :
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{{ var1.attribute }}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Interpoler un élément d'une variable (clé ou index)
|
||||||
|
|
||||||
|
De la même façon, si votre variable de contexte est une **liste** ou un **dictionnaire** dont vous voulez récupérer un élement, et que vous connaissez son index ou bien sa clé, vous pouvez utiliser la syntaxe suivante :
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{{ var1.nom_de_la_clé }} {# clé de dictionnaire qui est une chaîne #}
|
||||||
|
{{ var2.numéro_d'index }} {# index de séquence #}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Interpoler la valeur de retour d'une méthode
|
||||||
|
|
||||||
|
Pour terminer, si votre variable de contexte est un objet dont vous voulez récupérer la valeur de retour d'une méthode, vous pouvez écrire :
|
||||||
|
|
||||||
|
```djangotemplate
|
||||||
|
{{ var1.nom_de_la_methode }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette méthode sera **toujours** appelée sans argument.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Interpolation et limitations
|
||||||
|
|
||||||
|
Le langage de templates de Django a des restrictions sur les interpolations :
|
||||||
|
|
||||||
|
- Vous ne pouvez pas passer d'arguments lorsque vous appelez une méthode et c'est intentionnel, seules les méthodes acceptant uniquement des arguments facultatifs sont utilisables.
|
||||||
|
- Vous ne pouvez pas utiliser d'attributs ou méthodes commençant par `_`{.py} (considérées comme privées en Python).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Filtres de templates
|
||||||
|
|
||||||
|
Lorsque vous effectuez une interpolation dans un template (`{{ ... }}`), vous avez la possibilité de transformer l'interpolation, souvent pour des questions de présentation. Pour effectuer ce genre de traitement sur une interpolation, vous devez utiliser des [filtres]{.naming}.
|
||||||
|
|
||||||
|
Les filtres permettent de transformer une interpolation et fonctionnent sur chaînes, dates, listes et d'autres types. (voir [filtres intégrés à Django](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#built-in-filter-reference))
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Filtre basique (sans argument)
|
||||||
|
|
||||||
|
Un filtre s'utilise simplement en le "pipant" à l'expression à transformer. Le résultat affiché sera celui du filtre.
|
||||||
|
Il est possible d'utiliser plusieurs filtres successivement en les chaînant.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{# Utilisation du filtre upper, pour mettre en majuscules #}
|
||||||
|
<h1>{{ name|upper }}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Filtre avec argument
|
||||||
|
|
||||||
|
Certains filtres acceptent un argument (**et un seul maximum**). Pour les utiliser, il suffit de faire suivre le nom du filtre par `:` et d'indiquer à la suite la valeur du paramètre, généralement sous forme d'une chaîne de caractères. Ceci est documenté pour chaque filtre disponible.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{# Utilisation du filtre date pour formater un objet datetime #}
|
||||||
|
<p>Date de création : {{ moment|date:"d m Y" }}</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Filtres fréquents
|
||||||
|
|
||||||
|
Parmi les nombreux [filtres disponibles de base dans Django](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#built-in-filter-reference), certains seront plus souvent utilisés que d'autres, comme par exemple :
|
||||||
|
|
||||||
|
| Filtre | Description |
|
||||||
|
|--------------------------------------------|----------------------------------------------------------------------------------------------------|
|
||||||
|
| `upper` et `lower` | Pour changer la casse du texte (majuscules et minuscules). |
|
||||||
|
| `default:value` | Si la valeur équivaut à `False`, utiliser `value` par défaut. |
|
||||||
|
| `default_if_none:value` | Si la valeur équivaut à `None`, utiliser `value` par défaut. |
|
||||||
|
| `date:format` | Interpoler un objet `datetime` avec un format spécifique. |
|
||||||
|
| `length` | Interpoler la longueur d'une collection. |
|
||||||
|
| `safe` | Ne pas échapper le contenu à interpoler. Permet d'insérer du HTML. |
|
||||||
|
| `truncatechars:num` et `truncatewords:num` | Couper et placer une ellipse (tronquer le texte après un certain nombre de caractères ou de mots). |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Balises de templates
|
||||||
|
|
||||||
|
Nous venons de voir que l'on pouvait "filtrer" du contenu provenant d'une interpolation. Et s'il était possible d'utiliser des
|
||||||
|
outils plus avancés, permettant de générer des données dynamiques, ou même de rendre du contenu conditionnel, par exemple ?
|
||||||
|
|
||||||
|
Le moteur de templates de Django fournit un système de [balises]{.naming} qui nous permet d'accéder à des comportements plus avancés.
|
||||||
|
Grâce aux balises, nous allons pouvoir utiliser des boucles, conditionner le rendu d'un segment de template, configurer le rendu par le
|
||||||
|
moteur ou encore modifier le contexte de rendu...
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Syntaxe des balises
|
||||||
|
|
||||||
|
Pour utiliser des balises de gabarit, le plus important, c'est d'en connaitre la syntaxe. Pour utiliser une balise, il faut écrire :
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% tag_name argument1 argument2... %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Entre les marqueurs `{% ... %}` il suffit d'indiquer le nom de la balise et d'y ajouter, séparés par des espaces, autant d'arguments que ce qu'accepte la balise. Certaines balises fonctionnent par paire, comme `{% if %}…{% endif %}`{.djangotemplate}, ou `{% for %}…{% endfor %}`{.djangotemplate}. Ces balises sont documentées dans la [documentation officielle de Django](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#built-in-tag-reference)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Balises natives
|
||||||
|
|
||||||
|
Pour aborder le chapitre des balises, nous allons nous intéresser à certaines balises précises dédiées à des outils importants.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Conditions
|
||||||
|
|
||||||
|
| Balise | Description |
|
||||||
|
|---------------|-------------------------------------------------------------------------------------------|
|
||||||
|
| `{% if %}` | Évalue une condition et affiche le contenu si la condition est vraie. |
|
||||||
|
| `{% elif %}` | Alternative à `{% if %}` si la première condition est fausse. |
|
||||||
|
| `{% else %}` | Définit un bloc à afficher si aucune des conditions précédentes n'est vraie. |
|
||||||
|
| `{% endif %}` | Termine une instruction conditionnelle. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Boucles
|
||||||
|
|
||||||
|
| Balise | Description |
|
||||||
|
|------------------------|-------------------------------------------------------------------------------------------|
|
||||||
|
| `{% for ... in ... %}` | Itère sur une séquence d'objets. |
|
||||||
|
| `{% empty %}` | Définit un bloc à afficher si la séquence est vide. |
|
||||||
|
| `{% endfor %}` | Termine une boucle. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Autres Balises Importantes
|
||||||
|
|
||||||
|
| Balise | Description |
|
||||||
|
|---------------------------------------|----------------------------------------------------------------|
|
||||||
|
| `{% include %}` | Inclut un autre template. |
|
||||||
|
| `{% extends %}` | Indique que le template hérite d'un autre template. |
|
||||||
|
| `{% block %}` | Définit un bloc de contenu modifiable dans un template hérité. |
|
||||||
|
| `{% endblock %}` | Termine un bloc de contenu. |
|
||||||
|
| `{% url %}` | Génère une URL vers une vue nommée. |
|
||||||
|
| `{% csrf_token %}` | Génère un jeton CSRF pour sécuriser les formulaires. |
|
||||||
|
| `{% comment %}` et `{% endcomment %}` | Commente une section du template. |
|
||||||
|
| `{% load %}` | Charge une bibliothèque de tags personnalisés. |
|
||||||
|
| `{% with %}` et `{% endwith %}` | Assigne une ou plusieurs variables pour une portée limitée. |
|
||||||
|
| `{% endwith %}` | Termine le bloc `with`. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
| Balise | Description |
|
||||||
|
|-----------------------|--------------------------------------------------------------------------------------|
|
||||||
|
| `{% autoescape %}` | Active ou désactive l'échappement automatique dans un bloc. |
|
||||||
|
| `{% endautoescape %}` | Termine le bloc `autoescape`. |
|
||||||
|
| `{% now %}` | Affiche la date et l'heure actuelles selon un format spécifié. |
|
||||||
|
| `{% spaceless %}` | Supprime les espaces blancs entre les balises HTML dans le contenu du bloc. |
|
||||||
|
| `{% endspaceless %}` | Termine le bloc `spaceless`. |
|
||||||
|
| `{% verbatim %}` | Ignore le rendu des balises Django dans le contenu du bloc. |
|
||||||
|
| `{% endverbatim %}` | Termine le bloc `verbatim`. |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Structures de contrôle : `if`
|
||||||
|
|
||||||
|
Vous pouvez grace aux balises `{% if %}`{.djangotemplate} et `{% else %}`{.djangotemplate} construire un document dont certaines zones seront rendues conditionnellement.
|
||||||
|
La syntaxe de la balise est similitaire aux structures conditionnelles en Python.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% if expression %}
|
||||||
|
Contenu si la condition est vraie
|
||||||
|
{% elif expression %}
|
||||||
|
Contenu si la premiere condition est fausse
|
||||||
|
{% else %}
|
||||||
|
Contenu si la condition est fausse
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Structures de contrôle : `for`
|
||||||
|
|
||||||
|
Si vous souhaitez générer du contenu pour chaque élément d'un itérable, vous pouvez utiliser la balise `{% for %}`{.djangotemplate}. La syntaxe
|
||||||
|
de la balise est similaire aux structures de boucle en Python, à l'exception de l'existence d'une clause `{% empty %}`{.djangotemplate},
|
||||||
|
dont le contenu sera affiché si l'itérable est vide.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% for name in iterable %}
|
||||||
|
Contenu de l'itération {{ name }}
|
||||||
|
{% empty %}
|
||||||
|
Contenu si la boucle est vide
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Inclusion de templates
|
||||||
|
|
||||||
|
La balise `{% include %}`{.djangotemplate} permet d'inclure un autre template dans le template actuel. L'intérêt d'inclure des templates est de pouvoir les utiliser comme **composants** dans certains cas répétitifs. Il est possible de transmettre un contexte spécifique lors de l'inclusion de templates.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% for user in users %}
|
||||||
|
{% include "path/to/user_template.html" with user=user %}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
<card>
|
||||||
|
<img src="{{ user.picture.url }}">
|
||||||
|
<p>{{ user.username }}</p>
|
||||||
|
</card>
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Héritage de templates
|
||||||
|
|
||||||
|
Dans un site web, beaucoup de pages partagent la même structure. Par exemple, sur de nombreux sites, la barre de navigation est identique et le pied de page également. Il semblerait redondant de devoir répéter dans les templates de diverses sections le même code HTML, notamment l'en-tête HTML ou le pied de page.
|
||||||
|
|
||||||
|
Django propose un système d'héritage de templates, permettant de partager des blocs de contenu entre plusieurs templates. La fonctionnalité est offerte via deux balises, `{% block %}`{.djangotemplate} et `{% extends %}`{.djangotemplate}.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Dans l'héritage de templates, vous avez deux notions de base :
|
||||||
|
|
||||||
|
1. Un [template de base]{.naming} ou template parent. Ce template est le template principal du site, et permet de fournir le contenu commun aux autres templates.
|
||||||
|
2. Un [template enfant]{.naming}. Ce template est le template qui hérite du template de base, et qui ajoute ou modifie du contenu via des [blocs]{.naming}.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Template de base
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{# Contenu commun aux templates enfants #}
|
||||||
|
{% block body %}{% endblock body %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
Un template de base propose un contenu fixe qui servira de base aux templates enfants. Ce même template proposera des duos de balises `{% block name %}`{.djangotemplate} et `{% endblock name %}`{.djangotemplate}, dont le contenu sera personnalisable par les templates enfants.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Dans un [template de base]{.naming}, qui contient un document HTML classique, on a tendance à décrire des blocs personnalisables (via la balise `{% block %}`{.djangotemplate}) pour des sections du document qui sont souvent les mêmes, en voici une liste :
|
||||||
|
|
||||||
|
- Le titre de la page (`<head><title>`) est souvent l'objet d'un bloc Django, nommé `title` ou `page_title`
|
||||||
|
- Si la page contient un titre ous forme de balise `<h1>`, son contenu peut faire l'objet d'un bloc Django, nommé `title`
|
||||||
|
- Dans la balise HTML `<head>`, on crée souvent un bloc Django destiné à ajouter des fichiers CSS, souvent nommé `css`
|
||||||
|
- Dans la balise HTML `<head>`, on crée souvent un bloc Django destiné à ajouter des fichiers JavaScript, souvent nommé `js`
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Template enfant
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% extends "path/to/base_template.html" %}
|
||||||
|
{% block body %}
|
||||||
|
Contenu de remplacement du block body, spécifique au template enfant
|
||||||
|
{% endblock body %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Un template enfant hérite via la balise `{% extends %}`{.djangotemplate} de Django. Il peut ensuite uniquement modifier le contenu des blocs `{% block %}`{.djangotemplate} et `{% endblock %}`{.djangotemplate} de son template de base. Tout le reste du contenu du template enfant sera ignoré.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Réutiliser des blocs dans un template enfant
|
||||||
|
|
||||||
|
Dans un template enfant, lorsque vous personnalisez un bloc avec la balise `{% block %}`{.djangotemplate}, vous avez la possibilité de récupérer
|
||||||
|
le contenu original du bloc provenant du template parent, grâce à une variable de contexte nommée `block.super`{.djangotemplate}.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% extends "path/to/base_template.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{{ block.super }}
|
||||||
|
Contenu ajouté au block body, spécifique au template enfant
|
||||||
|
{% endblock body %}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
#### Stratégies de création des templates
|
||||||
|
|
||||||
|
En général, lorsque l'on souhaite exploiter les fonctionnalités d'héritage de templates, on essaie d'organiser l'arborescence des templates
|
||||||
|
de façon à faciliter leur maintenance. Quelques astuces à ce sujet :
|
||||||
|
|
||||||
|
- Le **template de base** peut se trouver à la racine d'un dossier de templates personnalisé (voir la clé `["DIRS"]`{.python} de `settings.TEMPLATES`)
|
||||||
|
- Le **template de base** peut aussi se trouver à la racine du répertoire `templates` de l'application Django principale.
|
||||||
|
- Les autres templates se trouvent généralement dans un sous-dossier de `templates` portant le nom de l'application Django.
|
||||||
|
- Les autres templates peuvent être de plusieurs types :
|
||||||
|
- Template pour une page entière
|
||||||
|
- Template utilisé avec la balise Django `{% include %}`{.djangotemplate}
|
||||||
|
- Template pour générer le contenu d'e-mails
|
||||||
|
- etc.
|
||||||
|
- Tous ces templates peuvent être organisés dans des répertoires séparés indiquant leur usage.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Balises utiles
|
||||||
|
|
||||||
|
Parmi les balises disponibles par défaut dans Django, quelques autres sont indispensables :
|
||||||
|
|
||||||
|
| Balise | Description |
|
||||||
|
|-------------------------------------------------------------------|-------------------------------------------------|
|
||||||
|
| `{% url %}`{.djangotemplate} | Rendre une URL que l'on a nommée |
|
||||||
|
| `{% with %}`{.djangotemplate} et `{% endwith %}`{.djangotemplate} | Créer un contexte temporaire |
|
||||||
|
| `{% load %}`{.djangotemplate} | Chargement de tags et filtres |
|
||||||
|
| `{% csrf_token %}`{.djangotemplate} | Insérer un champ de sécurité dans un formulaire |
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### `{% url %}`{.djangotemplate}
|
||||||
|
|
||||||
|
La balise `{% url %}`{.djangotemplate} permet de rendre une URL que l'on a nommée. Si nous avons défini l'URL suivante :
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("url/path/<type:param>", view_name, name="named"),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Nous pouvons insérer dans un template l'URL fixe, quelle qu'elle soit, en utilisant la balise `{% url %}`{.djangotemplate} comme suit.
|
||||||
|
Si l'URL exacte nécessite une valeur spécifique d'un argument ou de plusieurs arguments, nous pouvons utiliser la syntaxe suivante :
|
||||||
|
|
||||||
|
```djangotemplate
|
||||||
|
<a href="{% url "named" param=value%}">Anchor text</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### `{% with %}`{.djangotemplate}
|
||||||
|
|
||||||
|
Lorsque certaines expressions complexes sont nécessaires à plusieurs endroits dans un template, ex. :
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{{ variable.method|filter1|filter2 }}
|
||||||
|
...
|
||||||
|
{{ variable.method|filter1|filter2 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
On peut utiliser la balise `{% with %}`{.djangotemplate} pour créer des variables de contexte temporaires.
|
||||||
|
Cela permet d'utiliser un nom de variable simple pour référencer cette expression si longue à rédiger.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% with name=variable.method|filter1|filter2 %}
|
||||||
|
{{ name }}
|
||||||
|
...
|
||||||
|
{{ name }}
|
||||||
|
{% endwith %}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### `{% load %}`{.djangotemplate}
|
||||||
|
|
||||||
|
Dans les templates de Django, la balise `{% load %}`{.djangotemplate} permet de charger des tags et des filtres personnalisés.
|
||||||
|
Par défaut, seuls les tags et filtres fournis par Django sont chargés. Lorsqu'une application fournit ses propres tags et filtres, il est possible de les charger via la balise `{% load %}`{.djangotemplate}, d'une manière similaire aux importations de modules Python.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% load mytags %}
|
||||||
|
{% load myfilters %}
|
||||||
|
```
|
||||||
|
|
||||||
|
La balise sera généralement nécessaire lors de l'usage de fichiers statiques (ex. CSS et JavaScript).
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
{% load static %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/style.css' %}">
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Balises et filtres personnalisés
|
||||||
|
|
||||||
|
Django permet de développer ses propres filtres et tags, qui pourront être utilisables dans nos
|
||||||
|
templates. Une fois créés, nous pouvons les importer dans un template via la balise `{% load %}`{.djangotemplate}.
|
||||||
|
|
||||||
|
Voir la [documentation officielle sur les filtres personnalisés](https://docs.djangoproject.com/fr/3.2/howto/custom-template-tags/#custom-template-tags-and-filters)
|
||||||
|
|
||||||
|
:::notes
|
||||||
|
Faire la démo de l'écriture d'un filtre notamment et d'une balise simple, puis proposer un exercice.
|
||||||
|
:::
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Fichiers statiques
|
||||||
|
|
||||||
|
[Documentation de la gestion des fichiers statiques](https://docs.djangoproject.com/en/dev/howto/static-files/)
|
||||||
|
|
||||||
|
Lorsque l'on sert du HTML, le document référence souvent des fichiers statiques tels que des feuilles de style CSS, des fichiers de code JavaScript ou encore des images spécifiques au site (bannière, logo, tout média qui n'est pas envoyé dynamiquement par les utilisateurs du site).
|
||||||
|
|
||||||
|
Django simplifie l'utilisation de ces fichiers pour le développement, et offre également des commandes de gestion (`manage.py`) pour préparer
|
||||||
|
le passage en production.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Il y a plusieurs étapes à suivre lorsque l'on souhaite utiliser des fichiers statiques en développement :
|
||||||
|
|
||||||
|
1. Vérifier que `"django.contrib.staticfiles"` est présent `settings.INSTALLED_APPS`
|
||||||
|
2. Avoir un répertoire `static/` dans une ou plusieurs applications (ou créer un répertoire personnalisé)
|
||||||
|
3. Ajouter dans `settings.py` une liste dans `STATICFILES_DIRS` qui contient les répertoires `static` que vous avez créés
|
||||||
|
4. Ajouter `{% load static %}`{.djangotemplate} aux templates utilisant la balise `{% static %}`{.djangotemplate}
|
||||||
|
5. Référencer le chemin relatif de la ressource via `{% static "chemin/relatif.ext" %}`
|
||||||
|
|
||||||
|
À l'exécution, et uniquement en mode `DEBUG = True`{.python}, le serveur de développement de Django sera capable
|
||||||
|
de trouver et servir correctement les ressources stockées dans les répertoires `static` au navigateur.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
311
documentation/05-forms.md
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
---
|
||||||
|
title: Formulaires Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Saisir des données avec les formulaires
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Les formulaires HTML
|
||||||
|
|
||||||
|
Le web n'a pas toujours eu de formulaires, mais c'est arrivé très tôt; Le navigateur [Mosaic du NCSA](https://en.wikipedia.org/wiki/NCSA_Mosaic) a introduit le
|
||||||
|
concept dans sa version 2.0, publiée en novembre 1993. Ils sont rentrés de facto dans la spécification HTML, qui a subi quelques améliorations depuis.
|
||||||
|
|
||||||
|
En HTML, les formulaires permettent à l'utilisateur d'interagir et d'envoyer des données.
|
||||||
|
Django fournit une classe `Form`{.python} qui permet de facilement construire des formulaires et d'abstraire leur validation du contenu.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Les formulaires avec Django
|
||||||
|
|
||||||
|
Pour pouvoir afficher des formulaires dans un site Django, il faut écrire des classes Python qui héritent d'une classe `Form`{.python} se trouvant dans le module `django.forms`{.python}. Les classes de formulaire décrivent les champs du formulaire à afficher, ainsi que leur type (texte, fichier, date, etc.) et leur contenu.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from datetime import date
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
class PersonForm(forms.Form):
|
||||||
|
"""Exemple de formulaire avec des champs de types divers."""
|
||||||
|
|
||||||
|
first_name = forms.CharField(max_length=32, label="first name")
|
||||||
|
last_name = forms.CharField(max_length=32, label="last name")
|
||||||
|
birth_date = forms.DateField(initial=date(1990, 1, 1), label="birthday")
|
||||||
|
number = forms.IntegerField(label="phone number")
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput, max_length=50, label="password")
|
||||||
|
```
|
||||||
|
|
||||||
|
Les classes de formulaire déclarent des attributs dont les types sont des instances de classes héritant de `Field`{.python}.
|
||||||
|
|
||||||
|
[Documentation des champs de formulaires](https://docs.djangoproject.com/en/dev/ref/forms/fields/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Utilisation de formulaires
|
||||||
|
|
||||||
|
Une fois un formulaire défini, on peut l'utiliser dans une vue.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.shortcuts import render
|
||||||
|
from demonstration.forms import PersonForm
|
||||||
|
|
||||||
|
def view_person_form(request):
|
||||||
|
"""Utilisation du formulaire PersonForm."""
|
||||||
|
form = PersonForm(data=request.POST or None)
|
||||||
|
return render(request, "person-form.html", {"form": form})
|
||||||
|
```
|
||||||
|
|
||||||
|
Lorsqu'une vue affiche un formulaire, elle doit l'initialiser. Un objet de formulaire peut être initialisé de deux manieres :
|
||||||
|
|
||||||
|
- `PersonForm(data=request.POST)`{.python} si la requête HTTP est de type `POST`;
|
||||||
|
- `PersonForm()`{.python} si la requête HTTP est de type `GET`.
|
||||||
|
|
||||||
|
Dans le premier cas, les données ``POST: dict``{.python} seront utilisées pour les associer à l'objet formulaire.
|
||||||
|
Dans le second cas, le formulaire sera vide et s'affichera sans contenu dans le template.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Utilisation des formulaires dans les templates
|
||||||
|
|
||||||
|
Lorsque l'on a instancié un objet `Form`{.python}, nous l'envoyons au contexte du template.
|
||||||
|
Interpoler l'objet suffit à en afficher les champs. Les données liées à l'objet seront visibles dans les champs.
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
...
|
||||||
|
<body>
|
||||||
|
<h1>Person form</h1>
|
||||||
|
{# Envoyer le formulaire à l'URL courante avec la methode POST #}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<input type="submit" value="OK">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
Notez que vous avez la responsabilité de fournir la balise `<form>`{.html} et la classique balise `<input type="submit">`{.html} pour faire fonctionner le formulaire.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
La représentation de l'objet `Form`{.python} dans un template peut prendre plusieurs formes.
|
||||||
|
|
||||||
|
| Syntaxe | Description |
|
||||||
|
|-----------------------------------|---------------------------------------------------------------------------------------------|
|
||||||
|
| `{{ form }}`{.djangotemplate} | Affiche les champs dans des balises `<div>`{.html}. |
|
||||||
|
| `{{ form.as_p }}`{.djangotemplate} | Affiche les champs dans des balises `<p>`{.html}. |
|
||||||
|
| `{{ form.as_table }}`{.djangotemplate} | Affiche les champs dans des balises `<th>`{.html} et `<td>`{.html}. |
|
||||||
|
|
||||||
|
Les options sont limitées mais suffisantes. Pour pouvoir utiliser un rendu compatible avec des bibliothèques CSS telles que Bootstrap, Bulma ou Foundation, on conseillera une application externe telle que [django-crispy-forms](https://django-crispy-forms.readthedocs.io/en/latest/index.html).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Attention lors du rendu
|
||||||
|
|
||||||
|
Notez que l'interpolation d'un objet formulaire dans un template ne génère jamais les balises `<form></form>`{.html} nécessaires,
|
||||||
|
ni même la classique balise `<input type="submit">`{.html} pour faire fonctionner le formulaire.
|
||||||
|
|
||||||
|
**La raison est simple** : les classes de formulaires existent notamment pour simpler
|
||||||
|
l'affichage et la validation des données.
|
||||||
|
Définir la destination de l'envoi des données et les boutons de validation est de la responsabilité du programmeur.
|
||||||
|
Techniquement, cela permet par exemple de cumuler plusieurs objets formulaire dans la même balise `<form>`{.html}, si besoin.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Validation des formulaires
|
||||||
|
|
||||||
|
Un objet `Form`{.python} peut aussi gérer la validation des données. Par défaut, il est capable de s'assurer que tous les champs obligatoires
|
||||||
|
sont remplis et que les données sont valides. Nous verrons par la suite comment on peut personnaliser la validation.
|
||||||
|
|
||||||
|
Pour confirmer dans la vue que des données expédiées par un formulaire sont correctes, nous devons utiliser la méthode `is_valid()`{.python}.
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from demonstration.forms import PersonForm
|
||||||
|
|
||||||
|
def view_person_form(request):
|
||||||
|
"""Utilisation du formulaire PersonForm."""
|
||||||
|
form = PersonForm(data=request.POST or None)
|
||||||
|
if form.is_valid():
|
||||||
|
... # Traitement des données
|
||||||
|
return redirect("accueil")
|
||||||
|
return render(request, "person-form.html", {"form": form})
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### La méthode `is_valid()`{.python}
|
||||||
|
|
||||||
|
La méthode `is_valid()`{.python} d'un formulaire renvoie `True`{.python} si toutes les données associées au formulaire sont correctes et `False`{.python} sinon.
|
||||||
|
Cette méthode exécute plusieurs méthodes de validation de l'objet qui s'occupent entre autres de confronter les données entrantes et le type des champs.
|
||||||
|
|
||||||
|
Si le formulaire contient des données considérées incorrectes, l'objet contiendra des attributs `.field_errors`{.python} et `.non_field_errors`{.python} contenant des messages d'erreur,
|
||||||
|
qui seront automatiquement affichés dans le template pour alerter l'utilisateur.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```djangotemplate {.numberLines}
|
||||||
|
<form action="" method="post" name="person-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="form-table">
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<td><input type="submit" name="submit-form" value="Validate"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** : La balise `{% csrf_token %}`{.djangotemplate} est une protection obligatoire lorsqu'on gère la méthode HTTP `POST`. Django est configuré par défaut pour qu'une page demandée via la méthode `POST` et qui ne possède pas une donnée de formulaire valide pour l'entrée `csrf_middleware_token` provoque une erreur HTTP 403.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Validation de la saisie utilisateur
|
||||||
|
|
||||||
|
Django propose un système simple dans les formulaires permettant de contrôler la saisie de données des utilisateurs.
|
||||||
|
Le système consiste principalement à écrire des méthodes des types suivants :
|
||||||
|
|
||||||
|
- `def clean_<field_name>() -> Any`{.python} : vérifie l'entrée du champ `field_name`
|
||||||
|
- `def clean() -> dict`{.python} : vérifier l'entrée de tous les champs
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Les méthodes `clean_<field_name>()`{.python} renvoient une donnée Python correspondant au type du champ,
|
||||||
|
ou peuvent lever une exception de type `ValidationError` si la donnée du champ est incorrecte ou non autorisée.
|
||||||
|
|
||||||
|
La méthode `clean()`{.python}, elle, renvoie un dictionnaire de données, mais peut aussi vérifier
|
||||||
|
plusieurs champs à la fois. Elle peut également lever une `ValidationError`{.python}.
|
||||||
|
Dans ce cas, le formulaire contient des données dans `.non_field_errors`{.python}.
|
||||||
|
|
||||||
|
:::notes
|
||||||
|
Jeter un œil à la documentation en ligne pour en voir un exemple.
|
||||||
|
:::
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```python {.numberLines}
|
||||||
|
from django import forms
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
text = forms.CharField(max_length=10, label="Texte")
|
||||||
|
|
||||||
|
def clean_text(self):
|
||||||
|
return self.data["text"] # Va aller dans le dictionnaire `self.cleaned_data`
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
return super().clean() # Renvoie un dictionnaire des données de champs "nettoyées"
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Gestion des téléchargements et fichiers média
|
||||||
|
|
||||||
|
La gestion des téléchargements est toujours un cas un peu délicat, que Django permet de gérer.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Pour gérer le téléchargement, il faut remplir quelques conditions :
|
||||||
|
|
||||||
|
- Créer un répertoire
|
||||||
|
- Définir `settings.MEDIA_URL`{.python} et `settings.MEDIA_ROOT`{.python}
|
||||||
|
- Avoir un formulaire HTML avec la méthode `POST` et le type d'encodage
|
||||||
|
`<form enctype="multipart/form-data">`{.html}.
|
||||||
|
|
||||||
|
Pour accéder aux fichiers uploadés :
|
||||||
|
|
||||||
|
- Ajouter `+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)`{.python} aux URLs du
|
||||||
|
projet pour servir les fichiers (ne fonctionne pas si `settings.DEBUG = False`{.python})
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```{.python .numberLines}
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media" # par exemple
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
```
|
||||||
|
|
||||||
|
```{.python .numberLines}
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", view_index, name="index"),
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
```
|
||||||
|
|
||||||
|
```{.djangotemplate .numberLines}
|
||||||
|
<form method="post" action="" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %} {{ form }} <input type="submit" value="OK">
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le code (approximatif) à ajouter pour prendre en compte les fichiers média et pour pouvoir les
|
||||||
|
afficher.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```{.python .numberLines}
|
||||||
|
class UploadForm(forms.Form):
|
||||||
|
"""Example of file upload form."""
|
||||||
|
image = forms.ImageField(max_length=128, required=True, label="image")
|
||||||
|
|
||||||
|
def save_uploaded_file(request: HttpRequest) -> str:
|
||||||
|
"""Custom method to process the upload of an image."""
|
||||||
|
storage = FileSystemStorage()
|
||||||
|
image = request.FILES["image"] # l'attribut FILES est un dictionnaire de champs fichier
|
||||||
|
name = storage.save(None, image)
|
||||||
|
return storage.url(name)
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemple de formulaire et d'une fonction à appeler pour sauver le fichier et récupérer son URL.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```{.python .numberLines}
|
||||||
|
def view_upload_form(request: HttpRequest): # noqa
|
||||||
|
"""View for the upload form."""
|
||||||
|
form = UploadForm(request.POST, request.FILES) if request.method == "POST" else UploadForm()
|
||||||
|
if form.is_valid():
|
||||||
|
image_url = save_uploaded_file(request)
|
||||||
|
return render(request, "template.html", {"form": form, "image_url": image_url})
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemple de vue utilisant notre formulaire
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Divers : Formulaire de recherche
|
||||||
|
|
||||||
|
L'écriture d'un formulaire de recherche est régie par les étapes suivantes :
|
||||||
|
|
||||||
|
- Afficher (méthode GET) la page contenant uniquement le formulaire.
|
||||||
|
- Valider le formulaire vers la même page (POST ou GET).
|
||||||
|
- Si le formulaire est valide, ajouter les résultats au contexte du template.
|
||||||
|
- Dans le template, s'il y a des résultats, alors les afficher sous le formulaire. Sinon, n'afficher que le formulaire.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Divers : Applications tierces pour gérer les formulaires
|
||||||
|
|
||||||
|
Il existe plusieurs applications dédiées à un affichage avancé des formulaires. La plus aboutie et réputée est `django-crispy-forms`.
|
||||||
|
|
||||||
|
```bash {.numberLines}
|
||||||
|
pip install django-crispy-forms
|
||||||
|
```
|
||||||
|
|
||||||
|
[Page officielle de la documentation du package](https://django-crispy-forms.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Divers : Applications tierces pour déboguer vos pages
|
||||||
|
|
||||||
|
Une application célèbre et utile pour déboguer vos pages en live est `django-debug-toolbar`.
|
||||||
|
|
||||||
|
```bash {.numberLines}
|
||||||
|
pip install django-debug-toolbar
|
||||||
|
```
|
||||||
|
|
||||||
|
[Page officielle de la documentation du package](https://django-debug-toolbar.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Atelier
|
1108
documentation/06-orm-models.md
Normal file
138
documentation/07-model-forms.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Utiliser des formulaires avec la base de données
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Définition de formulaires basés sur les modèles
|
||||||
|
|
||||||
|
### Qu'est-ce qu'un `ModelForm` ?
|
||||||
|
|
||||||
|
Un `ModelForm` est une classe spéciale de formulaire dans Django qui génère automatiquement un formulaire à partir d'un modèle spécifié.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django import forms
|
||||||
|
from .models import MonModel
|
||||||
|
|
||||||
|
class MonModelForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = MonModel
|
||||||
|
fields = '__all__'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ici, un formulaire est créé pour le modèle `MonModel`. L'option `fields = '__all__'` signifie que tous les champs du modèle sont inclus dans le formulaire.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Utilisation des formulaires et validation
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Utilisation de `ModelForm` dans une vue
|
||||||
|
|
||||||
|
Un `ModelForm` peut être utilisé dans une vue de la même manière qu'un formulaire normal. Voici un exemple de vue qui utilise un `ModelForm`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.shortcuts import render
|
||||||
|
from .forms import MonModelForm
|
||||||
|
|
||||||
|
def ma_vue(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = MonModelForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
else:
|
||||||
|
form = MonModelForm()
|
||||||
|
return render(request, 'ma_template.html', {'form': form})
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans cet exemple, si la requête est une requête POST et que le formulaire est valide, le modèle associé est automatiquement enregistré en base de données grâce à la méthode `form.save()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Gestion des téléchargements
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Téléchargements de fichiers avec `ModelForm`
|
||||||
|
|
||||||
|
Pour gérer le téléchargement de fichiers avec un `ModelForm`, il est nécessaire d'utiliser un champ `FileField` ou `ImageField` dans le modèle. Il faut également passer `request.FILES` à l'instance `ModelForm`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MonModel(models.Model):
|
||||||
|
mon_fichier = models.FileField(upload_to='mes_fichiers/')
|
||||||
|
|
||||||
|
# Dans la vue
|
||||||
|
def ma_vue(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = MonModelForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
else:
|
||||||
|
form = MonModelForm()
|
||||||
|
return render(request, 'ma_template.html', {'form': form})
|
||||||
|
```
|
||||||
|
|
||||||
|
Ici, si un fichier est téléchargé via le formulaire, il est automatiquement sauvegardé dans le répertoire `mes_fichiers/`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
# Utiliser des formulaires avec la base de données
|
||||||
|
|
||||||
|
Utiliser des formulaires qui correspondent à nos définitions de modèles est assez simple. Il suffit de créer une classe héritant de `django.forms.ModelForm`. Le formulaire correspondant contient automatiquement des champs
|
||||||
|
reflétant ceux de notre modèle.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Définition de formulaires basés sur les modèles
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Utilisation des formulaires et validation
|
||||||
|
|
||||||
|
L'utilisation des formulaires de modèles ressemble beaucoup à l'utilisation de formulaires standard. Il existe toutefois quelques différences, notamment pour la gestion de l'enregistrement de données et la modification de données existantes.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Gestion des téléchargements
|
||||||
|
|
||||||
|
Lorsque vous souhaitez gérer des téléchargements de fichier, vous devriez configurer deux paramètres dans votre projet, qui sont :
|
||||||
|
|
||||||
|
- Le répertoire des fichiers média : `MEDIA_ROOT`
|
||||||
|
- L'URL de base pour les afficher, `MEDIA_URL` (par défaut `"/media/"`)
|
||||||
|
- Configurer les URLs pour servir des fichiers média :
|
||||||
|
- Ajouter `+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)` aux URLs du projet pour servir les fichiers (ne fonctionne pas si `DEBUG = False`
|
||||||
|
- Avoir un formulaire HTML avec la méthode `POST` et le type d'encodage `enctype="multipart/form-data"`.
|
||||||
|
|
||||||
|
Sans `MEDIA_ROOT`, les fichiers seront uploadés et sauvés à la racine de votre projet (même pas dans un répertoire `media`), ce qui est fortement déconseillé.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
L'avantage par rapport aux formulaires standard est que la gestion du téléchargement des fichiers se fait automatiquement
|
||||||
|
lorsque vous persistez vos données de formulaire en faisant
|
||||||
|
|
||||||
|
```python
|
||||||
|
form = MyForm(request.POST, request.FILES, instance=my_instance) # notez le request.FILES
|
||||||
|
form.save(commit=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Lorsque votre modèle possède des champs fichier, vous pouvez facilement accéder à l'URL du fichier uploadé en utilisant la propriété `url` du champ `FileField` ou `ImageField` :
|
||||||
|
|
||||||
|
```djangotemplate
|
||||||
|
{{ item.picture_field.url }} {# URL commençant par settings.MEDIA_URL #}
|
||||||
|
```
|
50
documentation/08-admin.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# L'interface d'administration de Django
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Présentation de l'interface d'administration
|
||||||
|
|
||||||
|
Ici, on navigue dans l'interface d'administration de Django.
|
||||||
|
Pour y accéder, il nous faut au moins un super-utilisateur dans notre base
|
||||||
|
de données, que l'on peut créer en ligne de commande :
|
||||||
|
|
||||||
|
```bash=
|
||||||
|
./manage.py migrate # s'assurer qu'on a tout à jour
|
||||||
|
./manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Configuration de l'administration
|
||||||
|
|
||||||
|
Lorsque l'on crée nos propres modèles, par défaut, on ne les voit pas s'afficher lorsque l'on navigue dans l'interface d'administration.
|
||||||
|
Heureusement, il est possible de configurer celle-ci afin qu'elle nous permette de gérer les données de nos modèles. (voir [documentation officielle](https://docs.djangoproject.com/fr/3.2/ref/contrib/admin/#modeladmin-objects))
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
La façon la plus simple d'enregistrer un modèle pour qu'il soit affiché est d'aller dans le fichier `admin.py` de notre application et d'y ajouter :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Cette méthode ajoute effectivement notre modèle à l'interface, mais l'affichage est plutôt rudimentaire. Il est possible de le personnaliser en s'y prenant autrement; on crée une classe de configuration de l'admin héritant de `admin.ModelAdmin`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Vous pouvez personnaliser votre page de liste de modèle dans l'admin en écrivant votre classe `ModelAdmin` et en y ajoutant des attributs documentés sur [le site officiel Django](https://docs.djangoproject.com/en/dev/ref/contrib/admin/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
### Pour aller plus loin
|
||||||
|
|
||||||
|
La [documentation officielle de Django](https://docs.djangoproject.com/fr/3.2/ref/contrib/admin/actions/#admin-actions) montre comment définir ce que l'on appelle des actions, c'est-à-dire des fonctions qui peuvent être exécutées sur des instances de votre modèle via l'interface d'administration.
|
93
documentation/09-auth.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# L'authentification et les utilisateurs
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## L'application d'authentification de Django
|
||||||
|
|
||||||
|
Django fournit directement dans le framework une application de gestion de l'authentification, avec les modèles associés. Généralement, ces modèles sont utilisés dans tout projet Django, et reconnus par toutes les applications tierces Django qui utilisent l'authentification ou les utilisateurs.
|
||||||
|
|
||||||
|
Cette application est `django.contrib.auth`. Lorsqu'on l'utilise avec certains middleware ainsi que `django.contrib.session`, on a de bons outils pour se connecter en tant qu'une instance d'utilisateur de notre base.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## S'authentifier programmatiquement
|
||||||
|
|
||||||
|
Django fournit plusieurs fonctions pour se dé/connecter en tant qu'une instance de `User` :
|
||||||
|
|
||||||
|
(Dans `django.contrib.auth`)
|
||||||
|
|
||||||
|
- `authenticate`
|
||||||
|
- `login`
|
||||||
|
- `logout`
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
`authenticate` permet de savoir si des identifiants de connexion sont valides. Par exemple :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.contrib.auth import login, logout, authenticate
|
||||||
|
user = authenticate(username="…", password="…") # ou email
|
||||||
|
```
|
||||||
|
|
||||||
|
S'ils le sont, la fonction renvoie l'utilisateur concerné, sinon la fonction renvoie `None`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
`login` modifie la session HTTP pour indiquer que nous sommes connecté en tant qu'un utilisateur. (nous avons donc
|
||||||
|
besoin d'un objet `request`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.contrib.auth import login, logout, authenticate
|
||||||
|
login(request, user) # user est une instance du modèle User
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
`logout` déconnecte tout utilisateur connecté dans la session.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Grâce aux middleware et aux processeurs de contexte, depuis les vues ou dans les templates, on peut accéder facilement à l'instance de l'utilisateur actuellement connecté :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def view(request):
|
||||||
|
print(request.user)
|
||||||
|
```
|
||||||
|
|
||||||
|
```djangotemplate
|
||||||
|
{{ user }} {# ou request.user puisque request est aussi dispo #}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## S'authentifier avec un formulaire
|
||||||
|
|
||||||
|
Django propose plusieurs vues pour gérer l'authentification, qui rendent des templates trouvés dans un répertoire `registration` (qu'il faut généralement redéfinir pour adapter le rendu de la page et du formulaire).
|
||||||
|
|
||||||
|
On peut aussi s'authentifier manuellement en créant une simple vue utilisant un formulaire standard...
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Ce formulaire utiliserait `authenticate` pour valider les champs et afficher une erreur si nécessaire. La vue réutiliserait `authenticate` une fois le formulaire valide pour récupérer un utilisateur et le connecter.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Considérations sur les mots de passe
|
||||||
|
|
||||||
|
Django enregistre normalement les utilisateurs en chiffrant le mot de passe. Cela se fait en interne en utilisant la clé secrète du paramètre `SECRET_KEY`.
|
||||||
|
|
||||||
|
Si l'on manipule une instance du modèle utilisateur pour modifier son mot de passe et qu'on y assigne une valeur en clair, elle sera enregistrée en clair dans la base de données.
|
||||||
|
|
||||||
|
Pour chiffrer correctement le mot de passe, il faut utiliser `user.set_password()` qui utilise le chiffrage.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Commandes pour l'authentification
|
||||||
|
|
||||||
|
- `changepassword <username>` : changer un mot de passe
|
||||||
|
- `createsuperuser` : créer un utilisateur qui a tous les droits (et accès à l'admin entre autres). Cet utilisateur possède `.is_superuser == True`
|
52
documentation/10-translation.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Traduction d'un projet Django (I18N)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Traduction dans le code Django
|
||||||
|
|
||||||
|
Pour marquer certaines chaînes dans une application comme étant traduisibles, il suffit de les passer comme arguments de la fonction `gettext_lazy`. Par convention, on importe la fonction en lui donnant l'alias `_`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
_("String to translate") # dans la langue de `LANGUAGE_CODE`
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Traduction dans les templates
|
||||||
|
|
||||||
|
Pour accéder aux tags spécifiques à la traduction, vous devez charger les templatetags de l'application `i18n` :
|
||||||
|
|
||||||
|
```djangotemplate
|
||||||
|
{% load i18n %}
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Commandes pour la traduction
|
||||||
|
|
||||||
|
Dans le répertoire de l'application à traduire :
|
||||||
|
|
||||||
|
- `django-admin makemessages -l fr` : fichier pour traducteur
|
||||||
|
- `django-admin compilemessages -l fr` : traductions compilées
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Bénéficier automatiquement de la traduction
|
||||||
|
|
||||||
|
Pour que Django affiche vos pages dans la langue de votre navigateur, il faut ajouter dans `MIDDLEWARE` une entrée `"django.middleware.locale.LocaleMiddleware"`.
|
||||||
|
|
||||||
|
Le middleware récupère depuis les en-têtes HTTP les langues préférées du navigateur, et si possible, active la première langue dont une traduction existe. Cela nécessite donc que le navigateur soit configuré pour préférer l'affichage des pages dans votre langue préférée.
|
||||||
|
|
||||||
|
:::notes
|
||||||
|
Faire la démo des langues préférées du navigateur avec Firefox
|
||||||
|
:::
|
88
documentation/11-advanced.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
title: Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Éléments avancés
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Envoyer des emails
|
||||||
|
|
||||||
|
Django propose de nombreuses façons d'envoyer des emails, mais la plus simple d'entre elles consiste à utiliser une fonction utilitaire nommée `send_mail` et configurer si nécessaire vos paramètres pour l'envoi de mail. La procédure est décrite dans la [documentation officielle](https://docs.djangoproject.com/en/dev/topics/email/).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*(voir, dans le projet `advanced`, l'exemple dans le notebook Jupyter.)*
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Créer des vues de téléchargement
|
||||||
|
|
||||||
|
Pour proposer un fichier au téléchargement dans une vue, il faut renvoyer un objet `HttpResponse` dont le contenu est le fichier cible, et y ajouter les en-têtes HTTP nécessaires au navigateur.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Lancement de tests d'un projet Django (unittest)
|
||||||
|
|
||||||
|
En Python comme dans d'autres langages, il existe des frameworks pour effectuer des tests unitaires, d'intégration, et même des tests de bout-en-bout. En Java, le framework le plus connu est JUnit, et Python s'en est directement inspiré pour créer `unittest`, disponible par défaut.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Pour effectuer des tests, Django utilise par défaut `unittest`. Ainsi, l'écriture de tests unitaires est identique à dans n'importe quel autre projet Python :
|
||||||
|
|
||||||
|
- On écrit, dans un module de `tests`, des classes héritant de `TestCase`.
|
||||||
|
- Ces classes peuvent redéfinir `setUp()`, `tearDown()`, et `setUpClass` et `tearDownClass` pour configurer des "fixtures".
|
||||||
|
- Les tests unitaires sont écrits dans des méthodes dont le nom débute par `test_`.
|
||||||
|
|
||||||
|
Pour démarrer les tests, il suffit ensuite de lancer `./manage.py test`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Lancement de tests (test client)
|
||||||
|
|
||||||
|
Lors des tests, on peut souhaiter vouloir tester la génération des pages de notre site (end-to-end), en, par exemple, testant qu'une URL ne renvoie pas de 404 ou que son contenu est attendu.
|
||||||
|
|
||||||
|
Pour ce faire, Django propose un "client" de tests, décrit dans `django.test.Client`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Son utilisation est simple :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Déploiement d'un projet Django
|
||||||
|
|
||||||
|
En production, on ne sert jamais une application Django en utilisant `runserver`. En général, on utilise une pile logicielle du type suivant :
|
||||||
|
|
||||||
|
- Apache/Nginx servant sur le port 80
|
||||||
|
- Serveur WSGI du type Gunicorn, qui sert l'application via un socket Unix ou un socket TCP en écoute sur localhost uniquement
|
||||||
|
- Nginx sert les ressources (statiques et média) publiques
|
||||||
|
- Nginx passe les autres demandes d'URLs au serveur WSGI
|
||||||
|
|
||||||
|
Voici un [tutorial pour configurer la pile Nginx/Gunicorn](https://python.developpez.com/actu/94911/Mise-en-production-d-un-site-Django-en-utilisant-Nginx-et-Gunicorn/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Interconnexion avec les réseaux sociaux
|
||||||
|
|
||||||
|
En plus de l'authntification, vous pouvez lier des utilisateurs à des données de connexion relatives à des réseaux sociaux divers.
|
||||||
|
|
||||||
|
L'application externe `django-allauth` propose une [documentation en ligne](https://django-allauth.readthedocs.io/en/latest/overview.html), et est l'application la plus maintenue pour gérer la connexion via de très nombreux réseaux sociaux ou services OAuth.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Divers : Procédure de tests
|
||||||
|
|
||||||
|
Pour aller plus loin, vous pouvez utiliser le paquet externe `pytest` à la place de `unittest`, aussi bien pour vos projets Python que Django.
|
||||||
|
Si l'outil est simple à mettre en place pour Python, préférez l'assembler avec [Pytest-Django](https://pytest-django.readthedocs.io/en/latest/).
|
||||||
|
|
||||||
|
*Portez attention au chapitre sur les bases de données de test avec pytest-django*
|
67
documentation/99-extra-tools.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Outils Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
---
|
||||||
|
|
||||||
|
# Outils supplémentaires Django
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Informations avancées de débogage en développement
|
||||||
|
|
||||||
|
Même si ce n'est pas nécessaire, il peut être intéressant pour un développeur
|
||||||
|
Django d'avoir à disposition, lorsqu'il teste ses vues, des informations sur les
|
||||||
|
performances de sa vue : temps d'exécution, temps d'exécution des requêtes de base
|
||||||
|
de données, templates utilisés etc.
|
||||||
|
|
||||||
|
[django-debug-toolbar](https://django-debug-toolbar.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Générer des graphiques (charts)
|
||||||
|
|
||||||
|
- [django-chartjs](https://django-chartjs.readthedocs.io/en/latest/)
|
||||||
|
- [django-slick-reporting](https://django-slick-reporting.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Afficher des données en table et filtrer
|
||||||
|
|
||||||
|
- django-tables2 : Afficher des tableaux depuis des modèles
|
||||||
|
- django-filter : Pouvoir filtrer un queryset via un formulaire
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Convertir une page HTML en PDF
|
||||||
|
|
||||||
|
Il existe quelques outils pour convertir des pages web HTML en ligne vers des fichiers PDF.
|
||||||
|
On utilise généralement des outils d'automatisation de navigateur, tels que Selenium.
|
||||||
|
|
||||||
|
`Playwright` est actuellement l'un des outils les plus aboutis pour automatiser un navigateur.
|
||||||
|
|
||||||
|
[Playwright](https://playwright.dev/python/docs/intro)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install playwright
|
||||||
|
```
|
||||||
|
|
||||||
|
Installer playwright nécessite au moins 500 Mo d'espace libre sur votre système de fichiers.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```python
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
with sync_playwright() as pw:
|
||||||
|
browser = pw.chromium.launch()
|
||||||
|
context = browser.new_context()
|
||||||
|
page = context.new_page() # ouvre un onglet
|
||||||
|
page.goto("protocol://url")
|
||||||
|
page.pdf(format="A4", print_background=True, scale=1.0, path=f"filename.pdf")
|
||||||
|
browser.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemple d'automatisation
|
75
documentation/99-important-settings.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
title: Paramètres fondamentaux de Django
|
||||||
|
author: Steve Kossouho
|
||||||
|
tags: [settings,django]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Paramètres fondamentaux de Django
|
||||||
|
|
||||||
|
Récapitulatif des paramètres les plus rencontrés sur les projets Django simples.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Catégories
|
||||||
|
|
||||||
|
- Applications prises en charge
|
||||||
|
- Base de données
|
||||||
|
- Templates
|
||||||
|
- Traduction
|
||||||
|
- URLs
|
||||||
|
- Service de fichiers (Média, Statiques)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Applications prises en charge par le projet
|
||||||
|
|
||||||
|
`INSTALLED_APPS` : Liste des applications utilisées par votre projet Django.
|
||||||
|
|
||||||
|
Le système interne de Django utilise cette information pour découvrir automatiquement certaines
|
||||||
|
informations pour le projet; templates, modèles, admin, fichiers statiques etc.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Base de données
|
||||||
|
|
||||||
|
`DATABASES` : Permet de configurer une ou plusieurs bases de données à utiliser avec le projet Django.
|
||||||
|
|
||||||
|
La configuration la plus simple à mettre en place utilise une base de données SQLite3. Notez que pour une
|
||||||
|
utilisation en production, une base de données SQLite3 est fortement déconseillée (du moins pour une application
|
||||||
|
complexe autorisant la connexion de plusieurs clients simultanément).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## Templates (Gabarits)
|
||||||
|
|
||||||
|
`TEMPLATES` : Configurer les moteurs de gabarits utilisés par notre projet. Dans 99.9% des cas, la
|
||||||
|
configuration de base est suffisante, à quelques exceptions près.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
```python {.number-lines}
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration des moteurs de gabarits
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Parmi les clés présentes dans le paramètre `TEMPLATES`, se trouvent les clés suivantes :
|
||||||
|
|
||||||
|
- `APP_DIRS` : Automatiquement prendre en compte les répertoires `templates` des applications présentes dans `INSTALLED_APPS`.
|
||||||
|
- `context_processors` : fonctions qui ajoutent des informations accessibles pendant le rendu d'un template.
|
BIN
documentation/assets/diagrams/Django formation-Middleware.png
Normal file
After Width: | Height: | Size: 40 KiB |
1
documentation/assets/diagrams/Django formation.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2021-04-22T06:24:48.667Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.5.1 Chrome/89.0.4389.82 Electron/12.0.1 Safari/537.36" etag="dzQ0P0qPcDWSRTx-KB1X" compressed="true" version="14.5.1" type="device" pages="3"><diagram id="GgAqahdueFiXzMDP5OXp" name="MVC">7VlNc5swEP01HOPhw9hwjO2kPbRTT9Jpm5NHBhmrFYgRwoH8+q5AGGESO6lJJ9Pm4Fi7Wklo973dJTaceVx84CjdfmYhpoZthoXhLAzbtsamB19SU9aaqW/WioiTUBm1ilvygJWyMctJiLOOoWCMCpJ2lQFLEhyIjg5xzu67ZhtGu6emKMI9xW2AaF/7nYRiW2s9e9rqP2ISbZuTrYlfz8SoMVY3ybYoZPeayrkynDlnTNSjuJhjKp3X+KVed/3E7P7BOE7EcxZMb74VIr/9dCNW0yDZLMfXFw8Xdr3LDtFcXVg9rCgbD5C4ctFsK2IKGguGSrUgcQTnUbKGvwEl6QpxAcMUsxRW29efCSUC8XL1ZbMhAear7yxGycqyvQI+ozSJ+pdQ99phLnChqdSlPmAWY8FLMFGzrvJvAzAl3mvRUqqtFqhGhxQ+ov2+rQthoLz4Ao+6pz2KsrSG6oYUGPaapYwkAvOrHXghUz7ew8UEIUTZVlpWghaITHD2C88ZZRw0CUtkoChaY7pkGRGEJaAOsNwcJqRPCUD704HBmgnBYs3gkpJITgiWghYpab/PhlDanGnYjmlanreoHzmVV4yLSCaDUZwFCI+qVSknGR4BRkBaoTSlcEx1+vnxt5wuAPxxDwDuIwBw7FcCwKQHAMiKxtwxLj1Jincs1FgIkUBrlOFVhBPMSTAAEibTLhI8t4cEZ9pHgvtaqcCyeuHGIVQXJcJFiShvMK2YcNXOzDjLk3AfY8bFlkUsgWAxGYQq2j+xEKUqlygXrIsF2JqXP9T6SriTwshtxEWhTy5KXVpCOMABMryV8snIZCznAT7iAOVsKAERFsccpSIgvXM00Lxy1q5bmweP27TH4FvMdzjn7+xt2Kul8FUmncPtAQgMVBy5pyg8dv4mhSdHKdxS82n6/szjtLFXcf0TRhdE/NDGGp9BaukshVITDsncZoY2GdzpWeMlmSEDZotL2WW3d6t010S6+czs4T8ze7hDJ49qKVwLlZpBxexM23kpFVob6k87yLWsg3b8lL133N5y3WP2MKifuMX6/upnwN/rpcKlbP2lM5MdK6u2xu93NTIWVW7qgriXfw7TVEzCsKYQzsgDWlf7mU1erSLhzgx38SiujhK4l4T2r4zqFEN/K3ssOZkj03cnnRioTvNlwGoj25iwzSYDhB/mqgHi5/fC9y3/f3tQ9JBzPMoCTlKxgkV4gJLlOid7zvFf7TmdswvWGdWprUh3ekE6uzoB9/6oQD1Sezs1K6Aoy+Ddo1u2rLPKVtPMnqxbkzfV9FrjXq74yhEROMZVspK72+YCxwig8pYTvjNUwr8A1E2b7RS9nbee8S33n21YB3iVfd2G9dnM998W880e8+cMvCebu9mYvr/36v/BZCEeqnVwPP9k6+AP0jqA2P7OUGea9tca5+o3</diagram><diagram id="Y5h737CUiNRvkyHO9ETn" name="Middleware">7Vnbcts2EP0aTZ+q4VWXR+vitB27dW1Pk/RFA5EghRgkWBCUqXx9FiAogiIjW6nU+sGesYRdLgBi9+wBFhq486T8wFG2uWUhpgPHCsuBuxg4ju1ZE/iSml2lGU+tShFzEmqjRvFAvmKtrM0KEuK8ZSgYo4JkbWXA0hQHoqVDnLPntlnEaHvWDMW4o3gIEO1qP5JQbCrtxBk3+l8wiTf1zPZoWj1JUG2sV5JvUMieDZW7HLhzzpioWkk5x1Q6r/bLn2JdLu1fV0HB8Pqf355GS8F+rga7PqXLfgkcp+KHh979ffUc4lX4e8THyb3j8ZvHR93F2iJaaH/ptYpd7UCSKA/PNiKhoLGhqVULksQwHyVr+AwoyVaIC2hmmGXQ27m+JZQIxHerP6KIBJivPrIEpSvbmZTwP8zS+JUr0x7YYi5wacRVr/QDZgkWfAcm+qmvF1aDVovPBgK0amMEv9Yhjbl4P27jV2ho157gZtvp+HmBwRUheGlEYfbZmkMrli0F6MMg4BBArUXGxYbFLEV02WhnnBUwnHwJC6TG5oaxTIftCxZipzMUFYK1g4pLIj7J7sOpo8XPSvS1tCj14ErYGcId5gQchbnWnRbVnBU8wMe8p2kD8RiLI3ZeZSd9dRQjHFMkyLZNEOeP+cuphfKsoryIlDJ0s4yRFNy43ILjch2XPe1Iz4Yo3+yDbAQvF5w94TmjTMYgZamEBEVrTO9YTgRhKagDnKoYzWQeEaDImwODNROCJYbBFSWxfCAkhmZIS/txIkJpPefAcS3LnkwW1StncolJGctNZZjkAcJD1SvjJMdDIAuQVijLKEyjZr8QEdhumwmmXocJ/B4mcJ1LocL7P3PbbiX2yDk5syE6fPfJFAyOkGIzlJLqsYKCb9UC7PPTg/dKerDdN8UPXocfHjA4qeDdLWHxBaUxe6ePmj4M3ljl0mncuRR/OKM2f0z8Dn94bpc//IudJEY/wB+t7LsQmRw/JZz7ROC+Nuf9N5Xz9XsbSX/PCqFOfIdJP98wUsrC66cC0hH6FN1jYZcBDL6G94EiCxsUbkaxk43fJ4qIpcLIU88Zj2ezmiBmKHiKFaYMk0j96a4aSLbcbfIMBSSNHxXU3KaU6C0pyDqRn2nEERBUEYiCy6qCMhSu1oiiVA61grkFfA/zbXwy0F7PA67X5gG/W1GMes4Ro4vxwOSdB5r0fpkHxm+LB/wOD/xV9HDAshzM3cHVNCiqM7IVvjOBwQQJS4lg/PLZ772x7N9fSf23VcT+8N+c9z+bz75z+FfSpe8Ixq9lgnMTgep6xTnaGQbqJJ4bI99JRQOosXNQljomJl40tyfWUXvb94/ZQ6N64QaB+5X/C1B2bzzuK/rKWJpL4lISvIlFkeIyfQPWg2RVWrxAU4dVRkLCsAI6zslXtFbjWXVZpALjzwb+ohd8R9OsQxT762o9y8C8Ee4jEGvoWK7uehqymtjWJiyKcoD4IYecIYJ1EhkRvFVOfUa8Z3e6ZSGJCMQC1tQK9DV4BxwmurF9sXTs7GMGAvrLyt7atQMWOCRSkoJZ/SuD1VNoBkRwUg6TZs2X21L8gytqf9LZUuz6sqi1p0xO3lNAbH6lqLDS/NbjLr8B</diagram><diagram id="q8YbaYhWte70mP5-ACzJ" name="Migrations">7Vldc6IwFP01PLYDBEQfV233Y7qznXFntn3qpBAhWyBMCIr76zeRBIjY1n6g69YX5Z7c3CT35CRXNMAkKT9TmEXfSYBiwzaD0gBTw7YtxxzyL4GsKsQbmRUQUhxIpwaY4T9IgsqtwAHKNUdGSMxwpoM+SVPkMw2DlJKl7jYnsT5qBkPUAWY+jLvoLxywqEKHttfgXxAOIzWyNRhVLQlUznIleQQDsmxB4MIAE0oIq56ScoJikTyVl7tvkOECnYHh/Gsaju0fjn11VgW7fEmXegkUpeydQ8vYOVuphKGA50+ahLKIhCSF8UWDjikp0gCJsCa3Gp8rQjIOWhz8jRhbyc0AC0Y4FLEklq2oxOym9XwrQp270pqWMvLaWCkjZXR10zZavYTZdFtbqp9f0MV6rmKwarFihRub45nMqiyRgvroCT9bbnBIQ/RkvGb/cOEhkiA+Zd6Roph7L/TZQamAsPZrWOYPkugXkC5nuYBxIUeaGhNgfBrNcYoZJilv7O4LmGeVPue4FAkdZ4hiPiFEOcZH5YJG1w3U5hvGOEz5s8+zvG6rpSQYCmAe1bspz6CP0/DneicBDuBkLXD1PcVJyBcd43v+CX2RrLsAUz4zIjJzGUAG72GOzvNFWBO+QJSh8hWUdxlSUUx5KKzUsSHtZXPGuBKKWscL8HridPAKHXMWMtEa4/ThPWXdSPm21bJd1n0rErxRaLLrNcF8OjX9wLE0+h1FvwpRnQCy1wa19TRezzboKHiG+DFX0P9AuXe5WArtWcDA1gXseh39Ko7b+nXNx7fKm/TrnfS71c/pRb+uq7O/b/06T+jXPul3B/264N/Sr+WcCul3lP1w10LaPmQhPdyhkD52Oe+5kLbNAxfS6iZokdphUF28kiydI42FZYQZElQI9yWFmSBxk/+cUfJQv51wn98RvRVFll7WWk6XjMEWMga9nardn6ofhgzX1snYpoz9kjE8litur1eVBXa9qwaHvKus0bGwdxQFyu6sewdlvfumoFuigFOJ8qISZXToEmXwcW/FzRJlGxn7vRW9j0tGp0TpjwxuNv+0Va9hmv8rwcVf</diagram></mxfile>
|
1
documentation/assets/diagrams/pattern-mvc.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2021-04-16T12:07:18.643Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.5.1 Chrome/89.0.4389.82 Electron/12.0.1 Safari/537.36" etag="mRoy3h5prpVIwsehNFBr" compressed="true" version="14.5.1" type="device"><diagram id="GgAqahdueFiXzMDP5OXp" name="Page-1">7Vldc5s6EP01PMbDh7HNY2InvQ/t1JPcaZsnjwwyVisQIwkH8uvvCoQRJrGTmnQyvZlxPNrVSkK75+wuseXNk+ITR9n2C4swtVw7KixvYbmuM3ZdS33sqKw1M3tcK2JOIm3UKu7II9ZKW2tzEmHRMZSMUUmyrjJkaYpD2dEhztlD12zDaPfUDMW4p7gLEe1rv5NIbvUt3Gmr/weTeNuc7EyCeiZBjbG+idiiiD0YKu/a8uacMVmPkmKOqXJe45d63c0zs/sH4ziVL1kwvf1WyPzu861cTcN0sxzfXDxe6OjsEM31hfXDyrLxAEkqF11tZUJB48BQqxYkieE8StbwHVKSrRCXMMwwy2C1e/OFUCIRL1dfNxsSYr76zhKUrhx3VsDfKEvj/iX0vXaYS1wYKn2pT5glWPISTPSsr/2rATbV4oMRLa3aGoFqdEjjI97v27oQBtqLr/Cof9qjSGQ1VDekwLDXVcZIKjG/3oEXhPbxHi42CBESW2VZCUYghOTsF54zyjhoUpaqQFG0xnTJBJGEpaAOsdocJpRPCUD784HBmknJEsPgkpJYTUiWgRZpab/PhlDanGm5nm07s9mifuRMXTEpYpUMRokIER5VqzJOBB4BRkBaoSyjcEx1+vnxd7wuAIJxDwD+EwDw3DcCwKQHAMiK1tyzLmeKFB9YqLEQIYnWSOBVjFPMSTgAEibTLhJmfg8J3rSPBP+tUoHj9MKNI6guWoSLElneYlox4bqdueIsT6N9jBmXWxazFILFVBCqaP/EUpa6XKJcsi4WYGte/tDrK+FeCSO/EReFObkoTWkJ4QAHqPBWymcjI1jOQ3zEAdrZUAJiLI85SkdAeedooHnlrF23Ng8et2mPwXeY73DOP9jbsNdI4SuhnMPdAQgMVBz5pyg89v4khSdHKdxS83n6/syTrLHXcf0dRhdE/jDGBp9BaumshNIQDsncZoY2GdybWeM1mUEAs+Wl6rLbu1W6G6LcfGb2CF6YPfyhk0e1FK6FSsOgYrYwdl4qhdGGBtMOch3noB0/ZT87bu/4/jF7GNRP3GJ9f/Uz4D/rpcKlav2VM9MdK6u2Juh3NSoWVW7qgriXfw7TVEKiqKYQFuQRrav97CavVpHwryx/8SSujhK4l4T2r4z6FMt8K3sqOdkjO/AnnRjoTvN1wGoj25iwzUYAwg9z1QDxC3rh+5b/f3tQ9JhzPBIhJ5lcwSI8QMnyvZM95/iP9pze2QXrjOrUVqR7syCdXZ2Ae79VoJ6ovZ2aFVIkBLx7dMuWc1bZaprZk3Vr8q6aXmfcyxX/ckQkTnCVrNTurr3ACQKovOeE7w2V8C8AddNmO01v771nfMf/axvWAV5l37ZhfTHzg/fFfLvH/DkD76nm7mpMP957zf9gsggP1Tp4s+Bk6xAM0jqA2P7OUGea9tca7/o/</diagram></mxfile>
|
BIN
documentation/assets/diagrams/pattern-mvc.png
Normal file
After Width: | Height: | Size: 21 KiB |
47
documentation/assets/diagrams/production.drawio
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.17 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.17">
|
||||||
|
<diagram name="Page-1" id="lALLRwfy-J3OwCSlcXKb">
|
||||||
|
<mxGraphModel dx="1217" dy="670" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="7tNKPUQDF-2DKlcSCBtx-1" target="7tNKPUQDF-2DKlcSCBtx-2">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-4" value="plop.com" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="7tNKPUQDF-2DKlcSCBtx-3">
|
||||||
|
<mxGeometry x="-0.32" y="2" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-1" value="" style="verticalLabelPosition=bottom;shadow=0;dashed=0;align=center;html=1;verticalAlign=top;strokeWidth=1;shape=mxgraph.mockup.containers.userFemale;strokeColor=#666666;strokeColor2=#008cff;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="364" y="20" width="100" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-2" value="" style="image;html=1;image=img/lib/clip_art/computers/Server_Rack_128x128.png" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="374" y="220" width="80" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="7tNKPUQDF-2DKlcSCBtx-5" target="7tNKPUQDF-2DKlcSCBtx-7">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="540" y="180" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="7tNKPUQDF-2DKlcSCBtx-5" target="7tNKPUQDF-2DKlcSCBtx-9">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-5" value="Apache ou nginx" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="344" y="170" width="120" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-7" value="/static/..." style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="550" y="170" width="120" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-8" value="Django" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.search_documents;fillColor=#F58534;gradientColor=none;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="310" width="60" height="63" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="7tNKPUQDF-2DKlcSCBtx-9" target="7tNKPUQDF-2DKlcSCBtx-8">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7tNKPUQDF-2DKlcSCBtx-9" value="/users/..." style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="550" y="250" width="120" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
BIN
documentation/assets/images/admin-register-recommended.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
documentation/assets/images/admin-register-simple.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
documentation/assets/images/advanced-file-download.png
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
documentation/assets/images/advanced-send-mail.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
documentation/assets/images/advanced-test-client.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
documentation/assets/images/discover-frameworks.png
Normal file
After Width: | Height: | Size: 311 KiB |
BIN
documentation/assets/images/forms-form-definition.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
documentation/assets/images/forms-form-html-upload.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
documentation/assets/images/forms-form-template.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
documentation/assets/images/forms-form-usage.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
documentation/assets/images/forms-media-urls.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
documentation/assets/images/modelforms-bind-instance.png
Normal file
After Width: | Height: | Size: 270 KiB |
BIN
documentation/assets/images/modelforms-definition.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
documentation/assets/images/modelforms-new-instance.png
Normal file
After Width: | Height: | Size: 258 KiB |
BIN
documentation/assets/images/orm-model-base-definition.png
Normal file
After Width: | Height: | Size: 289 KiB |
BIN
documentation/assets/images/orm-model-migration-concept.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
documentation/assets/images/orm-model-relation-fields.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
documentation/assets/images/pattern-mvc.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
documentation/assets/images/templating-control.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
documentation/assets/images/templating-folder-example.png
Normal file
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 94 KiB |
BIN
documentation/assets/images/templating-interpolation-view.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
documentation/assets/images/templating-interpolation.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
documentation/assets/images/templating-tags-static.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
documentation/assets/images/translation-template-examples.png
Normal file
After Width: | Height: | Size: 169 KiB |
3
documentation/assets/images/urls-middleware-flow.svg
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
documentation/assets/images/views-middleware-diagram.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
documentation/assets/images/views-middleware-flow.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
documentation/assets/images/views-urlpatterns-arguments.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
documentation/assets/images/views-urlpatterns-base.png
Normal file
After Width: | Height: | Size: 64 KiB |
82
file-watchers.xml
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<TaskOptions>
|
||||||
|
<TaskOptions>
|
||||||
|
<option name="arguments" value="--highlight-style=/home/steve/Code/dependencies/reveal/pandoc/monokaipdf.theme -N -V fontsize:12pt -V geometry:margin=2cm -V mainfont:"Museo Sans" -V colorlinks:true -o slides/office/$FileNameWithoutExtension$.docx -t docx $FileName$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="md" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="Markdown → Office" />
|
||||||
|
<option name="output" value="" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="pandoc" />
|
||||||
|
<option name="runOnExternalChanges" value="true" />
|
||||||
|
<option name="scopeName" value="Documentation" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$FileDir$" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
|
<TaskOptions>
|
||||||
|
<option name="arguments" value="-t revealjs -s -o slides/html/$FileNameWithoutExtension$.html $FileName$ --standalone --embed-resources --highlight-style /home/steve/Code/dependencies/reveal/pandoc/monokai.theme --columns=120 -V revealjs-url=/home/steve/Code/dependencies/reveal/ -V theme=training -V slidenum=true -V hash=true -V width=1920 -V height=900 -V slideNumber:true -V previewLinks:true" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="md" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="Markdown → HTML" />
|
||||||
|
<option name="output" value="" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="pandoc" />
|
||||||
|
<option name="runOnExternalChanges" value="true" />
|
||||||
|
<option name="scopeName" value="Documentation" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$FileDir$" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
|
<TaskOptions>
|
||||||
|
<option name="arguments" value="-r "$ProjectName$ Slides.zip" html pdf office" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="*" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="Slides → ZIP" />
|
||||||
|
<option name="output" value="$ProjectFileDir$/documentation/slides/" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="zip" />
|
||||||
|
<option name="runOnExternalChanges" value="true" />
|
||||||
|
<option name="scopeName" value="Slides" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$/documentation/slides/" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
|
<TaskOptions>
|
||||||
|
<option name="arguments" value="--highlight-style=/home/steve/Code/dependencies/reveal/pandoc/monokaipdf.theme --toc --pdf-engine=lualatex -N -V fontsize:12pt -V geometry:margin=2cm -V colorlinks:true -V mainfont:Cabin -V monofont:"JetBrains Mono" -o slides/pdf/$FileNameWithoutExtension$.pdf $FileName$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="md" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="Markdown → PDF" />
|
||||||
|
<option name="output" value="" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="pandoc" />
|
||||||
|
<option name="runOnExternalChanges" value="true" />
|
||||||
|
<option name="scopeName" value="Documentation" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$FileDir$" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
|
</TaskOptions>
|
7
logo.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||||
|
<g>
|
||||||
|
<rect fill="#092E20" x="0" y="0" width="256" height="256" rx="28"></rect>
|
||||||
|
<path d="M186.377012,94.197636 L186.377012,160.424478 C186.377012,183.243286 184.707519,194.187745 179.699037,203.649406 C175.060358,212.741266 168.937684,218.490548 156.323731,224.798721 L129.794937,212.183571 C142.410087,206.247593 148.531564,201.05481 152.427049,193.074749 C156.509231,184.91278 157.808923,175.451119 157.808923,150.593015 L157.808923,94.197636 L186.377012,94.197636 Z M140.928486,50.0787207 L140.928486,182.316986 C126.272844,185.099476 115.512688,186.212472 103.826231,186.212472 C68.9487718,186.212472 50.7686431,170.445031 50.7686431,140.205054 C50.7686431,111.079269 70.0629644,92.1583404 99.9295492,92.1583404 C104.567032,92.1583404 108.091519,92.5281423 112.359199,93.6411381 L112.359199,50.0787207 L140.928486,50.0787207 Z M102.713236,115.160254 C88.243093,115.160254 79.8944275,124.065418 79.8944275,139.647359 C79.8944275,154.860696 87.8720944,163.208164 102.527736,163.208164 C105.680028,163.208164 108.278215,163.022665 112.359199,162.467364 L112.359199,116.643052 C109.020212,115.531253 106.237722,115.160254 102.713236,115.160254 Z M186.377012,50.2307105 L186.377012,79.5419941 L157.808923,79.5419941 L157.808923,50.2307105 L186.377012,50.2307105 Z" fill="#FFFFFD"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
31
source/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Projets de démo Django
|
||||||
|
|
||||||
|
Chacun des répertoires ci-dessous peut être récupéré comme projet Django, à savoir :
|
||||||
|
|
||||||
|
- `advanced` : fonctions avancées (URL téléchargement)
|
||||||
|
- `authentication` : bouts de code pour gérer les utilisateurs
|
||||||
|
- `forms` : test des formulaires
|
||||||
|
- `orm` : projet d'interface utilisant des données en base
|
||||||
|
- `templating` : utilisation du langage de templates
|
||||||
|
- `translation` : traduction
|
||||||
|
|
||||||
|
Les répertoires contiennent un fichier `manage.py` et peuvent être lancés avec la commande habituelle :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifiez quand même les fichiers `urls.py` pour savoir quelles URLs sont disponibles pour chaque projet (l'un d'entre eux ne répond pas à l'URL http://127.0.0.1/ mais répond à d'autres URLs).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Les répertoires de projet peuvent être ouverts avec PyCharm (contiennent un répertoire `.idea`). Vous devrez peut-être créer un environnement virtuel pour ces projets. Pour cela :
|
||||||
|
|
||||||
|
- rendez-vous dans le menu `File` → `Settings`
|
||||||
|
- dans la catégorie `Project` → `Python Interpreter`, vérifiez que l'interpréteur est dans un répertoire relatif à votre projet, ou créez un nouvel interpréteur `virtualenv`.
|
||||||
|
|
||||||
|
Avant de pouvoir exécuter le projet, vous devez installer les dépendances présentes dans requirements.txt :
|
||||||
|
|
||||||
|
```{.bash .numberLines}
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
0
source/__init__.py
Normal file
31
source/advanced/.idea/Advanced.iml
generated
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="advanced/settings.py" />
|
||||||
|
<option name="manageScript" value="manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/jupyter/.ipynb_checkpoints" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3 (training)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="GOOGLE" />
|
||||||
|
<option name="myDocStringFormat" value="Google" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
</component>
|
||||||
|
</module>
|
12
source/advanced/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
source/advanced/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
8
source/advanced/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/advanced.iml" filepath="$PROJECT_DIR$/.idea/advanced.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
11
source/advanced/.idea/workspace.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showExcludedFiles" value="false" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="settings.editor.selected.configurable" value="configurable.group.tools" />
|
||||||
|
</component>
|
||||||
|
</project>
|
0
source/advanced/advanced/__init__.py
Normal file
16
source/advanced/advanced/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for advanced project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'advanced.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
139
source/advanced/advanced/settings.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
"""
|
||||||
|
Django settings for advanced project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-4f#(qt450m(73!m#q%6jhl*t@0_%xn4$)ing$)-2qey-^bv*xy"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"django_extensions",
|
||||||
|
"various",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "advanced.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "advanced.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "database.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
# Email configuration
|
||||||
|
|
||||||
|
# Set a backend in console for demonstration purposes.
|
||||||
|
# The correct backend should be SMTP in production.
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
|
# Media configuration
|
||||||
|
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
26
source/advanced/advanced/urls.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""advanced URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from various.views import view_file_download
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("download", view_file_download, name="download"),
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
source/advanced/advanced/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for advanced project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'advanced.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
BIN
source/advanced/database.sqlite3
Normal file
81
source/advanced/jupyter/Various examples.ipynb
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "fc76bc88",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Content-Type: text/plain; charset=\"utf-8\"\n",
|
||||||
|
"MIME-Version: 1.0\n",
|
||||||
|
"Content-Transfer-Encoding: 7bit\n",
|
||||||
|
"Subject: Title\n",
|
||||||
|
"From: noreply@example.aa\n",
|
||||||
|
"To: noreply@example.aa\n",
|
||||||
|
"Date: Fri, 16 Apr 2021 22:44:20 -0000\n",
|
||||||
|
"Message-ID: <161861306067.29777.1833249302107691814@manjaro>\n",
|
||||||
|
"\n",
|
||||||
|
"Body of the email.\n",
|
||||||
|
"-------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Simple example to send a text email\n",
|
||||||
|
"from django.core.mail import send_mail\n",
|
||||||
|
"send_mail(\"Title\", \"Body of the email.\", \"noreply@example.aa\", [\"noreply@example.aa\"])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "badac700",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Maggle\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Django Shell-Plus",
|
||||||
|
"language": "python",
|
||||||
|
"name": "django_extensions"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
22
source/advanced/manage.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'advanced.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
BIN
source/advanced/media/django-upload.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
9
source/advanced/various/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class VariousConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "various"
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = "various.VariousConfig"
|
1
source/advanced/various/admin/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .room import *
|
51
source/advanced/various/admin/room.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import F
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from various.models import Room
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Room)
|
||||||
|
class RoomAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin configuration for rooms.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
list_display = ["id", "name", "length", "width", "height", "get_volume_display"]
|
||||||
|
list_editable = ["name", "length", "width", "height"]
|
||||||
|
actions = ["action_fix_minimum"]
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""
|
||||||
|
Change queryset to add a computed field for volume.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: HTTP
|
||||||
|
|
||||||
|
"""
|
||||||
|
return super().get_queryset(request).annotate(volume=F("width") * F("length") * F("height"))
|
||||||
|
|
||||||
|
def get_volume_display(self, obj: Room) -> str:
|
||||||
|
return f"{obj.get_volume()} cm³"
|
||||||
|
|
||||||
|
get_volume_display.short_description = _("area")
|
||||||
|
get_volume_display.admin_order_field = "volume"
|
||||||
|
|
||||||
|
def action_fix_minimum(self, request: HttpRequest, queryset: models.QuerySet):
|
||||||
|
"""
|
||||||
|
Change room dimensions to have at least 1cm in every axis.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: HTTP request.
|
||||||
|
queryset: selected rooms.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for room in queryset: # type: Room
|
||||||
|
room.width = max(1, room.width)
|
||||||
|
room.length = max(1, room.length)
|
||||||
|
room.height = max(1, room.height)
|
||||||
|
room.save()
|
||||||
|
self.message_user(request, _("The selected rooms have been updated."))
|
59
source/advanced/various/locale/fr/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-04-18 11:30+0000\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
#: various/admin/room.py:34
|
||||||
|
msgid "area"
|
||||||
|
msgstr "aire"
|
||||||
|
|
||||||
|
#: various/admin/room.py:51
|
||||||
|
msgid "The selected rooms have been updated."
|
||||||
|
msgstr "Les salles sélectionnées ont été mises à jour."
|
||||||
|
|
||||||
|
#: various/models/room.py:11
|
||||||
|
msgid "name"
|
||||||
|
msgstr "nom"
|
||||||
|
|
||||||
|
#: various/models/room.py:12
|
||||||
|
msgid "description"
|
||||||
|
msgstr "description"
|
||||||
|
|
||||||
|
#: various/models/room.py:13 various/models/room.py:14
|
||||||
|
#: various/models/room.py:15
|
||||||
|
msgid "centimeters"
|
||||||
|
msgstr "centimètres"
|
||||||
|
|
||||||
|
#: various/models/room.py:13
|
||||||
|
msgid "width"
|
||||||
|
msgstr "largeur"
|
||||||
|
|
||||||
|
#: various/models/room.py:14
|
||||||
|
msgid "length"
|
||||||
|
msgstr "longueur"
|
||||||
|
|
||||||
|
#: various/models/room.py:15
|
||||||
|
msgid "height"
|
||||||
|
msgstr "hauteur"
|
||||||
|
|
||||||
|
#: various/models/room.py:18
|
||||||
|
msgid "room"
|
||||||
|
msgstr "salle"
|
||||||
|
|
||||||
|
#: various/models/room.py:19
|
||||||
|
msgid "rooms"
|
||||||
|
msgstr "salles"
|
29
source/advanced/various/migrations/0001_initial.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 3.2 on 2021-04-18 11:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Room',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=32, unique=True, verbose_name='name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
|
('width', models.PositiveIntegerField(default=0, help_text='centimeters', verbose_name='width')),
|
||||||
|
('length', models.PositiveIntegerField(default=0, help_text='centimeters', verbose_name='length')),
|
||||||
|
('height', models.PositiveIntegerField(default=0, help_text='centimeters', verbose_name='height')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'room',
|
||||||
|
'verbose_name_plural': 'rooms',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
source/advanced/various/migrations/__init__.py
Normal file
1
source/advanced/various/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .room import *
|
49
source/advanced/various/models/room.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class Room(models.Model):
|
||||||
|
"""
|
||||||
|
Room definition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=32, blank=False, unique=True, verbose_name=_("name"))
|
||||||
|
description = models.TextField(blank=True, verbose_name=_("description"))
|
||||||
|
width = models.PositiveIntegerField(default=0, help_text=_("centimeters"), verbose_name=_("width"))
|
||||||
|
length = models.PositiveIntegerField(default=0, help_text=_("centimeters"), verbose_name=_("length"))
|
||||||
|
height = models.PositiveIntegerField(default=0, help_text=_("centimeters"), verbose_name=_("height"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("room")
|
||||||
|
verbose_name_plural = _("rooms")
|
||||||
|
|
||||||
|
def get_area(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the area of the room.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Area of the room in square centimeters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.width * self.length
|
||||||
|
|
||||||
|
def get_volume(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the volume of the room.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Volume of the room in cube centimeters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.width * self.length * self.height
|
||||||
|
|
||||||
|
def is_empty(self) -> bool:
|
||||||
|
"""
|
||||||
|
Tell if the room is empty (has no volume).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the room has no volume (zero).
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.width * self.length * self.height == 0
|
1
source/advanced/various/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .room import *
|
55
source/advanced/various/tests/room.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from django import test
|
||||||
|
|
||||||
|
from various.models import Room
|
||||||
|
|
||||||
|
|
||||||
|
class RoomTestCase(test.TestCase):
|
||||||
|
"""
|
||||||
|
Basic test case for rooms.
|
||||||
|
|
||||||
|
This method is executed before every `test_` function.
|
||||||
|
To run those automatic tests, in a terminal, just run
|
||||||
|
|
||||||
|
`./manage.py test`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.room1 = Room(name="Kitchen", width=260, length=320, height=250) # basic
|
||||||
|
self.room2 = Room(name="Fake", width=0, length=320, height=250) # a 2D object
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Cette méthode est exécutée une seule fois avant ous les tests."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
"""
|
||||||
|
End unit test.
|
||||||
|
|
||||||
|
Is executed after every `test_` method.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_base_room(self):
|
||||||
|
"""
|
||||||
|
Basic test using the fixture set up in the `setUp` method.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.assertEqual(self.room2.get_volume(), 0)
|
||||||
|
self.assertEqual(self.room1.get_area(), 83200)
|
||||||
|
|
||||||
|
def test_dummy_page(self):
|
||||||
|
"""
|
||||||
|
Test the Django test client.
|
||||||
|
|
||||||
|
Used to test that pages of the projet answer properly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
client = test.Client()
|
||||||
|
response = client.get("/admin/")
|
||||||
|
self.assertNotEqual(response.status_code, 404)
|
21
source/advanced/various/views.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.core.files.storage import DefaultStorage
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
def view_file_download(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Serve a media file like a download.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: HTTP request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Media file as an attachment to download.
|
||||||
|
|
||||||
|
"""
|
||||||
|
storage = DefaultStorage() # Objet capable de manipuler des fichiers média
|
||||||
|
with storage.open("django-upload.jpg", "rb") as file: # relative to MEDIA_ROOT
|
||||||
|
response = HttpResponse(file, content_type="image/jpeg")
|
||||||
|
# Use list notation to set headers
|
||||||
|
response["Content-Disposition"] = "attachment; filename=django-upload.jpg"
|
||||||
|
return response
|
30
source/authentication/.idea/Authentication.iml
generated
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="authentication/settings.py" />
|
||||||
|
<option name="manageScript" value="manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3 (training)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="GOOGLE" />
|
||||||
|
<option name="myDocStringFormat" value="Google" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
</component>
|
||||||
|
</module>
|
12
source/authentication/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
source/authentication/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
8
source/authentication/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/authentication.iml" filepath="$PROJECT_DIR$/.idea/authentication.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
11
source/authentication/.idea/workspace.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showExcludedFiles" value="false" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="settings.editor.selected.configurable" value="configurable.group.tools" />
|
||||||
|
</component>
|
||||||
|
</project>
|
BIN
source/authentication/authentication.sqlite3
Normal file
0
source/authentication/authentication/__init__.py
Normal file
16
source/authentication/authentication/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for authentication project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'authentication.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
130
source/authentication/authentication/settings.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"""
|
||||||
|
Django settings for authentication project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-fjg#b7qx5e5g3e2vcfb@eg9b3!xy1c+nix5*y=k7h6j&&pc)e8"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"users",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "authentication.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "authentication.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "authentication.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = "/profile"
|
27
source/authentication/authentication/urls.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""authentication URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from users.views import view_user, view_authentication_code
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
# https://docs.djangoproject.com/fr/3.1/topics/auth/default/#module-django.contrib.auth.views
|
||||||
|
path("", include("django.contrib.auth.urls")),
|
||||||
|
path("profile", view_user),
|
||||||
|
path("codedemo", view_authentication_code),
|
||||||
|
]
|
16
source/authentication/authentication/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for authentication project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'authentication.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
source/authentication/manage.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'authentication.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
9
source/authentication/users/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UsersConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "users"
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = "users.UsersConfig"
|
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="" method="post" name="login-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" name="login-button" value="Login">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>User</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if user.is_anonymous %}
|
||||||
|
You are not connected with a user.
|
||||||
|
{% else %}
|
||||||
|
Welcome, you are connected as {{ user.username }}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
14
source/authentication/users/templates/users/user-page.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>User</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if user.is_anonymous %}
|
||||||
|
You are not connected with a user.
|
||||||
|
{% else %}
|
||||||
|
Welcome, you are connected as {{ user.username }}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
22
source/authentication/users/views.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from annoying.decorators import render_to
|
||||||
|
from django.contrib.auth import login, logout, authenticate
|
||||||
|
|
||||||
|
|
||||||
|
@render_to("users/user-page.html")
|
||||||
|
def view_user(request):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@render_to("users/user-code-page.html")
|
||||||
|
def view_authentication_code(request):
|
||||||
|
# First, logout if we're already connected
|
||||||
|
logout(request)
|
||||||
|
# Show that we have no connected user session for the request
|
||||||
|
print(request.user)
|
||||||
|
# Check authentication with the current settings
|
||||||
|
user = authenticate(username="root", password="root")
|
||||||
|
# A user is returned only if the credentials are correct
|
||||||
|
if user is not None:
|
||||||
|
# Login the obtained user in the request
|
||||||
|
login(request, user)
|
||||||
|
return {"auth_user": user}
|
33
source/forms/.idea/Forms.iml
generated
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="forms/settings.py" />
|
||||||
|
<option name="manageScript" value="manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3 (training)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PackageRequirementsSettings">
|
||||||
|
<option name="requirementsPath" value="$MODULE_DIR$/../../requirements.pip" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="GOOGLE" />
|
||||||
|
<option name="myDocStringFormat" value="Google" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
</component>
|
||||||
|
</module>
|
12
source/forms/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
source/forms/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|