2012-02-03 29 views
24

estoy trabajando en alguna solución universal para el problema con los archivos estáticos y actualizaciones en elladjango archivos estáticos de versiones

Ejemplo: permite decir que no hubo sitio con /static/styles.css archivo - y el sitio fue utilizado para una mucho tiempo - por lo que una gran cantidad de visitantes en caché este archivo en el navegador

Ahora estamos haciendo cambios en este archivo CSS, y la actualización en el servidor, pero algunos usuarios todavía tienen versión antigua (a pesar de la fecha de modificación devuelta por el servidor)

La solución obvia: agregue alguna versión al archivo /static/styles.css?v=1.1

pero en este caso desarrollador debe realizar un seguimiento de los cambios en este archivo y aumentar manualmente la versión

solución de 2 - cuenta hash MD5 del fichero y añadir a la URL /static/styels.css/?v={mdp5hashvalue}

la que se ve mucho mejor, pero MD5 se debe calcular de alguna forma automática ..

que sea posible modo de ver - crear un poco de etiqueta de plantilla como esta

{% static_file "style.css" %} 

la que rendirá

<link src="/static/style.css?v=md5hash"> 

PERO, no quiero esta etiqueta para calcular MD5 en cada carga de página, y yo no quiero para almacenar hachís en django-caché, porque entonces vamos a tener que limpiar después de la actualización de archivos ..

alguna idea?

+1

Ya hay muchas soluciones para este problema, comience desde allí, por ejemplo. http://djangopackages.com/grids/g/static-builders/ como @ChrisPratt sugiere! – Stefano

Respuesta

24

Django 1.4 incluye ahora CachedStaticFilesStorage que hace exactamente lo que necesita (bien ... casi).

Lo utiliza con la tarea manage.py collectstatic. Todos los archivos estáticos se recopilan de sus aplicaciones, como de costumbre, pero este administrador de almacenamiento también crea una copia de cada archivo con el hash MD5 adjunto al nombre. Entonces, por ejemplo, digamos que tiene un archivo css/styles.css, también creará algo como css/styles.55e7cbb9ba48.css.

Por supuesto, como mencionó, el problema es que no desea que sus vistas y plantillas calculen el hash MD5 todo el tiempo para encontrar las URL adecuadas para generar. La solución es el almacenamiento en caché. Ok, usted pidió una solución sin almacenamiento en caché, lo siento, es por eso que dije casi. Pero no hay ninguna razón para rechazar el almacenamiento en caché, realmente. CachedStaticFilesStorage usa un caché específico llamado staticfiles. De forma predeterminada, usará su sistema de caché existente y ¡voilà! Pero si no quiere que use su caché regular, quizás porque es un Memcache distribuido y desea evitar la sobrecarga de las consultas de red solo para obtener nombres de archivos estáticos, entonces puede configurar un caché de RAM específico solo para staticfiles. Es más fácil de lo que parece: echa un vistazo a this excellent blog post. Así es como se vería:

CACHES = { 
    'default': { 
    'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 
    'LOCATION': '127.0.0.1:11211', 
    }, 
    'staticfiles': { 
    'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 
    'LOCATION': 'staticfiles-filehashes' 
    } 
} 
+0

Para lectores futuros, [esta publicación] (https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#cachedstaticfilesstorage) me ayudó a implementar esto de manera rápida y sencilla – Anupam

1

¿Qué tal si siempre tiene un parámetro de URL en su URL con una versión y cada vez que tenga una versión principal, cambie la versión en su parámetro de URL. Incluso en el DNS. Así que si www.yourwebsite.com cargas de hasta www.yourwebsite.com/index.html?version=1.0 continuación, después de la versión principal del navegador debe cargar www.yourwebsite.com/index.html?version=2.0

supongo que esto es similar a la solución 1. En vez de seguimiento de los archivos se puede realizar un seguimiento de directorios enteros? Por ejemplo, ratehr than /static/style/css?v=2.0 puede hacer /static-2/style/css o hacerlo incluso granular /static/style/cssv2/.

13

Sugeriría usar algo como django-compressor. Además de manejar automáticamente este tipo de cosas para usted, también combinará y miniaturizará automáticamente sus archivos para una carga de página más rápida.

Aunque no termine de usarlo en su totalidad, puede inspeccionar su código para obtener ayuda en la configuración de algo similar. Ha sido mejor investigado que cualquier cosa que puedas obtener de una respuesta simple de StackOverflow.

+0

Solo para el registro, django-compressor usa tiempos de modificación de archivos para este propósito, de manera similar a la recomendación en la respuesta de @ssbb - https://github.com/django-compressor/django-compressor/blob/develop/compressor/ storage.py – Wtower

+0

Puede usar esta solución incluso si está haciendo su compresión js manualmente en otro lugar, usando un compresor liviano con django como Closure Compiler con configuración WHITESPACE_ONLY, para que pueda aprovechar la asignación de nombres estáticos/de caché. –

8

Puedo usar mi propio templatetag que se suman a la fecha de modificación del archivo url: https://bitbucket.org/ad3w/django-sstatic

+1

La mejor respuesta de todas, me salvó el día (y parte de mi vida <3). Simple, fácil y directo. –

+0

En lugar de construir la ruta usted mismo, es mejor dejar que django lo busque: de django.contrib.staticfiles.finders importar buscar full_path = find (ruta) –

5

Está reinventando la rueda y creando una implementación propia tan mala? Además, me gustaría que el código de bajo nivel (nginx, por ejemplo) sirva mis archivos estáticos en producción en lugar de python, incluso con back-end. Y una cosa más: me gustaría que los enlaces permanezcan igual después del nuevo cálculo, por lo que el navegador solo busca nuevos archivos. Así here's punto de vista mío:

template.html:

{% load md5url %} 
<script src="{% md5url "example.js" %}"/> 

cabo html:

static/example.js?v=5e52bfd3 

settings.py:

STATIC_URL = '/static/' 
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') 

nombreaplic/templatetags/md5url.py:

import hashlib 
import threading 
from os import path 
from django import template 
from django.conf import settings 

register = template.Library() 


class UrlCache(object): 
    _md5_sum = {} 
    _lock = threading.Lock() 

    @classmethod 
    def get_md5(cls, file): 
     try: 
      return cls._md5_sum[file] 
     except KeyError: 
      with cls._lock: 
       try: 
        md5 = cls.calc_md5(path.join(settings.STATIC_ROOT, file))[:8] 
        value = '%s%s?v=%s' % (settings.STATIC_URL, file, md5) 
       except IsADirectoryError: 
        value = settings.STATIC_URL + file 
       cls._md5_sum[file] = value 
       return value 

    @classmethod 
    def calc_md5(cls, file_path): 
     with open(file_path, 'rb') as fh: 
      m = hashlib.md5() 
      while True: 
       data = fh.read(8192) 
       if not data: 
        break 
       m.update(data) 
      return m.hexdigest() 


@register.simple_tag 
def md5url(model_object): 
    return UrlCache.get_md5(model_object) 

Nota, para aplicar los cambios uwsgi una aplicación (que es un proceso específico) debe ser reiniciado.

+0

Esto es agradable, pero ¿este enfoque no hace que md5 se calcule en cada solicitud? – Wtower

+0

No, el resultado se coloca en campos de clases estáticas. Luego tratamos de obtenerlo: cls._md5_sum [archivo] – deathangel908

+0

Sí, notado después de mi comentario. Gracias. – Wtower

1

Hay una actualización para @ deathangel908 code. Ahora funciona bien con el almacenamiento S3 también (y con cualquier otro almacenamiento, creo). La diferencia es usar el almacenamiento de archivos estáticos para obtener contenido de archivos. Original no funciona en S3.

nombreaplic/templatetags/md5url.py:

import hashlib 
import threading 
from django import template 
from django.conf import settings 
from django.contrib.staticfiles.storage import staticfiles_storage 

register = template.Library() 


class UrlCache(object): 
    _md5_sum = {} 
    _lock = threading.Lock() 

    @classmethod 
    def get_md5(cls, file): 
     try: 
      return cls._md5_sum[file] 
     except KeyError: 
      with cls._lock: 
       try: 
        md5 = cls.calc_md5(file)[:8] 
        value = '%s%s?v=%s' % (settings.STATIC_URL, file, md5) 
       except OSError: 
        value = settings.STATIC_URL + file 
       cls._md5_sum[file] = value 
       return value 

    @classmethod 
    def calc_md5(cls, file_path): 
     with staticfiles_storage.open(file_path, 'rb') as fh: 
      m = hashlib.md5() 
      while True: 
       data = fh.read(8192) 
       if not data: 
        break 
       m.update(data) 
      return m.hexdigest() 


@register.simple_tag 
def md5url(model_object): 
    return UrlCache.get_md5(model_object) 
2

La principal ventaja de esta solución: usted no tiene que modificar nada en las plantillas.

Esto agregará la versión de compilación al STATIC_URL, y luego el servidor web lo eliminará con una regla Rewrite.

configuraciones.py

# build version, it's increased with each build 
VERSION_STAMP = __versionstr__.replace(".", "") 
# rewrite static url to contain the number 
STATIC_URL = '%sversion%s/' % (STATIC_URL, VERSION_STAMP) 

Así que la URL final sería, por ejemplo, esto:

/static/version010/style.css 

Y luego Nginx tiene una regla de reescribir de nuevo a /static/style.css templatetag

location /static { 
    alias /var/www/website/static/; 
    rewrite ^(.*)/version([\.0-9]+)/(.*)$ $1/$3; 
} 
1

simple vstatic que crea versionado URL de archivos estáticos que extiende el comportamiento de Django:

from django.conf import settings 
from django.contrib.staticfiles.templatetags.staticfiles import static 

@register.simple_tag 
def vstatic(path): 
    url = static(path) 
    static_version = getattr(settings, 'STATIC_VERSION', '') 
    if static_version: 
     url += '?v=' + static_version 
    return url 

Si desea establecer automáticamente STATIC_VERSION al git actual comprometerse de hash, puede utilizar el siguiente fragmento (código python3 ajuste si es necesario):

import subprocess 


def get_current_commit_hash(): 
    try: 
     return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip().decode('utf-8') 
    except: 
     return '' 

En settings.py llamada get_current_commit_hash(), así que esto sólo se calculará una vez:

STATIC_VERSION = get_current_commit_hash() 
Cuestiones relacionadas