Chapitre 11: Générer du contenu autre que HTML
Habituellement lorsque l’on parle de développement de sites web, nous parlons de produire du HTML. Bien sûr, il y a sur le web bien plus que du HTML; nous utilisons le web pour distribuer des données dans toutes sortes de formats: RSS, PDFs, images, et ainsi de suite.
Jusqu’à présent nous nous sommes concentrés sur le cas courant de production de HTML, mais dans ce chapitre nous allons faire un détour et regarder comment utiliser Django pour produire d’autres types de contenu.
Django propose des outils primitifs que vous pouvez utiliser pour produire du contenu courant autre que du HTML:
- flux de syndication RSS/Atom
- Plan de site (un format XML format développé à l’origine par Google, qui donnent des indications aux moteurs de recherche)
Nous examinerons chacun de ces outils un peu plus tard, et commencerons par aborder les principes de base.
Les bases: vues et types MIME
Souvenez-vous du chapitre 3 -
> Une fonction view, ou view pour faire court (ndt: une vue en français), est simplement une fonction Python qui prend en charge une requête internet et retourne une réponse. Cette réponse peut être le contenu d’une page web au format HTML, ou une redirection, ou une erreur 404, ou un document XML, ou une image - ou n’importe quoi, vraiment.
Plus formellement, une vue Django doit
- accepter une instance HttpRequest en premier argument,
- retourner une instance HttpResponse.
La clef pour renvoyer du contenu autre que du HTML depuis une view réside dans la classe HttpResponse, et plus spécialement dans l’argument constructeur du mimetype.
En modifiant le type MIME, nous indiquons au navigateur que nous renvoyons une réponse d’un format différent.
À titre d’exemple, jetons un oeil sur une vue qui retourne une image PNG. Pour garder les choses simples, nous lirons uniquement le fichier depuis le disque:
from django.http import HttpResponse
def my_image(request):
image_data = open("/path/to/my/image.png", "rb").read()
return HttpResponse(image_data, mimetype="image/png")
C’est tout ! Si vous remplacez le chemin de l’image dans l’appel à open() par celui d’une image réelle, vous pouvez utilisez cette vue très simple pour servir une image, que le navigateur affichera correctement.
L’autre chose importante à garder à l’esprit est que les objets HttpResponse implémentent l’API de fichiers standards de Python. Cela signifie que vous pouvez utiliser une instance HttpResponse à n’importe quel endroit où Python (ou une bibliothèque tierce) attend un fichier.
En guise d’explication au fonctionnement, observons la production d’un CSV sous Django.
Produire du CSV
CSV est un simple format de données couramment utilisé dans les logiciels de feuille de calcul (les tableurs). C’est à la base une série de tableau de lignes, dont chaque cellule d’une ligne est séparée par une virgule (CSV signifie comma-separated values, valeurs séparées par des virgules). Voici par exemple quelques données sur des passagers aériens «indisciplinés» au format CSV:
Year,Unruly Airline Passengers 1995,146 1996,184 1997,235 1998,200 1999,226 2000,251 2001,299 2002,273 2003,281 2004,304 2005,203
Note
La liste précédente contient des nombres réels; ils proviennent courtoisement de l’Administration Fédérale de l’Aviation américaine. Voir l’adresse http://www.faa.gov/data_statistics/passengers_cargo/unruly_passengers/.
Bien que CSV semble simple, ce n’est pas un format qui a été formellement défini. Des composants logiciels différents produisent diverses variantes de CSV, le rendant un peu plus complexe à utiliser. Heureusement, Python est fourni avec une bibliothèque de CSV standard, csv, largement plus consistante:
Puisque le module csv opère sur des objets de la famille des fichiers, un raccourci est d’utiliser à la place un HttpResponse:
import csv
from django.http import HttpResponse
# Number of unruly passengers each year 1995 - 2005. In a real
application
# this would likely come from a database or some other back-end data
store.
UNRULY_PASSENGERS = [146,184,235,200,226,251,299,273,281,304,203]
def unruly_passengers_csv(request):
# Create the HttpResponse object with the appropriate CSV
header.
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment;
filename=unruly.csv'
# Create the CSV writer using the HttpResponse as the "file"
writer = csv.writer(response)
writer.writerow(['Year', 'Unruly Airline Passengers'])
for (year, num) in zip(range(1995, 2006), UNRULY_PASSENGERS):
writer.writerow([year, num])
return response
Le code et ses commentaires doivent être suffisamment clairs, mais certains points méritent tout de même une attention particulière:
- La réponse fournit le type MIME text/csv (à la place du text/html par défaut). Ceci indique au navigateur que le document est un fichier CSV.
- La réponse propose un en-tête additionnel Content-Disposition, qui contient le nom du fichier CSV. Cet en-tête (à vrai dire, la partie «pièce jointe») indiquera au navigateur qu’il lui faut proposer un endroit où sauvegarder le fichier (au lieu de simplement l’afficher). Ce nom de fichier est arbitraire; appelez le comme bon vous semble. Il sera utilisé par les navigateurs dans une fenêtre de dialogue «Enregistrer sous».
- Intéragir avec l’API de génération de CSV est chose facile: passer simplement response en premier argument de csv.writter. La fonction csv.writer attend un objet de type fichier, et les objets HttpResponse correspondent à la demande.
- Pour chaque ligne de votre fichier CSV, appelez writer.writerow, en lui fournissant un objet itérable tel qu’une liste ou un tuple.
- Le module CSV prend soin du renseignement pour vous, vous n’avez donc pas à vous soucier des échappements de chaînes de caractères présentant des guillemets ou des virgules. Passez simplement l’information à writerow(), et il fera ce qu’il faut.
C’est en général le schéma que vous utiliserez à chaque fois que vous aurez besoin de retourner du contenu autre que HTML: créer un objet réponse HttpResponse (avec un type MIME spécial), le transmettre à quelque chose attendant un fichier, puis retourner la réponse.
Regardons quelques exemples supplémentaires.
Générer des PDFs
Le format Portable Document Format (PDF) est un format développé par Adobe qui est utilisé pour représenter des documents imprimables complets, avec un formattage au pixel près, des polices de caractères embarquées, et des graphiques vectoriels en 2D. Vous pouvez voir un document PDF comme l’équivalent numérique d’un document imprimé; par conséquent, les PDFs sont habituellement utilisés lorsque vous devez envoyer un document à quelqu’un en vue d’une impression.
Vous pouvez facilement générer des PDFs avec Python et Django grâce à l’excellente bibliothèque open source ReportLab (http://www.reportlab.org/rl_toolkit.html). L’avantage de la génération dynamique des fichiers PDFs est qu’elle vous permet de personnaliser les PDFs selon le propos - disons, pour différents utilisateurs ou différents éléments du contenu.
Par exemple, nous utilisons Django et ReportLab chez KUSports.com pour générer à la demande le calendrier du tournoi NCAA, prêt à imprimé.
Installer ReportLab
Avant toute génératio de PDF, cependant, vous devrez installer ReportLab. C’est généralement assez simple: il suffit de télécharger et d’installer la bibliothèque depuis http://www.reportlab.org/downloads.html.
Le guide de l’utilisateur (naturellement disponible uniquement sous forme de fichier PDF) à l’adresse http://www.reportlab.org/rsrc/userguide.pdf propose des instructions d’installation complémentaire.
Note
Si vous utilisez une distribution Linux moderne, vous devriez vérifier dans votre gestionnaire de paquets avant d’installer ReportLab. La plupart des dépôts ont ajouté ReportLab.
Par exemple, si vous utilisez la distribution (excellente) Ubuntu, un simple apt-get install python-reportlab fera l’affaire impécablement.
Testez votre installation en l’important dans votre interpréteur Python intéractif:
>>> import reportlab
Si cette commande ne lève pas d’erreurs, l’installation a fonctionné.
Écrire vos propres vues
Tout comme CSV, la génération dynamique de PDFs avec Django est aisée puisque l’API ReportLab se comporte comme un objet fichier.
Voici un exemple de type «Bonjour Monde»:
from reportlab.pdfgen import canvas
from django.http import HttpResponse
def hello_pdf(request):
# Create the HttpResponse object with the appropriate PDF
headers.
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = 'attachment;
filename=hello.pdf'
# Create the PDF object, using the response object as its
"file."
p = canvas.Canvas(response)
# Draw things on the PDF. Here's where the PDF generation
happens.
# See the ReportLab documentation for the full list of
functionality.
p.drawString(100, 100, "Hello world.")
# Close the PDF object cleanly, and we're done.
p.showPage()
p.save()
return response
Quelques notes cependant:
- Nous utilisons ici le type MIME application/pdf. Ceci indique aux navigateurs que le document est un fichier PDF, plutôt qu’un fichier HTML. Si vous négligez cette information, les navigateur tenterons probablement d’interpréter la réponse comme du HTML, ce qui aboutira en un charabia effrayant dans la fenêtre du navigateur.
- Intéragir avec l’API de ReportLab API est facile: transmettez lui simplement la response en premier argument de canvas.Canvas. La classe Canvas attends des objets de type fichiers, et les objets HttpResponse correspondent parfaitement.
- Toutes les méthodes de génération de PDF suivantes sont appelées sur l’objet PDF (dans notre cas, p), pas sur response.
- Finallement, il est important d’appeler showPage() et save() sur le fichier PDF (ou vous obtiendrez un fichier PDF corrompu).
PDFs complexes
Si vous créez un document PDF complexe (ou tout gros blob de donnée), envisagez l’utilisation de la bibliothèque cStringIO comme endroit de stockage temporaire de votre fichier PDF. La bibliothèque cStringIO fourni une interface écrite en C pour un maximum d’efficacité.
Revoici l’exemple précédent, «Bonjour Monde», réécrit de façon à utiliser cStringIO:
from cStringIO import StringIO
from reportlab.pdfgen import canvas
from django.http import HttpResponse
def hello_pdf(request):
# Create the HttpResponse object with the appropriate PDF
headers.
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = 'attachment;
filename=hello.pdf'
temp = StringIO()
# Create the PDF object, using the StringIO object as its
"file."
p = canvas.Canvas(temp)
# Draw things on the PDF. Here's where the PDF generation
happens.
# See the ReportLab documentation for the full list of
functionality.
p.drawString(100, 100, "Hello world.")
# Close the PDF object cleanly.
p.showPage()
p.save()
# Get the value of the StringIO buffer and write it to the
response.
response.write(temp.getvalue())
return response
Autres possibilités
Il existe bien d’autre type de contenu que vous pouvez générer avec Python. Voici quelques idées supplémentaires et des pointeurs vers des bibliothèques que vous pouvez utiliser pour les implémenter:
ZIP files: la librairie standard Python est fourni avec le module zipfile, qui peut à la fois lire et écrire des fichiers ZIP compressés. Vous pouvez l’utiliser pour fournir des archives d’une groupe de fichiers à la demande, ou bien compresser de gros document à la volée. Vous pouvez, de la même façon, produire des fichiers TAR en utilisant le module de la bibliothèque standard tarfile.
Dynamic images: La Python Imaging Library (PIL; http://www.pythonware.com/products/pil/) est une boîte à outils fantastique pour produire des images (PNG, JPEG, GIF, et bien d’autre). Vous pouvez l’utiliser pour transformer automatiquement des images en galeries, composer de multiples images en une seule, ou même faire du traitement d’image depuis le web.
Graphiques et tracés: Il existe de nombreuses bibliothèques incroyablement puissante en Python pour générer des graphiques et des tracés que vous pouvez utiliser pour produire des cartes à la demande, des graphiques, des tracés, des diagrammes. Puisque nous ne pouvons pas toutes les lister, en voici quelques unes:
- matplotlib (http://matplotlib.sourceforge.net/) peut être utilisée pour produire des tracès de grande qualité, du type de ceux habituellement générés par MatLab ou Mathematica.
- pygraphviz (https://networkx.lanl.gov/wiki/pygraphviz), une interface pour la boîte à outils Graphiviz (http://graphviz.org/), qui peut être utilisé pour générer des diagrammes structurés de graphes et de réseaux.
En général, toute bibliothèque Python capable d’écrire dans un fichier peut s’insérer dans Django. Les possibilités sont réellement infinies.
À présent que nous avons étudier les bases de la génératino de contenu autre que HTML, ajoutons un niveau d’abstraction. Django est livré avec quelques outils assez astucieux permettant de générer quelques uns des types courants de contenu autre que le HTML.
Le framework de syndication de flux
Django est fourni avec une framework de génération de syndication de contenu de haut niveau qui permet de créer facilement des flux RSS et Atom.
Qu’est-ce que le RSS ? Qu’est-ce qu’Atom ?
RSS et Atom sont tous deux des formats basés sur XML que vous pouvez utiliser pour fournir automatiquement des «flux» à jour du contenu de votre site. Lisez http://www.whatisrss.com/ au sujet des RSS, et http://www.atomenabled.org/ pour obtenir plus d’information au sujet de Atom.
Pour créer un flux de syndication tout ce que vous avez à faire est d’écrire une petite classe Python. Vous pouvez créer autant de flux que vous le désirez.
Le framework de génération de flux est une vue qui s’applique à /feeds/ par convention. Django utilise le reliquat de l’URL (tout ce qui se trouve après /feeds/) pour déterminer le flux à retourner.
Pour créer un flux, vous écrirez une classe Feed et pointerez dessus depuis votre URLConf (voir les chapitres 3 et 8 pour en savoir plus sur les URLConfs).
Initialisation
Pour activer les flux de syndication sur votre site Django, ajoutez cet URLConf:
(r'^feeds/(?P<url>.*)/$',
'django.contrib.syndication.views.feed',
{'feed_dict': feeds}
),
Cette ligne indique à Django qu’il faut utiliser le framework RSS pour gérer toutes les URLs commencant "feeds/". (Vous pouvez changer ce prefixe "feeds/" pour répondre à vos propres besoins).
Cette ligne de l’URLConf présente un argument supplémentaire: {'feed_dict': feeds}. Utilisez cet argument pour transmettre au framework de syndication les flux qui doivent être publiés sous cet URL.
Plus spécialement, feed_dict doit être un dictionnaire qui met en correspondance le slug du flux (une courte étiquette représentant l’URL) avec sa classe Feed. Vous pouvez définir le feed_dict dans l’URLConf lui même. Voici un exemple complet d’URLconf:
from django.conf.urls.defaults import *
from myproject.feeds import LatestEntries, LatestEntriesByCategory
feeds = {
'latest': LatestEntries,
'categories': LatestEntriesByCategory,
}
urlpatterns = patterns('',
# ...
(r'^feeds/(?P<url>.*)/$',
'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
# ...
)
L’exemple précédent enregistre deux flux:
- Le flux représenté par LatestEntries sera disponible sous feeds/latest/.
- le flux représenté par LatestEntriesByCategory sera disponible sous feeds/categories/.
Une fois paramétré, vous devrez définir les classes Feed elles mêmes.
Une classe Feed est une simple classe Pytohn qui représente un flux de syndication. Un flux peut être simple (par exemple, un flux «nouveautés du site», ou un flux basique affichant les dernières entrées d’un blog) ou plus complexe (par exemple, un flux affichant toutes les entrées de blog d’une catégorie particulière, ou la catégorie est variable).
Les classes Feed doivent être des sous classes django.contrib.syndication.feeds.Feed. Elle peuvent se trouver n’importe où dans votre arborescence de code.
Un simple flux
Cet exemple simple, extrait de chicagocrime.org, décrit un flux des cinq dernièrs éléments d’actualités:
from django.contrib.syndication.feeds import Feed
from chicagocrime.models import NewsItem
class LatestEntries(Feed):
title = "Chicagocrime.org site news"
link = "/sitenews/"
description = "Updates on changes and additions to
chicagocrime.org."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
Les choses importantes qu’il faut noter ici sont les suivantes:
La classe sous-classe django.contrib.syndication.feeds.Feed.
title, link, et description correspondent aux éléments standards RSS <title>, <link>, et <description>, respectivement.
items() est simplement une méthode qui renvoie une liste d’objets qui doivent être inclus dans le flux sous forme d’éléments <item>. Bien que cet exemple renvoie des objets NewsItem en utilisant l’API de base de données de Django, items() n’a pas à renvoyer d’instances de modèle.
Vous pouvez récupérer quelques fonctionnalités «gratuitement» en utilisant les modèles Django, mais items() peut renvoyer tous les types d’objets que vous désirez.
Il reste juste une étape. Dans un flux RSS, chaque <item>``propose ``<title>, <link>, et <description>. Nous devons indiquer au framework quelles sont les données à placer dans ces éléments.
Pour spécifier les contenus de <title> et de <description>, créez des gabarits Django (voir le chapitre 4) appelés feeds/latest_title.html et feeds/latest_description.html, ou latest est le slug spécifié dans l’URLconf pour le flux indiqué. Notez que l’extension .html est requise.
Le système RSS interprète le gabarit pour chaque élément, lui transmettant les deux variables de contexte du gabarit:
obj: l’objet courant (un parmis tous les objets que vous avez renvoyé dans items()).
site: un objet django.models.core.sites.Site représentant le site courant. C’est utile pour {{ site.domain }} ou {{ site.name }}.
Si vous ne créez pas de gabarit pour le titre ou pour la description, le framework utilisera le gabarit "{{ obj }}" par défaut - autrement dit, la représentation normale, sous forme de chaîne, de l’objet.
Vous pouvez aussi changer les noms de ces deux gabarits en précisant title_template et description_template comme attributs de votre classe Feed.
Pour préciser les contenus de <link>, vos avez deu options.Pour chaque élément de items(), Django commence par essayer d’exécuter une méthode get_absolute_url() sur cet objet. Si cette méthode n’existe pas, il tente d’appeler une méthode item_link()``dans la classe ``Feed, lui passant un unique paramètre, item, qui est l’objet lui même.
À la fois get_absolute_url() et item_link() doivent retourner l’URL de l’élément comme une chaîne standard Python.
Pour l’exemple précédent LatestEntries, nous pouvons avoir des gabarits de flux très simples. latest_title.html contient:
{{ obj.title }}et latest_description.html contient:
{{ obj.description }}C’est presque trop facile !
Un flux plus complexe
Le framework supporte aussi les flux plus complexes, via les paramètres.
Par exemple, chicagocrime.org offre un flux RSS des crimes récents pour chaque ronde de la police de chicago. Il serait insensé de créer une classe séparée pour chaque ronde de police; cela violerait le principe Don’t Repeat Yourself (DRY) et couplerait les données avec la logique de programmation.
Au lieu de cela, le framework de syndication vous autorise à faire des flux génériques qui renvoient les éléments selon l’information contenu dans l’URL du flux.
Sur chicagocrime.org, le flux pour les rondes de police sont accessibles via des URLs semblablent à celles-ci:
- http://www.chicagocrime.org/rss/beats/0613/: reonvoie les crimes récents pour la ronde 0613
- http://www.chicagocrime.org/rss/beats/1424/: renvoie les crimes récents pour la ronde 1424
Le slug est ici "beats". Le framework de syndication voit le bout de l’URL qui suit le slug - 0613 et 1424 - et vous donne un point d’entré pour le renseigner sur la signification de cette partie et sur son influence auprès dès éléments publiés dans le flux.
Un exemple rendra cela plus clair. Voici le code des flux spécifiques aux rondes:
from django.core.exceptions import ObjectDoesNotExist
class BeatFeed(Feed):
def get_object(self, bits):
# In case of "/rss/beats/0613/foo/bar/baz/", or other
such
# clutter, check that bits has only one member.
if len(bits) != 1:
raise ObjectDoesNotExist
return Beat.objects.get(beat__exact=bits[0])
def title(self, obj):
return "Chicagocrime.org: Crimes for beat %s" %
obj.beat
def link(self, obj):
return obj.get_absolute_url()
def description(self, obj):
return "Crimes recently reported in police beat %s" %
obj.beat
def items(self, obj):
crimes =
Crime.objects.filter(beat__id__exact=obj.id)
return crimes.order_by('-crime_date')[:30]
Voici l’algorithme de base du framework RSS, pour cette classe donnée et pour une requête sur l’URL /rss/beats/0613/:
Le framework récupère l’URL /rss/beats/0613/ et note qu’il y a un bout supplémentaire après le slug. Il coupe cette fin de chaîne au niveau du caractère ("/") et appelle la méthode get_object() de la classe Feed, lui transmettant au passage la partie restante.
Dans ce cas, la partie est ['0613']. Pour une requête vers /rss/beats/0613/foo/bar/, la partie restante serait ['0613', 'foo', 'bar'].
get_object() est responsable de récupérer la ronde de police précisée, depuis la partie indiquée.
Danc ce cas, on utilise l’API de base de données de Django pour récupérer la ronde. Notez que get_object() doit lever django.core.exceptions.ObjectDoesNotExist si des paramètres invalides sont fournis. Il n’y a pas de try/except autour de l’appel à Beat.objects.get(), parce que cela n’est pas nécessaire. Cette fonction lève Beat.DoesNotExist en cas d’erreur, et Beat.DoesNotExist est une sous classe de ObjectDoesNotExist. Lever ObjectDoesNotExist dans get_object() indique à Django qu’il lui faut produire une erreur 404 pour cette requête.
Pour générer les <title>, <link>, et <description> du flux, Django utilise les méthodes title(), link(), et description(). Dans l’exemple précédent, il y avait des attributs de classes sous forme de simple chaîne, mais cet exemple illustre le fait qu’il peut sagir soit de chaînes soit des méthodes. Pour chaque title, link, et description, Django suit cet algorithme:
- Il tente d’appeler la méthode, lui passant l’argument obj, où obj est l’objet retourné par get_object().
- en cas d’échec, il tente d’appeler une méthode sans arguments.
- en cas d’échec, il utilise l’attribut de classe.
Finallement, notez que la fonction items() de cet exemple prends aussi l’argument obj. L’algorithme pour items est le même que celui décrit dans l’étape précédente - il commence par essayer items(obj), ensuite items(), et finallement un attribut de classe items (qui doit être une liste).
La documentation complète pour toutes les méthodes et attributs des classes Feed est toujours disponible sur le site de la documentation officielle de Django (http://www.djangoproject.com/documentation/0.96/syndication_feeds/).
Spécifier le type de flux
Par défaut, le framework de syndication produit du RSS 2.0. Pour modifier cela, ajoutez un attribut feed_type à votre classe Feed:
from django.utils.feedgenerator import Atom1Feed
class MyFeed(Feed):
feed_type = Atom1Feed
Notez que vous paramétrez feed_type pour un objet de classe, pas pour une instance. Actuellement les types de flux sont renseignés dans le tableau 11-1.
Tableau 11-1. Types de flux
| Feed Class | Format |
| django.utils.feedgenerator.Rss201rev2Feed | RSS 2.01 (default) |
| django.utils.feedgenerator.RssUserland091Feed | RSS 0.91 |
| django.utils.feedgenerator.Atom1Feed | Atom 1.0 |
Pièces associées
Pour préciser des pièces jointes (c’est à dire, des médias ressources associés à un élément flux tel qu’un flux de podcast MP3), utilisez les points d’entrée item_enclosure_url, item_enclosure_length, et item_enclosure_mime_type, par exemple:
from myproject.models import Song
class MyFeedWithEnclosures(Feed):
title = "Example feed with enclosures"
link = "/feeds/example-with-enclosures/"
def items(self):
return Song.objects.all()[:30]
def item_enclosure_url(self, item):
return item.song_url
def item_enclosure_length(self, item):
return item.song_length
item_enclosure_mime_type = "audio/mpeg"
Ceci implique, bien sure, que vous ayez créé un objet Song avec des champs song_url et song_length (c’est à dire, la taille en octets).
Langage
Les flux créés par le framework de syndication incluent automatiquement la balise appropriée <language> (RSS 2.0) ou l’attribut xml:lang (Atom). Ceci provient directement de votre paramètre LANGUAGE_CODE.
URLs
La méthode/l’attribut link peut retourner soit un URL absolut (par exemple, "/blog/") ou un URL avec le domaine pleinement qualifié et le protocol ( par exemple, "http://www.example.com/blog/"). Si link ne retourne pas le domaine, le framework de syndication inserera le domaine du site courant, conformément à votre paramètre SITE_ID.
Le flux Atom requière un <link rel="self"> qui définit l’emplacement actuel du flux. Le framework de syndication ajoute cela automatiquement, en utilisant le domaine du site courant selon le paramètre SITE_ID.
Publier des flux RSS et Atom conjointement
Certain développeurs aiment à rendre disponible à la fois les versions Atom et RSS de leurs flux. Ceci est simple à faire avec Django: créez simplement une sous classe de votre classe feed et fixez le feed_type à une valeur différente. Ensuite mettez à jour votre URLconf pour ajouter les versions supplémentaires. Voici un exemple complet:
from django.contrib.syndication.feeds import Feed
from chicagocrime.models import NewsItem
from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed):
title = "Chicagocrime.org site news"
link = "/sitenews/"
description = "Updates on changes and additions to
chicagocrime.org."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
class AtomSiteNewsFeed(RssSiteNewsFeed):
feed_type = Atom1Feed
Et voici l’URLconf l’accompagnant:
from django.conf.urls.defaults import *
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
feeds = {
'rss': RssSiteNewsFeed,
'atom': AtomSiteNewsFeed,
}
urlpatterns = patterns('',
# ...
(r'^feeds/(?P<url>.*)/$',
'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
# ...
)
Le framework «plan de site»
Un plan de site (sitemap) est un fichier XML sur votre site web qui indique aux indexeurs des moteurs de recherches la fréquence de changement de vos pages et combien la relation entre certaines pages de votre site est «importante». Cette information facilite l’indexation de votre site par les moteurs de recherches.
Par exemple, voici un morceau de sitemap pour le site de Django (http://www.djangoproject.com/sitemap.xml):
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.djangoproject.com/documentation/</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>http://www.djangoproject.com/documentation/0_90/</loc>
<changefreq>never</changefreq>
<priority>0.1</priority>
</url>
...
</urlset>
Plus en savoir plus au sujet des sitemaps, consultez http://www.sitemaps.org/.
Le framework de sitemap sous Django automatise la création de ce fichier XML en vous laissant l’expression de cette information en code Python. Pour créer une sitemap, vous devez simplement écrire une classe Sitemap et pointer dessus depuis votre URLconf.
Installation
Pour installer l’application sitemap, suivez ces étapes:
- Ajoutez 'django.contrib.sitemaps' à votre paramètre INSTALLED_APPS.
- Assurez vous que 'django.template.loaders.app_directories.load_template_source' soit dans votre paramètre TEMPLATE_LOADERS. Il s’y trouve par défaut, vous n’aurez donc pas à modifier cela à moins que vous ne l’ayez changer auparavant.
- Assurez vous d’avoir installer le framework de sites (voir le chapitre 14).
Note
L’application sitemap n’installe aucune table dans la base de données. La seule raison pour laquelle est doit être parmis les INSTALLED_APPS est que le chargeur de gabarit doit pouvoir trouver les gabarits par défaut.
Initialisation
Pour activer la génération d’une sitemap sur votre site Django, ajoutez cette ligne à votre URLconf:
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
Cette ligne indique à Django qu’il faut construire une sitemap lorsqu’un client accède à /sitemap.xml.
Le nom du fichier sitemap n’est pas important, mais son emplacement, si. Les moteurs de recherche indexerons seulement les liens de votre sitemap pour le niveau d’URL courant et suivant. Par conséquent, si sitemap.xml réside dans votre répertoire racine, il peut référencer tout URL dans votre site web. Cependant, si votre sitemap réside dans /content/sitemap.xml, il ne pourra référencer les URLs commencant par /content/.
La vue sitemap prend un argument supplémentaire, requis: {'sitemaps': sitemaps}. sitemaps doit être un dictionnaire qui met en correspondance une courte étiquette de section (par exemple, blog ou news) avec sa classe Sitemap (par exemple, BLogSitemap ou NewsSitemap). Il peut aussi mettre en correspondance avec une instance de classe Sitemap (par exemple, BlogSitemap(some_var)).
Les classes Sitemap
Une classe Sitemap est une simple classe Python qui représente une «rubrique» d’entrées dans votre sitemap. Par exemple, une classe Sitemap peut représenter toutes les entrées de votre blog, alors qu’une autre peut représenter tous les événement de votre calendrier d’événements.
Dans le cas le plus simple, toutes ces rubriques sont mélangées au sein d’un même sitemap.xml, mais il est aussi possible d’utiliser le framework pour générer un index de sitemap qui référence des fichiers sitemaps individuels, un par rubrique (comme on le détaille sous peu).
Les classes Sitemap doivent sous classer django.contrib.sitemaps.Sitemap. Elles peuvent être placé n’importe où dans votre arborescence de code.
Par exemple, considérons que nous ayons un système de blog, avec un modèle Entry, et que vous vouliez que votre sitemap inclu tous les liens vers vos entrées individuelles de blog. Voici à quoi pourrait ressembler votre classe Sitemap:
from django.contrib.sitemaps import Sitemap
from mysite.blog.models import Entry
class BlogSitemap(Sitemap):
changefreq = "never"
priority = 0.5
def items(self):
return Entry.objects.filter(is_draft=False)
def lastmod(self, obj):
return obj.pub_date
Déclarer un Sitemap ressemble étrangement à la déclaration d’un Feed; Ceci est du à la conception.
Tout comme les classes Feed, les membres de Sitemap peuvent être des méthodes ou des attributs. Voyez les étapes dans la section «Un exemple complexe» pour en savoir plus au sujet de son fonctionnement.
Une classe Sitemap peut définir les méthodes et attributs suivants:
items (requis): fournit une liste d’objets. Le framework ne s’occupe pas de savoir quel est le type des objets; tout ce qui compte est que ces objets soit transmis aux méthodes location(), lastmod(), changefreq(), et priority().
location (optionel): donne l’URL absolut d’une objet donné. Ici, «URL absolut» signifie que l’URL ne contient pas le protocol ou le domaine. Voici quelques exemples:
- Bon: '/foo/bar/'
- Mauvais: 'example.com/foo/bar/'
- Mauvais: 'http://example.com/foo/bar/'
Si location n’est pas fourni, le framework appellera la méthode get_absolute_url() sur chaque objets retournés par items().
lastmod (optionel): la date de «dernière modification» de l’objet, sous forme d’objet Python datetime.
changefreq (optionel): la fréquence de modification des objets. Les valeurs possible sont les suivantes (comme l’indique la spécification Sitemaps):
- 'always'
- 'hourly'
- 'daily'
- 'weekly'
- 'monthly'
- 'yearly'
- 'never'
priority (optionel): une suggestion de priorité d’indexaton entre 0.0 et 1.0. La priorité par défaut d’une page est 0.5; lisez la documentation de http://sitemaps.org pour en savoir plus sur le fonctionnement de priority.
Raccourcis
Le framework de sitemap fournit quelques classiques bien pratiques dans les cas courants. Elles sont décrites dans les sections qui suivent.
FlatPageSitemap
La classe django.contrib.sitemaps.FlatPageSitemap consulte toutes les pages statiques définies dans le site courant et créer une entrée dans la sitemap. Ces entrées incluent seulement l’attribut location - et pas lastmod, changefreq, ou priority.
Voir le chapitre 14 pour en savoir plus sur les pages statiques.
GenericSitemap
La classe GenericSitemap fonctionne avec toutes les vues génériques (voir le chapitre 9) que vous avez déjà.
Pour l’utiliser, créer une instance et passez lui le même info_dict que celui que vous avez transmis aux vues génériques. La seule obligation est que le dictionnaire possède une entrée queryset. Elle peut aussi avoir une entrée date_field qui spécifie un champ date pour les objets récupérés depuis le queryset. Cela sera utilisé pour l’attribut lastmod dans la sitemap générée. Vous pouvez aussi ajouter les arguments mot-clef priority et changefreq au constructeur GenericSitemap pour spécifier ces attributs pour toutes les URLs.
Voici un exemple d’URLconf utilisant à la fois FlatPageSitemap et GenericSiteMap (toujours avec l’hypotétique objet Entry précédent):
from django.conf.urls.defaults import *
from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap
from mysite.blog.models import Entry
info_dict = {
'queryset': Entry.objects.all(),
'date_field': 'pub_date',
}
sitemaps = {
'flatpages': FlatPageSitemap,
'blog': GenericSitemap(info_dict, priority=0.6),
}
urlpatterns = patterns('',
# some generic view using info_dict
# ...
# the sitemap
(r'^sitemap.xml$',
'django.contrib.sitemaps.views.sitemap',
{'sitemaps': sitemaps})
)
Créer un index de sitemap
Le framework de sitemap possède aussi l’aptitude à créer un index de sitemap qui référence des fichiers sitemap individuels, un pour chaque rubrique définies dans votre dictionnaire sitemaps. Les seules différences d’utilisation sont les suivantes:
- Vous utilisez deux vues dans votre URLconf: django.contrib.sitemaps.views.index et django.contrib.sitemaps.views.sitemap.
- La vue django.contrib.sitemaps.views.sitemap doit prendre un argument mot-clef section.
Voici un exemple de ce à quoi pourrait ressembler ces lignes dans l’URLconf avec notre exemple précédent:
(r'^sitemap.xml$',
'django.contrib.sitemaps.views.index',
{'sitemaps': sitemaps}),
(r'^sitemap-(?P<section>.+).xml$',
'django.contrib.sitemaps.views.sitemap',
{'sitemaps': sitemaps})
Cela générera automatiquement un fichier sitemap.xml qui référence à la fois sitemap-flatpages.xml et sitemap-blog.xml. Les classes Sitemap et le dictionnaire sitemaps ne change pas du tout.
«Pinger» Google
Vous pourriez vouloir «pinger» Google lorsque votre sitemap change, pour lui signaler de réindexer votre site. Le framework fournit une fonction pour faire cela: django.contrib.sitemaps.ping_google().
Note
Au moment d’écrire ce livre, seul Google répondait aux pings de sitemap. Cependant, il est très probable que Yahoo et MSN supporte bientôt ces pings eux aussi.
À ce moment, nous aurons probablement changé le nom ping_google() en quelque chose du genre ping_search_engines(), assurez donc de consulter la dernière documentation à propos des sitempas à l’adresse http://www.djangoproject.com/documentation/0.96/sitemaps/.
ping_google() accepte deux arguments optionnels, sitemap_url, qui doit être l’URL absolut de la sitemap de votre site (par exemple, '/sitemap.xml'). Si cette argument n’est pas fourni, ping_google() tentera de déterminer votre sitemap grâce à une analyse de votre URLconf.
ping_google() lève l’exception django.contrib.sitemaps.SitemapNotFound s’il ne peut déterminer l’URL de votre sitemap.
Un façon utile d’appeler ping_google() est de la faire depuis une méthode save() du modèle:
from django.contrib.sitemaps import ping_google
class Entry(models.Model):
# ...
def save(self):
super(Entry, self).save()
try:
ping_google()
except Exception:
# Bare 'except' because we could get a
variety
# of HTTP-related exceptions.
pass
Une solution plus efficace, cependant, est d’appeler ping_google() depuis un script cron ou depuis tout autre ordonnanceur de tâche. La fonction fait une requête HTTP vers les serveurs de Goolge, vous n’introduirez pas cette surchage réseau à chaque fois que vous appellez save().
Et ensuite ?
Ensuite, nous continuerons à creuser plus avant parmis tous les outils géniaux que Django vous offre par défaut. Le chapitre 12 examine tous les outils nécessaires pour fournir des sites personnalisés pour l’utilisateur: les sessions, les utilisateurs, et l’identification.
En avant !
Dernière modification: 2009-11-16 21:23:15.117281