ГЛАВНАЯ      ДОКУМЕНТАЦИЯ      СТАТЬИ      ПРОГРАММЫ      ССЫЛКИ      ФОРУМ      ДРУГОЕ   

Трансляция charset в объектах request и response в Django

В одном из Django-приложений, которое я разрабатываю, возникла ситуация, когда очень хочется, чтобы сайт общался с клиентами в кодировке UTF-8.
При этом стандартной кодировкой системы (шаблоны, данные в базе данных), в которой работает это приложение, является Windows-1251. Конвертировать данные в базе и т.п. - not an option, т.к. с этими данными работает еще куча уже написанного софта, который так привык. Соответственно, во избежание глюков с кодировкой, желательно, чтобы DEFAULT_CHARSET был Windows-1251, и все внутренние операции со строками и базой были в этой кодировке.
Что делаем? Пишем middleware.
# module proj.middleware.translationmiddleware
from django.conf import settings
from django.http import *
from urllib import quote, unquote

import re
CHARSET_RE = re.compile(r'(.+; charset=)(.+)', re.IGNORECASE)
SKIP_CONTENT_FOR = (HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseNotModified, HttpResponseNotAllowed)

class TranslationMiddleware(object):
    def __init__(self):
        self.trCset = getattr(settings, 'TRANSLATE_CHARSET', 'UTF-8')
        self.defCset = settings.DEFAULT_CHARSET
   
    def tr(self, data, _from, _to):
        if _to == _from:
            return data
        return data.decode(_from).encode(_to)       
   
    def process_request(self, request):
        GET = request.GET.copy()
        POST = request.POST.copy()
       
        for dic in (GET, POST):
            for key in dic:
                newlist = []
                for val in dic.getlist(key):
                    newlist.append(self.tr(val, self.trCset, self.defCset))
                dic.setlist(key, newlist)
            dic._mutable = False
       
        request.GET, request.POST = GET, POST
        if hasattr(request, '_request'):
            del(request._request)
           
        for key in request.COOKIES:
            request.COOKIES[key] = self.tr(request.COOKIES[key], self.trCset, self.defCset)
       
           
    def process_response(self, request, response):
        klass = response.__class__
        if klass not in SKIP_CONTENT_FOR:
            new_response = klass()
            new_response.content = self.tr(response.content, self.defCset, self.trCset)
        else:
            new_response = response
           
        new_response._charset = self.trCset
       
        for key in response.headers:
            if key == 'Content-Type':
                ctype = CHARSET_RE.sub(r'\1%s' % self.trCset, response.headers[key])
                new_response[key] = ctype
            elif key == 'Location':
                value = unquote(response.headers[key])
                value = self.tr(value, self.defCset, self.trCset)
                new_response[key] = quote(value, RESERVED_CHARS)
            else:
                new_response[key] = self.tr(response.headers[key], self.defCset, self.trCset)
       
        for key in response.cookies:
            value = self.tr(response.cookies[key].value, self.defCset, self.trCset)
            kwargs = {}
            for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
                kwargs[var] = response.cookies[key].get(var.replace('_', '-'), None)
               
            new_response.set_cookie(key, value, **kwargs)       
           
        return new_response
В данном случае мне нужно было транслировать из и в UTF-8, но это можно переопределить в settings.py проекта, установив переменную TRANSLATE_CHARSET. Значение кодировки для "внутреннего пользования" и для трансляции из и во внешний мир устанавливаются при инициализации объекта middleware.
Middleware определяет методы process_request и process_response, для транслирования кодировки в данных пришедшего запроса и для перекодировки ответа клиенту, соответственно.
К моменту запуска process_request мы уже имеем готовый объект request с заполненными объектами request.GET и request.POST. Эти объекты - неизменяемые экземпляры класса QueryDict, так что сначала делаются их копии, которые можно изменять. После трансляции данных в нужную внутреннюю кодировку, новым объектам тоже ставится флаг неизменяемости, и они присваиваются вместо старых request.GET и request.POST. request.FILES, конечно, не трогаем, как и request.raw_post_data — эти данные остаются в оригинальной кодировке, так что если будет нужно что-то получать из них, с кодировкой нужно будет разбираться отдельно. Дополнительно, удаляем атрибут request._request, если он присутствует (это если уже был доступ к атрибуту request.REQUEST), чтобы не осталось ссылок на старые GET и POST.
Также транслируем cookies. request.COOKIES — обычный dict, так что его не копируем, а изменяем по месту.
process_response осуществляет обратную операцию - перекодировку ответа сервера. Создается новый объект new_response того же класса, что и оригинальный response, перекодируем его содержимое, если класс response это поддерживает (у объекта есть содержимое, а не только заголовки); если этот объект не должен содержать контента, то новый объект не создается, работаем со старым.
Также перекодируем cookies и HTTP-заголовки. Как правило, заголовки не содержат ничего кроме ASCII, но это может быть и редирект - заголовок Location, который вполне может содержать не-ASCII-данные, закодированные URL-кодировкой. В этом случае нам надо раскодировать его обратно перед трансляцией в другую кодировку, а после перекодировки - закодировать обратно функцией urllib.quote.
process_response возвращает объект new_response, из которого потом и строится ответ сервера клиенту.
В файле settings.py нашего проекта в начало списка MIDDLEWARE_CLASSES вставляем путь к нашему middleware - "proj.middleware.translationmiddleware.TranslationMiddleware" (мы ставим его первым, для того, чтобы его process_request отработал прежде других middleware и обработки запроса, а process_response — в конце обработки, после всех других middleware, непосредственно перед выдачей ответа), рестарт приложения, voila! Страницы отдаются в нужной "внешней" кодировке, а данные запросов перекодируются во "внутреннюю" на лету.
Данный код опробован в Django, работающего по FastCGI. Я не пробовал его под mod_python, но, полагаю, разницы быть не должно.

Источники:
        https://www.ragbag.ru/2006/11/21/django_translation_middleware

Автор: Деркачев М.   
ПОМОЩЬ САЙТУ :
sms.Є®ЇЁ«Є  *PythonUA*
Copyright © 2006 python.com.ua