He estado tratando de usar la función de procesamiento de vista condicional de Django. Básicamente, quiero denegar las operaciones de actualización en una entidad si desde entonces ha sido modificada por otro usuario, y eso parece funcionar bien con el decorador de condiciones @ proporcionado por Django.
Sin embargo, hay un problema que noté mientras lo probaba y luego revisé las fuentes de Django y encontré lo que creo que podría ser un error, pero solo quería confirmar aquí primero antes de enviar un informe de error a Django y una solución.
Se llama al decorador cuando llega una nueva solicitud, primero calcula la ETag y la marca de tiempo de Última modificación en función de las funciones que se le pasaron al decorador, luego pasa el control a la función get_conditional_response() . Aquí se realizaría la verificación de ETag y Última modificación y, si no coinciden con lo proporcionado en la solicitud, se denegará la solicitud. Hasta aquí todo bien.
Si se aprueban las comprobaciones, se permite la solicitud y se llama a la vista para procesar la solicitud y generar la respuesta. Mientras se procesaba la solicitud, si se trataba de un método inseguro, por ejemplo, PUT o PATCH , actualizaría la entidad, lo que probablemente cambiaría los valores de ETag y Última modificación.
Sin embargo, noté que una respuesta exitosa a PUT o PATCH se devuelve con la marca de tiempo ETag o Last Modified calculada antes de que se realizara la actualización, y ahora estos valores no son válidos o están obsoletos. Esto me parece mal. Hacer un GET nuevo en la misma entidad le proporciona al usuario valores actualizados de ETag y Última modificación en la respuesta.
¿No cree que el decorador condition() debería verificar si el método de solicitud no es seguro, luego debería hacer un cálculo nuevo de ETag y Last Modified después del procesamiento de la vista, y luego agregar los valores nuevos a la respuesta?
Estoy de acuerdo en que hay un error aquí, aunque creo que es un poco diferente de lo que describes.
Las solicitudes condicionales se definen en RFC 7232 , pero desafortunadamente ese documento no es muy explícito acerca de cuándo exactamente se deben usar los encabezados condicionales en una respuesta. Dice :
2.4. Cuándo usar etiquetas de entidad y fechas de última modificación
En 200 (OK) respuestas a GET o HEAD, un servidor de origen...
Eso podría llevar a suponer que el uso de los encabezados no está definido en otras respuestas.
Sin embargo, RFC 7231 permite explícitamente el uso de ETags en la respuesta a un PUT , coincidiendo con la nueva representación (como era su intuición). Sin embargo, tenga en cuenta esta advertencia :
Un servidor de origen NO DEBE enviar un campo de encabezado de validación (Sección 7.2), como una ETag o un campo de Última modificación, en una respuesta exitosa a
PUT, a menos que los datos de representación de la solicitud se hayan guardado sin ninguna transformación aplicada al cuerpo...
Es decir, el cliente usará la presencia o ausencia de la ETag para determinar si su representación (que acaba de enviar como el cuerpo a PUT ) fue o no la que realmente se almacenó. (Consulte esta pregunta para obtener más detalles sobre este punto).
Sin embargo, la API de solicitud condicional de Django no permite hacer esta distinción. Específicamente, no hay forma de que el usuario indique si una vista guardó o no la representación sin "transformación aplicada al cuerpo". Por lo tanto, no hay forma de que el decorador condition() sepa si se justifica o no agregar una ETag.
Entonces, lo único que debe hacer es ser conservador y no devolver encabezados condicionales en este caso. Siéntete libre de crear un ticket (o puedo hacerlo yo).
Cree un middleware personalizado para manejar etag en la solicitud GET/HEAD. El siguiente código ( Django 1.10 ) muestra cómo crear y procesar etag usando middleware.
Nota: no habilite USE_ETAGS en el archivo de configuración
from django.utils.cache import get_conditional_response, set_response_etag from django.utils.http import unquote_etag class ETag(object): def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. def __call__(self, request): # before view response = self.get_response(request) # after view try: if request.method in ('GET', 'HEAD'): if not response.has_header('ETag'): set_response_etag(response) etag = response.get('ETag') return get_conditional_response( request, etag=unquote_etag(etag), last_modified=None, response=response, ) except Exception, e: pass return response Estoy usando Django 1.10. Si está utilizando versiones inferiores, anule el método process_response(self, request, response) con la lógica implementada dentro del método __call__ . Y no olvide agregar esto a MIDDLEWARE/MIDDLEWARE_CLASSES en el archivo de configuración
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # myapp contains middleware.py file and # ETag class is implemented inside the middleware.py file 'myapp.middleware.Etag', ]