Пару лет назад посчастливилось мне иметь блог на WordPress. До наших дней блог, как и вся база постов к моему сожалению не дожила, но рассказать я хочу не о самом блоге, а о том с помощью чего вся информация публиковалась. В то время Microsoft не так давно запустила Windows 7, а вместе с ней и пакет приложений Windows Live. Вот и я решил взглянуть – чего в этом пакете вкусного? Больше всего мне приглянулось приложение Windows Live Writer – программа позволяющая набирать и форматировать тексты, и публиковать их в блог. Удобный интерфейс, множество инструментов для форматирования, возможность хранить черновики локально, и многое другое – я влюбился. Спустя некоторое время свой блог без Windows Live Writer я уже не представлял.



Прошла пара лет. Как уже писал – того блога не стало, а я увлекся программированием на Python, а затем и Django. В процессе изучения фреймворка появилось желание завести новый блог, но на этот раз – программную часть написать самому, благо с Django это просто и увлекательно. И вот уже появилась главная страничка с новостями, несколько разделов, поддержка комментариев и т.д., но кое чего не хватало – удобного редактирования и публикации текстов. Вот тут я и вспомнил о Windows live Writer. Могу ли я воспользоваться им для публикации сообщений в свой блог? Как оказалось – могу, всё достаточно просто. По умолчанию приложение поддерживает публикацию лишь в несколько популярных блог-сервисов, но ничего не мешает добавить новый – достаточно того, чтобы блог поддерживал публикацию на основе MetaWeblog API. О самом стандарте рассказывать не буду – Wiki сделает это лучше меня. Мы же остановимся на том как реализовать серверную часть MetaWeblog на сайте с Django. Сам процесс достаточно прост, но как выяснилось – информации на русском языке по теме не то чтобы много, и в основном это PHP, или .NET код. Этот текст не в коем случае не претендует на полноту – моя цель скорее просто направить таких же начинающих как и я по верной дороге, чтобы разобраться в дальнейшем не составило труда.

И так, что нам понадобится? Для начала XML-RPC сервер (именно на основе XML-RPC функционирует MetaWeblog API). В поставку Python по умолчанию входит xmlrpclib которой могло бы быть достаточно, но мне показалось что иметь сервер взаимодействующий непосредственно с Django – удобнее. Спустя некоторое время поисков был найден django-xmlrpc – удобный сервер – обработчик XML-RPC запросов. Вот им мы и будем пользоваться. Установка проста, и не должна вызывать вопросов. По окончанию установки по адресу http://domain/xmlrpc/ можно посмотреть список зарегистрированных методов. Основной настройкой является переменная
XMLRPC_METHODS, определение которой необходимо поместить куда-нибудь в setting.py, и представляет она из себя картеж картежей, в которых содержатся путь к функции – обработчику, и название метода. Например:

XMLRPC_METHODS = (('myproject.myapp.views.get_users_blogs', 'blogger.getUsersBlogs'),)

Таким образом мы зарегистрировали метод с названием blogger.getUsersBlogs, обработчиком для которого является функция. Т.к. метод ровно с таким названием нам в будущем понадобится – предлагаю зарегистрировать его и вам.

Теперь перейдем непосредственно к функции по заданному адресу. По умолчанию Meta Weblog API имеет несколько методов которые потребуется реализовать, а конкретно:

  • metaWeblog.newPost()
  • metaWeblog.getPost()
  • metaWeblog.editPost()
  • metaWeblog.getCategories()
  • metaWeblog.getRecentPosts()
  • metaWeblog.newMediaObject()

Подробнее о принимаемых, и возвращаемых методами данных можно посмотреть например тут. Тем не менее нам также потребуются методы:

  • blogger.getUsersBlogs()
  • blogger.deletePost()
  • blogger.getUserInfo()

Но если последние два нужны, и всё же опциональны, то без первого Windows Live Writer регистрировать свой сайт у себя попросту откажется. Именно по этой причине с blogger.getUsersBlogs() мы и начнем. И так – вот код функции – обработчика:

from django_xmlrpc.decorators import xmlrpc_func

@xmlrpc_func(returns='string', args=['string', 'string', 'string',])
def get_users_blogs(appKey, username, password):
    user = u_authenticate(username, password)
    return [{'isAdmin': user.is_superuser,
            'url': 'http://127.0.0.1:8000/',
            'blogid': '1',
            'blogName': 'MyWebBlog'}]


Код прост, и всё же требует комментария. Первое что бросается в глаза – декоратор xmlrpc_func. Параметра как мы видим у него два, и первый отвечает за тип возвращаемых методом данных, а второй за тип принимаемых. Зачем этот декоратор нужен? Он добавляет XML-RPC подпись к нашему методу. Вообще строго говоря мы можем свободно обойтись и без неё, но приложения – клиенты могут читать эту подпись для получения нужной информации о методе. Ну вот и добавим, что нам, жалко что ли?

Сама функция принимает 3 параметра:
  1. appKey – уникальный ключ приложения – клиента, мы его использовать не будем.
  2. username – имя пользователя в сервисе
  3. password – пароль пользователя соответственно.

Функция u_authenticate возвращает пользователя из БД, если таковой в базе вообще имеется, и пароль верен. Вы можете написать такую функцию сами в зависимости от своих требований проверки, но можете воспользоваться и той что есть у меня:

from xmlrpclib import Fault

def u_authenticate(username, password):
    try:
        user = User.objects.get(username__exact=username)
    except User.DoesNotExist:
        raise Fault('1', 'Username is incorrect.')
    if not user.check_password(password):
        raise Fault('1', 'Password is invalid.')
    if not user.is_staff or not user.is_active:
        raise Fault('2', 'User account unavailable.')
    return user


Fault генерит XML-RPC Error, первым параметром которой является код ошибки (произвольный, на ваше усмотрение), и её текстовое представление. Именно этот код и текст покажет приложение – клиент в случае проблем.

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

  1. isAdmin – показывает является ли пользователь администратором.
  2. url – непосредственно адрес вашего блога в сети
  3. blogid – уникальный ID вашего блога в сервисе. Нужен на тот случай, если блогов на вашем сайте – несколько. Если же у вас таковой лишь один – достаточно любого числа, к примеру нуля.
  4. blogName – имя вашего блога, будет использоваться в приложениях – клиентах в качестве названия.

Обратите внимание на то что url в коде, как и имя сайта заданны строго, что вообще не совсем верно – в случае изменения адреса, или имени сайта придется менять код множества функций. По хорошему желательно хранить такие вещи в отдельных переменных где-нибудь в настройках, но тут я оставил всё в таком виде для упрощения понимания.

На самом деле это всё. Уже можно пробовать добавить блог в Windows Live Writer, указав в качестве адреса для взаимодействия что-то вроде http://127.0.0.1:8000/xmlrpc/, и Meta Weblog API в качестве интерфейса. Если всё сделано верно, то Windows Live Writer зарегистрирует блог у себя, а также предложит скачать визуальную тему блога (html, css) для функции предварительного просмотра перед публикацией, но сейчас нам придется отказаться – для определения темы Windows Live Writer публикует в наш блог временную запись, в то время как метод для публикации записей metaWeblog.newPost() у нас ещё не реализован. Ну и чего мы ждем?

Аналогично первым делом добавим метод в XMLRPC_METHODS:

('myproject.myapp.views.new_post', 'metaWeblog.newPost')


Теперь приступим к функции – обработчику:

from datetime import datetime

@xmlrpc_func(returns='string', args=['string', 'string', 'string', 'struct', 'boolean'])
def new_post(blog_id, username, password, post, publish):
    user = u_authenticate(username, password)
    item = News()
    item.title = post['title']
    item.text_news = post['description']
    if post.get('dateCreated'):
        item.date  = datetime.strptime(str(post['dateCreated']), '%Y%m%dT%H:%M:%S')
    else:
        item.date = datetime.now()
    item.author = user
    item.public = publish
    item.save()
    return item.pk


Код также очень прост, но прокомментирую на всякий случай:
Первые три принимаемых значения должны быть ясны, остановлюсь на оставшихся двух:

  1. post – непосредственно публикуемый контент. Представляет из себя словарь, который может содержать в себе очень много чего… По умолчанию это конечно title – заголовок записи, description – основной текст, dateCreated – дата создания записи, и categories – теги к записи. Тем не менее это лишь небольшая часть возможных данных предоставляемых клиентом, а dateCreated к примеру вообще может отсутствовать, если пользователь Windows Live Writer забыл указать дату. Всё это конечно стоит учитывать, и использовать в зависимости от ваших целей \ желаний, но перечислять все возможные ключи, которые могут оказаться в post я не буду, их проще посмотреть самому. В конце концов только вам решать какие из полученных данных использовать у себя.
  2. publish – переменная типа Boolean, равна True если запись стоит опубликовать немедленно, и False если стоит поместить в черновики.

Весь остальной код достаточно прост. Первым делом мы проверяем пользователя на подлинность. Затем создается объект item на основе простейшей модели новостей News(), и заполняется полученными данными. Остановиться пожалуй стоит лишь на item.date. Дело в том что ключ dateCreated содержит в себе дату и время публикации записи, что очевидно, в текстовом формате. Именно поэтому строку придется разобрать на составляющие, и преобразовать в объект даты. Если же ключ dateCreated не пришел вовсе, то в качестве даты и времени создания указываем текущие время и дату. Повторюсь – это простейшая модель новостей, и в реальности в вашем проекте данных может оказаться сильно больше. К примеру тут отсутствует обработка ключа categories, т.к. конкретно эта модель новостей их не содержит вовсе, но если вам нужно – никто не запрещает пользоваться.

По окончанию заполнения объекта item он сохраняется в БД, а функция возвращает item.pk. Вот тут также стоит остановиться подробнее. Метод newPost() обязан возвращать клиенту уникальный идентификатор опубликованной записи, чтобы в дальнейшем иметь возможность найти эту запись для возможного редактирования \ удаления. В качестве уникального идентификатора для item можно использовать pk, но вообще это опять же не слишком хорошо. Дело в том что pk по сути представляет из себя порядковый номер записи в БД на момент публикации. А теперь задумайтесь, что произойдет если опубликовать запись через Windows Live Writer, затем удалить её в панели администратора Django, из неё же запостить запись новую, а затем повторно попробовать опубликовать удаленную запись через Windows Live Writer? А произойдет простая штука: Windows Live Writer понятия не имеет о том что с момента публикации запись уже удалялась, а на её месте появилась другая, но с таким же как и у прошлой pk. Таким образом запись опубликованная через панель администратора будет банально перезаписана той, что будет опубликована из Windows Live Writer. Понятно что в жизни с такой ситуацией не то чтобы сталкиваешься каждый день, но тем не менее, похоже тут стоит возвращать нечто более уникальное, чем pk. Тем не менее для демонстрации pk нам х��атит за глаза.

Вот и всё, можно идти в Windows Live Writer, и радоваться возможности публиковать новые записи :).

Ниже также приведу реализацию методов metaWeblog.editPost(), и blogger.deletePost(). Код не содержит ничего специфичного, поэтому подробно останавливаться на нем я не буду.

@xmlrpc_func(returns='boolean', args=['string', 'string', 'string', 'struct', 'boolean'])
def edit_post(post_id, username, password, post, publish):
    user = u_authenticate(username, password)
    item = News.objects.get(id=post_id, author=user)
    item.title = post['title']
    item.text_news = post['description']
    if post.get('dateCreated'):
        item.date  = datetime.strptime(str(post['dateCreated']), '%Y%m%dT%H:%M:%S')
    else:
        item.date = datetime.now().date()
    item.author = user
    item.public = publish
    item.save()
    return True

@xmlrpc_func(returns='boolean', args=['string', 'string', 'string', 'string', 'string'])
def delete_post(apikey, post_id, username, password, publish):
    print post_id
    user = u_authenticate(username, password)
    News.objects.get(id=post_id, author=user).delete()
    return True 


Обе функции просто возвращают True в случае успеха. Теперь мы без проблем можем попробовать загрузить тему нашего блога в Windows Live Writer – приведенных методов достаточно.

Реализацию всех остальных методов приводить не стану. Приведенной выше информации вполне достаточно для того чтобы понять как работает Meta Weblog API, и реализовать нужные вам методы не должно составить труда. На этом всё, теперь точно всё :). Достаточно просто, но недостаток русскоязычной информации по теме на Python, и каких либо серверных библиотек меня несколько удивил, надеюсь в этой статье несколько заполнить пробел. Лично я – доволен тем что теперь могу публиковать новости в свой будущий блог через привычную, и удобную программу, а вам возможно приглянется другой клиент с поддержкой Meta Weblog API – на Windows Live Writer свет клином как не странно не сошелся. Спасибо за внимание.