Sam & Max » static files http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Servir les fichiers statiques Django avec un .htaccess Apache 4 http://sametmax.com/servir-les-fichiers-statiques-django-avec-un-httaccess-apache/ http://sametmax.com/servir-les-fichiers-statiques-django-avec-un-httaccess-apache/#comments Fri, 30 Aug 2013 18:14:43 +0000 http://sametmax.com/?p=7235 comment servir les fichiers statiques Django, mais j'ai réalisé ensuite que les gens n'avaient pas forcément accès aux fichiers de configuration du serveur.]]> Servir le Javascript, les images et le CSS avec Django a toujours été une grande question pour les débutants.

J’avais fais un giga article pour expliquer comment servir les fichiers statiques avec ce framework, mais j’ai réalisé ensuite que les gens n’avaient pas forcément accès aux fichiers de configuration du serveur.

Parfois, tout ce qu’ils ont c’est un pauvre fichier .htaccess. Tout n’est pas perdu, avec une petite règle de rewrite :

AddHandler fcgid-script .fcgi
RewriteEngine On
RewriteRule /static/ /chemin/vers/dossier/racine/apache/static
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ site.fcgi/$1 [QSA,L]

Attention à ce que /static/ corresponde bien à l’arborescence complète de sous dossiers de /chemin/vers/dossier/racine/apache/ et à settings.STATIC_ROOT.

Par exemple :

AddHandler fcgid-script .fcgi
RewriteEngine On
RewriteRule /sous/dossier/static/ /chemin/vers/dossier/racine/apache/sous/dossier/static
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ site.fcgi/$1 [QSA,L]

Et settings.STATIC_ROOT = "/sous/dossier/static/".

En effet, on fait un rewrite ici, on ne sert pas le sous dossier directement.

P.S: ça va sans le dire, mais ça va mieux en le disant, c’est de la prod, donc faut faire un manage.py collecstatic avant, et s’assurer d’avoir les bonnes valeurs pour settings.STATIC_ROOT et settings.STATIC_URL/

]]>
http://sametmax.com/servir-les-fichiers-statiques-django-avec-un-httaccess-apache/feed/ 4
Comment servir les fichiers statiques avec Django en dev et en prod 13 http://sametmax.com/comment-servir-les-fichiers-statiques-avec-django-en-dev-et-en-prod/ http://sametmax.com/comment-servir-les-fichiers-statiques-avec-django-en-dev-et-en-prod/#comments Mon, 22 Oct 2012 22:57:28 +0000 http://sametmax.com/?p=2706 Servir les fichiers CSS, javascript et les images avec Django a toujours été la plus grande source de confusion (heureusement ça s’est bien amélioré avec la 1.4, donc upgradez si vous pouvez). C’est d’autant plus déroutant que la manière de faire est différente selon la version de Django, et selon que l’on est en production ou en développement.

Donc, pour profiter de cet article:

  • Lisez les parties qui vous concernent uniquement.
  • Ne lisez pas en diagonale.
  • Si ça ne marche pas, partez de zéro (avec un projet tout neuf), et une fois que ça marche, essayez sur le votre.

C’est typiquement le cas où il faut comprendre ce qui se passe et pas juste copier/coller du code (ceci est une petite pique à Max qui est en train de décuver comme une loque sur le canap pendant que j’écris cette partie de l’article).

Préparation

Les fichiers statiques se gèrent à base de chemins absolus. Afin de faciliter celà, vous devriez donc toujours avoir ceci tout en haut de votre fichier settings.py:

import os
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

Si votre fichier de settings n’est pas à la racine du projet (typiquement dans un projet Django 1.4), faites:

import os
SETTINGS_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(SETTINGS_DIR)

Le fichiers de settings étant un fichier Python, on peut mettre tout le code que l’on souhaite dedans, et ce code nous permet d’obtenir de manière dynamique le chemin absolu vers votre dossier de projet. Il servira de base pour tous les chemins vers les fichiers statiques.

__file__ contient le chemin vers le fichier en cours. abspath() permet d’obtenir le chemin absolu. dirname() permet d’obtenir le nom du dossier contenant le fichier.

Notez aussi l’emplacement des fichiers statiques de la Django admin dans un coin (dans la doc de votre site par exemple). Ces fichiers se trouvent dans django/contrib/admin/media, mais si vous galerez pour les trouver, vous pouvez toujours lancer la commande:

python -c "import django, os; print os.path.join(os.path.dirname(django.__file__), 'contrib/admin/media')"

Moi ça donne un truc comme ça:

/home/sam/.virtualenvs/project_env/local/lib/python2.7/site-packages/django/contrib/admin/media

En phase de développement, de la version 1.0 à la version 1.2

Vous êtes sur votre machine, et vous utilisez la commande runserver. C’est donc Django qui va servir vos fichier statiques, et nous allons voir comment, pour chaque version de Django, on peut lui demander de le faire.

C’est une vieille version, servir les fichiers statiques est assez relou, alors on va gruger pour vous faciliter la vie. Non, ce n’est pas la meilleur manière de faire, mais c’est la manière de faire qui vous évitera de retourner sur un forum pour demander pourquoi ça ne marche pas.

Créez un dossier nommé “static” à la racine de votre projet. Mettez tous vos fichiers statiques dedans: js, css, images… Organisez ça en sous-dossier si vous voulez, mais mettez tout dedans, y compris les fichiers statiques des autres apps Django que vous avez installé (et qu’il va falloir copier/coller ou alors faire un lien symbolique). La seule exception est l’admin.

Ce n’est pas particulièrement propre ni pratique, mais ça va marcher. Le surcoût en maintenant sera largement compensé par le fait que ce sera très simple à comprendre et à servir.

Dans votre fichier de settings, pointez MEDIA_ROOT sur ce dossier:

MEDIA_ROOT = os.path.join(PROJECT_DIR, 'static')

Et choissisez un chemin explicite pour MEDIA_URL:

MEDIA_URL = '/static/'

Ne touchez pas à ADMIN_MEDIA_PREFIX. Ne mettez pas MEDIA_URL égale à ADMIN_MEDIA_PREFIX.

Dans le fichier urls.py principal, c’est à dire ce lui est généralement à la racine de votre projet, faites:

# on importe ici tout les trucs nécessaires à urls.py
from django.conf.urls.defaults import *
# on va récupérer MEDIA_ROOT depuis le fichier de settngs
from django.conf import settings
 
# on active l'admin
from django.contrib import admin
admin.autodiscover()
 
# on créé un pattern vide. Cela va nous permettre d'insérer le service
# des fichiers statiques en premier
urlpatterns = patterns('')
 
# On active maintenant le service des fichiers statiques par Django
# mais uniquement en mode DEBUG (pour éviter de l'oublier en prod)
if settings.DEBUG:
    urlpatterns += patterns('',
        (r'%s/(?P<path>.*)$' % settings.MEDIA_URL.strip('/'),
         'django.views.static.serve',
         {'document_root': settings.MEDIA_ROOT}),
    )
 
# Ensuite on déclare nos patterns normalement
# ATTENTION, le signe doit-être "+=", pas "=", sinon vous allez tout écraser
urlpatterns += patterns('',
    # Example:
    (r'^', include('app.urls')),
 
    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
    # to INSTALLED_APPS to enable admin documentation:
    (r'^admin/doc/', include('django.contrib.admindocs.urls')),
 
    # Uncomment the next line to enable the admin:
    (r'^admin/(.*)', admin.site.root),
)

Vous notez que contrairement à ce qu’on voit la doc officielle, on met l’url pour les fichiers statiques en premier. Cela donne une forme étrange à la déclation des urls car on a un urlpatterns vide au début, mais c’est très important: à un moment vous allez déclarer une URl attrape-tout qui court-circuitera tout ce qu’il y a après, et ce jour là vous passerez des heures à chercher pourquoi vos fichiers statiques ne sont pas déclarés.

Le strip() n’est pas obligatoire si vous mettez les slashes correctement partout. Mais ce n’est jamais le cas.

Maintenant, dans votre html, vous pouvez faire ceci:

<link href="/static/chemin/du/fichier/a/relatif/au/dossier/static.css"  rel="stylesheet" />

Et pareil pour le js, les images, etc.

Le chemin est bel et bien écrit en dur dans le templates HTML. Encore une fois, ce n’est pas la meilleure manière de faire, c’est juste celle qui vous garanti que ça marche. Si vous vous sentez à l’aise avec le processus, vous pouvez changer celà en utilisant un context_processor pour ajouter MEDIA_URL à chaque template et remplacer /static/ par {{ MEDIA_URL }}.

Télécharger le projet complet d’exemple

En phase de développement, pour la version 1.3 et 1.4

C’est globalement beaucoup plus facile. Il suffit de mettre tous vos fichiers statiques dans un dossier nommé “static” dans le dossier d’une de vos app pour qu’ils soient trouvés automatiquement quand vous lancerez manage.py runserver.

Voici à quoi ressemblera votre settings.py:

# plus rien à voir avec avant, MEDIA_ROOT et MEDIA_URL designent
# l'endroit où vous voulez que vos utilisateur upload et download des fichiers
# Ce n'est donc plus un paramètre primordial
MEDIA_ROOT = os.path.join(PROJECT_DIR, 'media')
MEDIA_URL = '/media/'
 
# Ce qui s'appelait avant MEDIA_ROOT s'appelle maintenant STATIC_ROOT
# mais il n'est utile pour la prod
# On le met quand même pour plus tard
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static')
 
# Le settings le plus important: à quelle adresse servir les fichiers statiques
# Utile en dev afin de pouvoir bénéficier des templates tags
STATIC_URL = '/static/'
 
# Les medias de l'admin sont enfin une URL comme une autre qu'on peut
# mettre sous notre propre arborescence
ADMIN_MEDIA_PREFIX = '/static/admin/'
 
# Vous pouvez OPTIONNELLEMENT rajouter ici une liste de chemin vers
# des dossiers de fichiers statiques qui seront ainsi aussi servit automatiquement
# pendant le développement. Par exemple un dossier à la racine
STATICFILES_DIRS = (
    os.path.join(PROJECT_DIR, 'more_static'),
)
# N'utilisez PAS le même dossier que STATIC_ROOT

Il n’y a rien à faire de plus, vos fichiers seront servis automatiquement en dev, pas besoin de toucher aux urls.

Maintenant il faut juste les déclarer vos URLs dans le template.

Pour Django 1.3, vous pouvez ajouter un fichiers statiques ainsi pour éviter d’écrire l’URL en dur:

{% load static %}
{% get_static_prefix as STATIC_PREFIX %}
 
<link href="{{ STATIC_PREFIX }}app/css/style.css"  rel="stylesheet" />

Pour django 1.4, c’est encore plus simple:

{% load staticfiles %}
<img src="{% static "app/css/style.css" %}" />

Encore un détail, si vous n’utilisez pas le serveur de dev Django mais un autre serveur de dev (ex: werkzeug), vos fichiers statiques ne seront pas servis automatiquement, mais il est facile d’y remédier. Ajoutez à votre urls.py:

from django.contrib.staticfiles.urls import staticfiles_urlpatterns
 
urlpatterns += staticfiles_urlpatterns()

Télécharger le projet complet d’exemple

Migration de 1.2 ou moins vers 1.3 ou 1.4

Bouger vos fichiers statiques du répertoire à la racine vers le répertoire nommé “static” d’une de vos apps (ou de plusieurs).

Ajoutez ceci dans votre fichier de config:

STATICFILES_DIRS = (
)
 
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)

Et ajoutez django.contrib.sites à vos INSTALLED_APPS.

Remplacez toutes les URls en dur dans le HTML par une version avec les templates tags (voir partie précédente).

Attention, ça ne couvre que la partie pour la gestion des fichiers statiques, il y a d’autres choses à migrer.

En production: toutes versions

Je ne vais pas expliquer ici comment mettre Django en production (c’est un article à lui tout seul, peut être même plusieurs). Ici je vais me concentrer sur comment servir les fichiers statiques, et présupposer DEBUG = False.

Si vous utilisez Djagno 1.2 ou moins, vous n’avez rien à faire côté Django. Si vous utilisez Django 1.3 ou 1.4, vous devez maintenant appeler la commande pyhon manage.py collectstatic. Celle-ci va prendre tous les fichiers statiques de tous les dossiers “static” des apps et ceux listés dans STATICFILES_DIRS et va les copier dans STATIC_ROOT.

Dans tous les cas, vous n’aurez qu’un seul dossier pour le js, le css et les images à servir avec Nginx ou Apache.

Nginx

Si vous avez votre propre serveur, je vous conseil Nginx plutôt qu’Apache pour mettre votre site en prod.

L’idée est de dire dans le fichier de configuration nginx que toutes les URLs qui concernent les fichiers statiques sont servies directement par Nginx, et les autres sont passées à Django:

server {
        listen      8080;
 
        ############################################################
        # la partie qui concerne les fichiers statiques commence ici
        ############################################################
 
        location /favicon.ico {
            # favicon.ico doit être à la racine du dossier img
            root  /chemin/absolu/vers/dossier/img;
        }
 
        # on sert nos fichiers statiques
        # l'url doit correspondre à MEDIA_URL
        location ~* ^/static/ {
            root  /chemin/absolu/vers/dossier/PARENT/du/dossier/statique;
            # on active gzip pour tous les petits fichiers qui contiennent du texte
            # si le navigateur le supporte
            gzip  on;
            gzip_http_version 1.0;
            gzip_vary on;
            gzip_comp_level 6;
            gzip_proxied any;
            gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
            # petite astuce gzip pour les gros fichier
            # voir http://blog.leetsoft.com/2007/7/25/nginx-gzip-ssl
            gzip_buffers 16 8k;
 
            # on desactive gzip pour les vieux navigateurs
            gzip_disable ~@~\MSIE [1-6].(?!.*SV1)~@~];
        }
 
       # on fait pareil mais pour l'admin Django
       # l'url doit corresponde à ADMIN_MEDIA_PREFIX
       location ~* ^/media/ {
            root  /chemin/absolu/du/dossier/PARENT/du/dossier/media/de/django;
            gzip  on;
            gzip_http_version 1.0;
            gzip_vary on;
            gzip_comp_level 6;
            gzip_proxied any;
            gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
            gzip_buffers 16 8k;
            gzip_disable ~@~\MSIE [1-6].(?!.*SV1)~@~];
        }
 
        ##############################################################
        # la partie qui concerne les fichiers statiques se termine ici
        ##############################################################
 
        location / {
            proxy_pass http://127.0.0.1:8000;
        }
}

Notez bien que la directive root suppose que vous mettiez le dossier parent du dossier que vous voulez servir, et que le dossier servi a le même nom que le chemin dans l’url.

Par exemple, si vos fichiers statiques sont dans /home/sam/project/static, alors l’url devra être ^/static/ et on passera à l’instruction root le chemin /sam/project.

Apache

Si vous êtes chez un hébergeur qui n’a qu’Apache, voici un manière de servir les fichiers statiques. Je ne connais pas Apache aussi bien que Nginx, donc je ne peux pas m’engager sur la gestion de Gzip.

# TOUS les chemins doivent être accessible pour le user apache (www-data le plus souvent)
# N'oubliez pas les slashes finaux, ils sont importants
 
WSGIPythonPath /chemin/absolu/vers/project/:/chemin/absolu/vers/dossier/parent/du/project/:/chemin/vers/site/package/du/virtualenv/
 
<VirtualHost *:80>
 
    WSGIScriptAlias / "/chemin/absolu/vers/fichier/project.wsgi"
 
    ########################################################
    # Début de la partie qui concerne les fichiers statiques
    ########################################################
 
    # on sert les fichiers statiques du site
    Alias /static/ "/chemin/absolu/vers/dossier/static/"
    Alias /favicon.ico "/chemin/absolu/vers/dossier/static/img/favicon.ico"
 
    # on donne l'autorisation à tous de les lire
    <Directory "/chemin/absolu/vers/dossier/static/">
        Order allow,deny
        Allow from all
    </Directory>
 
    # on sert les fichiers statiques de l'admin
    Alias /media/ "/chemin/absolu/vers/dossier/django/contrib/admin/media/"
    # on donne l'autorisation à tous de les lire
    <Directory "/chemin/absolu/vers/dossier/django/contrib/admin/media/">
        Order allow,deny
        Allow from all
    </Directory>
 
    ######################################################
    # Fin de la partie qui concerne les fichiers statiques
    ######################################################
 
</VirtualHost>

Si vous faites bien gaffe aux droits d’accès des dossiers (récursivement) et que vous avec pas oublié de “/” à la fin d’un chemin, tout ira bien.

Pour Apache, servir les fichiers statiques est généralement la partie facile, c’est faire marcher Django et mod_wsgi qui est difficile: problèmes de droits, mauvaise configuration, Python path qui foine… Mais je m’en branle, puisque c’est pas l’objet de l’article, donc démerdez-vous.

Notes de fin

Jusqu’à la version 1.2, MEDIA_ROOT et MEDIA_URL ne sont que des conventions qui ne servent pas à grand chose à part à être utilisées par des contexts managers et à vous éviter les chemins en durs dans les templates et urls.py.

A partir de Django 1.3, STATIC_ROOT et STATIC_URL sont vraiment utilisées: STATIC_ROOT est là où Django va mettre le resultat de la commande collecstatic et STATIC_URL et l’url que va utiliser la vue staticfiles_urlpatterns.

MEDIA_ROOT et MEDIA_URL existent toujours, mais sont utilisés pour designer le dossier et l’url qui pointent vers les contenus uploadés et downlodés par les utilisateurs. Si vous voulez les servir, il faut faire exactement la même chose à l’époque de Django 1.2 et avant: mettre la route à la main dans urls.py (mais cette fois, il y a un raccourcis pour ça), et rajouter une entrée supplémentaire dans le fichier de conf Nginx ou Apache.

Sinon, vous pouvez aussi vous éviter tout ce merdier en utilisant le middleware qui sert automatiquement tous les fichiers statiques de django_quicky.

Enfin, je n’ai pas abordé les storage backends, tout simplement parce que je n’en utilise pas, donc je ne peux pas vous donner de bons conseils sur la question.

]]>
http://sametmax.com/comment-servir-les-fichiers-statiques-avec-django-en-dev-et-en-prod/feed/ 13