diff --git a/adoc/diag-9ca0787305b4c33019ebc11adc951857.png b/adoc/diag-9ca0787305b4c33019ebc11adc951857.png
new file mode 100644
index 0000000..0336c4c
Binary files /dev/null and b/adoc/diag-9ca0787305b4c33019ebc11adc951857.png differ
diff --git a/source/diag-9ca0787305b4c33019ebc11adc951857.png b/source/diag-9ca0787305b4c33019ebc11adc951857.png
new file mode 100644
index 0000000..0336c4c
Binary files /dev/null and b/source/diag-9ca0787305b4c33019ebc11adc951857.png differ
diff --git a/source/django/mvc.adoc b/source/django/mvc.adoc
index 38303eb..c73ba19 100644
--- a/source/django/mvc.adoc
+++ b/source/django/mvc.adoc
@@ -1,10 +1,10 @@
-== Contrôleurs
+== Modèle-vue-template
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 les données gérées par la base et l'interface utilisateur.
include::mvc/views.adoc[]
-include::mvc/views.templates.adoc[]
+include::mvc/templates.adoc[]
include::mvc/layout.adoc[]
include::mvc/urls.adoc[]
diff --git a/source/django/mvc/layout.adoc b/source/django/mvc/layout.adoc
index edc62ff..854246b 100644
--- a/source/django/mvc/layout.adoc
+++ b/source/django/mvc/layout.adoc
@@ -1,158 +1,165 @@
-************
-Mise en page
-************
+=== Mise en page
+Pour que nos pages soient un peu plus *eye-candy* que ce qu'on a présenté ci-dessus, nous allons modifié notre squelette pour qu'il se base sur `Bootstrap `_. Nous placerons une barre de navigation principale, la possibilité de se connecter pour l'utilisateur et définirons quelques emplacements à utiliser par la suite. Reprenez votre fichier `base.html` et modifiez le comme ceci:
-Pour que nos pages soient un peu plus *eye-candy* que ce qu'on a présenté ci-dessus, nous allons modifié notre squelette pour qu'il se base sur `Bootstrap `_. Nous placerons une barre de navigation principale, la possibilité de se connecter pour l'utilisateur et définirons quelques emplacements à utiliser par la suite. Reprenez votre fichier ``base.html`` et modifiez le comme ceci:
+[source,html]
+----
-.. code-block:: html
+{% load staticfiles %}
- {% load staticfiles %}
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Gwift
+
-
-
-
-
-
-
-
-
-
-
- Gwift
-
+
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
- {% block content %}{% endblock %}
+
+
+
+
+
+
+
+
+
+ {% block content %}{% endblock %}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+----
Quelques remarques:
- * La première ligne du fichier inclut le *tag* ``{% load staticfiles %}``. On y reviendra par la suite, mais en gros, cela permet de faciliter la gestion des fichiers statiques, notamment en les appelent grâce à la commande ``{% static 'img/header.png' %}`` ou ``{% static 'css/app_style.css' %}``.
- * La balise ``
`` est bourée d'appel vers des ressources stockées sur des :abbr:`CDN (Content Delivery Networks)`.
- * Les balises ``{% block content %} {% endblock %}`` permettent de faire hériter du contenu depuis une autre page. On l'utilise notamment dans notre page ``templates/wish/list.html``.
- * Pour l'entête et le bas de page, on fait appel aux balises ``{% include 'nom_du_fichier.html' %}``: ces fichiers sont des fichiers physiques, placés sur le filesystem, juste à côté du fichier ``base.html``. De façon bête et méchante, cela inclut juste du contenu HTML. Le contenu des fichiers ``_menu_items.html`` et ``_footer.html`` est copié ci-dessous.
+* La première ligne du fichier inclut le *tag* `{% load staticfiles %}`. On y reviendra par la suite, mais en gros, cela permet de faciliter la gestion des fichiers statiques, notamment en les appelent grâce à la commande `{% static 'img/header.png' %}` ou `{% static 'css/app_style.css' %}`.
+* La balise `
` est bourée d'appel vers des ressources stockées sur des :abbr:`CDN (Content Delivery Networks)`.
+* Les balises `{% block content %} {% endblock %}` permettent de faire hériter du contenu depuis une autre page. On l'utilise notamment dans notre page `templates/wish/list.html`.
+* Pour l'entête et le bas de page, on fait appel aux balises `{% include 'nom_du_fichier.html' %}`: ces fichiers sont des fichiers physiques, placés sur le filesystem, juste à côté du fichier `base.html`. De façon bête et méchante, cela inclut juste du contenu HTML. Le contenu des fichiers `_menu_items.html` et `_footer.html` est copié ci-dessous.
-.. code-block:: html
+[source,html]
+----
+
-
-
- {% extends "base.html" %}
+{% extends "base.html" %}
- {% block content %}
- Mes listes de souhaits
-
- {% for wishlist in wishlists %}
- {{ wishlist.name }}: {{ wishlist.description }}
- {% endfor %}
-
- {% endblock %}
-
-
-
-.. code-block:: html
-
-
-
-
-
-
- Login / Register
-
-
+{% block content %}
+ Mes listes de souhaits
+
+ {% for wishlist in wishlists %}
+ {{ wishlist.name }}: {{ wishlist.description }}
+ {% endfor %}
+{% endblock %}
+----
+
-.. code-block:: html
-
-
-
- Copylefted '16
-
+[source,html]
+----
+
+
+
+----
+
+[source,html]
+----
+
+
+ Copylefted '16
+
+----
En fonction de vos affinités, vous pourriez également passer par `PluCSS `_, `Pure `_, `Knacss `_, `Cascade `_, `Semantic `_ ou `Skeleton `_. Pour notre plus grand bonheur, les frameworks de ce type pullulent. Reste à choisir le bon.
*A priori*, si vous relancez le serveur de développement maintenant, vous devriez déjà voir les modifications... Mais pas les images, ni tout autre fichier statique.
-Fichiers statiques
-==================
+==== Fichiers statiques
Si vous ouvrez la page et que vous lancez la console de développement (F12, sur la majorité des navigateurs), vous vous rendrez compte que certains fichiers ne sont pas disponibles. Il s'agit des fichiers suivants:
- * ``/static/css/style.css``
- * ``/static/img/favicon.ico``
- * ``/static/img/gwift-20x20.png``.
+* `/static/css/style.css`
+* `/static/img/favicon.ico`
+* `/static/img/gwift-20x20.png`.
-En fait, par défaut, les fichiers statiques sont récupérés grâce à deux handlers: ``django.contrib.staticfiles.finders.FileSystemFinder`` et ``django.contrib.staticfiles.finders.AppDirectoriesFinder``. En fait, Django va considérer un répertoire ``static`` à l'intérieur de chaque application. Si deux fichiers portent le même nom, le premier trouvé sera pris. Par facilité, et pour notre développement, nous placerons les fichiers statiques dans le répertoire ``gwift/static``. On y trouve donc:
+En fait, par défaut, les fichiers statiques sont récupérés grâce à deux handlers:
-.. code-block:: shell
+. `django.contrib.staticfiles.finders.FileSystemFinder` et . `django.contrib.staticfiles.finders.AppDirectoriesFinder`.
- [inclure un tree du répertoire gwift/static]
+En fait, Django va considérer un répertoire `static` à l'intérieur de chaque application. Si deux fichiers portent le même nom, le premier trouvé sera pris. Par facilité, et pour notre développement, nous placerons les fichiers statiques dans le répertoire `gwift/static`. On y trouve donc:
-Pour indiquer à Django que vous souhaitez aller y chercher vos fichiers, il faut initialiser la `variable `_ ``STATICFILES_DIRS`` dans le fichier ``settings/base.py``. Vérifiez également que la variable ``STATIC_URL`` est correctement définie.
+[source,bash]
+----
+[inclure un tree du répertoire gwift/static]
+----
-.. code-block:: python
+Pour indiquer à Django que vous souhaitez aller y chercher vos fichiers, il faut initialiser la `variable `_ `STATICFILES_DIRS` dans le fichier `settings/base.py`. Vérifiez également que la variable `STATIC_URL` est correctement définie.
- # gwift/settings/base.py
-
- STATIC_URL = '/static/'
-
-.. code-block:: python
+[source,python]
+----
+# gwift/settings/base.py
- # gwift/settings/dev.py
-
- STATICFILES_DIRS = [
- os.path.join(BASE_DIR, "static"),
- ]
+STATIC_URL = '/static/'
+----
+
+[source,python]
+----
+# gwift/settings/dev.py
+
+STATICFILES_DIRS = [
+ os.path.join(BASE_DIR, "static"),
+]
+----
En production par contre, nous ferons en sorte que le contenu statique soit pris en charge par le front-end Web (Nginx), raison pour laquelle cette variable n'est initialisée que dans le fichier des paramètres liés au développement.
diff --git a/source/django/mvc/templates.adoc b/source/django/mvc/templates.adoc
index 537a952..8575740 100644
--- a/source/django/mvc/templates.adoc
+++ b/source/django/mvc/templates.adoc
@@ -1,130 +1,127 @@
-*********
-Templates
-*********
+=== Templates
Avant de commencer à interagir avec nos données au travers de listes, formulaires et IHM sophistiquées, quelques mots sur les templates: il s'agit en fait de *squelettes* de présentation, recevant en entrée un dictionnaire contenant des clés-valeurs et ayant pour but de les afficher dans le format que vous définirez. En intégrant un ensemble de *tags*, cela vous permettra de greffer les données reçues en entrée dans un patron prédéfini.
Une page HTML basique ressemble à ceci:
-.. code-block:: html
+[source,html]
+----
+
+
+
+
+
+
+
+
+ Hello world!
+
+
+----
-
-
-
-
-
-
-
-
- Hello world!
-
-
-
-Notre première vue permettra de récupérer la liste des objets de type ``Wishlist`` que nous avons définis dans le fichier ``wish/models.py``. Supposez que cette liste soit accessible *via* la clé ``wishlists`` d'un dictionnaire passé au template. Elle devient dès lors accessible grâce aux tags ``{% for wishlist in wishlists %}``. A chaque tour de boucle, on pourra directement accéder à la variable ``{{ wishlist }}``. De même, il sera possible d'accéder aux propriétés de cette objet de la même manière: ``{{ wishlist.id }}``, ``{{ wishlist.description }}``, ... et d'ainsi respecter la mise en page que nous souhaitons.
+Notre première vue permettra de récupérer la liste des objets de type `Wishlist` que nous avons définis dans le fichier `wish/models.py`. Supposez que cette liste soit accessible *via* la clé `wishlists` d'un dictionnaire passé au template. Elle devient dès lors accessible grâce aux tags `{% for wishlist in wishlists %}`. A chaque tour de boucle, on pourra directement accéder à la variable `{{ wishlist }}`. De même, il sera possible d'accéder aux propriétés de cette objet de la même manière: `{{ wishlist.id }}`, `{{ wishlist.description }}`, ... et d'ainsi respecter la mise en page que nous souhaitons.
En reprenant l'exemple de la page HTML définie ci-dessus, on pourra l'agrémenter de la manière suivante:
-.. code-block:: html
+[source,django]
+----
+
+
+
+
+
+
+
+
+ Mes listes de souhaits
+
+ {% for wishlist in wishlists %}
+ {{ wishlist.name }}: {{ wishlist.description }}
+ {% endfor %}
+
+
+
+----
-
-
-
-
-
-
-
-
- Mes listes de souhaits
-
- {% for wishlist in wishlists %}
- {{ wishlist.name }}: {{ wishlist.description }}
- {% endfor %}
-
-
-
+Vous pouvez déjà copier ce contenu dans un fichier `templates/wsh/list.html`, on en aura besoin par la suite.
+==== Structure et configuration
-Vous pouvez déjà copié ce contenu dans un fichier ``templates/wsh/list.html``, on en aura besoin par la suite.
+Il est conseillé que les templates respectent la structure de vos différentes applications, mais dans un répertoire à part. Par convention, nous les placerons dans un répertoire `templates`. La hiérarchie des fichiers devient alors celle-ci:
-Structure et configuration
-==========================
+[source,bash]
+----
+$ tree templates/
+templates/
+└── wish
+ └── list.html
+----
-Il est conseillé que les templates respectent la structure de vos différentes applications, mais dans un répertoire à part. Par convention, nous les placerons dans un répertoire ``templates``. La hiérarchie des fichiers devient alors celle-ci:
+Par défaut, Django cherchera les templates dans les répertoirer d'installation. Vous devrez vous éditer le fichier `gwift/settings.py` et ajouter, dans la variable `TEMPLATES`, la clé `DIRS` de la manière suivante:
-.. code--block:: bash
+[source,python]
+----
+TEMPLATES = [
+ {
+ ...
+ 'DIRS': [ 'templates' ],
+ ...
+ },
+]
+----
- $ tree templates/
- templates/
- └── wish
- └── list.html
+==== Builtins
-Par défaut, Django cherchera les templates dans les répertoirer d'installation. Vous devrez vous éditer le fichier ``gwift/settings.py`` et ajouter, dans la variable ``TEMPLATES``, la clé ``DIRS`` de la manière suivante:
+Django vient avec un ensemble de *tags*. On a vu la boucle `for` ci-dessus, mais il existe https://docs.djangoproject.com/fr/1.9/ref/templates/builtins/[beaucoup d'autres tags nativement présents]. Les principaux sont par exemple:
-.. code-block:: python
+* `{% if ... %} ... {% elif ... %} ... {% else %} ... {% endif %}`: permet de vérifier une condition et de n'afficher le contenu du bloc que si la condition est vérifiée.
+* Opérateurs de comparaisons: `<`, `>`, `==`, `in`, `not in`.
+* Regroupements avec le tag `{% regroup ... by ... as ... %}`.
+* `{% url %}` pour construire facilement une URL
+* ...
- TEMPLATES = [
- {
- ...
- 'DIRS': [ 'templates' ],
- ...
- },
- ]
-
-Builtins
-========
-
-Django vient avec un ensemble de *tags*. On a vu la boucle ``for`` ci-dessus, mais il existe `beaucoup d'autres tags nativement présents `_. Les principaux sont par exemple:
-
- * ``{% if ... %} ... {% elif ... %} ... {% else %} ... {% endif %}``: permet de vérifier une condition et de n'afficher le contenu du bloc que si la condition est vérifiée.
- * Opérateurs de comparaisons: ``<``, ``>``, ``==``, ``in``, ``not in``.
- * Regroupements avec le tag ``{% regroup ... by ... as ... %}``.
- * ``{% url %}`` pour construire facilement une URL
- * ...
-
-Non-builtins
-============
+==== Non-builtins
En plus des quelques tags survolés ci-dessus, il est également possible de construire ses propres tags. La structure est un peu bizarre, car elle consiste à ajouter un paquet dans une de vos applications, à y définir un nouveau module et à y définir un ensemble de fonctions. Chacune de ces fonctions correspondra à un tag appelable depuis vos templates.
Il existe trois types de tags *non-builtins*:
- 1. Les filtres - on peut les appeler grâce au *pipe* ``|`` directement après une valeur dans le template.
- 2. Les tags simples - ils peuvent prendre une valeur ou plusieurs en paramètre et retourne une nouvelle valeur. Pour les appeler, c'est *via* les tags ``{% nom_de_la_fonction param1 param2 ... %}``.
- 3. Les tags d'inclusion: ils retournent un contexte (ie. un dictionnaire), qui est ensuite passé à un nouveau template.
+1. Les filtres - on peut les appeler grâce au *pipe* `|` directement après une valeur dans le template.
+2. Les tags simples - ils peuvent prendre une valeur ou plusieurs en paramètre et retourne une nouvelle valeur. Pour les appeler, c'est *via* les tags `{% nom_de_la_fonction param1 param2 ... %}`.
+3. Les tags d'inclusion: ils retournent un contexte (ie. un dictionnaire), qui est ensuite passé à un nouveau template.
Pour l'implémentation:
- 1. On prend l'application ``wish`` et on y ajoute un répertoire ``templatetags``, ainsi qu'un fichier ``__init__.py``.
- 2. Dans ce nouveau paquet, on ajoute un nouveau module que l'on va appeler ``tools.py``
+ 1. On prend l'application `wish` et on y ajoute un répertoire `templatetags`, ainsi qu'un fichier `__init__.py`.
+ 2. Dans ce nouveau paquet, on ajoute un nouveau module que l'on va appeler `tools.py`
3. Dans ce module, pour avoir un aperçu des possibilités, on va définir trois fonctions (une pour chaque type de tags possible).
-.. code-block:: shell
+[source,bash]
+----
+[Inclure un tree du dossier template tags]
+----
- [Inclure un tree du dossier template tags]
-
-.. code-block:: python
+[source,python]
+----
+# wish/tools.py
- # wish/tools.py
+from django import template
+
+from wish.models import Wishlist
+
+register = template.Library()
+
+@register.filter(is_safe=True)
+def add_xx(value):
+ return '%sxx' % value
+
+@register.simple_tag
+def current_time(format_string):
+ return datetime.datetime.now().strftime(format_string)
+
+@register.inclusion_tag('wish/templatetags/wishlists_list.html')
+def wishlists_list():
+ return { 'list': Wishlist.objects.all() }
+----
- # coding=utf-8
-
- from django import template
-
- from wish.models import Wishlist
-
- register = template.Library()
-
- @register.filter(is_safe=True)
- def add_xx(value):
- return '%sxx' % value
-
- @register.simple_tag
- def current_time(format_string):
- return datetime.datetime.now().strftime(format_string)
-
- @register.inclusion_tag('wish/templatetags/wishlists_list.html')
- def wishlists_list():
- return { 'list': Wishlist.objects.all() }
-
-
-Pour plus d'informations, la `documentation officielle est un bon début `_.
\ No newline at end of file
+Pour plus d'informations, la https://docs.djangoproject.com/en/stable/howto/custom-template-tags/#writing-custom-template-tags[documentation officielle est un bon début].
\ No newline at end of file
diff --git a/source/django/mvc/urls.adoc b/source/django/mvc/urls.adoc
index 5bc8779..d4a9d9e 100644
--- a/source/django/mvc/urls.adoc
+++ b/source/django/mvc/urls.adoc
@@ -1,19 +1,18 @@
-****
-URLs
-****
+=== 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
+[source,python]
+----
+# gwift/urls.py
- # gwift/urls.py
+from django.conf.urls import include, url
+from django.contrib import admin
- from django.conf.urls import include, url
- from django.contrib import admin
-
- urlpatterns = [
- url(r'^admin/', include(admin.site.urls)),
- ]
+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:
@@ -23,32 +22,34 @@ Le champ `urlpatterns` associe un ensemble d'adresses à des fonctions. Dans le
# admin.site.urls.py
-Reverse
-=======
+==== Reverse
En associant un nom ou un libellé à chaque URL, il est possible de récupérer sa *traduction*. Cela implique par contre de ne plus toucher à ce libellé par la suite...
-Dans le fichier ``urls.py``, on associe le libellé ``wishlists`` à l'URL ``r'^$`` (c'est-à-dire la racine du site):
+Dans le fichier `urls.py`, on associe le libellé `wishlists` à l'URL `r'^$` (c'est-à-dire la racine du site):
-.. code-block:: python
+[source,python]
+----
+from wish.views import WishListList
- from wish.views import WishListList
-
- urlpatterns = [
- url(r'^admin/', include(admin.site.urls)),
- url(r'^$', WishListList.as_view(), name='wishlists'),
- ]
+urlpatterns = [
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^$', WishListList.as_view(), name='wishlists'),
+]
+----
De cette manière, dans nos templates, on peut à présent construire un lien vers la racine avec le tags suivant:
-.. code-block:: html
-
- {{ yearvar }} Archive
+[source,html]
+----
+{{ yearvar }} Archive
+----
De la même manière, on peut également récupérer l'URL de destination pour n'importe quel libellé, de la manière suivante:
-.. code-block:: python
+[source,python]
+----
+from django.core.urlresolvers import reverse_lazy
- from django.core.urlresolvers import reverse_lazy
-
- wishlists_url = reverse_lazy('wishlists')
\ No newline at end of file
+wishlists_url = reverse_lazy('wishlists')
+----
\ No newline at end of file
diff --git a/source/django/mvc/views.adoc b/source/django/mvc/views.adoc
index 4ccd254..5bc2711 100644
--- a/source/django/mvc/views.adoc
+++ b/source/django/mvc/views.adoc
@@ -1,146 +1,141 @@
-****
-Vues
-****
+=== Vues
-Une vue correspond à un contrôleur dans le pattern MVC. Tout ce que vous pourrez définir au niveau du fichier ``views.py`` fera le lien entre le modèle stocké dans la base de données et ce avec quoi l'utilisateur pourra réellement interagir (le ``template``).
+Une vue correspond à un contrôleur dans le pattern MVC. Tout ce que vous pourrez définir au niveau du fichier `views.py` fera le lien entre le modèle stocké dans la base de données et ce avec quoi l'utilisateur pourra réellement interagir (le `template`).
-Chaque vue peut etre représentée de deux manières: soit par des fonctions, soit par des classes. Le comportement leur est propre, mais le résultat reste identique. Le lien entre l'URL à laquelle l'utilisateur accède et son exécution est faite au travers du fichier ``gwift/urls.py``, comme on le verra par la suite.
+Chaque vue peut etre représentée de deux manières: soit par des fonctions, soit par des classes. Le comportement leur est propre, mais le résultat reste identique. Le lien entre l'URL à laquelle l'utilisateur accède et son exécution est faite au travers du fichier `gwift/urls.py`, comme on le verra par la suite.
-Function Based Views
-====================
+==== Function Based Views
-Les fonctions (ou ``FBV`` pour *Function Based Views*) permettent une implémentation classique des contrôleurs. Au fur et à mesure de votre implémentation, on se rendra compte qu'il y a beaucoup de répétitions dans ce type d'implémentation: elles ne sont pas obsolètes, mais dans certains cas, il sera préférable de passer par les classes.
+Les fonctions (ou `FBV` pour *Function Based Views*) permettent une implémentation classique des contrôleurs. Au fur et à mesure de votre implémentation, on se rendra compte qu'il y a beaucoup de répétitions dans ce type d'implémentation: elles ne sont pas obsolètes, mais dans certains cas, il sera préférable de passer par les classes.
-Pour définir la liste des ``WishLists`` actuellement disponibles, on précédera de la manière suivante:
+Pour définir la liste des `WishLists` actuellement disponibles, on précédera de la manière suivante:
- 1. Définition d'une fonction qui va récupérer les objets de type ``WishList`` dans notre base de données. La valeur de retour sera la construction d'un dictionnaire (le *contexte*) qui sera passé à un template HTML. On démandera à ce template d'effectuer le rendu au travers de la fonction ``render``, qui est importée par défaut dans le fichier ``views.py``.
- 2. Construction d'une URL qui permettra de lier l'adresse à l'exécution de la fonction.
- 3. Définition du squelette.
+. Définition d'une fonction qui va récupérer les objets de type `WishList` dans notre base de données. La valeur de retour sera la construction d'un dictionnaire (le *contexte*) qui sera passé à un template HTML. On démandera à ce template d'effectuer le rendu au travers de la fonction `render`, qui est importée par défaut dans le fichier `views.py`.
+. Construction d'une URL qui permettra de lier l'adresse à l'exécution de la fonction.
+. Définition du squelette.
-Définition de la fonction
--------------------------
+===== Définition de la fonction
-.. code-block:: python
+[source,python]
+----
+# wish/views.py
- # wish/views.py
+from django.shortcuts import render
+from .models import Wishlist
- from django.shortcuts import render
- from .models import Wishlist
+def wishlists(request):
+ w = Wishlist.objects.all()
+ return render(request, 'wish/list.html',{ 'wishlists': w })
+----
- def wishlists(request):
- w = Wishlist.objects.all()
- return render(request, 'wish/list.html',{ 'wishlists': w })
+===== Construction de l'URL
-Construction de l'URL
----------------------
+[source,python]
+----
+# gwift/urls.py
-.. code-block:: python
+from django.conf.urls import include, url
+from django.contrib import admin
- # gwift/urls.py
+from wish import views as wish_views
- from django.conf.urls import include, url
- from django.contrib import admin
+urlpatterns = [
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^$', wish_views.wishlists, name='wishlists'),
+]
+----
- from wish import views as wish_views
+===== Définition du squelette
- urlpatterns = [
- url(r'^admin/', include(admin.site.urls)),
- url(r'^$', wish_views.wishlists, name='wishlists'),
- ]
+A ce stade, vérifiez que la variable `TEMPLATES` est correctement initialisée dans le fichier `gwift/settings.py` et que le fichier `templates/wish/list.html` ressemble à ceci:
-Définition du squelette
------------------------
+[source,jinj2]
+----
+
+
+
+
+
+
+
+
+ Mes listes de souhaits
+
+ {% for wishlist in wishlists %}
+ {{ wishlist.name }}: {{ wishlist.description }}
+ {% endfor %}
+
+
+
+----
-A ce stade, vérifiez que la variable ``TEMPLATES`` est correctement initialisée dans le fichier ``gwift/settings.py`` et que le fichier ``templates/wish/list.html`` ressemble à ceci:
-
-.. code-block:: html
-
-
-
-
-
-
-
-
-
- Mes listes de souhaits
-
- {% for wishlist in wishlists %}
- {{ wishlist.name }}: {{ wishlist.description }}
- {% endfor %}
-
-
-
-
-Exécution
----------
+===== Exécution
A présent, ajoutez quelques listes de souhaits grâce à un *shell*, puis lancez le serveur:
-.. code-block:: shell
+[source,bash]
+----
+$ python manage.py shell
+>>> from wish.models import Wishlist
+>>> Wishlist.create('Décembre', "Ma liste pour les fêtes de fin d'année")
+
+>>> Wishlist.create('Anniv 30 ans', "Je suis vieux! Faites des dons!")
+
+----
- $ python manage.py shell
- >>> from wish.models import Wishlist
- >>> Wishlist.create('Décembre', "Ma liste pour les fêtes de fin d'année")
-
- >>> Wishlist.create('Anniv 30 ans', "Je suis vieux! Faites des dons!")
-
-
-Lancez le serveur grâce à la commande ``python manage.py runserver``, ouvrez un navigateur quelconque et rendez-vous à l'adresse `http://localhost:8000 `_. Vous devriez obtenir le résultat suivant:
+Lancez le serveur grâce à la commande `python manage.py runserver`, ouvrez un navigateur quelconque et rendez-vous à l'adresse `http://localhost:8000 `_. Vous devriez obtenir le résultat suivant:
.. image:: mvc/my-first-wishlists.png
:align: center
Rien de très sexy, aucune interaction avec l'utilisateur, très peu d'utilisation des variables contextuelles, mais c'est un bon début! =)
-Class Based Views
-=================
+==== Class Based Views
-Les classes, de leur côté, implémente le *pattern* objet et permettent d'arriver facilement à un résultat en très peu de temps, parfois même en définissant simplement quelques attributs, et rien d'autre. Pour l'exemple, on va définir deux classes qui donnent exactement le même résultat que la fonction ``wishlists`` ci-dessus. Une première fois en utilisant une classe générique vierge, et ensuite en utilisant une classe de type ``ListView``.
+Les classes, de leur côté, implémente le *pattern* objet et permettent d'arriver facilement à un résultat en très peu de temps, parfois même en définissant simplement quelques attributs, et rien d'autre. Pour l'exemple, on va définir deux classes qui donnent exactement le même résultat que la fonction `wishlists` ci-dessus. Une première fois en utilisant une classe générique vierge, et ensuite en utilisant une classe de type `ListView`.
-Classe générique
-----------------
+===== Classe générique
blah
-ListView
---------
+===== ListView
-Les classes génériques implémentent un aspect bien particulier de la représentation d'un modèle, en utilisant très peu d'attributs. Les principales classes génériques sont de type ``ListView``, [...]. L'implémentation consiste, exactement comme pour les fonctions, à:
+Les classes génériques implémentent un aspect bien particulier de la représentation d'un modèle, en utilisant très peu d'attributs. Les principales classes génériques sont de type `ListView`, [...]. L'implémentation consiste, exactement comme pour les fonctions, à:
- 1. Définir la classe
- 2. Créer l'URL
- 3. Définir le squelette.
+. Définir la classe
+. Créer l'URL
+. Définir le squelette.
-.. code-block:: python
+[source,python]
+----
+# wish/views.py
- # wish/views.py
+from django.views.generic import ListView
- from django.views.generic import ListView
+from .models import Wishlist
- from .models import Wishlist
+class WishListList(ListView):
+ context_object_name = 'wishlists'
+ model = Wishlist
+ template_name = 'wish/list.html'
+----
- class WishListList(ListView):
- context_object_name = 'wishlists'
- model = Wishlist
- template_name = 'wish/list.html'
+[source,python]
+----
+# gwift/urls.py
+from django.conf.urls import include, url
+from django.contrib import admin
-.. code-block:: python
+from wish.views import WishListList
- # gwift/urls.py
+urlpatterns = [
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^$', WishListList.as_view(), name='wishlists'),
+]
+----
- from django.conf.urls import include, url
- from django.contrib import admin
+C'est tout. Lancez le serveur, le résultat sera identique. Par inférence, Django construit beaucoup d'informations: si on n'avait pas spécifié les variables `context_object_name` et `template_name`, celles-ci auraient pris les valeurs suivantes:
- from wish.views import WishListList
-
- urlpatterns = [
- url(r'^admin/', include(admin.site.urls)),
- url(r'^$', WishListList.as_view(), name='wishlists'),
- ]
-
-C'est tout. Lancez le serveur, le résultat sera identique. Par inférence, Django construit beaucoup d'informations: si on n'avait pas spécifié les variables ``context_object_name`` et ``template_name``, celles-ci auraient pris les valeurs suivantes:
-
- * ``context_object_name``: ``wishlist_list`` (ou plus précisément, le nom du modèle suivi de ``_list``)
- * ``template_name``: ``wish/wishlist_list.html`` (à nouveau, le fichier généré est préfixé du nom du modèle).
+ * `context_object_name`: `wishlist_list` (ou plus précisément, le nom du modèle suivi de `_list`)
+ * `template_name`: `wish/wishlist_list.html` (à nouveau, le fichier généré est préfixé du nom du modèle).
diff --git a/source/gwift/key-points.adoc b/source/gwift/key-points.adoc
index 292574a..07efa90 100644
--- a/source/gwift/key-points.adoc
+++ b/source/gwift/key-points.adoc
@@ -1,104 +1,100 @@
-*********
-A retenir
-*********
+== A retenir
-Constructeurs
-=============
+=== Constructeurs
-Si vous décidez de définir un constructeur sur votre modèle, ne surchargez pas la méthode ``__init__``: créez plutôt une méthode static de type ``create()``, en y associant les paramètres obligatoires ou souhaités:
+Si vous décidez de définir un constructeur sur votre modèle, ne surchargez pas la méthode `__init__`: créez plutôt une méthode static de type `create()`, en y associant les paramètres obligatoires ou souhaités:
-.. code-block:: python
+[source,python]
+----
+class Wishlist(models.Model):
- class Wishlist(models.Model):
+ @staticmethod
+ def create(name, description):
+ w = Wishlist()
+ w.name = name
+ w.description = description
+ w.save()
+ return w
- @staticmethod
- def create(name, description):
- w = Wishlist()
- w.name = name
- w.description = description
- w.save()
- return w
+class Item(models.Model):
- class Item(models.Model):
+ @staticmethod
+ def create(name, description, wishlist):
+ i = Item()
+ i.name = name
+ i.description = description
+ i.wishlist = wishlist
+ i.save()
+ return i
+----
- @staticmethod
- def create(name, description, wishlist):
- i = Item()
- i.name = name
- i.description = description
- i.wishlist = wishlist
- i.save()
- return i
+Mieux encore: on pourrait passer par un `ModelManager` pour limiter le couplage; l''accès à une information stockée en base de données ne se ferait dès lors qu'au travers de cette instance et pas directement au travers du modèle. De cette manière, on limite le couplage des classes et on centralise l'accès.
-Mieux encore: on pourrait passer par un ``ModelManager`` pour limiter le couplage; l''accès à une information stockée en base de données ne se ferait dès lors qu'au travers de cette instance et pas directement au travers du modèle. De cette manière, on limite le couplage des classes et on centralise l'accès.
+=== Relations
-Relations
-=========
+==== Types de relations
-Types de relations
-------------------
-
- * ForeignKey
- * ManyToManyField
- * OneToOneField
+* ForeignKey
+* ManyToManyField
+* OneToOneField
Dans les examples ci-dessus, nous avons vu les relations multiples (1-N), représentées par des **ForeignKey** d'une classe A vers une classe B. Il existe également les champs de type **ManyToManyField**, afin de représenter une relation N-N. Les champs de type **OneToOneField**, pour représenter une relation 1-1.
Dans notre modèle ci-dessus, nous n'avons jusqu'à présent eu besoin que des relations 1-N: la première entre les listes de souhaits et les souhaits; la seconde entre les souhaits et les parts.
-Mise en pratique
-----------------
+==== Mise en pratique
Dans le cas de nos listes et de leurs souhaits, on a la relation suivante:
-.. code-block:: python
+[source,python]
+----
+# wish/models.py
- # wish/models.py
-
- class Wishlist(models.Model):
- pass
+class Wishlist(models.Model):
+ pass
- class Item(models.Model):
- wishlist = models.ForeignKey(Wishlist)
+class Item(models.Model):
+ wishlist = models.ForeignKey(Wishlist)
+----
-Depuis le code, à partir de l'instance de la classe ``Item``, on peut donc accéder à la liste en appelant la propriété ``wishlist`` de notre instance. *A contrario*, depuis une instance de type ``Wishlist``, on peut accéder à tous les éléments liés grâce à ``_set``; ici ``item_set``.
+Depuis le code, à partir de l'instance de la classe `Item`, on peut donc accéder à la liste en appelant la propriété `wishlist` de notre instance. *A contrario*, depuis une instance de type `Wishlist`, on peut accéder à tous les éléments liés grâce à `_set`; ici `item_set`.
-Lorsque vous déclarez une relation 1-1, 1-N ou N-N entre deux classes, vous pouvez ajouter l'attribut ``related_name`` afin de nommer la relation inverse.
+Lorsque vous déclarez une relation 1-1, 1-N ou N-N entre deux classes, vous pouvez ajouter l'attribut `related_name` afin de nommer la relation inverse.
-.. code-block:: python
+[source,python]
+----
+# wish/models.py
- # wish/models.py
-
- class Wishlist(models.Model):
- pass
+class Wishlist(models.Model):
+ pass
- class Item(models.Model):
- wishlist = models.ForeignKey(Wishlist, related_name='items')
+class Item(models.Model):
+ wishlist = models.ForeignKey(Wishlist, related_name='items')
+----
A partir de maintenant, on peut accéder à nos propriétés de la manière suivante:
-.. code-block:: python
+[source,python]
+----
+# python manage.py shell
- # python manage.py shell
+>>> from wish.models import Wishlist, Item
+>>> w = Wishlist('Liste de test', 'description')
+>>> w = Wishlist.create('Liste de test', 'description')
+>>> i = Item.create('Element de test', 'description', w)
+>>>
+>>> i.wishlist
+
+>>>
+>>> w.items.all()
+[]
+----
- >>> from wish.models import Wishlist, Item
- >>> w = Wishlist('Liste de test', 'description')
- >>> w = Wishlist.create('Liste de test', 'description')
- >>> i = Item.create('Element de test', 'description', w)
- >>>
- >>> i.wishlist
-
- >>>
- >>> w.items.all()
- []
+Remarque: si, dans une classe A, plusieurs relations sont liées à une classe B, Django ne saura pas à quoi correspondra la relation inverse. Pour palier à ce problème et pour gagner en cohérence, on fixe alors une valeur à l'attribut `related_name`.
-Remarque: si, dans une classe A, plusieurs relations sont liées à une classe B, Django ne saura pas à quoi correspondra la relation inverse. Pour palier à ce problème et pour gagner en cohérence, on fixe alors une valeur à l'attribut ``related_name``.
-
-Querysets & managers
---------------------
-
- * http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm
- * https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.iterator
- * http://blog.etianen.com/blog/2013/06/08/django-querysets/
+=== Querysets & managers
+* http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm
+* https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.iterator
+* http://blog.etianen.com/blog/2013/06/08/django-querysets/
diff --git a/source/gwift/metamodel.adoc b/source/gwift/metamodel.adoc
index 092b99b..ce3acb1 100644
--- a/source/gwift/metamodel.adoc
+++ b/source/gwift/metamodel.adoc
@@ -1,9 +1,7 @@
-**********
-Métamodèle
-**********
+=== Métamodèle
-Sous ce titre franchement pompeux, on va un peu parler de la modélisation du modèle. Quand on prend une classe (par exemple, ``Wishlist`` que l'on a défini ci-dessus), on voit qu'elle hérite par défaut de ``models.Model``. On peut regarder les propriétés définies dans cette classe en analysant le fichier ``lib\site-packages\django\models\base.py``. On y voit notamment que ``models.Model`` hérite de ``ModelBase`` au travers de `six `_ pour la rétrocompatibilité vers Python 2.7.
+Sous ce titre franchement pompeux, on va un peu parler de la modélisation du modèle. Quand on prend une classe (par exemple, `Wishlist` que l'on a défini ci-dessus), on voit qu'elle hérite par défaut de `models.Model`. On peut regarder les propriétés définies dans cette classe en analysant le fichier `lib\site-packages\django\models\base.py`. On y voit notamment que `models.Model` hérite de `ModelBase` au travers de `six `_ pour la rétrocompatibilité vers Python 2.7.
-Cet héritage apporte notamment les fonctions ``save()``, ``clean()``, ``delete()``, ... Bref, toutes les méthodes qui font qu'une instance est sait **comment** interagir avec la base de données. La base d'un `ORM `_, en fait.
+Cet héritage apporte notamment les fonctions `save()`, `clean()`, `delete()`, ... Bref, toutes les méthodes qui font qu'une instance est sait **comment** interagir avec la base de données. La base d'un `ORM `_, en fait.
-D'autre part, chaque classe héritant de ``models.Model`` possède une propriété ``objects``. Comme on l'a vu dans la section **Jouons un peu avec la console**, cette propriété permet d'accéder aux objects persistants dans la base de données.
+D'autre part, chaque classe héritant de `models.Model` possède une propriété `objects`. Comme on l'a vu dans la section **Jouons un peu avec la console**, cette propriété permet d'accéder aux objects persistants dans la base de données.
diff --git a/source/gwift/refactoring.adoc b/source/gwift/refactoring.adoc
index 324f9bf..ff04665 100644
--- a/source/gwift/refactoring.adoc
+++ b/source/gwift/refactoring.adoc
@@ -1,157 +1,152 @@
-***********
-Refactoring
-***********
+== Refactoring
-On constate que plusieurs classes possèdent les mêmes 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:
+On constate que plusieurs classes possèdent les mêmes 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:
- * L'héritage par classe abstraite
- * L'héritage classique
- * 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.
-.. code-block:: python
+[source,python]
+----
+# wish/models.py
- # 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:
-.. code-block:: sql
+[source,sql]
+----
+--$ python manage.py sql wish
+BEGIN;
+CREATE TABLE "wish_wishlist" (
+ "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "created_at" datetime NOT NULL,
+ "updated_at" datetime NOT NULL
+)
+;
+CREATE TABLE "wish_item" (
+ "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "created_at" datetime NOT NULL,
+ "updated_at" datetime NOT NULL
+)
+;
+CREATE TABLE "wish_part" (
+ "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "created_at" datetime NOT NULL,
+ "updated_at" datetime NOT NULL
+)
+;
- --$ python manage.py sql wish
- BEGIN;
- CREATE TABLE "wish_wishlist" (
- "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- "created_at" datetime NOT NULL,
- "updated_at" datetime NOT NULL
- )
- ;
- CREATE TABLE "wish_item" (
- "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- "created_at" datetime NOT NULL,
- "updated_at" datetime NOT NULL
- )
- ;
- CREATE TABLE "wish_part" (
- "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- "created_at" datetime NOT NULL,
- "updated_at" datetime NOT NULL
- )
- ;
+COMMIT;
+----
- COMMIT;
-
-
-
-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:
- * Une table ``AbstractModel``, qui reprend les deux champs ``created_at`` et ``updated_at``
- * Une table ``Wishlist``
- * Une table ``Item``
- * Une table ``Part``.
+* Une table `AbstractModel`, qui reprend les deux champs `created_at` et `updated_at`
+* Une table `Wishlist`
+* Une table `Item`
+* Une table `Part`.
A nouveau, en analysant la sortie SQL de cette modélisation, on obtient ceci:
-.. code-block:: sql
+[source,sql]
+----
+--$ python manage.py sql wish
- --$ python manage.py sql wish
+BEGIN;
+CREATE TABLE "wish_abstractmodel" (
+ "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "created_at" datetime NOT NULL,
+ "updated_at" datetime NOT NULL
+)
+;
+CREATE TABLE "wish_wishlist" (
+ "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
+)
+;
+CREATE TABLE "wish_item" (
+ "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
+)
+;
+CREATE TABLE "wish_part" (
+ "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
+)
+;
- BEGIN;
- CREATE TABLE "wish_abstractmodel" (
- "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- "created_at" datetime NOT NULL,
- "updated_at" datetime NOT NULL
- )
- ;
- CREATE TABLE "wish_wishlist" (
- "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
- )
- ;
- CREATE TABLE "wish_item" (
- "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
- )
- ;
- CREATE TABLE "wish_part" (
- "abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
- )
- ;
-
- COMMIT;
+COMMIT;
+----
Le problème est que les identifiants seront définis et incrémentés au niveau de la table mère. 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. Il est parfois nécessaire de passer par cette modélisation, mais en étant conscient des risques inhérents.
-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éfinir les classes suivantes:
-.. code-block:: python
+[source,python]
+----
+# wish/models.py
- # wish/models.py
+class Wishlist(models.Model):
+ name = models.CharField(max_length=255)
+ description = models.CharField(max_length=2000)
+ expiration_date = models.DateField()
- class Wishlist(models.Model):
- name = models.CharField(max_length=255)
- description = models.CharField(max_length=2000)
- expiration_date = models.DateField()
+ @staticmethod
+ def create(self, name, description, expiration_date=None):
+ wishlist = Wishlist()
+ wishlist.name = name
+ wishlist.description = description
+ wishlist.expiration_date = expiration_date
+ wishlist.save()
+ return wishlist
- @staticmethod
- def create(self, name, description, expiration_date=None):
- wishlist = Wishlist()
- wishlist.name = name
- wishlist.description = description
- wishlist.expiration_date = expiration_date
- wishlist.save()
- return wishlist
+class ChristmasWishlist(Wishlist):
+ class Meta:
+ proxy = True
- class ChristmasWishlist(Wishlist):
- class Meta:
- proxy = True
-
- @staticmethod
- def create(self, name, description):
- christmas = datetime(current_year, 12, 31)
- w = Wishlist.create(name, description, christmas)
- w.save()
+ @staticmethod
+ def create(self, name, description):
+ christmas = datetime(current_year, 12, 31)
+ w = Wishlist.create(name, description, christmas)
+ w.save()
- class EasterWishlist(Wishlist):
- class Meta:
- proxy = True
-
- @staticmethod
- def create(self, name, description):
- expiration_date = datetime(current_year, 4, 1)
- w = Wishlist.create(name, description, expiration_date)
- w.save()
-
+class EasterWishlist(Wishlist):
+ class Meta:
+ proxy = True
+ @staticmethod
+ def create(self, name, description):
+ expiration_date = datetime(current_year, 4, 1)
+ w = Wishlist.create(name, description, expiration_date)
+ w.save()
+----
diff --git a/source/gwift/specs.adoc b/source/gwift/specs.adoc
index a4bc11f..4a0745e 100644
--- a/source/gwift/specs.adoc
+++ b/source/gwift/specs.adoc
@@ -1,17 +1,15 @@
-********************
-Besoins utilisateurs
-********************
+== 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 à la réalisation desquels ils souhaitent participer.
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
+* 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.
+* 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.
@@ -19,107 +17,91 @@ A chaque souhait, on pourrait de manière facultative ajouter un prix. Dans ce c
Un souhait pourrait aussi être réalisé plusieurs fois. Ceci revient à dupliquer le souhait en question.
-********************
-Besoins fonctionnels
-********************
+== Besoins fonctionnels
-Gestion des utilisateurs
-========================
+=== Gestion des utilisateurs
Pour gérer les utilisateurs, nous allons faire en sorte de surcharger ce que Django propose: par défaut, on a une la possibilité de gérer des utilisateurs (identifiés par une adresse email, un nom, un prénom, ...) mais sans plus.
Ce qu'on peut souhaiter, c'est que l'utilisateur puisse s'authentifier grâce à une plateforme connue (Facebook, Twitter, Google, etc.), et qu'il puisse un minimum gérer son profil.
-Gestion des listes
-==================
+=== Gestion des listes
-Modèlisation
-------------
+==== Modèlisation
Les données suivantes doivent être associées à une liste:
- * un identifiant
- * un identifiant externe (un GUID, par exemple)
- * un nom
- * une description
- * le propriétaire, associé à l'utilisateur qui l'aura créée
- * une date de création
- * une date de modification
+* un identifiant
+* un identifiant externe (un GUID, par exemple)
+* un nom
+* une description
+* le propriétaire, associé à l'utilisateur qui l'aura créée
+* une date de création
+* une date de modification
-Fonctionnalités
----------------
+==== 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
+* 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
-====================
+=== Gestion des souhaits
-Modélisation
-------------
+==== 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, afin de représenter l'objet ou l'idée
- * un nombre (1 par défaut)
- * un prix facultatif
- * un nombre de part, facultatif également, si un prix est fourni.
+* un identifiant
+* identifiant de la liste
+* un nom
+* une description
+* le propriétaire
+* une date de création
+* une date de modification
+* une image, afin de représenter l'objet ou l'idée
+* un nombre (1 par défaut)
+* un prix facultatif
+* un nombre de part, facultatif également, si un prix est fourni.
-Fonctionnalités
----------------
+==== 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érentes parts 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.
+* 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érentes parts 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
-====================================
+=== Gestion des réalisations de souhaits
-Modélisation
-------------
+==== 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
+* 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
----------------
+==== Fonctionnalités
- * L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie ou non. Il doit également avoir un pourcentage de complétion sur la possibilité de réalisation de son souhait, entre 0% et 100%.
- * L'utilisateur doit pouvoir voir la ou les personnes ayant réalisé 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é.
+* L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie ou non. Il doit également avoir un pourcentage de complétion sur la possibilité de réalisation de son souhait, entre 0% et 100%.
+* L'utilisateur doit pouvoir voir la ou les personnes ayant réalisé 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
-========================================================================
+=== Gestion des personnes réalisants les souhaits et qui ne sont pas connues
-Modélisation
-------------
+==== 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
----------------
+* un identifiant
+* un nom
+* une adresse email facultative
+==== Fonctionnalités
diff --git a/source/gwift/tests.adoc b/source/gwift/tests.adoc
index 1430b08..3e70cc5 100644
--- a/source/gwift/tests.adoc
+++ b/source/gwift/tests.adoc
@@ -1,13 +1,6 @@
-===============
-Tests unitaires
-===============
+== Tests unitaires
-*************
-Méthodologies
-*************
-
-Pourquoi s'ennuyer à écrire des tests?
-======================================
+=== Pourquoi s'ennuyer à écrire des tests?
Traduit grossièrement depuis un article sur `https://medium.com `_:
@@ -22,153 +15,151 @@ Traduit grossièrement depuis un article sur `https://medium.com `_
+* `Django factory boy `_
diff --git a/source/main.adoc b/source/main.adoc
index a3015ae..36c0493 100644
--- a/source/main.adoc
+++ b/source/main.adoc
@@ -86,7 +86,7 @@ include::django/admin.adoc[]
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`` :)).
+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.
diff --git a/source/main.html b/source/main.html
index e7a318a..9a15d9b 100644
--- a/source/main.html
+++ b/source/main.html
@@ -2,21 +2,22 @@
-
+
-
+
Minor swing with Django
\ No newline at end of file
diff --git a/source/toolchain/summary.adoc b/source/toolchain/summary.adoc
index 84daf7e..614980c 100644
--- a/source/toolchain/summary.adoc
+++ b/source/toolchain/summary.adoc
@@ -2,13 +2,8 @@
En résumé, la création d'un **nouveau** projet Django demande plus ou moins toujours les mêmes actions:
- 1. Configurer un environnement virtuel
- 1. Installer les dépendances et les ajouter dans le fichier ``requirements.txt``
- 2. Configurer le fichier ``settings.py``
+. Configurer un environnement virtuel
+. Installer les dépendances et les ajouter dans le fichier ``requirements.txt``
+. Configurer le fichier ``settings.py``
-
-. Cookie-cutter
-
-C'est ici que le projet `Cookie-Cutter `_ va être intéressant: les X premières étapes peuvent être *bypassées* par une simple commande.
-
- * `Cookiecutter-Django `_
+C'est ici que le projet http://cookiecutter.readthedocs.io/en/latest/readme.html[CookieCutter] va être intéressant: les X premières étapes peuvent être *bypassées* par une simple commande.