Archive

Archives pour la catégorie ‘django’

Django : Envoyer des emails HTML avec images inline (intégrées)

17/02/2010 admin Comments off

Voyons comment envoyer des emails multiparties (texte et HTML) avec des images inline (intégrées dans le mail lui-même, et non en pièce jointe), et ceci en utilisant des templates afin de profiter (par exemple) de l’i18n avec gettext, des filtres et tags, de l’utilisation du contexte…

Ce code est un mélange de deux méthodes complémentaires, une sur les mails HTML par Ross Poulton, et l’autre venant d’un djangosnippet par sleytr.

Pour en faciliter l’utilisation et la maintenance, j’ai mis ce petit module django-nice-emails sur bitbucket.

Plutôt que de copier le code ici, je vais plutôt en décrire les grandes étapes:

  1. Utiliser le setting DEFAULT_FROM_EMAIL si l’expéditeur n’est pas fourni
  2. Créer un django.template.Context à partir du dictionnaire fourni (permet de remplacer les {{ var }} dans les templates)
  3. Utiliser le contexte créé pour initialiser le contenu texte, HTML ainsi que le sujet
  4. Transformer le destinataire fourni en liste (si ce n’est pas déjà une liste de destinataires)
  5. Créer un django.core.mail.EmailMultiAlternatives qui est la base de notre email (basé sur le contenu texte)
  6. Rajouter la partie HTML
  7. Rajouter les images en inline si nécéssaire
  8. Envoyer le mail

Rien de compliqué donc dans ce code qui fait moins de 20 lignes « utiles ».

Voyons maintenant un exemple d’utilisation avec de la traduction et de l’héritage de templates:

Les templates

On utilise ici la méthode de Ross Poulton qui consiste à ne fournir en paramètre template_name que la base du nom de fichier, sans l’extension. On fournit ensuite au django.template.loader ce template_name avec l’extension .txt et .html, ces templates doivent donc exister tous les deux.

templates/test_email.txt

{% load i18n %}
{% trans "Bonjour" %} {{ nom }},

{% blocktrans %}Ceci est un exemple de "nice-email" que je vous fait parvenir,
à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.

{% trans "Cordialement" %}

Mathieu Agopian

templates/test_email.html

{% extends "base_email.html" %}
{% load i18n %}
{% block email_content %}
<p>{% trans "Bonjour" %} {{ nom }},</p>

<p>
{% blocktrans %}Ceci est un exemple de "nice-email" que je vous fait parvenir,
à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.
</p>

<p>{% trans "Cordialement" %}</p>

<p><em>Mathieu Agopian</em></p>
<img src="cid:signature" />
{% endblock email_content %}

templates/base_email.html

<table width="600">
<tr><td><img src="cid:logo" /></td></tr>
<tr><td>
    {% block email_content %}{% endblock email_content %}
</td></tr>
</table>

Le contexte

Un simple dictionnaire python pour chaque tag utilisé dans les templates:

context = {'nom': 'Johnny Biboul'}

Les images

Elles doivent être passées en paramètres dans un tuple de tuples, sous la forme ((‘/chemin/vers/image.png’, ‘tagimage’), ‘/chemin/vers/image2.png’, ‘tagimage2′), …). Si les images sont dans le répertoire images du MEDIA_ROOT:

images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'image', 'logo.png'), 'logo'))

Dans les templates, on utilisera les images sous la forme <img src=’cid:tagimage’ />.

Le code

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from os import path
from django.conf import settings
from django.utils.translation import ugettext
from utils.nicemails import send_nice_email

context = {'nom': 'Johnny Biboul'}

images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'images', 'logo.png'), 'logo'))

subject = ugettext(u"Test de mail pour %(nom)s") % {'nom': '{{ nom }}'}

send_nice_email(
        template_name='test_email',
        email_context=context,
        subject=subject,
        recipients='johnny@biboul.com',
        sender='foo bar ',
        images=images)

Conclusion

Il vous suffit de mettre ce code dans une de vos vues pour pouvoir faire de jolis mails de confirmation d’inscription, des newsletters, ou voir même (bouh! c’est mal!) du mass-mailing. Veillez néanmoins à ne pas forcer la dose sur le html, ou les images inlines!

lancer gunicorn avec runit

10/02/2010 admin Comments off

ATTENTION votre serviteur a fait le test pour vous sur une ubuntu: après avoir installé runit et runit-run, le système ne démarre plus. Pour suivre les étapes de ce billet, il ne faut pas installer runit-run, qui ne doit être installé que lorsque l’on souhaite remplacer totalement le système d’initialisation (et cela demande plus de configuration qu’une simple installation du paquet).

Pour les malheureux qui ont fait les frais de la première version de ce billet (demandant d’installer runit-run), je ne peux que m’excuser platement, et vous fournir la méthode « au secours rescue moi! »: récuperer une installation avec un cd ubuntu. Une fois chrooté sur la partition root, il vous restera à désinstaller le paquet fautif et redémarrer:
        $ aptitude purge runit-run

Edité le 2010/02/10 à 20:58

Pour faire suite au précédent billet gunicorn: un server wsgi ultra simple à utiliser et configurer, voici une recette rapide pour lancer automatiquement (et monitorer) gunicorn avec runit.

Pourquoi runit et pas sysvinit, inittab, upstart, …

Je vous laisse consulter la page benefits sur le site officiel pour vous faire une idée. Pour les personnes ne parlant pas anglais, voici un bref résumé:

  1. Un répertoire par service, contenant un script run
  2. Un environnement d’exécution propre et prédictible pour chaque processus
  3. Service de logging (optionnel) qui sera lancé en même temps que le processus, et en couvrira toute la durée de vie (redémarrages compris!)
  4. Très peu encombrant, efficace… et peut complètement remplacer le système d’initialisation de votre linux

Utiliser runit avec le système d’initialisation actuel

Pour nous faciliter la vie, et ne pas avoir à modifier/importer de nombreux scripts de démarrage pour tous les démons et services déjà installés, nous allons utiliser runit « avec » le système d’initialisation actuel.

Installer runit

$ aptitude install runit

ATTENTION: Si vous installez aussi runit-run, il vous faut absolument configurer votre système (How to replace init), et ce, avant de rebooter (sinon votre système ne démarrera pas, et vous serez contraint à utiliser une méthode de récupération, comme celle présentée en tête de ce billet).

Créer un répertoire pour le service gunicorn et son script de lancement

$ mkdir /etc/sv/gu-monprojet
$ vi /etc/sv/gu-monprojet/run

Et voici le contenu du script run

#!/bin/bash
source /path/to/venv/bin/activate # activer le virtualenv
cd /path/to/django/project
exec gunicorn_django -b localhost:8080 --workers=3

Avec la version de gunicorn utilisée pour l’écriture de cet article, il est nécessaire d’être dans le répertoire du projet django (là ou se situe le fichier settings.py) pour lancer gunicorn_django.

Dans une future version (la modification est dans le trunk à l’heure de l’écriture) il suffira d’indiquer le chemin vers le fichier settings.py comme paramètre à la commande gunicorn_django.

Enfin, ne pas oublier de rajouter les droits d’exécution sur le script qu’on vient de créer:

$ chmod a+x /etc/sv/gu-monprojet

Indiquer à runit qu’il doit lancer le script

Pour celà, un simple lien symbolique, et dans les secondes qui suivent le script sera lancé:

$ ln -s /etc/sv/gu-monprojet /etc/service/

Et c’est tout!

Il suffit maintenant d’en profiter en allant sur http://localhost:8080, en configurant apache pour proxiser les requêtes directement dessus (cf le billet gunicorn: un server wsgi ultra simple à utiliser et configurer), ou encore en utilisant la commande sv pour gérer le service gunicorn:

$ sv status gu-monprojet
$ sv check gu-monprojet
$ sv up gu-monprojet
$ sv down gu-monprojet
$ sv restart gu-monprojet
$ sv hup gu-monprojet
...

gunicorn: un server wsgi ultra simple à utiliser et configurer

09/02/2010 admin Comments off

Deux billets le même jour, c’est fête!

Voici une recette simple pour installer, configurer et utiliser gunicorn avec apache et django.

Installer gunicorn

Pour installer gunicorn dans son environnement virtuel:

$ pip install -E /path/to/venv install gunicorn

Configurer Apache en proxy

Apache servira les fichiers statiques, et « proxisera » toutes les autres requêtes directement à gunicorn qui sera lancé en local sur le port 8080:

<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com

DocumentRoot /path/to/django/project

<Proxy *>
    Order deny,allow
    Allow from all
</Proxy>

# laisser apache servir les fichiers statiques
ProxyPass /robots.txt !
ProxyPass /favicon.ico !
ProxyPass /static/ !

# proxiser toutes les autres requêtes vers gunicorn
ProxyPass / http://localhost:8080/

# robots.txt et favicon.ico sont dans /path/to/django/project/static/
Alias /robots.txt /path/to/django/project/static/robots.txt
Alias /favicon.ico /path/to/django/project/static/favicon.ico

<Directory /path/to/django/project>
    Order deny,allow
    Allow from all
    Options -Indexes
</Directory>
</VirtualHost>

Pour que le tout fonctionne correctement, il faut activer les modules mod_proxy et mod_proxy_html (et en option mod_cache):

$ a2enmod proxy proxy_http cache

Puis de redémarrer le server Apache:

$ /etc/init.d/apache2 restart

Lancer gunicorn

Il suffit de se placer dans le répertoire du projet django (avec le virtualenv activé), puis de taper:

$ gunicorn_django -b localhost:8080 --workers=2

Un ordre d’idée pour le calcul des workers: un de plus que le nombre de CPUs de la machine.

Conclusion

On peut alors se créer un script (a placer dans /etc/init.d) et l’activer pour qu’il se lance automatiquement au démarrage avec la commande update-rc.d (sous Debian), ou utiliser runit (jamais testé, peut-être un futur billet?).

Encore mieux, remplacer Apache par Nginx! (jamais testé non plus, et sûrement un futur billet ;) ).

On peut difficilement faire plus simple!

Django, sqlite et mod_wsgi, attention au piège!

14/03/2009 admin 2 commentaires

Tout d’abord, je tiens à préciser que le problème qui suit n’est pas limité à l’utilisation de django ou de mod_wsgi.

Le contexte

Utilisation de SQLite pour un projet django déployé sur mod_wsgi:

# settings.py
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/opt/mysite/mysite.db'

Et voici les permissions sur le système de fichier:

-rw-rw-rw- 1 ohan ohan 29696 2009-03-14 13:30 mysite.db

Tous les répertoires parents sont eux en 755 (lecture et exécution), ce qui ne devrait donc poser aucun problème, même pour l’utilisateur utilisé par les processus apache/mod_wsgi.

Le problème

Lors de la première tentative d’accès à la base de donnée (par exemple en accédant à l’administration django), une erreur 500 INTERNAL SERVER ERROR est renvoyée, et dans le fichier de log d’apache:

OperationalError: unable to open database file

La solution

Lors de l’accès à un fichier de base de données, SQLite va créer un fichier journal qui lui servira (entre autres) à gérer les accès à cette base. Plus d’informations sur la page expliquant les méthodes de vérouillage: locking in sqlite v3.

Pour créer ce fichier, il faut donc que l’utilisateur puisse écrire dans le répertoire parent.

chmod a+rw /opt/mysite

Ce problème ne devrait se présenter que lors d’un déploiement en environnement de production pour un projet qui utilise SQLite, ou sur un environnement de test si, comme moi, vous préférez tester sur apache directement, et non sur le runserver.

Categories: django, mod_wsgi, sqlite Tags:

Checklist: différences entre MySQL et les modèles Django

02/03/2009 admin Comments off

Comme vu dans un précédent billet (MySQL et les modèles Django), il existe des inconsistances entre une base de donnée MySQL et sa représentation par des modèles Django.

Création des tables à partir des modèles Django

Lorsqu’on utilise python manage.py syncdb, les tables créées dans la base de données

  1. N’auront aucun commentaire, que ce soit sur les champs ou les tables, qu’il y ai ou non des help_text et des docstrings
  2. N’auront aucun champ ENUM: les champs possédant une liste de choix sont représentés par un VARCHAR de longueur égale à la taille du plus long des choix
  3. Les valeurs par défaut indiquées dans les modèles ne sont pas transmises dans les scripts de création des tables
  4. Les BooleanField sont représentés par des TINYINT de 1bit
  5. Un DateTimeField avec le paramètre auto_add_now ne sera pas représenté par un TIMESTAMP avec la valeur par défaut CURRENT_TIMESTAMP

Inspection des tables dans Django

Par ailleurs, si la base de donnée existe déjà, et que les modèles sont créés automatiquement par l’utilisation de la commande python manage.py inspectdb, il faudra garder à l’esprit que:

  1. Les commentaires sur les champs et tables ne sont pas traduits en help_text ou docstrings, il faudra donc les dupliquer manuellement
  2. Un champ ENUM sera représenté par un CharField
  3. Les tailles de champ ne sont pas respectées. Par exemple, un Charfield sera avec un max_length de 135 par défaut
  4. Les champs TIMESTAMP avec une valeur par défaut de CURRENT_TIMESTAMP ne seront pas importés en champ avec une valeur par défaut auto_add_now
  5. Les valeurs par défaut ne sont pas importées

Il y a deux derniers points à noter:

  1. Le script d’inspection va importer les champs primary_key pour chaque table, alors qu’ils peuvent être automatiquement générés de manière transparente: un modèle sans primary_key explicite en aura un de manière implicite
  2. Le script d’inspection va créer un modèle pour les tables utilisées pour les relations ManyToMany alors que là aussi, Django peut les générer de manière implicite dans la plupart des cas (lorsque cette table ne contient pas d‘information supplémentaire)

Je trouve préférable de laisser la gestion implicite des primary_key et des tables ManyToMany quand c’est possible, dans un soucis de concision et de lisibilité.

Néanmoins, celà introduit une inconsistance entre les modèles et leur représentation dans la base de donnée, inconsistance qui peut porter à confusion.

Plus de cohérence entre les modèles et la base de donnée

Plusieurs possibilités:

  • créer un script python qui va introspecter les modèles, et automatiquement rajouter les help_text comme commentaires aux champs, et les docstrings comme commentaires aux tables, ou vice-versa
  • Utiliser des Custom SQL
  • Créer des Custom fields
  • Pour le type ENUM, il existe un django snippet qui vise à apporter une meilleure cohérence dans son utilisation

Partagez vos astuces et faites part d’autres incohérences dans les commentaires!

Categories: django Tags:

MySQL et les modèles Django

24/02/2009 admin Comments off

Le problème

D’un côté, une base de donnée MySQL maintenue par MySQLWorkbench. De l’autre, Django et ses modèles.

Entre les deux… pas grand chose.

Les acquis

MySQL et MySQLWorkbench

MySQLWorkbench est un outil excellent pour pouvoir maintenir une base de donnée:

  1. Gestion des commentaires sur les tables et champs
  2. Affichage des relations entre les tables d’une base de données sous forme d’un schéma
  3. Facilité de modification d’une base de donnée en production par l’export de scripts SQL

Django et les modèles

Django, de son côté, nécéssite une représentation de la base de donnée par des modèles. Ces modèles sont des classes codées en Python:

  1. Facilité de suivi des modifications d’un modèle par le biais d’un contrôle de version (Git, Mercurial, SVN …) accompagné de commentaires pour chaque commit
  2. Facilité de documentation: un paramètre help_text pour chaque champ, un docstring pour chaque modèle
  3. Abstraction de certaines tables et champs générés automatiquement (comme les primary key ou les tables ManyToMany)

Les outils

Comme indiqué plus haut, MySQLWorkbench permet d’exporter des scripts SQL de création et/ou de modification. Il est aussi possible de reverse engineer le dump SQL d’une base de donnée, et ainsi créer toutes les tables dans l’interface graphique, et y visualiser les relations sous forme de schéma.

Django permet l’export de scripts SQL de création, mais aussi l’inspection d’une base de données (par la commande python manage.py inspectdb), ou encore l’exécution de scripts SQL custom à chaque création/modification d’une table (par la commande python manage.py syncdb).

Les méthodes

La méthode basique

… qui casse complètement le principe DRY (Don’t Repeat Yourself): maintenir les deux parties en parallèle.

Lorsqu’il faut rajouter une table dans la base, ou y apporter une modification, faire la modification dans MySQLWorkbench, puis répliquer l’ajout/la modification dans les modèles Django.

Pour: Méthode simple, pas de procédure ou méthode à mettre en place.
Contre: Méthode manuelle, sujette à l’erreur humaine (faute de typo, oubli…).

MySQLWorkbench => Django

  1. Maintenance dans l’outil MySQLWorkbench, puis export des scripts de création/modification.
  2. Execution de ce script sur la base de données pour la mettre à jour
  3. Utilisation de inspectdb avec Django pour mettre à jour les modèles

Pour: Méthode automatique, donc pas de risque de typo ou d’oubli
Contre: Méthode très complexe à mettre en place. En effet, inspectdb est loin de créer des modèles fidèles, et il manque de nombreuses informations (un futur billet sera écrit sur ce sujet).

Django => MySQLWorkbench

  1. Ajout ou modification d’un modèle dans Django
  2. Mise à jour de la base de donnée en utilisant python manage.py syncdb (et des scripts custom si nécéssaire)
  3. Utilisation de MySQLWorkbench pour reverse engineer les scripts de création de la base de donnée

Pour: Méthode automatique, donc pas de risque de typo ou d’oubli. De plus, contrairement à la méthode précédente, la représentation des tables est 100% fidèle à la structure de la base de donnée créée par les Django.
Contre: Toutes les informations de commentaires (et d’autres, comme nous le verrons dans un futur billet) sont perdues à chaque modification.

Conclusion

Je n’ai malheureusement pas trouvé à l’heure actuelle de méthode miracle. Il y a des pistes, mais il semble qu’il n’y ai rien de concret pour le moment.

Pour certains cas particuliers, l’une ou l’autre méthode sera préférée:

  1. Création d’une nouvelle table: la méthode Django => MySQLWorkbench sera facile à mettre en place, et il n’y aura rien de perdu vu qu’il n’y a pas d’existant
  2. Modification d’une table par le rajout d’un nouveau champ: l’une des deux méthodes automatique peuvent faire l’affaire
  3. Modification d’un champ d’une table: la méthode manuelle sera en général la plus rapide et la plus simple à mettre en place
  4. Modifications complexes, multiples, perte de donnée possible…: dans ce cas, point de salut, il ne reste que la méthode manuelle, et une grande concentration!

Et vous, quelle est votre méthode?

Categories: django Tags:

Django FileField et ImageField, upload_to et shell python

19/02/2009 admin 2 commentaires

Le paramètre upload_to des FileField et ImageField

Le champ upload_to permet d’indiquer où sauver un fichier de type django.core.files.File par rapport au MEDIA_ROOT spécifié dans les settings.

Ce champ peut être une chaîne de caractères, ou un callable.

Chaîne de caractères pour upload_to

Pour notre example, prenons le modèle suivant:

class MonModele(models.Model):
    fichier = models.FileField(upload_to="chemin")

Dans ce cas simple, tous les fichiers seronts sauvés dans le répertoire <MEDIA_ROOT>/chemin/.

Si on upload un fichier nommé MonFichier.txt, le chemin complet (sans le MEDIA_ROOT) sera chemin/MonFichier.txt.

Il est par ailleurs possible d’utiliser une syntaxe strftime comme indiqué dans la documentation du module time.

Pour stocker un fichier avec la date et l’heure:

class MonModele(models.Model):
    fichier = models.FileField(upload_to="chemin/%Y%m%d_%H%M%S")

Et le résultat sera chemin/2009-02-19_18:40:03/MonFichier.txt, ce qui n’est pas vraiment ce à quoi on s’attendait.

En effet, cela créera un répertoire par fichier, au lieu de stocker la date et l’heure dans le nom du fichier.

Pour arriver à nos fins, il nous faut utiliser une fonction pour le calcul du chemin de stockage du fichier.

Fonction pour upload_to

Utiliser une fonction pour le calcul du chemin de stockage permet:

  1. de modifier le nom du fichier lui-même, et pas seulement son répertoire de stockage
  2. d’utiliser des informations spécifiques à l’instance du modèle pour lequel on stocke le fichier

Il nous faut par contre respecter les contraintes suivantes:

  1. la fonction aura deux paramètres: self, l’instance, et filename, le nom du fichier uploadé
  2. il n’est plus possible d’utiliser directement la syntaxe strftime

Voici le code qui va stocker un fichier avec la date et l’heure (et prendra la date et l’heure automatiquement ajoutée au champ DateTimeField avec le paramètre auto_add_now):

class MonModele(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    def upload_path(self, filename):
        return 'chemin/%s_%s' % (self.date.strftime("%Y%m%d_%H%M%S"), filename)
    fichier = models.FileField(upload_to=upload_path)

Ce coup-ci, on obtient bien un fichier chemin/2009-02-19_18:40:03_MonFichier.txt, comme prévu.

Tester un modèle avec FileField dans l’interpréteur Python

Pour pouvoir tester un modèle avec un FileField dans un intepréteur Python (python manage.py shell), il y a quelques précautions à prendre, comme commencer par « sauver » le File, comme indiqué sur la page suivante.

En effet, pour que le modèle soit correctement instancié, il faut:

  1. créer une instance du modèle
  2. la sauver (dans notre cas, afin que la date soit automatiquement stockée, et devienne accessible dans upload_path)
  3. ouvrir le fichier à « uploader »
  4. créer un django.core.files.File à partir de ce fichier
  5. sauver ce fichier pour pouvoir ensuite le tester
>>> from mysite.models import *
>>> mm = MonModele()
>>> mm.save()
>>> mm.date
datetime.datetime(2009, 2, 19, 19, 4, 49, 538465)
>>> from django.core.files import File
>>> f = File(open("MonFichier.txt"))
>>> mm.fichier.save(f.name, f, save=False)
>>> mm.fichier
<FieldFile: chemin/20090219_190449_settings.py>

Pour « sauver » le fichier, il faut fournir à la méthode save le nom du fichier (f.name), le fichier lui-même (f), et un paramètre spécifiant si on veut sauvegarder le fichier dans la base de donnée ou pas.

Si nous ne voulions pas accéder à date qui est stockée automatiquement, il aurait été inutile de sauvegarder l’instance de MonModele (mm), et il aurait alors fallut remplacer le code de upload_path de la sorte

def upload_path(self, filename):
    return 'chemin/%s_%s' % (self.date.strftime("%Y%m%d_%H%M%S"), filename)

par

def upload_path(self, filename):
    import time
    return 'chemin/%s_%s' % (time.strftime("%Y%m%d_%H%M%S"), filename)
Categories: django Tags:

Django svn et mod_wsgi, attention au piège!

17/02/2009 admin 2 commentaires

Scénario

Notre cher utilisateur biboul se décide à installer la version de développement de django, que nous appellerons django-trunk, comme indiqué sur la page How To Install Django.

Il lance donc les commandes suivantes:

biboul@laptop:~$ svn co http://code.djangoproject.com/svn/django/trunk/ django-trunk
biboul@laptop:~$ ln -s `pwd`/django-trunk/django /usr/lib/python2.5/site-packages/django
biboul@laptop:~$ ln -s `pwd`/django-trunk/django/bin/django-admin.py /usr/local/bin

Il a bien entendu

  1. configuré son serveur apache pour utiliser le module WSGI
  2. testé avec le script wsgi « hello world » que la configuration était bonne
  3. modifié le script wsgi de manière à utiliser son application django mysite

Le problème

Lors de la première requête à son application mysite, une belle « 500 Internal Server Error » s’affiche, avec les messages d’erreurs suivants dans le fichier /var/log/apache2/error_log:

...  mod_wsgi (pid=5803): Target WSGI script '/opt/tcs/tcs.wsgi' cannot be loaded as Python module.
...  mod_wsgi (pid=5803): Exception occurred processing WSGI script '/opt/tcs/tcs.wsgi'.
...  Traceback (most recent call last):
...       File "/opt/tcs/tcs.wsgi", line 6, in <module>
...           import django.core.handlers.wsgi
...  ImportError: No module named django.core.handlers.wsgi

Mais pourquoi donc, alors qu’un import django.core.handlers.wsgi fonctionne correctement, que ce soit dans l’interpréteur ou dans le shell django?

La réponse

Tout simplement parce que le répertoire django-trunk (dont notre cher utilisateur biboul a fait un lien symbolique dans le répertoire site-packages) n’est pas accessible à un utilisateur différent, si il n’est pas dans le même group.
Or, par défaut et sur la plupart des distributions Linux, les processus apache sont lancé avec un utilisateur limité (www-data, www ou encore apache).

La solution

Un simple chmod 755 des répertoires parents au répertoire django-trunk est suffisant pour régler ce problème.
Une autre solution, plus propre et sécurisée, serait de placer le répertoire django-trunk dans le répertoire /opt, et de modifier les liens symboliques pour utiliser ce nouvel emplacement.

Categories: django, mod_wsgi Tags: