La Grille De L'Amour

Ce wiki a été archivé en 2018.

Le nouveau wiki se trouve à: ressources.labomedia.org

Les fonctionnalités sont désactivées: vous pouvez faire une recherche sur Google site:https://wiki.labomedia.org et découvrir La Labomedia.

De Centre de Ressources Numériques - Labomedia
Aller à : navigation, rechercher

La grille de l'Amour est une application web qui permet de créer une œuvre collective (sous la forme d'une grille (pleine d'Amour)).

Elle est visible ici.

Initialement développée sous le framework python Karrigel, elle est en train d'être portée sous Django, dont cette page fait l'objet.

Ce n'est pas un tutoriel mais une présentation du code dans un but informatif plus que pédagogique.

Sources actuelles (projet non terminé) publiées sous les termes de la licence GPL v3.0. Vous pouvez donc les réutiliser, mofidier et diffuser à loisir :

Structure générale

Le projet s'appelle grille_de_l_amour est n'est constitué que d'une seule application, nommée appli.

urls.py

Toutes les pages sont générées à partir du fichier views.py de appli. L' urls.py du projet est donc très simple et renvoie toutes les requêtes vers l' urls.py d' appli.

#-*- coding: utf-8 -*-

from django.conf.urls import patterns, include, url

urlpatterns = patterns('',
    
    url(r'', include('grille_de_l_amour.appli.urls')),

)

base.html

Django permet d'utiliser un template de template (on parle alors d'héritage). C'est à dire un modèle sur lequel se basera les autres templates HTML du projet.

Dans notre cas, ce template permet, entre autre, de définir un menu commun à toutes les pages.

<!DOCTYPE html>
<html lang="fr">

    <head>

        <meta charset="utf-8" />
        <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}grille.css" />
        <link rel="shortcut icon" type="image/x-icon" href="{{ MEDIA_URL }}anima.gif" />
        <title>{% block title %}{% endblock %}</title>
        {% block script %}{% endblock %}

    </head>

    <body>

        <div id="menu">
            <table>
                <tr>
                    <td>
                        <a href="/grille">La grille</a>
                    </td>
                    <td>
                        <a href="/matrice-L">La matrice L</a>
                    </td>
                    <td>
                        <a href="/matrice-XL">La matrice XL</a>
                    </td>
                    <td>
                        <a href="/matrice-XXL">La matrice XXL</a>
                    </td>
                    <td>
                        <a href="/carte">La carte</a>
                    </td>
                    <td>
                        <a href="/film">Le film</a>
                    </td>
                    <td>
                        <a href="http://wiki.labomedia.org/index.php/La_Grille_De_L%27Amour">Les sources</a>
                    </td>
                </tr>
            </table>
        </div>
        <div id="content">
            {% block contenu %}
            {% endblock %}
        </div>

    </body>

</html>

Pour l'utiliser nous écrirons {% extends "base.html" %} en début de chaque templates.

L'Application

Elle se nomme appli.

urls.py

#-*- coding: utf-8 -*-


from django.conf.urls import patterns, include, url

urlpatterns = patterns('grille_de_l_amour.appli.views',

    # Permet de récupérer la couleur choisie ainsi que le numéro de la case cliquée
    url(r'^grille/(?P<couleur>[0-9]{1})/(?P<num_case>[0-9]{,3})/?$', 'modif_pixel'),
    # Permet de récupérer la couleur choisie et de passer l'instruction de sauvegarde de l'image
    url(r'^grille/(?P<couleur>[01234567])/save/?$', 'ajout_image'),
    # Le 'name' nous permet de définir /grille comme l'url par défaut
    url(r'grille/?$', 'page_grille', name="accueil_grille"),

    # Permet de récupérer le niveau de zoom, la latitude et la longitude 
    # tels que proposés par OSM dans ses url : http://www.openstreetmap.org
    url(r'^carte/(?P<niv_zoom>[0-9]{1,2})/(?P<latitude>[-]?[0-9]{1,3}([.][0-9]{1,6})?)/(?P<longitude>[-]?[0-9]{1,3}([.][0-9]{1,6})?)/?$', 'page_carte'),
    url(r'^carte/?$', 'origine'),

    url(r'^matrice-L/?$', 'page_matrice_30'),
    url(r'^matrice-XL/?$', 'page_matrice_69'),
    url(r'^matrice-XXL/?$', 'page_matrice_100'),
    url(r'^film/?$', 'page_film'),

    url(r'', 'accueil'),

)

Concernant la carte

La version précédente de La grille de l'Amour ne permettait pas à l'utilisateur de choisir la carte affichée.

C'est désormais possible avec la nouvelle version qui permet d'exploiter le niveau de zoom ainsi que les latitudes et longitudes tels que fournis par Open Street Map dans son URL. Il suffit de copier/coller cette partie du chemin dans l'url.

Pour tester un chemin, Django utilise des expressions rationnelles.

Exemples de chemins possibles dans OSM

  • /4/44.43/11.73 pour un niveau de zoom qui vaut 4, une latitude de 44.43 et une longitude de 11.73
  • /18/47.90072/1.91409 pour un niveau de zoom qui vaut 18, une latitude de 47.90072 et une longitude de 1.91409
  • /6/10/5 pour un niveau de zoom qui vaut 6, une latitude de 10 et une longitude de 5

Explications du RegEx utilisé

  • ^/ le premier caractère est un /
  • (?P<niv_zoom>[0-9]{1,2})
    • ?P<niv_zoom> la variable récupérée aura pour nom niv_zoom
    • [0-9]{1,2} la variable sera composée de 1 ou 2 éléments et uniquement de chiffre.
  • / les différentes variables sont séparées par des /
  • (?P<latitude>[-]?[0-9]{1,3}([.][0-9]{1,6})?)
    • ?P<latitude> la variable récupérée aura pour nom latitude
    • [-]? le premier élément est optionnel mais peut être un -
    • [0-9]{1,3} cette partie de la variable est composé de 1 à 3 éléments et uniquement de chiffre.
    • ([.][0-9]{1,6})? cette partie de la variable est optionnelle...
      • [.] ...mais commence forcément par un point (sans les crochets, un point signifie "n'importe quel caractère")...
      • [0-9]{1,6} ...suivi de 1 à 6 chiffres.
  • / les différentes variables sont séparées par des /
  • (?P<longitude>[-]?[0-9]{1,3}([.][0-9]{1,6})?) même règle que la variable précédente mais avec longitude pour nom
  • /?$' le dernier caractère est optionnel mais peut être un /

view.py

#-*- coding: utf-8 -*-

import sqlite3
import random
import math

from django.shortcuts import render, redirect
from grille_de_l_amour.appli.models import Pixel, NombreImages
from ajout import ajout


# Respecte l'orde des couleurs telles qu'affichées à l'écran dans la palette
TAB_COULEUR = ['000000', 'ffffff', 'ff0000', '0000ff', '00ff00', 'ffff00', 'ff00ff', '00ffff' ]


#########################################
# Partie gérant l'affichage de la carte #
#########################################


def deg2num(lat_deg, lon_deg, zoom=16):
    """
        Formule qui permet d'obtenir le nom y/x de la tuile en fonction de ses coordonnées
        Récupérée ici : https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
    """

    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)

    return (xtile, ytile)

def origine(request):
    """
        url proposée par défaut
    """

    # Orléans 
    return redirect (page_carte, niv_zoom=14, latitude=47.9033, longitude=1.9149)

    # Nantes
    #return redirect (page_carte, niv_zoom=14, latitude=47.2138, longitude=-1.5562)


def page_carte(request, niv_zoom, latitude, longitude):
    """
        construit la portion de html constituée des <img> relatives aux tuiles
        et l'envoie au template de la carte (qui ne l'échappera pas)
    """

    # Correspondance entre les couleurs les les skins de CloudMade 
    # disponibles ici: http://maps.cloudmade.com/editor
    # Respecte l'orde des couleurs telles qu'affichées à l'écran dans la palette

    TAB_PALETTE = [
                '16340',        # noir
                '45499',        # blanc
                '33602',        # rouge 
                '36041',        # bleu
                '7543',         # vert 
                '39461',        # jaune
                '7134',         # violet
                '20538'         # turquoise
                ]

    LETTRE = ['a', 'b', 'c']

    #  ajustement nécéssaire entre le passage de 256*256 à 64*64
    zoom = int(niv_zoom) + 2 
    coord = deg2num(float(latitude), float(longitude), zoom)

    # pour recentrer
    y = coord[1] - 4
 
    top = 0
    cpt = 0
    grille = ''

    for i in range(10):

        # pour recentrer
        x = coord[0] - 7
 
        left = 0

        for j in range(13):

            for pixel in Pixel.objects.filter(nb = cpt):

                couleur_pixel = int(pixel.couleur)

            ligne = '<img class="tuile" style="left: {0}px; top: {1}px;" '.format(left, top)
            ligne += 'src="http://{0}.tile.cloudmade.com/75b297222833420cb33b584f6f2d24c8/'.format(LETTRE[random.randint(0, 2)])
            ligne += '{0}/64/{1}/{2}/{3}.png">\n'.format(TAB_PALETTE[couleur_pixel], zoom, x, y)

            grille += ligne
            x += 1
            left += 64
            cpt += 1

        y += 1
        top += 64

    return render(request, 'tpl_carte.html', {'liste_img': grille})


#######################################
# Partie gérant l'interface de dessin #
#######################################


def page_grille(request, selcoul=0):

    cpt = 0
    grille = ''
    nb_total = NombreImages.objects.get(nom='total').valeur

    for i in range(10):

        grille += """               <tr>"""

        for j in range(13):

            for pixel in Pixel.objects.filter(nb = cpt):

                couleur_pixel = TAB_COULEUR[int(pixel.couleur)]
                grille += '<td style="background:#{0}"></td>'.format(couleur_pixel)

            cpt += 1

        grille += '</tr>\n'

    return render(request, 'tpl_grille.html', {'tab_grille': grille, 'selcoul': selcoul, 'nb_total': nb_total})


def modif_pixel(request, couleur, num_case):

    selcoul = int(couleur)
    nb_case = int(num_case)

    if ((nb_case > 129) or (selcoul > 7)):

        return redirect('accueil_grille')

    for pixel in Pixel.objects.filter(nb = int(num_case)):
        pixel.couleur = selcoul
        pixel.save()

    return page_grille (request, selcoul)


def ajout_image(request, couleur):

    nb = ajout()

    return page_grille (request, couleur)


###########################################
# Partie gérant l'affichade de la matrice #
###########################################


def page_matrice(request, nb_par_ligne):
    """
        Construit la liste d'images à afficher en dessous de matrice.png
        quand la dernière ligne n'est pas complète.

    """

    liste_img = ''
    nb_total = int(NombreImages.objects.get(nom='total').valeur)
    nb_lignes = nb_total/nb_par_ligne
    nb_images_rest = nb_total%nb_par_ligne

    left = 0
    top = nb_lignes*10

    name = 8999999999 - nb_total + nb_images_rest

    for i in range(nb_images_rest): 

            ligne = '''<img class="vignette" style="left: {0}px; top: {1}px;" src="/media/images/grille-{2}.png">\n'''.format(left, top, name)
            liste_img += ligne

            left += 13 # décale de la largeur d'une image
            name -= 1

    return render(request, 'tpl_matrice.html', {'liste_img': liste_img, 'categorie_matrice':"matrice{0}".format(nb_par_ligne)})

def page_matrice_30(request):

    return page_matrice (request, 30)

def page_matrice_69(request):
    
    return page_matrice (request, 69)

def page_matrice_100(request):

    return page_matrice (request, 100)


####################################################
# Partie gérant la création et l'affichage du film #
####################################################


def page_film(request):

    nom_film_temp = random.randint(0, 10000)

    #os.chdir("./grille_de_l_amour/static")
    #os.system('''mencoder "mf://grille_de_l_amour/static/images/*.png" -fps 7 -o ./grille_de_l_amour/static/film-{0}.avi -ovc lavc -lavcopts vcodec=mjpeg'''.format(nom_film_temp))
    #os.system('mencoder -oac copy -ovc copy -idx -o film.avi film-{0}.avi film-old.avi'.format(nom_film_temp))
    #os.system('ffmpeg -i film.avi -b 1024000 -f flv -y film.flv')
    #os.system('rm film-{0}.avi'.format(nom_film_temp))

    return render(request, 'tpl_film.html')


########################
# Partie gérant le 404 #
########################


def accueil(request):

    return redirect('accueil_grille')

Concernant la carte

Les tuiles utilisées sont produites par CloudMade, une société qui s'appuie sur les données d'OpenStreetMap.

Pour pouvoir se servir de ces tuiles, il est nécessaire d'avoir une clef API de la librairie Leaflet. Un simple enregistrement en tant que dev permet de l'obtenir.

Googlemap étant passé par là, la "norme" pour la taille d'une tuile est 256*256, ce qui est trop grand pour l'usage que nous voulons en avoir. Heureusement, Leaflet propose aussi des tuiles de 64*64 pour les ordinateurs téléphoniques. Ce sont ces tuiles que nous utilisons.

La structure d'une URL pour afficher une tuile de CloudMade est la suivante :

<img src="http://{0}.tile.cloudmade.com/{1}/{2}/{3}/{4}/{5}/{6}.png">
  • {0} : Correspond au serveur de CloudMade qui va traiter la requète. C'est soit 'a', 'b' ou 'c'. À choisir aléatoirement pour répartir la charge.
  • {1} : C'est la clef API. Ce sera donc la même pour toutes les images.
  • {2} : C'est le n° du skin qui va être utilisé pour générer la tuile. Ce n° s'obtient avec l'éditeur de skin de CloudMade.
  • {3} : C'est la taille de la tuile. Généralement c'est 256. Mais nous utilisons des tuiles plus petites pour pouvoir en afficher plus. Ce sera donc 64. À noter que 256 et 64 sont les deux seules valeurs possibles.
  • {4} : X : Ce nombre est déterminé à partir de la longitude de la tuile et du niveau de zoom.
  • {5} : Y : Ce nombre est déterminé à partir de la latitude de la tuile et du niveau de zoom.

Le couple X/Y.png correspond au nom de la tuile.

L'algorithme (+ codes), pour le déterminer est fourni par Open Street Map ici. Le code python est celui là :

def deg2num(lat_deg, lon_deg, zoom):

    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)

    return (xtile, ytile)

Or, ce code est valable pour des tuiles des 256 pixels de côté. On constate alors qu'utiliser des tuiles de 64 pixels de côtés (256/2/2), revient à avancer de deux niveaux de zoom par rapport aux nombres totales de tuiles possibles. Et effectivement, en essayant l'ago avec un niveau de zoom auquel on ajoute "2", à latitude et longitude identiques, on obtient une correspondance entre :

  • les tuiles de CloudMade de 64*64
  • et ce qu'affiche OpenStreetMap avec des tuiles 256*256 et le niveau de zoom initial

(et on a été bien content à ce moment là)

Pour afficher les tuiles collées les une aux autres, nous avons conservé la méthode d'OpenStreetMap qui leur attribue à chacune un left et un top de placement avec une position: absolute; dans une div centrée.

Concernant la grille

Afin de gérer la création de chaque images, ainsi que de la matrice qui les contient toutes, nous avons écrit une fonction ajout que, par souci de clarté, nous avons placé dans un autre fichier, ajout.py. D'où son import.

ajout.py

Le fichier ajout.py fait appel à la librairie PIL (Python Image Library)(doc ici):

# -*- coding: utf-8 -*-

from grille_de_l_amour.appli.models import Pixel, NombreImages
from django.conf import settings

import Image
import sqlite3
import os

chemin_media = settings.MEDIA_ROOT

# Respecte l'orde des couleurs telles qu'affichées à l'écran dans la palette
TAB_COULEUR = ['000000', 'ffffff', 'ff0000', '0000ff', '00ff00', 'ffff00', 'ff00ff', '00ffff' ]

# Bien garder à l'esprit que le nommage des images respecte une nomemclature DÉCRÉMENTALE
# La première image étant nommée 'grille-8999999999.png' 
# la suivante 'grille-8999999998.png' et ainsi de suite

def ajout():

    # Cette partie construit une image png de 13*10 avec 
    # les informations de la base de données de la grille
    # et la sauvegrade dans le dossier static/images
    
    obj_temp = NombreImages.objects.get(nom='total')
    nb_images = int(obj_temp.valeur)

    export = Image.new('RGB', (13, 10))

    for i in range(130):

            for pixel in Pixel.objects.filter(nb = i):

                couleur_pixel_hex = TAB_COULEUR[int(pixel.couleur)]
                couleur_pixel_dec = int(couleur_pixel_hex, 16)

                rouge = (couleur_pixel_dec/256)/256
                vert = (couleur_pixel_dec/256)%256
                bleu = couleur_pixel_dec%256
                ligne = i/13
                colonne = i%13
                export.putpixel((colonne, ligne), (rouge, vert, bleu))
   
    numero_pix = 9000000000-nb_images-1   
    export.save(settings.MEDIA_ROOT + 'images/grille-{0}.png'.format(numero_pix))
    
    nb_images += 1

    obj_temp.valeur = nb_images
    obj_temp.save()



    # Cette partie ajoute une ligne à matrice.png
    # quand il y a 30, 69 ou 100 nouvelles images

    liste_categorie = [30, 69, 100]

    for nb_image_par_ligne in liste_categorie :

        nb_lignes = nb_images/nb_image_par_ligne 
        nb_images_rest = nb_images%nb_image_par_ligne

        if (nb_images_rest == 0):

            # Récupère la matrice en cours ainsi que ses hauteur et largeur.
            matrice = Image.open(chemin_media + 'matrice{0}.png'.format(str(nb_image_par_ligne)))
            width = matrice.size[0]
            height = matrice.size[1]

            # Crée une image vide plus grande d'une ligne (10px) que la matrice précédente
            # et copie la matrice dedans.
            temp = Image.new('RGB', (width, height+10))
            temp.paste(matrice, (0, 0, width, height))

            # L'ordonnée des images de la nouvelle ligne correspond 
            # à la hauteur de la matrice précedente puisqu'elles se
            # place juste en dessous.
            y = height
            x = 0

            for i in range(nb_image_par_ligne-1, -1, -1):

                # Compte, à rebourd, en partant de 'nb_image_par_ligne-1' jusqu'à 0
                # et ajoute cette valeur à numero_pix qui est
                # la dernière image enregistrée 
                # (s'explique par le choix de la nomenclature)             

                vignette = Image.open(chemin_media + 'images/grille-{0}.png'.format(numero_pix + i))

                # Ajoute l'image 
                temp.paste(vignette, (x, y, x+13, y+10))
                
                x += 13 # décale de la largeur d'une vignette pour la suivante

            temp.save(chemin_media + 'matrice{0}.png'.format(str(nb_image_par_ligne)))

makeMultiImage.py

Avant de pouvoir ajouter chaque nouvelle image à matrice.png, il a fallu créer ce fichier à parti des 8500 images déjà obtenues avec la première version de la Grille de l'Amour.

Voici le fichier qui nous a permis de faire ça.

À noter que toutes les images étaient dans un dossier images et qu'elles suivaient cette nomenclature: grille-xxx.png avec xxx = 8999999999 pour la première image, auquel on retire 1 pour chaque images suivantes. (choix effectué pour qu'mencoder produise le film, dans l'ordre, en partant de la fin)

# -*- coding: utf-8 -*-

# Si pour une raison ou pour une autre, il est nécessaire de recréer une matrice.png
# à partir des toutes les images grille-xxx.png, il suffit de lancer ce script

import Image
import os


# Récupère le nombre d'images dans le dossier "images"
sortie = os.popen('''ls images/ | wc -l''').read()

# La sortie de os.popen... est un string, il est donc nécessaire de le convertir
nb_images = int(sortie)

def makeImage(nb_image_par_ligne):

    nb_lignes = nb_images/nb_image_par_ligne - 1

    if (nb_images%nb_image_par_ligne != 0):
        nb_lignes +=1

    width = nb_image_par_ligne * 13
    height = nb_lignes * 10

    patchwork_par_le_bas = Image.new('RGB', (width, height), "white")
    patchwork_par_le_haut = Image.new('RGB', (width, height), "white")

    init_name = 9000000000
    name = init_name - 1
    last_name = init_name - nb_images

    y = 0

    for i in range(nb_lignes): 

        x = 0   

        for j in range(nb_image_par_ligne):

            if (name < last_name):
                break
            
            temp = Image.open('images/grille-{0}.png'.format(name))
            patchwork_par_le_bas.paste(temp, (x, y, x+13, y+10))
            patchwork_par_le_haut.paste(temp, (width-13-x, height-10-y, width-x, height-y))

            x += 13
            name -= 1

        y += 10

    patchwork_par_le_bas.save('matrice{0}.png'.format(str(nb_image_par_ligne)))

makeImage(30)
makeImage(69)
makeImage(100)

Templates

Il est à noter que la fonction Django échappe le HTML par défaut.

Il est possible de contourner ce problème en utilisant dans le views.py la fonction mark_safe() (from django.utils.safestring import mark_safe), ou de le mentionner dans le template avec mon_texte|safe. C'est cette deuxième solution que nous avons choisie.

tpl_carte.html

{% extends "base.html" %}

{% block title %}La carte{% endblock %}

{% block contenu %}
        <div id="carte">
                {{ liste_img|safe }}
        </div>
{% endblock contenu %}

tpl_grille.html

{% extends "base.html" %}

{% block title %}La grille{% endblock %}

{% block script %}    
        <script type="text/javascript" src="{{ MEDIA_URL }}jquery-1.6.2.min.js"></script>
        <script type="text/javascript">

            $(document).ready(function(){{
                var $buttons = $('#grille').find('td');
                 
                $buttons.bind('click', function(e) {{
                  change_couleur($buttons.index($(this)));      
                }});
            }});
    
        </script>

        <script type="text/javascript">

            var selcoul = {{ selcoul }};
            var onetimeonly = true;
            
            function change_couleur (num_case){{

                window.location = "http://localhost:8000/grille/"+ selcoul + "/" + num_case + "/";

            }}
            
            function ajout_image (){{

                if (onetimeonly == true) {{ 
           
                    window.location = "http://localhost:8000/grille/"+ selcoul + "/save";
                    onetimeonly = false;

                }}
            
            }}

        </script>
{% endblock %}

{% block contenu %} 
        <div id="grille">
            <table>
{{ tab_grille|safe }}
            </table>
        </div>
    
        <table id="palette">
            <tr>
                <td style="background:#000000" onClick="selcoul=0;"></td>
                <td style="background:#ffffff" onClick="selcoul=1;"></td>
                <td style="background:#ff0000" onClick="selcoul=2;"></td>
                <td style="background:#0000ff" onClick="selcoul=3;"></td>
                <td style="background:#00ff00" onClick="selcoul=4;"></td>
                <td style="background:#ffff00" onClick="selcoul=5;"></td>
                <td style="background:#ff00ff" onClick="selcoul=6;"></td>
                <td style="background:#00ffff" onClick="selcoul=7;"></td>
            </tr>
        </table>

        <div id="bouton">
            <a OnClick="ajout_image()">Ajouter cette image aux {{ nb_total }} autres du chef d'oeuvre d'art majeur collectif</a>
        </div>
{% endblock %}

tpl_matrice.html

{% extends "base.html" %}

{% block title %}La matrice{% endblock %}

{% block contenu %}
        <div id="{{ categorie_matrice }}">
            <img id="matrice_png" src="{{ MEDIA_URL }}{{ categorie_matrice }}.png">
            {{ liste_img|safe }}
        </div>

{% endblock contenu %}

tpl_ligne.html

{% extends "base.html" %}

{% block title %}La ligne{% endblock %}

{% block contenu %}
        <div id="ligne">
            {{ liste_img|safe }}
        </div>
{% endblock contenu %}

tpl_colonne.html

{% extends "base.html" %}

{% block title %}La colonne{% endblock %}

{% block contenu %}
        <div id="colonne">
            {{ liste_img|safe }}
        </div>
{% endblock contenu %}

tpl_film.html

Pour l'instant, nous faisons appel à un player flash, mais il devrait, en toute logique prochainement être remplacé par un player en HTML5.

{% extends "base.html" %}

{% block title %}Le film{% endblock %}

{% block contenu %}
        <div id="matrice">
            <object type="application/x-shockwave-flash" data="http://webtv.levillagenumerique.org/x.swf" width="390" height="300">

                                    <param name="movie" value="http://webtv.levillagenumerique.org/x.swf" />

                                    <param name="allowFullScreen" value="true" />
                                    <param name="FlashVars" value="flv=http://localhost:8000/static/film.flv&amp;margin=0&amp;autoplay=1&amp;autoload=0&amp;showvolume=1&amp;showtime=1&amp;showfullscreen=1&amp;playertimeout=500&amp;buffershowbg=0&amp;buffer=5&amp;buffermessage= &amp;showmouse=autohide&amp;showiconplay=1&amp;iconplaycolor=00FF00&amp;iconplaybgalpha=25" />
            </object>
        </div>
{% endblock contenu %}

models.py

Il y aurait surement plus rationnelle comme base de données, mais celle-ci fonctionne.

À noter que le champs nb est incrémentale et que la classe NombreImages n'est ici que pour contenir une seule information... le nombre d'images.

#-*- coding: utf-8 -*-
from django.db import models
 
class Pixel(models.Model):
    couleur = models.IntegerField()
    nb = models.IntegerField()

class NombreImages(models.Model):
    nom = models.CharField(max_length=10)
    valeur = models.IntegerField()

Et comme on ne maitrise pas très bien les bdd, pour les premiers tests, nous avons rempli les champs en passant par le shell via la commande :

python manage.py shell

Puis en entrant successivement les lignes suivantes :

from grille_de_l_amour.appli.models import Pixel
from grille_de_l_amour.appli.models import NombreImages

import random

for i in range(130):
    Pixel(couleur = random.randint(0, 7), nb = i).save()

total = NombreImages(valeur = 8817, nom = "total")
total.save()

Apparence

La grille

Illustr-grille-de-l-amour.png

La carte

Illustr-carte-leaflet.png

La matrice

Matrice.png

Passage en production

La mise en ligne a été effectuée sur une Debian Squeeze avec Python 2.6.6 et Django 1.5.2.

Il a fallu installer le module WSGI :

apt-get install libapache2-mod-wsgi

Et modifier le fichier relatif au VirtualHost :

WSGIPythonPath /home/grilleamour/www/

<VirtualHost *:80>

        ServerName grilledelamour.labomedia.org

        Alias /media/ /home/grilleamour/www/media/

        <Directory /home/grilleamour/www/media/>
                Order deny,allow
                Allow from all
        </Directory>

        WSGIScriptAlias / /home/grilleamour/www/grille_de_l_amour/wsgi.py

        <Directory /home/grilleamour/www/>
            <Files wsgi.py>
                Order deny,allow
                Allow from all
            </Files>
        </Directory>

</VirtualHost>

L'archive du projet est faite pour fonctionner en local. Pour l'utiliser en ligne voici les modifications à effectuer :

  • dans l'urls.py de l'appli : commenter la ligne qui commence par url(r'^media/
  • dans settings.py :
    • passer DEBUG à false
    • renseigner le nom de domaine par lequel le projet est accessible : ALLOWED_HOSTS = ['grilledelamour.labomedia.org']
    • renseigner une URL absolue pour la base de donneé
    • renseigner une URL absolue pour MEDIA_ROOT
  • dans tpl_grille.py : modifier les window.location pour qu'ils pointent vers l'url en ligne
  • dans tpl_film.py : modifier l'url du src de la vidéo

Afin de ne pas avoir a lancer la commande collectstatic à chaque modif, et afin que le passage localhost>>online soit le moins problématique, nous avons mis le CSS, JavaScript, favicon, etc... dans le dossier media. Toutes les images crées avec la grille y sont aussi stockées.

Pour que Django puisse écrire dans la base de donnée, nous lui avons donné les droits sur le dossier de cette façon :

chown grilleamour:www-data www/

Commandes utiles :

nano /etc/apache2/sites-available/grilledelamour.labomedia.org
/etc/init.d/apache2 restart
tail -f /var/log/apache2/error.log

Ressources techniques

Questions posées sur le site du zéro (3/3 de résolues):

Le tuto du site du zéro : ici.

Les articles de Sam&Max sur Django : ici.

La doc officielle de Django 1.5 : ici