Журнал LinuxFormat - перейти на главную

LXF107:Django

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

В гостях хорошо, а дома лучше

ЧАСТЬ 3 В мире существуют языки, отличные от английского, и компьютеры попроще IBM Roadrunner. Никита Шультайс покажет, как Django справляется и с тем, и с другим.

До сих пор мы созерцали «админку» на английском языке, однако ее русификация – тоже не проблема. Для этого нужно всего лишь добавить в кортеж MIDDLEWARE_CLASSES в файле settings.py следующую строку:

'django.middleware.locale.LocaleMiddleware',

Она вызывает специальный обработчик, который отыскивает нужный перевод. Чтобы определить, какой язык требуется, Django делает следующее:

  1. Ищет в текущей сессии пользователя значение ключа django_language.
  2. Если поиск завершился провалом, Django изучает HTTP-заголовок Accept-Language, генерируемый вашим браузером в соответствии с локальными настройками.
  3. Наконец, если ничего не было найдено, используется значение переменной LANGUAGE_CODE, установленное в файле settings.py.

Если основной аудиторией вашего сайта будут русскоязычные пользователи, имеет смысл присвоить переменной LANGUAGE_CODE в файле settings.py значение ru.

Основным понятием, которым оперирует Django при интернационализации, являются строки перевода – помеченный особым образом текст в ваших исходных кодах или шаблонах. Собственно процесс перевода реализуется библиотекой gettext, которая входит в состав Python. Чтобы ваше приложение стало доступным на нескольких языках, нужно пройти несколько этапов:

  1. Определить строки перевода в исходных текстах и шаблонах.
  2. Запустить утилиту make-messages.py, которая найдет все строки перевода и создаст из них языковой файл.
  3. Перевести полученный языковой файл.
  4. Запустить утилиту mcompile-messages.py, которая скомпилирует языковой файл в формат, пригодный для его дальнейшего использования системой.

При этом предполагается, что строки перевода используют английский язык.

Что переводить?

Для начала давайте разберемся, как помечаются строки перевода в исходных текстах приложения. Откройте файл news/views.py. В первую очередь, нужно импортировать функцию ugettext() (она, в отличии от gettext(), отлично справляется с Unicode):

from django.utils.translation import ugettext as _

Как можно заметить, для импортированной функции мы создали псевдоним: _ (подчеркивание). Это было сделано для повышения удобочитаемости кода, содержащего большое число строк перевода.

Затем, давайте передадим функции _() текст, который мы собираемся перевести, и присвоим результат переменной application_name:

application_name = _("news")

Поместим эту строку в представление last_news, а переменную application_name передадим в шаблон:

context = RequestContext(request, {
"last_news":news,
"application_name":application_name
})

Теперь откройте шаблон news/templates/news/lats_news.html и добавьте имя нашего приложения в строку 4:

1-3 ...
4 <h2>{{ application_name }}</h2>
5-...

В шаблон news/templates/news/news_detail.html нужно импортировать тэги интернационализации. Для этого в строке 2 напишите:

{% load i18n %}

после чего в шаблоне будет доступен тэг {% trans %} (и не только), с помощью которого мы будем отмечать строки перевода. Замените текст в строке 5:

5 <h1>{{ news.title }}</h1>

на

<h1>{% trans "News" %}<br/>{{ news.title }}</h1>

Думаю, вы уже догадались, как все это работает. В соответствии с нашим планом, настало время запустить специальную утилиту и создать языковый файл.

Вавилонское смешение

Для начала, откройте терминал, перейдите в директорию приложения news и создайте в ней каталог locale:

cd news
mkdir locale

Утилита make-messages.py находится в поддиректории bin установки Django. Вызовите ее со следующими параметрами:

python /путь/до/django/bin/make-messages.py -l ru

Если все пройдет удачно, в директории news/locale будут созданы дополнительные каталоги ru/LC_MESSAGES, а в них – языковый файл django.po, который и нужно переводить. Откройте его в любом текстовом редакторе (для удобства можно пользоваться программой KBabel из KDE). Вы увидите следующее:

#: views.py:23
msgid "news"
msgstr ""
#: templates/news/news_detail.html:5
msgid "News"
msgstr ""

Первая запись в каждом блоке указывает место, где была найдена строка перевода, далее идет текст, который нужно перевести, и наконец, сам перевод (у нас его пока нет). Мы специально создали два варианта слова «news» – с заглавной буквы и с маленькой, чтобы наглядно продемонстрировать, что система воспринимает их как разные слова. Заполните пустые строки русскими «новости» и «Новости». После того, как с переводом языкового файла будет покончено, скомпилируйте его. Для этого воспользуйтесь утилитой compile-messages.py, которая опять же входит в состав Django:

python /путь/до/django/bin/compile-messages.py

В каталоге LC_MESSAGES появляется скомпилированный языковой файл django.mo. Все готово: наше приложение теперь поддерживает два языка.

Смена языка на сайте

Мы уже выяснили, как задать язык для клиентов с отключенными cookie, однако далеко не все пользователи догадаются так поступить, да и у большинства ваших посетителей cookie так или иначе включены. Значит, чтобы сменить язык сайта, нужно только переписать cookie, которые за это отвечают. И опять Django не оставляет нас в беде. Для начала откроем главный файл с URL-картами – urls. py, и после строки

(r'^news/', include('news.urls')),

добавим

(r'^i18n/', include('django.conf.urls.i18n')),

Далее, откроем файл settings.py и добавим следующие строки:

TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.auth",
"django.core.context_processors.i18n",
"django.core.context_processors.request",
)

Так мы подключим к нашей системе три контекстных процессора, которые добавляют в шаблоны глобальные переменные для работы с текущим пользователем, переменные языковых настроек и содержимое HTTP-запроса, соответственно. Откройте файл media/templates/index.html и после тэга <body> добавьте:

  1. <form action="/i18n/setlang/" method="post">
  2. <input name="next" type="hidden" value="{{ request.PATH_INFO }}" />
  3. <select name="language">
  4. {% for lang in LANGUAGES %}
  5. <option value="{{ lang.0 }}"
  6. {% ifequal lang.0 LANGUAGE_CODE %}
  7. selected="selected"
  8. {% endifequal%}>{{ lang.1 }}</option>
  9. {% endfor %}
  10. </select>
  11. <input type="submit" value="Go" />

В строке 1 задается форма, данные из которой будут направлены на встроенное в Django приложение django.views.i18n.set_language, расположенное по адресу /i18n/setlang/ и отвечающее за смену языка сайта. Оно принимает POST-запрос, содержащий следующие переменные:

  • next – адрес, куда будет перенаправлен пользователь после смены языка. Это, как правило, адрес страницы, на которой мы решили поменять язык. Он содержится в переменной request.PATH_INFO, которую мы передаем в шаблон с помощью контекстного процессора django.core.context_processors.request.
  • language – язык, который был выбран.
(thumbnail)
Рис. 2. Многообразие языков, поддерживаемых Django.

Список языков доступен в переменной LANGUAGES, но если вы посмотрите на сайт (рис. 2), то увидите огромный перечень, из которого нам нужны только два языка. Дело в том, что если в settings.py не определена какая-либо переменная, то ее значение автоматически берется из файла настроек Django – django/conf/global_settings.py, а там представлен кортеж LANGUAGES, содержащий все языки, поддерживаемые системой: это более 40 наименований (в том числе, языки, в которых символы пишутся справа налево). Чтобы оставить только те из них, которые нам действительно необходимы, добавим в файл settings.py следующий кортеж:

LANGUAGES = (
('en', gettext_noop('English')),
('ru', gettext_noop('Russian')),
)

не забыв написать перед ним:

gettext_noop = lambda s: s

Теперь, если вы загрузите сайт, вам будут доступны только два языка. Вернемся опять к нашей форме: в строке 6 вы найдете новый тэг шаблона ifequal – он сравнивает две переданные переменные, и если они одинаковы, выполняет код в строке 7.

Особенности перевода админки

В отличиe от представлений, где мы вызывали функцию ugettext, в «админке» желательно использовать функцию ленивого (отложенного) перевода ugettext_lazy. Ее главное отличие в том, что перевод осуществляется не сразу, а в момент использования строки, например, при обработке шаблона в «админке» Django. Импортируйте эту функцию в файле news/models.py:

from django.utils.translation import ugettext_lazy as _

Каждое поле модели принимает первым позиционным аргументом свое имя, поэтому строку

title = models.CharField(max_length=70)

можно заменить на

title = models.CharField(_('title'),max_length=70)

В дополнение к имени поля можно передать текст справки. Замените на

description = models.CharField(help_text=_('Not more than 255 symbols.'),max_length=255)

И, наконец, если вам уже надоело слово «News» на главной странице «админки», измените его на более подходящее. Добавьте в класс News подкласс Meta (напомню, что он обеспечивает дополнительные настройки модели), а внутри него объявите переменную, отвечающую за множественное число:

class Meta:
verbose_name_plural = _('news')

Добавьте каждому полю имя, текст справки (если требуется) и форму множественного числа. Перейдите в каталог news и проделайте те же действия по созданию, переводу и компиляции языковых файлов, что я описывал ранее. После всех операций у вас должно получиться что-то вроде изображенного на рис. 3 и 4.

(thumbnail)
Рис. 3. Русифицированная главная страница админки…
(thumbnail)
Рис. 4. ...и форма добавления новостей.

Кэширование

Наиболее слабыми звеньями работы сайта являются доступ к базе данных и компилирование шаблонов, поэтому стоит сразу задуматься о кэшировании данных. Django прекрасно справляется этой задачей и позволяет создавать и хранить кэш самыми различными способами и в самых разных местах:

  • В оперативной памяти. Такая возможность достигается с помощью программы Memcached, которая служит промежуточным звеном между данными в памяти и Django. Несомненно, этот способ является наибыстрейшим, однако ваш сервер должен обладать достаточным количеством памяти, чтобы хватило и для Django, и для кэша.
  • В базе данных. В этом случае о скорости доступа к данным нельзя говорить однозначно, так как тут нужно учитывать настройки самой БД; однако если все сделано с умом, можно ожидать хороших результатов.
  • В файловой системе. Самый простой вариант. Его мы и будем использовать.

Откроем файл settigns.py и объявим в нем переменную:

CACHE_BACKEND = 'file:///путь/до/кэша'

Слово file означает, что мы собираемся использовать файловую систему (против memcached и db для оперативной памяти и СУБД, соответственно), далее идут три слэша и путь до директории, в которой вы собираетесь хранить данные.


Django предоставляет высокоуровневые механизмы для генерации кэша и извлечения его из базы данных. Так, самым верхним объектом является кэш всех страниц сайта. Однако если на странице находятся динамические данные, которые индивидуальны для каждого пользователя, например, строка приветствия («Здравствуйте, Имя»), то все посетители будут видеть приветствие для пользователя, который зашел на сайт самым первым. Если же таких элементов нет, это – самый быстрый способ создать кэш сайта. Чтобы включить его, достаточно добавить строку 'django.middleware.cache.CacheMiddleware' в кортеж MIDDLEWARE_CLASSES в файле настроек. Она должна стоять самой первой в кортеже. На нашем сайте нет приветствия, однако есть возможность смены языков, и после того как кэш страницы создан для одного языка, он будет отображаться и для другого.

Следующим глобальным объектом для кэширования является представление. И опять, здесь мы сталкиваемся с теми же недостатками, что и при кэшировании всех страниц сайта, с единственным отличием: мы сами вправе выбирать, какие страницы будут кэшироваться, а какие – нет. Вы можете импортировать в ваше представление декоратор cache_page:

from django.views.decorators.cache import cache_page

и применить его к какому-либо представлению, например:

@cache_page(60 * 15)
def last_news(request):
...

Декоратор принимает единственный параметр – время жизни кэша в секундах.

Аналогично можно кэшировать и шаблоны. Откройте файл news/templates/news/last_news.html и добавьте после {% extends «index.html» %} строку:

{% load cache %}

далее, перед {% for news in last_news %} добавьте

{% cache 31536000 last_news LANGUAGE_CODE %}

а после {% endfor %}:

{% endcache %}

Сначала мы подключаем дополнительные тэги для кэширования, затем вызываем тэг {% cache %} с тремя параметрами (вообще-то достаточно двух). Первый – время жизни кэша в секундах (у нас это целый год), второй – название, а третий – тоже название, но в нашем случае это переменная, которая вычисляется динамически, а следовательно, для разных языков у нас создается свой кэш. То же самое можно сделать и для приветствия.

Сигналы

Кэширование позволяет сделать сайт очень быстрым, но как быть, если вы определили время жизни кэша в три дня и добавили новость через два? Ведь пока кэш жив, система будет отображать именно его, и нашим посетителям придется ждать целый день. С другой стороны, ставить слишком маленький промежуток тоже нет смысла – тогда от системы кэширования не будет пользы. Предугадать невозможно, да и незачем. Оптимальный вариант – удалять кэш при наступлении какого-либо события (новость добавлена, новость удалена и т.д.). Уведомлением о событии служит сигнал. Создадим внутри директории news файл signals.py следующего содержания:

  1. from django.core.cache import cache
  2. from settings import LANGUAGES
  3.  
  4. def delete_last_news_cache():
  5. for lang in LANGUAGES:
  6. cache.delete('last_news:'+ lang[0])

Обратите внимание на строку 6, где мы генерируем имя кэша. Здесь учтено, что Django соединяет все, что было передано тэгу {% cache %}, символом двоеточия. Поскольку мы не знаем, для каких языков был создан кэш, то удаляем все возможные варианты – в нашем случае их всего два.

Как же сделать, чтобы этот код выполнялся? Проще простого. Откроем файл news/models.py и добавим в начало следующие строки:

from django.db.models import signals
from django.dispatch import dispatcher
from news.signals import delete_last_news_cache

а в конец:

dispatcher.connect(delete_last_news_cache,signal=signals.post_save,sender=News)
dispatcher.connect(delete_last_news_cache,signal=signals.post_delete,sender=News)

Теперь в системе зарегистрированы два сигнала от передатчика (sender) News. Первый сигнал генерируется после сохранения объекта в базе данных, то есть при его создании или изменении, а второй – после удаления. В обоих случаях выполняется функция delete_last_news_cache, определенная выше. Вообще, для обращения к базе данных в Django существует семь сигналов: pre_init, post_init, pre_save, post_save, pre_delete, post_delete и post_syncdb, названия которых говорят сами за себя.

И сверх этого

Кроме возможности создавать кэш ваших данных, Django поддерживает и другие способы оптимизации. Чтобы сделать ваш сайт еще, быстрее нужно добавить в кортеж MIDDLEWARE_CLASSES в файле settings.py следующие строки:

'django.middleware.http.ConditionalGetMiddleware',
'django.middleware.gzip.GZipMiddleware',

Первая из них включает поддержку условных GET-запросов (использование ETag и Last-Modified в HTTP-заголовках), а вторая отвечает за сжатие содержимого для браузеров, которые поддерживают такую возможность (а это все современные реализации).

Персональные инструменты
купить
подписаться
Яндекс.Метрика