2009-12-11

Pylons, SQLAlchemy, кодировка

Долгие мучения с вышеуказанной связкой побудили меня написать эту заметку.

Работа с SQLite не вызывала проблем, т.к. эта СУБД хранит данные в Unicode, и Pylons+mako по умолчанию с ним отлично работают. Просмотр данных в SQLite можно делать с помощью:
1) консольной утилиты (sudo apt-get install sqlite3)
2) SQLite Database browser (sudo apt-get install sqlitebrowser)
3) плагином для Firefox (http://code.google.com/p/sqlite-manager/)
Все они нормально отображают данные, без крякозябрей. На web-страницы шаблонизатор mako также выводит все корректно.

А вот с MySQL дело несколько сложнее, т.к. он может отдавать данные в разных кодировках. Чтобы получить данные в нужной, а не в той, что стоит в настройках сервера по умолчанию (они располагаются в /etc/mysql/my.cnf), нужно дать команду "SET NAMES кодировка".
Моя история была такова, что потребовалось данные из одной БД, которую использовала CMS Joomla, перетащить в другую, чтобы работать с ней из Pylons. Как я понял, Joomla при работе с БД использовала "SET NAMES latin1", что для англоязычных товарищей - нормально и привычно уже много лет. И все бы ничего, да есть засада.
Программы для работы с MySQL, которые я использую в основном, это:
1) MySQL Query Browser (sudo apt-get install mysql-query-browser)
2) phpMyAdmin на сервере
Эти приложения по умолчанию предпочитают кодировку соединения UTF8. Поэтому, что в одном, что в другом кириллица была видна как "ПеÑ". Есть приложения, которые справляются с этой проблемой, но в phpMyAdmin на сервере этого не исправишь.
Так что-же делать? Перевести данные из одной кодировки в другую. Мне пришлось написать скрипт, который создает два соединения с MySQL, причем в первом устанавливаю "SET NAMES latin1" (для донорской базы), во втором "SET NAMES utf8" (для новой). Остается пройтись по записям, считывая их в latin1 и записывая в utf8.
После такой обработки вышеприведенные программы отображают кириллицу как полагается. Остается только настроить Pylons и SQLAlchemy.
Итак, для корректного отображения данных нужно дать серверу MySQL команду "SET NAMES utf8". Где же это сделать? Я нашел три варианта. Предположим, проект называется app.
1) Файл /app/config/environment.py, метод load_environment
from sqlalchemy import engine_from_config
from app.model import init_model
def load_environment(global_conf, app_conf):
    engine = engine_from_config(config, 'sqlalchemy.')
    engine.execute("SET NAMES utf8")
    init_model(engine)
2) Файл /app/model/__init__.py, метод init_model
def init_model(engine):
    engine.execute("SET NAMES utf8")
В обоих случаях, при старте проекта в консоли будет видно, что эти команды выполняются. И все вроде бы пошло гладко! Но это до поры. А именно, до поры создания нового соединения.
Как известно, SQLAlchemy использует так называемый pool, т.е. некий список соединений, который использует по мере занятости, циклически. Их колличество можно менять. Пока я этого не понял, неоднократно был удивляем странным поведением пилонов. Так вот, пока хватает одного соединения, все идет как задумано, но стоит перейти на второе - косяки, вперед! "SET NAMES" для них никто не задавал, поэтому кодировка будет "как получится", а именно знаками вопросов для кириллицы. Вот тут то и пригодится третий способ. Он задает команду при создании соединения, и все pool-ы будут передавать данные с нужной кодировкой. Почитать об этом больше можно тут.
3) Файл /development.ini
sqlalchemy.url = mysql://root:@localhost:3306/church?init_command=set%20names%20%22utf8%22
sqlalchemy.convert_unicode=true
sqlalchemy.encoding='utf8'

Теперь немного о причине таких махинаций. Только таким образом мне удалось добиться того, чтобы поиск русских слов в базе данных происходил без учета регистра. В SQLite он не работает, т.к. там Unicode, и выбирать не приходится. И только в MySQL, и с данными в utf8 это получилось. Кстати, со словами, состоящими из латинских символов, проблем нет. Если есть другие способы, буду рад добавить их.

2009-12-08

Pylons и mako: Почему метод контроллера у меня вызывается дважды?

Записка на память. Ситуация:
Метод контроллера при варианте

return "ABC"

отрабатывает один раз.
При варианте

return render("/abc.mako")

метод вызывается дважды. Но не во всех шаблонах.
Причина: наличие в шаблоне строки

<link rel="stylesheet" type="text/css" href="/" media="all">

Pylons пытается подгрузить по ссылке содержимое. Если ничего не указано, то он обращается к тому же контроллеру и тому же методу, откуда вызван рендер шаблона. Стилей он там не находит, но метод срабатывает. В результате лишняя нагрузка, и ненужная деятельность.

2009-12-07

Программа для изучения Библии в Linux. Опрос.

Изначально программа для изучения Библии в ОС Linux задумывалась исключительно для этих целей. Никаких там посторонних, хоть и христианских, книжек, никаких песен или комментариев. И, пока что, так оно и остается.
Но, наконец-то братья откликнулись, и в комментариях было высказано предложение добавить возможность работы с модулями "Цитаты" Тимофея Ха либо напрямую, либо конвертировать их в формат моей разработки. Я размышляю над этим, и при возможности, займусь.
А пока что, мне было бы интересно, какие переводы Священного Писания вы считаете востребованными? Для меня RST и KJV более чем достаточно. Хотелось бы еще Женевскую учебную Библию добавить, но ее я в модулях Цитаты не нашел, а то обязательно бы прикрутил.
Прошу отписываться по этому поводу.

P.S. - самая актуальная версия - по ссылке справа вверху.

2009-11-22

Ноутбук ASUS FSKseries, Ubuntu 9.10 и наушники

Вопрос: на ноутбуке ASUS F3Kseries при установленной Ubuntu 9.10, при вставленных наушниках, звук идет и во встроенные колонки и в наушники. Хотелось бы, чтобы звучали только наушники, раз уж они воткнуты.
Ответ: открываем файл
/etc/modprobe.d/alsa-base.conf
и добавляем туда строку
options snd-hda-intel model=auto
Перезагружаем. Все работает как надо.

2009-10-19

FCKeditor + Pylons

Не так давно передо мной встала задача: подобрать и реализовать на сайте хороший WYSIWYG редактор для проекта на Pylons. Перебрав несколько вариантов, я остановился на FCKeditor, так как у него имеется встроенный менеджер медиа-файлов, т.е. присутствует механизм загрузки файлов на сервер и выборки оттуда для вставки в текст.

Все это распрекрасно, с одним маленьким НО - он не готов для прикручивания к пилонам. Нет, конечно разработчики учли присутствие на белом свете языка Python (в наличии также коннекторы для asp, aspx, cfm, lasso, perl, php). Но только как wsgi. Через mod_python, cgi - пожалуйста, все почти готово. А для меня - не очень. Поискав в интернете ответ, так и не нашел толкового пошагового решения. Потому, набив кучу шишек, представляю мое HowTo:

Как прикрутить FCKeditor к Pylons, пошаговое руководство

Создадим проект, к примеру "myapp":

$ paster create -t pylons myapp
$ cd myapp

Идем на http://ckeditor.com/download и скачиваем последнюю версию FCKeditor. Вероятно, новее уже не будет, потому что разработчики переключились на новые продукты - CKeditor и CKFinder, которые по сути - редактор и менеджер файлов по отдельности. Кстати, другой известный WYSIWYG редактор, TinyMCE, идет подобной дорогой (у него нет поддержки Python, кстати). И я бы выбрал этот новенький, красивенький CKeditor, если бы не одно обстоятельство - разработчики тоже не сделали поддержки для Python. Может быть появится попозже. Может быть я сам как-нибудь займусь и перепишу коннектор для него. Может быть. А пока - продолжим.

Извлечем из архива папку fckeditor в myapp/public/

Создадим шаблон myapp/templates/index.mako вот такого вида:

#coding:utf8
<html>
<head>
    <title>FCKEditor</title>
    <script type="text/javascript" src="/ckeditor/ckeditor.js"></script>
</head>
<body>
<script type="text/javascript" src="/fckeditor/fckeditor.js"></script>
    <form action="" method="post" target="_self">
        <input type='hidden' name='body' id='body' value='Hello World' />

        <script type="text/javascript">
            var oFCKeditor = new FCKeditor() ;
            oFCKeditor.BasePath = '/fckeditor/';
            oFCKeditor.ToolbarSet='Default'
            oFCKeditor.Height    = 300 ;
            oFCKeditor.InstanceName = 'body' ;
            oFCKeditor.Create() ;
        </script>
    </form>
</body>
</html>

Удалим файл myapp/public/index.html
Создадим контроллер index:

 $ paster controller index

В контроллере myapp/controller/index.py в классе IndexController укажем отобразить вышесозданный шаблон:

class IndexController(BaseController):
    def index(self):
        return render("/index.mako")


Пропишем путь в myapp/config/routing.py для корня нашего сайта:

    # CUSTOM ROUTES HERE
    map.connect('/',controller='index',action='index')

Запустим сервер:

$ paster serve --reload development.ini

Теперь в нашем любимом браузере по адресу localhost:5000 видим - редактор уже присутствует. И он работает, получить данные при нажатии кнопки сохранения можно через request.POST['body'].
Вам больше ничего и не надо? Тогда заканчиваем читать здесь. А мне еще нужно осуществить загрузку файлов на сайт и просмотр их на сервере.

Создадим контроллер fckeditor, в который будем направлять запросы редактора на просмотр и загрузку файлов:


$ paster controller fckeditor

Добавим в файл myapp/config/routing.py пути роутинга, чтобы приложение обращалось не к файлам в своем базовом каталоге, а к нашему контроллеру. По умолчанию, в файлах конфигурации FCKeditor все настроено на работу с php.


    # CUSTOM ROUTES HERE
    map.connect('/fckeditor/editor/filemanager/connectors/php/*path_info', controller='fckeditor')
    map.connect('/',controller='index',action='index')

В файле myapp/controllers/fckeditor.py перед объявлением класса добавим путь к нашим файлам Python-коннекторов, идущих в комплекте с FCKeditor, и импортируем из них нужные классы.

import sys
sys.path.append("/media/disk/Projects/myapp/myapp/public/fckeditor/editor/filemanager/connectors/py")
from connector import FCKeditorConnector
from upload import FCKeditorQuickUpload


Также не забудьте удалить или переименовать папку myapp/public/fckeditor/editor/filemanager/connectors/php, иначе пилоны найдут эту папку и предпочтут брать файлы оттуда, игнорируя правило, прописанное в routing.py

Теперь, собственно, класс FckeditorController из myapp/controllers/fckeditor.py

class FckeditorController(BaseController):
    def __call__(self,environ,start_response):
        id=environ['wsgiorg.routing_args'][1]['path_info']
        if id=='connector.php':
            environ['SCRIPT_FILENAME'] = 'connector.py'
            conn = FCKeditorConnector(environ)
        elif id == 'upload.php':
            environ['SCRIPT_FILENAME'] = "upload.py"
            conn = FCKeditorQuickUpload(environ)
        else:
            start_response ("200 Ok", [('Content-Type','text/html')])
            yield "Unknown page requested: "
            yield environ['SCRIPT_NAME']
            return
        data = conn.doResponse()
        start_response ("200 Ok", conn.headers)
        yield str(data)


Для нашего удобства настроим myapp/public/fckeditor/fckconfig.js. Пусть Интерфейс будет русским!

FCKConfig.AutoDetectLanguage    = false ;
FCKConfig.DefaultLanguage        = 'ru' ;

Добавим в настройках development.ini каталог для загрузок и хранения файлов:

[DEFAULT]
static_store = %(here)s/myapp/public/upload

Если вы сами не создадите эту папку - Pylons умница сделает это за вас.
Теперь немного пошаманим в настройках коннектора
myapp/public/fckeditor/editor/filemanager/connectors/py/config.py

Enabled = True
UserFilesPath = '/upload/'
from pylons import config
UserFilesAbsolutePath = config['static_store']+'/'


И наконец, последний штрих - внесем парочку измененеий в файл
myapp/public/fckeditor/editor/filemanager/connectors/py/fckcommands.py
а именно в класс UploadFileCommandMixin:

class UploadFileCommandMixin (object):
    from pylons import request
    # Заменить if self.request.has_key("NewFile"): на
    if request.POST.has_key("NewFile"):
        # Заменить newFile = self.request.get("NewFile", "") на
        newFile = request.POST["NewFile"]

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