0,0
рейтинг
1 мая 2012 в 02:08

Разработка → Добавляем поддержку Windows Live Writer (Meta Weblog API) в блог на Django


Пару лет назад посчастливилось мне иметь блог на 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 свет клином как не странно не сошелся. Спасибо за внимание.
Семёнов Александр @S0ulReaver
карма
4,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (1)

  • +3
    Спасибо, интересно.
    Я XMLRPC как-то в страхе стороной обходил. Теперь понял, что не зря.

    По поводу PK:
    если в БД стоит autoincrement, то PK не должен повториться

    In [5]: from django.contrib.auth.models import User
    In [6]: u=User(username='test', email='test@test.com')
    In [7]: u.set_password('test')
    In [8]: u.save()
    In [9]: u.pk
    Out[9]: 4L
    
    In [10]: u.delete()
    In [11]: u = User.objects.order_by('-pk')[0]
    In [12]: u.pk
    Out[12]: 3L
    
    In [13]: u=User(username='test', email='test@test.com')
    In [14]: u.set_password('test')
    In [15]: u.save()
    In [16]: u.pk
    Out[16]: 5L
    
    

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.