public:appro-s7:td_web_hamsters

Ceci est une ancienne révision du document !


En vous basant sur l'ensemble des TD précédents, vous devez à présent programmer un site web en Django qui reproduit les fonctionnalités de l'animalerie vue dans le cadre du modèle MVC. Attention, vous devez cette fois-ci personnaliser le modèle de données pour l'adapter à un univers de votre choix.

Exemples d'univers :

  • Jeu de rôle
  • Tamagotchi
  • Pet shop
  • Pocket Monster
  • Aquarium
  • Elevage d'escargots
  • Course hippique
  • Equipe de foot
  • Athlètes
  • etc.

Vous pouvez bien sûr conserver la structure de base qui est d'avoir un ensemble de créatures et/ou de personnages, et un ensemble de lieux avec des fonctions différentes (par exemple, pour un centre d'entrainement : terrain de foot, cantine, salle de muscu, dortoir). Il peut y avoir des variantes : Pour une maison des poupées il peut y avoir plusieurs chambres, une pièce commune, un jardin et une terrasse. Laissez libre cours à votre imagination.

Les créatures passent par différents états au cours de la journée en fonction des lieux qu'ils visitent.

Rappel

Reprenez cette structure de données de type AnimalEquipement utilisée dans les TD précédents

Créez un projet 'petland' et reprenez toutes les étapes 1 à 15 du tutoriel précédent avec le modèle suivant, en remplaçcant "Animal" par "Character" pour être plus général.

model.py

from django.db import models
 
class Equipement(models.Model):
    id_equip = models.CharField(max_length=100, primary_key=True)
    disponibilite = models.CharField(max_length=20)
    photo = models.CharField(max_length=200)
    def __str__(self):
        return self.id_equip
 
 
class Character(models.Model):
    id_char = models.CharField(max_length=100, primary_key=True)
    etat = models.CharField(max_length=20)
    type = models.CharField(max_length=20)
    race = models.CharField(max_length=20)
    photo = models.CharField(max_length=200)
    lieu = models.ForeignKey(Equipement, on_delete=models.CASCADE)
    def __str__(self):
        return self.id_animal

Vous deviez obtenir une interface simple de ce type:

Il est bien sûr possible d'améliorer les choses en utilisant des fonctions de mise en page plus évoluées

La mise en page du site repose sur l'utilisation de fichiers de style (css) ainsi que de composants javascript de type :

Vous pouvez exploiter les photos qui sont définies dans les attributs du modèle pour obtenir par exemple:

(repris du tutoriel django_forms)

La dernière chose que nous voulons faire sur notre site web, c'est créer une manière de mettre a jour l'etat des animaux. Les formulaires (forms) vont nous permettre de rendre le site dynamique !

Comme toutes les choses importantes dans Django, les formulaires ont leur propre fichier : forms.py.

Nous allons devoir créer un fichier avec ce nom dans notre dossier blog.

petland
    └── forms.py

Ouvrez maintenant ce fichier dans l'éditeur de code et tapez le code suivant :

from django import forms
 
from .models import Animal
 
class MoveForm(forms.ModelForm):
 
    class Meta:
        model = Animal
        fields = ('lieu',)

<p>Tout d&apos;abord, nous avons besoin d&apos;importer les formulaires Django (<code>from django import forms</code>), puis notre modèle <code>Animal</code> (<code>from .models import Animal</code>).</p> <p>Comme vous l&apos;avez probablement deviné, <code>MoveForm</code> est le nom de notre formulaire. Nous avons besoin d&apos;indiquer à Django que ce formulaire est un <code>ModelForm</code> (pour que Django fasse certaines choses automatiquement pour nous). Pour cela, nous utilisons <code>forms.ModelForm</code>.</p> <p>Ensuite, nous avons la <code>class Meta</code> qui nous permet de dire à Django quel modèle il doit utiliser pour créer ce formulaire (<code>model = Animal</code>).</p> <p>Enfin, nous précisions quel⋅s sont le⋅s champ⋅s qui doivent figurer dans notre formulaire. Dans notre cas, nous souhaitons que seul le <code>lieu</code> apparaisse dans notre formulaire. </p> <p>Et voilà, c&apos;est tout ! Tout ce qu&apos;il nous reste à faire, c&apos;est d&apos;utiliser ce formulaire dans une <em>vue</em> et de l&apos;afficher dans un template.</p> <p>Nous allons donc une nouvelle fois suivre le processus suivant et créer : un lien vers la page, une URL, une vue et un template.</p>

Il est temps d'ouvrir petland/templates/petland/base.html dans l'éditeur de code et ajouter un lien vers la vue animal_detail.

<a href="{% url 'animal_detail' id_animal=animal.id_animal %}">{{ animal.id_animal }}</a>

Ouvrez le fichier

petland/urls.py

dans l'éditeur de code et mettez ceci:

from django.urls import path
from . import views
 
urlpatterns = [
    path('', views.animal_list, name='animal_list'),
    path('animal/<str:id_animal>/', views.animal_detail, name='animal_detail'),
    path('animal/<str:id_animal>/?<str:message>', views.animal_detail, name='animal_detail_mes'),
]

<p>Ouvrez maintenant le fichier <code>petland/views.py</code> dans l&apos;éditeur de code et ajoutez les lignes suivantes avec celles du <code>from</code> qui existent déjà :</p>

from django.shortcuts import render, get_object_or_404, redirect
from .forms import MoveForm
from .models import Animal, Equipement
 
# Create your views here.
def animal_list(request):
    animals = Animal.objects.filter()
    return render(request, 'petland/pet_list.html', {'animals': animals})
 
def animal_detail(request, id_animal):
    animal = get_object_or_404(Animal, id_animal=id_animal)
    lieu = animal.lieu
    form=MoveForm()
    return render(request,
                  'petland/animal_detail.html',
                  {'animal': animal, 'lieu': lieu, 'form': form})

  <p>Afin de pouvoir créer un nouveau formulaire <code>Move</code>, nous avons besoin d&apos;appeler la fonction <code>MoveForm()</code> et de la passer au template. Nous reviendrons modifier cette <em>vue</em> plus tard, mais pour l&apos;instant, créons rapidement un template pour ce formulaire.</p>

<p>Nous avons à présent besoin de créer un fichier <code>animal_detail.html</code> dans le dossier <code>petland/templates/animalerie</code> et de l&apos;ouvrir dans l&apos;éditeur de code. Afin que notre formulaire fonctionne, nous avons besoin de plusieurs choses :</p> <ul> <li>Nous avons besoin d&apos;afficher le formulaire. Pour cela, nous n&apos;avons qu&apos;à utiliser <code>{{ form.as_uk }}</code>.</li> <li>La ligne précédente va avoir besoin d&apos;être entourée des balises HTML <code>&lt;form method=&quot;POST&quot;&gt;...&lt;/form&gt;</code>.</li> <li>Nous avons besoin d&apos;un bouton <code>Valider</code>. Nous allons le créer à l&apos;aide d&apos;un bouton HTML : <code>&lt;button type=&quot;submit&quot;&gt;Valider&lt;/button&gt;</code>.</li> <li>Enfin, nous devons ajouter <code>{% csrf_token %}</code> juste après <code>&lt;form ...&gt;</code>. C&apos;est très important car c&apos;est ce qui va permettre de sécuriser votre formulaire ! Si vous oubliez ce détail, Django se plaindra lorsque vous essaierez de sauvegarder le formulaire:</li> </ul> <p><img src="images/csrf2.png" alt="CSFR Forbidden page"></p> <p>Ok, voyons maintenant à quoi devrait ressembler le HTML contenu dans le fichier <code>animal_detail.html</code> :

{% extends 'animalerie/base.html' %}
{% load static %}
{% block content %}
<div class="page-header">
    <h1>
        <a href="/">{{ animal.id_animal }}</a>
    </h1>
    <form method="POST" class="post-form">{% csrf_token %}
        <b> Changer : </b> {{ form.as_ul }}
        <button type="submit" class="btn btn-outline-light">OK</button>
        <a href="{% url 'animal_list' %}">Back</a>
    </form>
</div>
 
 
{% endblock %}

<p>Rafraîchissons la page ! Et voilà : le formulaire s&apos;affiche sous la forme d'une liste d'options!</p> <p>Mais attendez une minute! Lorsque vous sélectionnez une option, que se passera-t-il?</p> Rien! Retournons à notre <em>vue</em>.</p>

<p>Ouvrez à nouveau <code>blog/views.py</code> dans l&apos;éditeur de code. Actuellement, <code>post_new</code> n&apos;est composé que des lignes de code suivantes :</p>

def animal_detail(request, id_animal):
    animal = get_object_or_404(Animal, id_animal=id_animal)
    form=MoveForm()
    return render(request,
                  'animalerie/animal_detail.html',
                  {'animal': animal, 'lieu': lieu, 'form': form})

<p>Lorsque nous envoyons notre formulaire, nous revenons à la même vue. Cependant, nous récupérons les données dans <code>request</code>, et plus particulièrement dans <code>request.POST</code>. Vous rappelez-vous comment dans le fichier HTML, notre définition de la variable <code>&lt;form&gt;</code> avait la méthode <code>method=&quot;POST&quot;</code>? Tous les champs du formulaire se trouvent maintenant dans <code>request.POST</code>. Veillez à ne pas renommer <code>POST</code> en quoi que ce soit d&apos;autre : la seule autre valeur autorisée pour <code>method</code> est <code>GET</code>. Malheureusement, nous n&apos;avons pas le temps de rentrer dans les détails aujourd&apos;hui.</p> <p>Donc dans notre <em>vue</em> nous avons deux situations différentes à gérer : la première quand on accède à la page pour la première fois et nous voulons un formulaire vide, et la seconde quand on revient à la <em>vue</em> avec les données que l&apos;on a saisies dans le formulaire. Pour gérer ces deux cas, nous allons utiliser une condition

    if request.method == "POST":
        [...]
    else:
        form = MoveForm()

<p>Il faut maintenant remplir à l&apos;endroit des pointillés <code>[...]</code>. Si <code>method</code> contient <code>POST</code> alors on veut construire le <code>MoveForm</code> avec les données du formulaire, n&apos;est-ce pas ? Nous allons le faire comme cela :

    form = MoveForm(request.POST, instance=animal)

<p>La prochaine étape est de vérifier que le formulaire a été rempli correctement (tous les champs obligatoires ont été remplis et aucune valeur incorrecte n&apos;a été envoyée). Nous allons faire ça en utilisant <code>form.is_valid()</code>.</p> <p>Testons donc si notre formulaire est valide et, si c&apos;est le cas, sauvegardons-le !</p>

    if form.is_valid():
        ancien_lieu = get_object_or_404(Equipement, id_equip=animal.lieu.id_equip)
        ancien_lieu.disponibilite = "libre"
        ancien_lieu.save()
        form.save()
        nouveau_lieu = get_object_or_404(Equipement, id_equip=animal.lieu.id_equip)
        nouveau_lieu.disponibilite = "occupé"
        nouveau_lieu.save()

<p>En gros, nous effectuons deux choses ici : nous sauvegardons le nouvel état de l'animal grâce à <code>form.save</code> et nous mettons à jour l'occupation des lieux. Rappelez vous, tout déplacement d'animal s'acccompagne d'un changement d'occupation. Nous devons également modifier les lieux. <code>ancien_lieu.save()</code> et <code>nouveau_lieu.save()</code> sauvegarderont les changements. Et voilà, la mise à jour est enregistrée !</p> <p>Enfin, ce serait génial si nous pouvions tout de suite aller à la page <code>animal_detail</code> avec le contenu que nous venons de créer. Pour cela, nous avons besoin d&apos;importer une dernière chose :   <p>Maintenant, nous allons ajouter la ligne qui signifie &quot;aller à la page <code>animal_detail</code> pour le changement qui vient d'être enregistré</p>

    return redirect('animal_detail', id_animal=id_animal)

<p><code>animal_detail</code> est le nom de la vue où nous voulons aller.   <p>Voyons à quoi ressemble maintenant notre <em>vue</em> ?</p>

def animal_detail(request, id_animal):
    animal = get_object_or_404(Animal, id_animal=id_animal)
    form=MoveForm()
    if form.is_valid():
        ancien_lieu = get_object_or_404(Equipement, id_equip=animal.lieu.id_equip)
        ancien_lieu.disponibilite = "libre"
        ancien_lieu.save()
        form.save()
        nouveau_lieu = get_object_or_404(Equipement, id_equip=animal.lieu.id_equip)
        nouveau_lieu.disponibilite = "occupé"
        nouveau_lieu.save()
        return redirect('animal_detail', id_animal=id_animal)
    else:
        form = MoveForm()
        return render(request,
                  'animalerie/animal_detail.html',
                  {'animal': animal, 'lieu': lieu, 'form': form})

<p>Voyons si ça marche. Allez à l&apos;adresse <a href="http://127.0.0.1:8000/animal/Tic/" target="_blank">http://127.0.0.1:8000/animal/Tic/</a>, selectionnez un nouveau lieu, sauvegardez ... et voilà ! La mise a jour est prise en compte !</p>

Il ne reste plus qu'à raffiner le modèle afin qu'il corresponde aux contraintes définies en début d'énoncé.

  • Si le lieu de destination n'est pas libre, alors le changement ne doit pas être accepté. Il ne faut donc pas sauvegarder l'état de l'animal immédiatement. Cela est possible grâce à l'option :
    form.save(commit=False) 
  • Avec cette option, le lieu est mis à jour dans l'objet animal mais pas dans la base de données! Il est alors possible de tester si le nouveau lieu est bien libre en regardant si animal.lieu.disponibilite est "libre".
  • Si c'est bon, alors effectuez les changements et n'oubliez pas de sauvegarder avec animal.save().
  • Si ce n'est pas le cas, alors les changements ne doivent pas être enregistrés.
  • N'oubliez pas non plus: le changement de lieu a pour conséquence un changement d'état, qui passe par exemple d'"affamé" à "repus" lorsqu'on le déplace sur la mangeoire. Reprenez toutes les conditions et tenez en compte pour la mise à jour de l'animal.
  • Il est bien sûr possible de reprendre en l'adaptant la structure de contrôleur vue au TD1.

Lorsqu'un changement n'est pas autorisé, il est préférable d'afficher un message d'avertissement. Le message transmis comme paramètre au template animal_detail.html. Le cadre sera activé uniquement si le message est non vide. Pensez à modifier les paramètres d'appel du template dans views.py.

    {% if message != ''%}
        <div class="alert alert-danger" role="alert">
            {{message}}
        </div>
    {% endif %}

<p>Nos modifications fonctionnent-elles sur PythonAnywhere ? Pour le savoir, déployons à nouveau !</p> <ul> <li>Tout d&apos;abord, commitez votre nouveau code et pushez le à nouveau sur GitHub:</li> </ul> <p></p><p class="code-label">command-line</p><p></p> <pre><code>$ git status $ git add . $ git status $ git commit -m &quot;Added views to create/edit blog post inside the site.&quot; $ git pus </code></pre><ul> <li>Puis, dans la console bash de <a href="https://www.pythonanywhere.com/consoles/" target="_blank">PythonAnywhere</a>:</li> </ul> <p></p><p class="code-label">Ligne de commande PythonAnywhere</p><p></p> <pre><code>$ cd ~/&lt;your-pythonanywhere-domain&gt;.pythonanywhere.com $ git pull [...] </code></pre><p>(N’oubliez pas de remplacer <code>&lt;your-pythonanywhere-domain&gt;</code> avec votre propre sous-domaine PythonAnywhere, sans les chevrons.)</p> <ul> <li>Enfin, allez sur <a href="https://www.pythonanywhere.com/web_app_setup/" target="_blank">&quot;Web&quot; page</a> (utilisez le bouton de menu en haut à droite de la console) et cliquez <strong>Reload</strong>. Actualisez votre blog <a href="https://subdomain.pythonanywhere.com" target="_blank">https://subdomain.pythonanywhere.com</a> pour voir les changements.</li> </ul> <p>Et normalement c&apos;est tout ! Félicitations ! :)</p>     </section>

  • public/appro-s7/td_web_hamsters.1699110737.txt.gz
  • Dernière modification : 2023/11/04 16:12
  • de edauce