Регистрация - Вход

Реклама на сайте


Трансляция 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


КОММЕНТАРИИ







Теги


RSS

Архив



производство флагштоков, флаги логотипом