migrate to sphinx/rst

This commit is contained in:
Fred Pauchet 2015-11-06 12:13:23 +01:00
parent 275473853b
commit 75f1d5877a
29 changed files with 1409 additions and 529 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ _book/
*.swp
*.pdf
*.*~
build
*.log

192
Makefile Normal file
View File

@ -0,0 +1,192 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Gwift.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Gwift.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Gwift"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Gwift"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View File

@ -1,27 +1,8 @@
# Installation
## Prérequis
Ce livre peut être compilé avec [Sphinx](...).
`sudo aptitude install nodejs`.
Après cela, lance `npm install` qui ira récupérer le contenu du fichier `package.json`.
Les lexers Pygments disponibles se trouvent sur cette page: http://pygments.org/docs/lexers/. Les principaux utilisés seront:
## Editeur
Il existe un éditeur pour Gitbook, qui offre pas mal de fonctionnalités sans avoir à tripatouiller la documentation; il est disponible à [l'adresse suivante](https://www.gitbook.com/editor/linux).
Pour l'installation, un petit `dpkg -i gitbook-editor.x.y.z.deb`.
Seul soucis: un compte Gitbook est requis.
# Initialisation
L'initiation du contenu peut se faire une fois que le module `gitbook-cli`aura été installé, grâce à la commande `nodejs node_modules/gitbook-cli/bin/gitbook.js init <folder>`.
Cette commande se base sur le fichier `SUMMARY.md` pour générer l'arborescence correcte des fichiers (vides, dans un premier temps).
# Compilation
* `nodejs node_modules/gitbook-cli/bin/gitbook.js build`
* `nodejs node_modules/gitbook-cli/bin/gitbook.js serve`
## Formats supportés
Pour les formats autres que HTML (PDF, Mobi & ePub), il faut installer `Calibre` grâce à un `aptitude install Calibre`. Attention qu'il y a un bon petit 300MB de dépendances...
* `shell`
* `python`

View File

@ -1,13 +0,0 @@
# Summary
* [Introduction](README.md)
* [Avant-propos](intro-01-prerequisites.md)
* [Une application Django](intro-02-create-django-app.md)
* [Avant d'aller plus loin](intro-03-before-going-further.md)
* [Gwift](gwift/README.md)
* [Besoins](gwift/specs.md)
* [Modèle](gwift/01-models.md)
* [Vues](gwift/02-views.md)
* [URLs](gwift/03-urls.md)
* [Forms](gwift/05-forms.md)
* [Administration](gwift/06-admin.md)

View File

@ -1,28 +0,0 @@
URLs
====
La gestion des URLs permet *grosso modo* d'assigner une adresse paramétrée ou non à une fonction Python. La manière simple consiste à modifier le fichier `gwift/settings.py` pour y ajouter nos correspondances. Par défaut, le fichier ressemble à ceci:
```python
# gwift/urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
]
```
Le champ `urlpatterns` associe un ensemble d'adresses à des fonctions. Dans le fichier *nu*, seul le *pattern* `^admin/` [^1] est défini, et inclut toutes les adresses qui sont définies dans le fichier `admin.site.urls`. Reportez-vous à l'installation de l'environnement: ce fichier contient les informations suivantes:
[^1] Rappelez-vous de vos expressions régulières: `^` indique le début de la chaîne.
```python
# admin.site.urls.py
```
## Reverse

View File

@ -1,108 +0,0 @@
Besoins
=======
[A remplir]
Besoins utilisateur
-------------------
Nous souhaitons développer un site où un utilisateur donné peut créer une liste contenant des souhaits et où d'autres utilisateurs, authentifiés ou non, peuvent choisir les souhaits qu'ils souhaitent réaliser.
Il sera nécessaire de s'authentifier pour :
1. Créer une liste associée à l'utilisateur en cours
1. Ajouter un nouvel élément à une liste
Il ne sera pas nécessaire de s'authentifier pour :
1. Faire une promesse d'offre pour un élément appartenant à une liste, associée à un utilisateur.
L'utilisateur ayant créé une liste pourra envoyer un email directement depuis le site aux personnes avec qui il souhaite partager sa liste, cet email contenant un lien permettant d'accéder à cette liste.
A chaque souhait, on pourrait de manière facultative ajouter un prix. Dans ce cas, le souhait pourrait aussi être subdivisé en plusieurs parts, de manière à ce que plusieurs personnes puissent participer à sa réalisation.
Un souhait pourrait aussi être réalisé plusieurs fois.
Besoins fonctionnels du site gwift
----------------------------------
### Gestion des utilisateurs
Pour gérer les utilisateurs, nous utiliserons ce que Django met par défaut à notre disposition.
### Gestion des listes
#### Modèlisation
Les données suivantes doivent être associées à une liste:
* un identifiant
* un identifiant externe
* un nom
* une description
* le propriétaire
* une date de création
* une date de modification
#### Fonctionnalités
1. Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer une liste dont il est le propriétaire
1. Un utilisateur doit pouvoir associer ou retirer des souhaits à une liste dont il est le propriétaire
1. Il faut pouvoir accéder à une liste, avec un utilisateur authentifier ou non, *via* son identifiant externe
1. Il faut pouvoir envoyer un email avec le lien vers la liste, contenant son identifiant externe
1. L'utilisateur doit pouvoir voir toutes les listes qui lui appartiennent
### Gestion des souhaits
#### Modélisation
Les données suivantes peuvent être associées à un souhait:
* un identifiant
* identifiant de la liste
* un nom
* une description
* le propriétaire
* une date de création
* une date de modification
* une image
* un nombre (1 par défaut)
* un prix facultatif
* un nombre de part facultatif, si un prix est fourni.
#### Fonctionnalités
1. Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer un souhait dont il est le propriétaire.
1. On ne peut créer un souhait sans liste associée
1. Il faut pouvoir fractionner un souhait uniquement si un prix est donné.
1. Il faut pouvoir accéder à un souhait, avec un utilisateur authentifié ou non.
1. Il faut pouvoir réaliser un souhait ou une partie seulement, avec un utilisateur authentifié ou non.
1. Un souhait en cours de réalisation et composé de différente part ne peut plus être modifié.
1. Un souhait en cours de réalisation ou réalisé ne peut plus être supprimé.
1. On peut modifier le nombre de fois qu'un souhait doit être réalisé dans la limite des réalisations déjà effectuées.
### Gestion des réalisations de souhaits
#### Modélisation
Les données suivantes peuvent être associées à une réalisation de souhait:
* identifiant du souhait
* identifiant de l'utilisateur si connu
* identifiant de la personne si utilisateur non connu
* un commentaire
* une date de réalisation
#### Fonctionnalités
1. L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie ou non.
1. L'utilisateur doit pouvoir voir la ou les personnes ayant réaliser un souhait.
1. Il y a autant de réalisation que de parts de souhait réalisées ou de nombre de fois que le souhait est réalisé.
### Gestion des personnes réalisants les souhaits et qui ne sont pas connues
#### Modélisation
Les données suivantes peuvent être associées à une personne réalisant un souhait:
* un identifiant
* un nom
* une adresse email facultative
#### Fonctionnalités

View File

@ -1,56 +0,0 @@
Une application Django
======================
Comme on l'a vu ci-dessus, `django-admin` permet de créer un nouveau projet. Django fait une distinction entre un **projet** et une **application**:
* Projet: ensemble des applications, paramètres, pages HTML, middlwares, dépendances, ... qui font que votre code fait ce qu'il est sensé faire.
* Application: *contexte* éventuellement indépendant, permettant d'effectuer une partie isolée de ce que l'on veut faire.
Pour `gwift`, on va notamment avoir une application pour la gestion des listes de souhaits et des éléments, une deuxième application pour la gestion des utilisateurs, voire une troisième application qui gérera les partages entre utilisateurs et listes.
On voit bien ici le principe de **contexte**: l'application viendra avec son modèle, ses tests, ses vues, son paramétrage, ... Et pourra éventuellement être réutilisée dans un autre projet. C'est en ça que consistent les [paquets Django](https://www.djangopackages.com/) déjà disponibles: ce sont simplement de petites applications empaquetées pour être réutilisables (eg. [Django-Rest-Framework](https://github.com/tomchristie/django-rest-framework), [Django-Debug-Toolbar](https://github.com/django-debug-toolbar/django-debug-toolbar), ...).
manage.py
---------
Comme expliqué un peu plus haut, le fichier `manage.py` est un *wrapper* sur les commandes `django-admin`. A partir de maintenant, nous n'utiliserons plus que celui-là pour tout ce qui touchera à la gestion de notre projet:
* `manage.py check` pour vérifier que votre projet ne rencontre aucune erreur
* `manage.py runserver` pour lancer un serveur de développement
* `manage.py test` pour découvrir les tests unitaires disponibles et les lancer.
La liste complète peut être affichée avec `manage.py help`. Vous remarquerez que ces commandes sont groupées:
* **auth**: création d'un nouveau super-utilisateur, changer le mot de passe pour un utilisateur existant.
* **django**: vérifier la *compliance* du projet, lancer un *shell*, *dumper* les données de la base, effectuer une migration du schéma, ...
* **sessions**: suppressions des sessions en cours
* **staticfiles**: gestion des fichiers statiques et lancement du serveur de développement.
Nous verrons plus tard comment ajouter de nouvelles commandes.
Structure d'une application
---------------------------
Maintenant que l'on a vu à quoi servait `manage.py`, on peut créer notre nouvelle application grâce à la commande `manage.py startapp <label>`.
Cette application servira à structurer les listes de souhaits, les éléments qui les composent et les parties que chaque utilisateur pour offrir. Essayez de trouver un nom éloquent, court et qui résume bien ce que fait l'application. Pour nous, ce sera donc `wish`. C'est parti pour `manage.py startapp wish`!
```shell
$ cd gwift
$ python manage.py startapp wish
```
Résultat? Django nous a créé un répertoire `wish`, dans lequel on trouve les fichiers suivants:
```shell
$ ls -l wish
admin.py __init__.py migrations models.py tests.py views.py
```
En résumé, chaque fichier a la fonction suivante:
* `admin.py` servira à structurer l'administration de notre application. Chaque information peut en effet être administrée facilement au travers d'une interface générée à la volée par le framework. On y reviendra par la suite.
* `__init__.py` pour que notre répertoire `wish` soit converti en package Python.
* `migrations/`, dossier dans lequel seront stockées toutes les différentes migrations de notre application.
* `models.py` pour représenter et structurer nos données.
* `tests.py` pour les tests unitaires.
* `views.py` pour définir ce que nous pouvons faire avec nos données.

View File

@ -1,76 +0,0 @@
Avant d'aller plus loin...
==========================
Avant d'aller plus loin, donc, un petit point sur les conventions, les tests (unitaires, orientés comportement, basés sur la documentation, ...) et sur la documentation. Plus que dans tout langage compilé, ceux-ci sont pratiquement obligatoires. Vous pourrez les voir comme une perte de temps dans un premier temps, mais nous vous promettons qu'ils vous en feront gagner par la suite.
## PEP8
Le langage Python fonctionne avec un système d'améliorations basées sur des propositions: les PEP, ou "Python Enhancement Proposal". Chacune d'entre elles doit être approuvée par le [Benevolent Dictator For Life](http://fr.wikipedia.org/wiki/Benevolent_Dictator_for_Life).
La PEP qui nous intéresse plus particulièrement pour la suite est la [PEP-8](https://www.python.org/dev/peps/pep-0008/), ou "Style Guide for Python Code". Elle spécifie des conventions d'organisation et de formatage de code Python, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, ... En bref, elle décrit comment écrire du code proprement pour que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
Sur cette base, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: pep8. Pour l'installer, passez par pip. Lancez ensuite la commande pep8 suivie du chemin à analyser (., le nom d'un répertoire, le nom d'un fichier `.py`, ...). Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options `--statistics -qq`.
```shell
$ pep8 . --statistics -qq
7 E101 indentation contains mixed spaces and tabs
6 E122 continuation line missing indentation or outdented
8 E127 continuation line over-indented for visual indent
23 E128 continuation line under-indented for visual indent
3 E131 continuation line unaligned for hanging indent
12 E201 whitespace after '{'
13 E202 whitespace before '}'
86 E203 whitespace before ':'
```
Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous voulez juste avoir un retour sur une analyse de votre code, essayez `pyflakes`: il analysera vos sources à la recherche de sources d'erreurs possibles (imports inutilisés, méthodes inconnues, etc.).
Finalement, la solution qui couvre ces deux domaines existe et s'intitule [flake8](https://github.com/PyCQA/flake8). Sur base la même interface que `pep8`, vous aurez en plus tous les avantages liés à `pyflakes` concernant votre code source.
## Tests et couverture de code
La couverture de code donne un pourcentage lié à la quantité de code couvert par les testss.
Attention que celle-ci ne permet pas de vérifier que le code est **bien** testé, elle permet juste de vérifier que le code est **testé**. Pour chaque fonction ou *statement* présent.
## Complexité de McCabe
La [complexité cyclomatique](https://fr.wikipedia.org/wiki/Nombre_cyclomatique) (ou complexité de McCabe) peut s'apparenter à une [...]
A nouveau, un greffon pour `flake8` existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes. Installez-le avec `pip install mccabe`, et activez-le avec le paramètre `--max-complexity`. Toute fonction dans la complexité est supérieure à 10 est considérée comme trop complexe.
```shell
```
## Documentation
Il existe plusieurs manières de générer la documentation d'un projet. Les plus connues sont [Sphinx](http://sphinx-doc.org/) et [MkDocs](http://www.mkdocs.org/). Le premier a l'avantage d'être plus reconnu dans la communauté Python que l'autre, de pouvoir *parser* le code pour en extraire la documentation et de pouvoir lancer des [tests orientés documentation](https://duckduckgo.com/?q=documentation+driven+development&t=ffsb). A contrario, votre syntaxe devra respecter [ReStructuredText](https://en.wikipedia.org/wiki/ReStructuredText). Le second a l'avantage d'avoir une syntaxe plus simple à apprendre et à comprendre, mais est plus limité dans son résultat.
Dans l'immédiat, nous nous contenterons d'avoir des modules documentés (quelle que soit la méthode Sphinx/MkDocs/...). Dans la continuié avec `Flake8`, il existe un greffon qui vérifie la présence de commentaires au niveau des méthodes et modules développés.
```shell
pip install flake8_docstrings
```
Lancez ensuite `flake8` avec la commande `flake8 . --exclude="migrations"`. Sur notre projet (presque) vide, le résultat sera le suivant:
```shell
$ flake8 . --exclude="migrations"
.\gwift\manage.py:1:1: D100 Missing docstring in public module
.\gwift\gwift\__init__.py:1:1: D100 Missing docstring in public module
.\gwift\gwift\urls.py:1:1: D400 First line should end with a period (not 'n')
.\gwift\wish\__init__.py:1:1: D100 Missing docstring in public module
.\gwift\wish\admin.py:1:1: D100 Missing docstring in public module
.\gwift\wish\admin.py:1:1: F401 'admin' imported but unused
.\gwift\wish\models.py:1:1: D100 Missing docstring in public module
.\gwift\wish\models.py:1:1: F401 'models' imported but unused
.\gwift\wish\tests.py:1:1: D100 Missing docstring in public module
.\gwift\wish\tests.py:1:1: F401 'TestCase' imported but unused
.\gwift\wish\views.py:1:1: D100 Missing docstring in public module
.\gwift\wish\views.py:1:1: F401 'render' imported but unused
```
Bref, on le voit: nous n'avons que très peu de modules, et aucun d'eux n'est commenté.
En plus de cette méthode, Django permet également de rendre la documentation accessible depuis son interface d'administration.

263
make.bat Normal file
View File

@ -0,0 +1,263 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
set I18NSPHINXOPTS=%SPHINXOPTS% source
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 2> nul
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Gwift.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Gwift.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

View File

@ -1,24 +0,0 @@
{
"name": "gwift-book",
"version": "0.0.0",
"description": "django paperification",
"main": "index.js",
"dependencies": {
"gitbook-cli": "^0.3.6"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.framasoft.org/Grimbox/gwift-book.git"
},
"keywords": [
"django",
"paper",
"tutorial"
],
"author": "",
"license": "CC-BY-SA-4.0"
}

1
requirements/base.txt Normal file
View File

@ -0,0 +1 @@
sphinx

View File

@ -1,57 +1,56 @@
# Administration
**************
Administration
**************
Un schéma relationnel demande à l'utilisateur de définir un ensemble de tables, permettant de représenter et stocker de l'information de manière pérenne. Plus précisément, chacune de ces tables devra
L'administration de Django est l'une des fonctionnalités qui saute le plus au yeux lorsqu'on présente le framework à un nouveau-venu: sur base du modèle défini dans les différentes applications, Django peut générer automagiquement les formulaires d'entrée de données, les pages de listing, des fonctions de recherche, ... Tout ça avec très très peu de lignes de code.
L'ORM de Django permet de définir travailler uniquement avec une définition de classes, et de faire en sorte que le lien avec la base de données soit géré uniquement de manière indirecte, par Django lui-même. On peut schématiser ce comportement par une classe = une table
L'un des problèmes par contre est que cette partie d'administration n'est pas destinée aux utilisateurs: c'est plus pour un *super-utilisateur* ou un gestionnaire que pour un utilisateur *lambda*.
Lors de la définition d'une nouvelle classe, et puisque l'ORM se base sur Active Records, il peut être intéressant de définir une valeur pour les options suivantes:
## Une représentation textuelle
Une représentation textuelle
============================
Surcharger la fonction `def __str__(self)` sur la classe permettra de retourner une chaîne de caractère qui représentera l'instance de la classe. Cette information est utilisée un peu partout dans le code, et donnera une meilleure idée de ce qu'on manipule.
En plus, c'est aussi ceci qui est appelé lorsque l'admin de Django historisera une action (et ceci sera inaltérable).
## URL absolue
URL absolue
===========
La méthode `def get_absolute_url(self)` retourne l'URL à laquelle on peut envoyer une requête pour obtenir le maximum d'informations
concernant cette instance.
Par exemple:
```python
def get_absolute_url(self):
return reverse('myapp.views.details', args=[self.id])
```
.. code-block:: python
def get_absolute_url(self):
return reverse('myapp.views.details', args=[self.id])
Lorsqu'on en aura besoin, il suffira d'appeler cette méthode pour atterrir d'office sur la page de détails pour cette instance.
## Meta
Meta
====
.. code-block:: python
class Meta:
ordering = ['-field1', 'field2']
verbose_name = 'my class in singular'
verbose_name_plural = 'my class when is in a list!'
```python
class Meta:
ordering = ['-field1', 'field2']
verbose_name = 'my class in singular'
verbose_name_plural = 'my class when is in a list!'
```
Titre
-----
=====
Le titre de l'administration peut être modifié de deux manières:
1. Soit en modifiant le template de l'administration
2. Soit en ajoutant l'assignation suivante dans le fichier `urls.py`: `admin.site.site_header = "SuperBook Secret Area`.
* Soit en modifiant le template de l'administration
* Soit en ajoutant l'assignation suivante dans le fichier `urls.py`: `admin.site.site_header = "SuperBook Secret Area`.
Greffons
--------
========
L'interface d'administration est extensible dans une certaine mesure. Notamment utiliser django_extensions pour avoir les ForeignKey auto-complétées.
L'interface d'administration est extensible dans une certaine mesure. Notamment utiliser ``django_extensions`` pour avoir les ForeignKey auto-complétées.
# Autre
# Administration
L'administration de Django est l'une des fonctionnalités qui saute le plus au yeux lorsqu'on présente le framework à un nouveau-venu: sur base du modèle défini dans les différentes applications, Django peut générer automagiquement les formulaires d'entrée de données, les pages de listing, des fonctions de recherche, ... Tout ça avec très très peu de lignes de code.
L'un des problèmes par contre est que cette partie d'administration n'est pas destinée aux utilisateurs: c'est plus pour un *super-utilisateur* ou un gestionnaire que pour un utilisateur *lambda*.

View File

@ -1,16 +1,17 @@
Introduction
============
Avant-propos
============
Django se présente comme un [Framework Web pour perfectionnistes ayant des deadlines](https://www.djangoproject.com/). [Django suit quelques principes](https://docs.djangoproject.com/en/dev/misc/design-philosophies/):
Django se présente comme un `Framework Web pour perfectionnistes ayant des deadlines <https://www.djangoproject.com/>`_. `Django suit quelques principes <https://docs.djangoproject.com/en/dev/misc/design-philosophies/>`_:
* Faible couplage et forte cohésion, pour que chaque composant ait son indépendance.
* Moins de code, plus de fonctionnalités.
* [Don't repeat yourself](https://fr.wikipedia.org/wiki/Sec): ne pas se répéter!
* `Don't repeat yourself <https://fr.wikipedia.org/wiki/Sec>`_: ne pas se répéter!
* Rapidié du développement.
Pour commencer, nous allons nous concentrer sur la création d'un site ne contenant qu'une seule application, même si en pratique le site contiendra déjà plusieurs applications fournies pas django, comme nous le verrons plus loin.
Pour prendre un exemple concret, nous allons créer un site permettant de gérer des listes de souhaits, que nous appellerons `gwift` (pour `GiFTs and WIshlisTs` :)).
La première chose à faire est de définir nos besoins du point de vue de l'utilisateur, c'est-à-dire ce que nous souhaitons qu'un utilisateur puisse faire avec l'application. Ensuite, nous pourrons traduire ces besoins en fonctionnalités et finalement effectuer le développement.
La première chose à faire est de définir nos besoins du point de vue de l'utilisateur, c'est-à-dire ce que nous souhaitons qu'un utilisateur puisse faire avec l'application. Ensuite, nous pourrons traduire ces besoins en fonctionnalités et finalement effectuer le développement.

362
source/conf.py Normal file
View File

@ -0,0 +1,362 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Gwift documentation build configuration file, created by
# sphinx-quickstart on Wed Oct 28 18:48:30 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import shlex
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.todo',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Gwift'
copyright = '2015, Fred & Céd\''
author = 'Fred & Céd\''
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'fr'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Gwiftdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
'inputenc': '',
'utf8extra': '',
'preamble': '''
\usepackage{fontspec}
''',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Gwift.tex', 'Gwift Documentation',
'Fred \\& Céd\'', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
latex_show_urls = True
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'gwift', 'Gwift Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Gwift', 'Gwift Documentation',
author, 'Gwift', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
#epub_basename = project
# The HTML theme for the epub output. Since the default themes are not optimized
# for small screen space, using the same theme for HTML and epub output is
# usually not wise. This defaults to 'epub', a theme designed to save visual
# space.
#epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or 'en' if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#epub_tocscope = 'default'
# Fix unsupported image types using the Pillow.
#epub_fix_images = False
# Scale large images.
#epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#epub_show_urls = 'inline'
# If false, no index is generated.
#epub_use_index = True

0
source/forms.rst Normal file
View File

View File

@ -1,39 +1,53 @@
*****
Forms
=====
*****
Les forms ne s'appliquent pas uniquement pour les rendus de formulaires dans votre page web, mais pour toute information qui doit être entrée dans la base de données. Ils jouent en fait plusieurs rôles: validation des données (en plus de celles déjà définies au niveau du modèle), contrôle de la manière dans les champs doivent être visualisables, ... et agissent comme une colle entre l'utilisateur et la modélisation de vos tables et structures de données.
## Dépendance avec le modèle
Dépendance avec le modèle
=========================
Un **form** peut dépendre d'une autre classe Django. Pour cela, il suffit de fixer l'attribut `model` au niveau de la `class Meta` dans la définition.
```python
from django import forms
.. code-block:: python
from wish.models import Wishlist
from django import forms
from wish.models import Wishlist
class WishlistCreateForm(forms.ModelForm):
class Meta:
model = Wishlist
fields = ('name', 'description')
class WishlistCreateForm(forms.ModelForm):
class Meta:
model = Wishlist
fields = ('name', 'description')
```
De cette manière, notre form dépendra automatiquement des champs déjà déclarés dans la classe `Wishlist`. Cela suit le principe de [DRY](don't repeat yourself), et évite qu'une modification ne pourrisse le code: en testant les deux champs présent dans l'attribut `fields`, nous pourrons nous assurer de faire évoluer le formulaire en fonction du modèle sur lequel il se base.
## Contrôle du rendu
Contrôle du rendu
=================
Le formulaire permet également de contrôler le rendu qui sera appliqué lors de la génération de la page. Si les champs dépendent du modèle sur lequel se base le formulaire, ces widgets doivent être initialisés dans l'attribut `Meta`. Sinon, ils peuvent l'être directement au niveau du champ.
```python
from django import forms
from datetime import date
from .models import Accident
.. code-block:: python
class AccidentForm(forms.ModelForm):
class Meta:
model = Accident
fields = ('gymnast', 'educative', 'date', 'information')
widgets = {
'date' : forms.TextInput(attrs={'class' : 'form-control', 'data-provide' : 'datepicker', 'data-date-format' : 'dd/mm/yyyy', 'placeholder' : date.today().strftime("%d/%m/%Y")}),
'information' : forms.Textarea(attrs={'class' : 'form-control', 'placeholder' : 'Informations about accident : context (why, where, …), consequencies, …'})
```
from django import forms
from datetime import date
from .models import Accident
class AccidentForm(forms.ModelForm):
class Meta:
model = Accident
fields = ('gymnast', 'educative', 'date', 'information')
widgets = {
'date' : forms.TextInput(
attrs={
'class' : 'form-control',
'data-provide' : 'datepicker',
'data-date-format' : 'dd/mm/yyyy',
'placeholder' : date.today().strftime("%d/%m/%Y")
}),
'information' : forms.Textarea(
attrs={
'class' : 'form-control',
'placeholder' : 'Context (why, where, ...)'
})

30
source/index.rst Normal file
View File

@ -0,0 +1,30 @@
.. Gwift documentation master file, created by
sphinx-quickstart on Wed Oct 28 18:48:30 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Gwift's documentation!
=================================
Contents:
.. toctree::
:maxdepth: 2
:numbered:
before-we-start
intro
specs
models
views
templates
forms
admin
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

9
source/intro.rst Normal file
View File

@ -0,0 +1,9 @@
============
Introduction
============
.. include:: intro/01-prerequisites.rst
.. include:: intro/02-create-django-app.rst
.. include:: intro/03-before-going-further.rst

View File

@ -1,114 +1,114 @@
Avant propos
============
*************
Environnement
*************
Avant de démarrer le développement, il est nécessaire de passer un peu de temps sur la configuration de l'environnement.
Nous allons utiliser [Python](https://www.python.org/), disponible sur la majorité des distributions Linux, ainsi que sur MacOS, dans des versions parfois différentes. Pour les utilisateurs de Windows, il sera sans doute nécessaire d'installer une version de l'interpréteur et de configurer la variable *PATH* pour votre utilisateur. Ajoutez-y `virtualenv` afin de créer un [environnement virtuel](http://sametmax.com/les-environnement-virtuels-python-virtualenv-et-virtualenvwrapper/), puis `virtualenvwrapper` pour en faciliter la gestion, et les prérequis seront remplis.
Nous allons utiliser `Python <https://www.python.org/>`_, disponible sur la majorité des distributions GNU/Linux, ainsi que sur MacOS, dans des versions parfois différentes. Pour les utilisateurs de Windows, il sera sans doute nécessaire d'installer une version de l'interpréteur et de configurer la variable *PATH* pour votre utilisateur. Ajoutez-y ``virtualenv`` afin de créer un `environnement virtuel <http://sametmax.com/les-environnement-virtuels-python-virtualenv-et-virtualenvwrapper/>`_, puis ``virtualenvwrapper`` pour en faciliter la gestion, et les prérequis seront remplis.
Les morceaux de code seront développés pour Python3.4+ et Django 1.8+. Ils nécessiteront peut-être quelques adaptations pour fonctionner sur une version antérieure.
Remarque : les commandes qui seront exécutés dans ce livre le seront depuis un shell sous linux. Certaines devrons donc être adaptés si vous êtes dans un autre environnemnet.
**Remarque** : les commandes qui seront exécutés dans ce livre le seront depuis un shell sous GNU/Linux. Certaines devrons donc être adaptées si vous êtes dans un autre environnemnet.
Création de l'environnement
---------------------------
### Environnement virtuel
===========================
Commencez par créer un environnement virtuel, afin d'y stocker les dépendances. Vérifiez dans votre fichier `~/.bashrc` (ou tout fichier lancé au démarrage de votre session) que la variable `WORKON_HOME` est bien définie. Faites ensuite un `source` sur le fichier `virtualenvwrapper.sh` (à adapter en fonction de votre distribution):
```shell
# ~/.bashrc
.. code-block:: shell
# ~/.bashrc
[...]
[...]
WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
```
WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
L'intérêt de ceci ? Ne pas devoir se soucier de l'emplacement des environnements virtuels, et pouvoir entièrement les découpler des sources sur lesquelles vous travaillez, en plus d'isoler le code, de créer un containeur pour les dépendances et d'être indépendant des librairies tierces déjà installées sur le système.
Lancez `mkvirtualenv gwift-env`.
```shell
$ mkvirtualenv -p python3 gwift-env
New python executable in gwift-env/bin/python3
Also creating executable in gwift-env/bin/python
Installing setuptools, pip...done.
```
.. code-block:: shell
$ mkvirtualenv -p python3 gwift-env
New python executable in gwift-env/bin/python3
Also creating executable in gwift-env/bin/python
Installing setuptools, pip...done.
Ceci créera l'arborescence de fichiers suivante, qui peut à nouveau être un peu différente en fonction du système d'exploitation:
```shell
$ ls gwift-env
Include/ Lib/ Scripts/
```
.. code-block:: shell
$ ls gwift-env
Include/ Lib/ Scripts/
Nous pouvons ensuite l'activer grâce à la commande `source gwift-env/bin/activate` avec `virtualenv` et `workon gwift-env` avec `virtualenvwrapper`.
A présent, tous les binaires présents dans cet environnement prendront le pas sur les binaires du système. De la même manière, une variable *PATH* propre est définie et utilisée, afin que les librairies Python soient stockées dans le répertoire `gwift-env/Lib/site-packages/`. C'est notamment ici que nous retrouverons le code-source de Django, ainsi que des librairies externes une fois que nous les aurons installées.
```shell
$ tree .
.virtualenvs
project1
project2
...
sources
gwift-project
other-project
...
```
.. code-block:: shell
### Fichiers sources
$ tree sources/ .virtualenvs/
sources/
├── gwift-book
├── gwift-project
└── other-project
.virtualenvs/
├── env-01
└── env-02
Fichiers sources
================
Nous commençons par créer le répertoire du projet, à savoir `gwift-project`.
```shell
$ mkdir gwift-project
$ cd gwift-project
```
.. code-block:: shell
$ mkdir gwift-project
$ cd gwift-project
Comme l'environnement est activé, on peut à présent y installer Django. La librairie restera indépendante du reste du système, et ne polluera pas les autres projets.
C'est parti: `pip install django`!
```shell
$ pip install django
Collecting django
Downloading Django-1.8.4-py2.py3-none-any.whl (6.2MB)
100% |################################| 6.2MB 91kB/s eta 0:00:01
Installing collected packages: django
Successfully installed django-1.8.4
```
.. code-block:: shell
$ pip install django
Collecting django
Downloading Django-1.8.4-py2.py3-none-any.whl (6.2MB)
100% |################################| 6.2MB 91kB/s eta 0:00:01
Installing collected packages: django
Successfully installed django-1.8.4
Les commandes de création d'un nouveau site sont à présent disponibles, la principale étant `django-admin startproject`. Par la suite, nous utiliserons `manage.py`, qui constitue un *wrapper* autour de `django-admin`.
Pour démarrer notre projet, nous lançons `django-admin startproject gwift`.
```shell
$ django-admin startproject gwift
```
.. code-block:: shell
$ django-admin startproject gwift
Cette action aura pour effet de créer un nouveau dossier `gwift`, dans lequel on trouve la structure suivante:
```shell
$ tree gwift
gwift
|-- gwift
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| |-- wsgi.py
|-- manage.py
.. code-block:: shell
```
$ tree gwift
gwift
|-- gwift
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| |-- wsgi.py
|-- manage.py
Chacun de ces fichiers sert à:
* `settings.py` contient tous les paramètres globaux à notre projet.
* `urls.py` contient les variables de routes, les adresses utilisées et les fonctions vers lesquelles elles pointent.
* `wsgi.py` contient la définition de l'interface [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface), qui permettra à votre serveur Web (Nginx, Apache, ...) de faire un pont vers votre projet.
* `wsgi.py` contient la définition de l'interface `WSGI <https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface>`_, qui permettra à votre serveur Web (Nginx, Apache, ...) de faire un pont vers votre projet.
Gestion des dépendances
-----------------------
=======================
Comme nous venons d'ajouter une dépendance à notre projet, nous allons créer un fichier reprenant tous les dépendances de notre projet. Ceux-ci sont placés normalement dans un fichier `requirements.txt`. Dans un premier temps, ce fichier peut être placé directement à la racine du projet, mais on préférera rapidement le déplacer dans un sous-répertoire spécifique (`requirements`), afin de grouper les dépendances en fonction de leur utilité:
@ -119,7 +119,7 @@ Comme nous venons d'ajouter une dépendance à notre projet, nous allons créer
Au début de chaque fichier, il suffira d'ajouter la ligne `-r base.txt`, puis de lancer l'installation grâce à un `pip install -r <nom du fichier>`. De cette manière, il est tout à fait acceptable de n'installer `flake8` et `django-debug-toolbar` qu'en développement par exemple. Dans l'immédiat, ajoutez simplement `django` dans le fichier `requirements/base.txt`.
```shell
$ mkdir requirements
$ echo django >> requirements/base.txt
```
.. code-block:: shell
$ mkdir requirements
$ echo django >> requirements/base.txt

View File

@ -0,0 +1,58 @@
**********************
Une application Django
**********************
Comme on l'a vu ci-dessus, ``django-admin`` permet de créer un nouveau projet. Django fait une distinction entre un **projet** et une **application**:
* **Projet**: ensemble des applications, paramètres, pages HTML, middlwares, dépendances, etc., qui font que votre code fait ce qu'il est sensé faire.
* **Application**: *contexte* éventuellement indépendant, permettant d'effectuer une partie isolée de ce que l'on veut faire.
Pour ``gwift``, on va notamment avoir une application pour la gestion des listes de souhaits et des éléments, une deuxième application pour la gestion des utilisateurs, voire une troisième application qui gérera les partages entre utilisateurs et listes.
On voit bien ici le principe de **contexte**: l'application viendra avec son modèle, ses tests, ses vues et son paramétrage. Elle pourra éventuellement être réutilisée dans un autre projet. C'est en ça que consistent les `paquets Django <https://www.djangopackages.com/>`_ déjà disponibles: ce sont simplement de petites applications empaquetées pour être réutilisables (eg. `Django-Rest-Framework <https://github.com/tomchristie/django-rest-framework>`_, `Django-Debug-Toolbar <https://github.com/django-debug-toolbar/django-debug-toolbar>`_, ...).
Gestion
=======
Comme expliqué un peu plus haut, le fichier `manage.py` est un *wrapper* sur les commandes `django-admin`. A partir de maintenant, nous n'utiliserons plus que celui-là pour tout ce qui touchera à la gestion de notre projet:
* `manage.py check` pour vérifier que votre projet ne rencontre aucune erreur
* `manage.py runserver` pour lancer un serveur de développement
* `manage.py test` pour découvrir les tests unitaires disponibles et les lancer.
La liste complète peut être affichée avec `manage.py help`. Vous remarquerez que ces commandes sont groupées:
* **auth**: création d'un nouveau super-utilisateur, changer le mot de passe pour un utilisateur existant.
* **django**: vérifier la *compliance* du projet, lancer un *shell*, *dumper* les données de la base, effectuer une migration du schéma, ...
* **sessions**: suppressions des sessions en cours
* **staticfiles**: gestion des fichiers statiques et lancement du serveur de développement.
Nous verrons plus tard comment ajouter de nouvelles commandes.
Structure d'une application
===========================
Maintenant que l'on a vu à quoi servait ``manage.py``, on peut créer notre nouvelle application grâce à la commande ``manage.py startapp <label>``.
Cette application servira à structurer les listes de souhaits, les éléments qui les composent et les parties que chaque utilisateur pour offrir. Essayez de trouver un nom éloquent, court et qui résume bien ce que fait l'application. Pour nous, ce sera donc ``wish``. C'est parti pour ``manage.py startapp wish``!
.. code-block:: shell
$ cd gwift
$ python manage.py startapp wish
Résultat? Django nous a créé un répertoire ``wish``, dans lequel on trouve les fichiers suivants:
.. code-block:: shell
$ ls -l wish
admin.py __init__.py migrations models.py tests.py views.py
En résumé, chaque fichier a la fonction suivante:
* ``admin.py`` servira à structurer l'administration de notre application. Chaque information peut en effet être administrée facilement au travers d'une interface générée à la volée par le framework. On y reviendra par la suite.
* ``__init__.py`` pour que notre répertoire ``wish`` soit converti en package Python.
* ``migrations/``, dossier dans lequel seront stockées toutes les différentes migrations de notre application.
* ``models.py`` pour représenter et structurer nos données.
* ``tests.py`` pour les tests unitaires.
* ``views.py`` pour définir ce que nous pouvons faire avec nos données.

View File

@ -0,0 +1,85 @@
**************************
Avant d'aller plus loin...
**************************
Avant d'aller plus loin, donc, un petit point sur les conventions, les tests (unitaires, orientés comportement, basés sur la documentation, ...) et sur la documentation. Plus que dans tout langage compilé, ceux-ci sont pratiquement obligatoires. Vous pourrez les voir comme une perte de temps dans un premier temps, mais nous vous promettons qu'ils vous en feront gagner par la suite.
PEP8
****
Le langage Python fonctionne avec un système d'améliorations basées sur des propositions: les PEP, ou "Python Enhancement Proposal". Chacune d'entre elles doit être approuvée par le `Benevolent Dictator For Life <http://fr.wikipedia.org/wiki/Benevolent_Dictator_for_Life>`_.
La PEP qui nous intéresse plus particulièrement pour la suite est la `PEP-8 <https://www.python.org/dev/peps/pep-0008/>`_, ou "Style Guide for Python Code". Elle spécifie des conventions d'organisation et de formatage de code Python, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, etc. En bref, elle décrit comment écrire du code proprement pour que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
Sur cette base, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: pep8. Pour l'installer, passez par pip. Lancez ensuite la commande pep8 suivie du chemin à analyser (., le nom d'un répertoire, le nom d'un fichier `.py`, ...). Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options `--statistics -qq`.
.. code-block:: shell
$ pep8 . --statistics -qq
7 E101 indentation contains mixed spaces and tabs
6 E122 continuation line missing indentation or outdented
8 E127 continuation line over-indented for visual indent
23 E128 continuation line under-indented for visual indent
3 E131 continuation line unaligned for hanging indent
12 E201 whitespace after '{'
13 E202 whitespace before '}'
86 E203 whitespace before ':'
Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous voulez juste avoir un retour sur une analyse de votre code, essayez `pyflakes`: il analysera vos sources à la recherche de sources d'erreurs possibles (imports inutilisés, méthodes inconnues, etc.).
Finalement, la solution qui couvre ces deux domaines existe et s'intitule `flake8 <https://github.com/PyCQA/flake8>`_. Sur base la même interface que `pep8`, vous aurez en plus tous les avantages liés à `pyflakes` concernant votre code source.
Tests et couverture de code
===========================
La couverture de code donne un pourcentage lié à la quantité de code couvert par les testss.
Attention que celle-ci ne permet pas de vérifier que le code est **bien** testé, elle permet juste de vérifier que le code est **testé**. Pour chaque fonction ou *statement* présent.
Complexité de McCabe
====================
La `complexité cyclomatique <https://fr.wikipedia.org/wiki/Nombre_cyclomatique>`_ (ou complexité de McCabe) peut s'apparenter à une [...]
A nouveau, un greffon pour `flake8` existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes. Installez-le avec `pip install mccabe`, et activez-le avec le paramètre `--max-complexity`. Toute fonction dans la complexité est supérieure à 10 est considérée comme trop complexe.
.. code-block:: shell
$
Documentation
=============
Il existe plusieurs manières de générer la documentation d'un projet. Les plus connues sont `Sphinx <http://sphinx-doc.org/>`_ et `MkDocs <http://www.mkdocs.org/>`_. Le premier a l'avantage d'être plus reconnu dans la communauté Python que l'autre, de pouvoir *parser* le code pour en extraire la documentation et de pouvoir lancer des `tests orientés documentation <https://duckduckgo.com/?q=documentation+driven+development&t=ffsb>`_. A contrario, votre syntaxe devra respecter `ReStructuredText <https://en.wikipedia.org/wiki/ReStructuredText>`_. Le second a l'avantage d'avoir une syntaxe plus simple à apprendre et à comprendre, mais est plus limité dans son résultat.
Dans l'immédiat, nous nous contenterons d'avoir des modules documentés (quelle que soit la méthode Sphinx/MkDocs/...). Dans la continuié avec `Flake8`, il existe un greffon qui vérifie la présence de commentaires au niveau des méthodes et modules développés.
.. code-block:: shell
pip install flake8_docstrings
Lancez ensuite `flake8` avec la commande `flake8 . --exclude="migrations"`. Sur notre projet (presque) vide, le résultat sera le suivant:
.. code-block:: shell
$ flake8 . --exclude="migrations"
.\gwift\manage.py:1:1: D100 Missing docstring in public module
.\gwift\gwift\__init__.py:1:1: D100 Missing docstring in public module
.\gwift\gwift\urls.py:1:1: D400 First line should end with a period (not 'n')
.\gwift\wish\__init__.py:1:1: D100 Missing docstring in public module
.\gwift\wish\admin.py:1:1: D100 Missing docstring in public module
.\gwift\wish\admin.py:1:1: F401 'admin' imported but unused
.\gwift\wish\models.py:1:1: D100 Missing docstring in public module
.\gwift\wish\models.py:1:1: F401 'models' imported but unused
.\gwift\wish\tests.py:1:1: D100 Missing docstring in public module
.\gwift\wish\tests.py:1:1: F401 'TestCase' imported but unused
.\gwift\wish\views.py:1:1: D100 Missing docstring in public module
.\gwift\wish\views.py:1:1: F401 'render' imported but unused
Bref, on le voit: nous n'avons que très peu de modules, et aucun d'eux n'est commenté.
En plus de cette méthode, Django permet également de rendre la documentation accessible depuis son interface d'administration.

View File

@ -1,4 +1,8 @@
# Modélisation
============
Modélisation
============
L'ORM de Django permet de définir travailler uniquement avec une définition de classes, et de faire en sorte que le lien avec la base de données soit géré uniquement de manière indirecte, par Django lui-même. On peut schématiser ce comportement par une classe = une table.
Comme on l'a vu dans la description des fonctionnalités, on va *grosso modo* avoir besoin des éléments suivants:
@ -10,29 +14,32 @@ Comme on l'a vu dans la description des fonctionnalités, on va *grosso modo* av
Nous proposons dans un premier temps d'éluder la gestion des utilisateurs, et de simplement se concentrer sur les fonctionnalités principales.
Cela nous donne ceci:
```python
# wish/models.py
.. code-block:: python
from django.db import models
# wish/models.py
from django.db import models
class Wishlist(models.Model):
pass
class Wishlist(models.Model):
pass
class Item(models.Model):
pass
class Item(models.Model):
pass
class Part(models.Model):
pass
```
class Part(models.Model):
pass
Les classes sont créées, mais vides. Entrons dans les détails.
[Ajouter pourquoi on hérite de `models.Model`, etc.)
## Listes de souhaits
******************
Listes de souhaits
******************
Comme déjà décrit précédemment, les listes de souhaits peuvent s'apparenter simplement à un objet ayant un nom et une description. Pour rappel, voici ce qui avait été défini dans les spécifications:
@ -45,70 +52,89 @@ Comme déjà décrit précédemment, les listes de souhaits peuvent s'apparenter
Notre classe `Wishlist` peut être étoffée de la manière suivante:
```python
# wish/models.py
.. code-block:: python
class Wishlist(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
```
# wish/models.py
class Wishlist(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
Que peut-on constater?
1. Que s'il n'est pas spécifié, un identifiant `id` sera automatiquement généré et accessible dans le modèle.
1. Que chaque type de champs (`DateTimeField`, `CharField`, `UUIDField`, ...) a ses propres paramètres d'initialisation. Il est intéressant de les apprendre ou de se référer à la documentation en cas de doute:
* La propriété `created_at` est gérée automatiquement par Django grâce à l'attribut `auto_now_add`: de cette manière, lors d'un **ajout**, une valeur par défaut ("*maintenant*") sera attribuée à cette propriété
* La propriété `updated_at` est également gérée automatique, cette fois grâce à l'attribut `auto_now` initialisé à `True`: lors d'une **mise à jour**, la propriété se verra automatiquement assigner la valeur du moment présent. Cela ne permet évidemment pas de gérer un historique complet et ne nous dira pas **quels champs** ont été modifiés, mais cela nous conviendra dans un premier temps.
* La propriété `external_id` est de type `UUIDField`. Lorsqu'une nouvelle instance sera instanciée, cette propriété prendra la valeur générée par la fonction `uuid.uuid4()`. *A priori*, chacun des types de champs possède une propriété `default`, qui permet d'initialiser une valeur sur une nouvelle instance.
* Que s'il n'est pas spécifié, un identifiant `id` sera automatiquement généré et accessible dans le modèle.
* Que chaque type de champs (`DateTimeField`, `CharField`, `UUIDField`, ...) a ses propres paramètres d'initialisation. Il est intéressant de les apprendre ou de se référer à la documentation en cas de doute.
Au niveau de notre modélisation:
* La propriété `created_at` est gérée automatiquement par Django grâce à l'attribut `auto_now_add`: de cette manière, lors d'un **ajout**, une valeur par défaut ("*maintenant*") sera attribuée à cette propriété
* La propriété `updated_at` est également gérée automatique, cette fois grâce à l'attribut `auto_now` initialisé à `True`: lors d'une **mise à jour**, la propriété se verra automatiquement assigner la valeur du moment présent. Cela ne permet évidemment pas de gérer un historique complet et ne nous dira pas **quels champs** ont été modifiés, mais cela nous conviendra dans un premier temps.
* La propriété `external_id` est de type `UUIDField`. Lorsqu'une nouvelle instance sera instanciée, cette propriété prendra la valeur générée par la fonction `uuid.uuid4()`. *A priori*, chacun des types de champs possède une propriété `default`, qui permet d'initialiser une valeur sur une nouvelle instance.
A présent, notre classe
## Elements
********
Elements
********
## Parties
blah
## Refactoring
*******
Parties
*******
blop
***********
Refactoring
***********
On constate que plusieurs classes possèdent les propriétés `created_at` et `updated_at`, initialisées aux mêmes valeurs. Pour gagner en cohérence, nous allons créer une classe dans laquelle nous définirons ces deux champs, et nous ferons en sorte que les classes `Wishlist`, `Item` et `Part` en héritent. Django gère trois sortes d'héritage:
1. L'héritage par classe abstraite
1. L'héritage classique
1. L'héritage par classe proxy.
* L'héritage par classe abstraite
* L'héritage classique
* L'héritage par classe proxy.
### Classe abstraite
Classe abstraite
================
L'héritage par classe abstraite consiste à déterminer une classe mère qui ne sera jamais instanciée. C'est utile pour définir des champs qui se répèteront dans plusieurs autres classes et surtout pour respecter le principe de DRY. Comme la classe mère ne sera jamais instanciée, ces champs seront en fait dupliqués physiquement, et traduits en SQL, dans chacune des classes filles.
```python
# wish/models.py
.. code-block:: python
# wish/models.py
class AbstractModel(models.Model):
class Meta:
abstract = True
class AbstractModel(models.Model):
class Meta:
abstract = True
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Wishlist(AbstractModel):
pass
class Wishlist(AbstractModel):
pass
class Item(AbstractModel):
pass
class Item(AbstractModel):
pass
class Part(AbstractModel):
pass
```
class Part(AbstractModel):
pass
En traduisant ceci en SQL, on aura en fait trois tables, chacune reprenant les champs `created_at` et `updated_at`, ainsi que son propre identifiant.
### Héritage classique
Héritage classique
==================
L'héritage classique est généralement déconseillé, car il peut introduire très rapidement un problème de performances: en reprenant l'exemple introduit avec l'héritage par classe abstraite, et en omettant l'attribut `abstract = True`, on se retrouvera en fait avec quatre tables SQL:
@ -119,50 +145,54 @@ L'héritage classique est généralement déconseillé, car il peut introduire t
Le problème est que les identifiants seront définis et incrémentés au niveau de la table mère, et que pour obtenir les informations héritées, nous seront obligés de faire une jointure. En gros, impossible d'obtenir les données complètes pour l'une des classes de notre travail de base sans effectuer un *join* sur la classe mère. Dans ce sens, cela va encore... Mais imaginez que vous définissiez une classe `Wishlist`, de laquelle héritent les classes `ChristmasWishlist` et `EasterWishlist`: pour obtenir la liste complètes des listes de souhaits, il vous faudra faire une jointure externe sur chacune des tables possibles, avant même d'avoir commencé à remplir vos données. La [dénormalisation] entre rapidement en jeu pour garder des performances correctes.
### Classe proxy
Classe proxy
============
Lorsqu'on définit une classe de type **proxy**, on fait en sorte que cette nouvelle classe ne définisse aucun nouveau champ sur la classe mère. Cela ne change dès lors rien à la traduction du modèle de données en SQL, puisque la classe mère sera traduite par une table, et la classe fille ira récupérer les mêmes informations dans la même table: elle ne fera qu'ajouter ou modifier un comportement dynamiquement, sans ajouter d'emplacements de stockage supplémentaires.
Nous pourrions ainsi défiinr les classes suivantes:
```python
# wish/models.py
.. code-block:: python
class Wishlist(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=2000)
expiration_date = models.DateField()
# wish/models.py
@staticmethod
def create(self, name, description):
wishlist = Wishlist()
wishlist.name = name
wishlist.description = description
wishlist.save()
return wishlist
class Wishlist(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=2000)
expiration_date = models.DateField()
class ChristmasWishlist(Wishlist):
@staticmethod
def create(self, name, description):
wishlist = Wishlist()
wishlist.name = name
wishlist.description = description
wishlist.save()
return wishlist
@staticmethod
def create(self, name, description):
w = Wishlist.create(name, description)
w.expiration_date = datetime(current_year, 12, 31)
w.save()
class ChristmasWishlist(Wishlist):
@staticmethod
def create(self, name, description):
w = Wishlist.create(name, description)
w.expiration_date = datetime(current_year, 12, 31)
w.save()
class EasterWishlist(Wishlist):
class EasterWishlist(Wishlist):
@staticmethod
def create(self, name, description):
w = Wishlist.create(name, description)
w.expiration_date = datetime(current_year, 4, 1)
w.save()
```
@staticmethod
def create(self, name, description):
w = Wishlist.create(name, description)
w.expiration_date = datetime(current_year, 4, 1)
w.save()
Cette représentation viole plusieurs principes suivis par Django et ne sert qu'à représenter une classe Proxy. Nous verrons plus loin qu'il sera plus facile de créer des formulaires dépendant de notre modèle `Wishlist` et dans lequel la date d'expiration sera fixée, plutôt que de créer de nouvelles classes modèles.
## Gestion des utilisateurs
************************
Gestion des utilisateurs
************************
Dans les spécifications, nous souhaitions pouvoir associer un utilisateur à une liste (*le propriétaire*) et un utilisateur à une part (*le donateur*). Par défaut, Django offre une gestion simplifiée des utilisateurs (pas de connexion LDAP, pas de double authentification, ...): juste un utilisateur et un mot de passe. Pour y accéder, un paramètre par défaut est défini dans votre fichier de settings: `AUTH_USER_MODEL`.

View File

@ -1,3 +1,4 @@
=====
Gwift
=====
@ -9,4 +10,4 @@ La première chose à faire est de définir nos besoins du point de vue de l'uti
Ensuite, nous pourrons traduire ces besoins en fonctionnalités et finalement effectuer le développement
.. include:: specs/00-specs.rst

122
source/specs/00-specs.rst Normal file
View File

@ -0,0 +1,122 @@
********************
Besoins utilisateurs
********************
Nous souhaitons développer un site où un utilisateur donné peut créer une liste contenant des souhaits et où d'autres utilisateurs, authentifiés ou non, peuvent choisir les souhaits qu'ils souhaitent réaliser.
Il sera nécessaire de s'authentifier pour :
* Créer une liste associée à l'utilisateur en cours
* Ajouter un nouvel élément à une liste
Il ne sera pas nécessaire de s'authentifier pour :
* Faire une promesse d'offre pour un élément appartenant à une liste, associée à un utilisateur.
L'utilisateur ayant créé une liste pourra envoyer un email directement depuis le site aux personnes avec qui il souhaite partager sa liste, cet email contenant un lien permettant d'accéder à cette liste.
A chaque souhait, on pourrait de manière facultative ajouter un prix. Dans ce cas, le souhait pourrait aussi être subdivisé en plusieurs parts, de manière à ce que plusieurs personnes puissent participer à sa réalisation.
Un souhait pourrait aussi être réalisé plusieurs fois.
********************
Besoins fonctionnels
********************
Gestion des utilisateurs
========================
Pour gérer les utilisateurs, nous utiliserons ce que Django met par défaut à notre disposition.
Gestion des listes
==================
Modèlisation
------------
Les données suivantes doivent être associées à une liste:
* un identifiant
* un identifiant externe
* un nom
* une description
* le propriétaire
* une date de création
* une date de modification
Fonctionnalités
---------------
* Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer une liste dont il est le propriétaire
* Un utilisateur doit pouvoir associer ou retirer des souhaits à une liste dont il est le propriétaire
* Il faut pouvoir accéder à une liste, avec un utilisateur authentifier ou non, *via* son identifiant externe
* Il faut pouvoir envoyer un email avec le lien vers la liste, contenant son identifiant externe
* L'utilisateur doit pouvoir voir toutes les listes qui lui appartiennent
Gestion des souhaits
====================
Modélisation
------------
Les données suivantes peuvent être associées à un souhait:
* un identifiant
* identifiant de la liste
* un nom
* une description
* le propriétaire
* une date de création
* une date de modification
* une image
* un nombre (1 par défaut)
* un prix facultatif
* un nombre de part facultatif, si un prix est fourni.
Fonctionnalités
---------------
* Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer un souhait dont il est le propriétaire.
* On ne peut créer un souhait sans liste associée
* Il faut pouvoir fractionner un souhait uniquement si un prix est donné.
* Il faut pouvoir accéder à un souhait, avec un utilisateur authentifié ou non.
* Il faut pouvoir réaliser un souhait ou une partie seulement, avec un utilisateur authentifié ou non.
* Un souhait en cours de réalisation et composé de différente part ne peut plus être modifié.
* Un souhait en cours de réalisation ou réalisé ne peut plus être supprimé.
* On peut modifier le nombre de fois qu'un souhait doit être réalisé dans la limite des réalisations déjà effectuées.
Gestion des réalisations de souhaits
====================================
Modélisation
------------
Les données suivantes peuvent être associées à une réalisation de souhait:
* identifiant du souhait
* identifiant de l'utilisateur si connu
* identifiant de la personne si utilisateur non connu
* un commentaire
* une date de réalisation
Fonctionnalités
---------------
* L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie ou non.
* L'utilisateur doit pouvoir voir la ou les personnes ayant réaliser un souhait.
* Il y a autant de réalisation que de parts de souhait réalisées ou de nombre de fois que le souhait est réalisé.
Gestion des personnes réalisants les souhaits et qui ne sont pas connues
========================================================================
Modélisation
------------
Les données suivantes peuvent être associées à une personne réalisant un souhait:
* un identifiant
* un nom
* une adresse email facultative
Fonctionnalités
---------------

0
source/templates.rst Normal file
View File

0
source/views.rst Normal file
View File

View File

@ -0,0 +1,6 @@
***********
Contrôleurs
***********
Dans un *pattern* MVC classique, la traduction immédiate du **contrôleur** est une **vue**. Et comme on le verra par la suite, la **vue** est en fait le **template**.
Les vues agrègent donc les informations à partir d'un des composants et les font transiter vers un autre. En d'autres mots, la vue sert de pont entre deux composants.

29
source/views/03-urls.rst Normal file
View File

@ -0,0 +1,29 @@
****
URLs
****
La gestion des URLs permet *grosso modo* d'assigner une adresse paramétrée ou non à une fonction Python. La manière simple consiste à modifier le fichier `gwift/settings.py` pour y ajouter nos correspondances. Par défaut, le fichier ressemble à ceci:
.. code-block:: python
# gwift/urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
]
Le champ `urlpatterns` associe un ensemble d'adresses à des fonctions. Dans le fichier *nu*, seul le *pattern* `admin`_ est défini, et inclut toutes les adresses qui sont définies dans le fichier `admin.site.urls`. Reportez-vous à l'installation de l'environnement: ce fichier contient les informations suivantes:
.. _`admin`: Rappelez-vous de vos expressions régulières: `^` indique le début de la chaîne.
.. code-block:: python
# admin.site.urls.py
Reverse
=======
Chaque