Миграция MovableType -> Drupal. День 2: миграция контента и URL
Предуведомление
Описанная ниже методика предназначена для заливки пустого сайта на Drupal. Задача доливки контента на сайт, где уже что-то есть - не ставилась. Более того, на стадии импорта комментариев все старые комментарии точно будут стерты.
Если вам нужно пополнение имеющегося сайта, то описанные ниже скрипты нужно взять за основу и допилить.
Кроме того, никакими enterprise-features, вроде транзакций или обработки ошибок я категорически не заморачивался. Предполагается, какбэ, что импортом данных мы занимаемся тихо в уголочке, поступлением новых данных на старый сайт можем управлять, а после завершения импорта просто подменим сайт на скаку.
Импорт записей
Задача: вытащить записи (посты) из БД MovableType и запихать их в БД Drupal в виде объектов типа Story. Создание Drupal-объекта связано с заполнением нескольких таблиц (node, node_revisions и прочие node_*, url_aliases), пополнением таблицы тегов, другими словами эту работу не хочется делать вручную (SQL-запросами), а хочется перевесить на внутреннюю механику Drupal (ведь при создании записи оно как-то само все делается...).
План работ тривиален и прост:
- Ставим модули Table Wizard и Migrate.
- Добавляем нужные поля в структуру данных записи Story (не вручную, включением готовых модулей).
- Запускаем скрипт, который перенесет нам данные постов в БД Drupal.
- Импортируем образованную таблицу с постами в Table Wizard.
- Делаем импорт через Migrate.
- Полируем результат.
Поля в структуре данных Story
У исходного сайта имеются:
- URL записей (постов), которые хочется сохранить.
- Теги у записей.
- Иерархическая структура рубрик.
Для сохранения URL нужно включить модуль Path (входит в core в Drupal 6.x). Прочие модули (особенно Pathauto) пока не включаем.
Для сохранения тегов и рубрик поступим следующим образом:
- Заведем две таксономии (словаря), Tags и Categories. Для каждой из них:
- Поставим галку (тип) Tags, дабы не набивать значения руками (для Categories - потом поменяем тип на Multiple Select/Required и переупорядочим их).
- Разрешим эти таксономии для типа данных, который собираемся использовать (Story)
- Кроме того, заведем новый Input Format (Administer -> Site Configuration -> Input Formats -> Add inpit format) у которого все фильтры выключены. Этот формат нам нужен т.к. на исходном сайте большинство текстов уже HTML-форматировано и повторное форматирование Друпалом не нужно. Если вы проводите все упражнения на новом (свежеустановленном) сайте, то этот формат получит номер 3, на что и рассчитан скрипт пре-миграции (см. ниже).
Скрипт пре-миграции данных
Прилагаемый скрипт (качать отсюда) вынимает все полезные данные из БД Movable Type и помещает их в табличку mt_posts в destintation БД. Запускать так:
./mt2drupal.pl source-db destination-db [blog-id]Параметры
- source-db - БД MovableType (PostgreSQL). Для переключения на MySQL нужно две строчки после слов Source DB закомментировать, а следующие две - расскомментировать.
- destination-db - БД Drupal. В прилагаемом скрипте настроено на PostgreSQL, для работы с MySQL нужно две строчки после слов Destination DB закомментировать, а следующие две - раскомментировать.
- blog-id - номер блога в БД MovableType. Если параметр не задан, то работа ведется с ID 1.
Необходимые комментарии:
- Скрипт создает таблицу mt_posts, содержащую всю необходимую информацию для импорта.
- Так как мы импортируем блог (с одним автором), то информацией об авторе я не заморачивался, присваиваю константу (userid) на этапе импорта.
- Формирование анонса доверено Drupal, точка по которой анонс отделяется от основного текста помечена стандартным Drupal placeholder (<!--break-->).
- Теги и категории преобразуются в разделенные запятыми списки, преобразование списка категорий в иерархический будет делаться вручную после импорта.
- Скрипт всасывает все данные одним запросом (select * from table), для импорта действительно больших (сотни тысяч и более записей) наборов данных лучше это делать кусочками. На тысяче все происходит быстро, на десятке тысяч должно занять приемлемое время.
- URL-ы формируются по правилам MT: /yyyy/dd/mm/${entry_basename}.html, но если на вашем исходном сайте правила формирования URL были другими, то придется поправить в скрипте строчку-другую.
Превращение таблицы mt_posts в Drupal View
В меню Drupal: Administer -> Content Management -> Table Wizard
Выбираем Add existing table, выбираем в списке таблицу mt_posts, жмем Add tables.
Важно: если работаем с PostgreSQL, то ставим галку Skip Full Analyze, если ее оставить, то получим кучу ругани. Анализ нам собственно не нужен, все данные уже готовы для импорта (подготовлены мегаскриптом на предыдущей стадии).
Импорт данных
В меню Drupal: Administer -> Content Management -> Migrate
Делаем новый content set с такими параметрами:
- Destination: Node: Story.
- Source view: созданный на предыдущем шаге View (он будет называться, если все по-умолчанию, tw: mt_posts (mt_posts))
Жмем Add и попадаем в картинку мэппинга полей таблицы mt_posts в объект Story (картинка слева, кликабельно). Тут все достаточно очевидно кроме двух моментов:
- Автор всегда uid 1 (друпаловский) т.е. суперпользователь.
- Поле teaser мы не заполняем (но в поле body на предыдущем этапе записан правильный разделитель) и оно заполнится автоматически на импорте.
После создания mapping, выбираем наш content set, ставим галку Import, выбираем Execute и запускаем.. Если все сделано правильно, то будут импортированы данные из mt_posts и записи появятся в списке статей Drupal.
Помимо этого, будет заполнена таблица migrate_map_1 (или с каким-то другим номером, если у вас есть другие импорты), сопоставляющая идентификаторы таблицы mt_posts (куда они попали из исходного MovableType) и node ID Друпала. Она понадобится нам в дальнейшем для:
- Импорта комментариев
- Импорта файла мэппинга LiveJournal - MovableType
Надо сказать, что без Migrate я бы посмотрел на задачу с пяти разных сторон, да и бросил бы нафиг.....
Импорт комментариев
Модуль Migrate: EPIC FAIL
В первой части я заметил, что у меня PostgreSQL, а не MySQL, что может вызвать проблемы. Оно и вызвало: импорт комментариев в PostgreSQL не заработал, причем даже до стадии импорта, при попытке импортировать тип комментариев во внутренних таблицах не инициализируется поле NOT NULL, постгрес кричит и все ломается сразу.
С MySQL все начинается лучше, криков нет (похоже что тамошний NOT NULL не работает нормально, но проверять не стал), но кончается так же: комментарии не импортированы, причем без какой-либо диагностики.
Остается второй путь - прямой перелив в базу данных.
По счастью, при вставке комментария нужно обновлять только две таблицы: comments и node_comment_statistics, отчего задача становится относительно несложной.
Скрипт импорта комментариев
Скрипт качаем тут. Для работы нужен тот же набор модулей, что и для скрипта пре-импорта записей.Параметры запуска
./mt2comments.pl src-db dest-db [post-map-table]
- src-db - исходная база MovableType (настроено на PostgreSQL, перенастройка заменой двух строк).
- dest-db - база данных Drupal. Используется PostgreSQL-specific функция установки секвенсора (setval(seq_name)), для использования с MySQL понадобится пару строк дописать.
- post-map-table - таблица в dest-db, задающая соответствие "ID записи MT" - "ID ноды Drupal". Эта таблица создается модулем Migrate на этапе импорта записей. Умолчание - migrate_map_1.
Скрипт сотрет все комментарии
Скрипт перенесет комментарии только для тех записей, которые есть в таблице мэппинга. Какой-либо дополнительной рихтовки после скрипта не требуется, все комментарии должны оказаться на месте и с нужным порядком в тредах.
Наведение марафета
URL записей и комментариев
URL записей должны были остаться прежними, если это не так, то нужно править скрипт пре-импорта записей и повторять импорт.
URL комментариев чудесным образом остались прежними (URL#comment-NNN, а ID комментариев мы сохранили). Если быть точным, то они остались прежними только для тех комментариев, которые влезают на одну страницу с основным текстом. Максимальное количество комментариев к записи у меня - 131, поэтому идем в Administer-Content Management-Content Types-Story-Comments settings и ставим количество комментариев на странице в 200, должно хватить.
Если по какой-то причине у страниц были дополнительные алиасы (у меня они были и были сделаны через симлинки) - добавляем эти алиасы в таблицу URL Aliases (Site Building - URL Aliases)
Теги и разделы сайта
В моем случае:- Тегов много (~400), разделов немного (16)
- Тегам назначаем новый URL (/tags/русское-название), разделам - старый (/transliterated).
- Чтобы не мучаться, не жалко с разделами поработать вручную один раз.
Методика:
- Ставим модуль Pathauto (и модуль Token, который у него в требованиях). Файл i18n-ascii.example.txt в каталоге modules/pathauto переименовываем в i18-ascii.txt, для русского языка он сойдет (а вот лежащий на вебе большой файл со многими языками - кривой, там мягкого знака нет).
- Ставим мой патч к Pathauto.
- Идем в настройки Pathauto (URL Aliases - Automated alias settings) и там:
- Меняем (если хотим) разделитель на подчеркивание (в этом случае нужно сходить еще в раздел Punctuation и убрать удаление underscore).
- Увеличиваем максимумы длины и, главное, максимальное количество алиасов при bulk update (до количества, большего чем число тегов-рубрик, скажем до 5000).
- Включаем транслитерацию, на следующем шаге отключим ее для тегов (а для всего прочего она нужна).
- Выключаем перегенерацию существующих алиасов, а то будем иметь сюрпризы при смене заголовков статей.
- Taxonomy term path settings: ставим Pattern for all Tags: tags/no-transliterate-me[catpath-raw], паттерн для Category ставим как-тов в духе category/[catpath-raw] (несущественно, на следующем шаге будем править руками).
- Ставим галку Bulk generate aliases for terms that are not aliased.
- И жмем OK.
- Заодно, можно сразу поменять правило вывода URL для nodes на что-то в духе [yyyy]/[mm]/[dd]/[title-raw].html
- В этой точке мы нагенерировали русских алиасов /tags/ для тегов и английских /category/... для разделов. Идем в список алиасов, отбираем по подстроке category и правим вручную на правильные URL категорий (т.к. правила транслитерации несколько разные в MT и в Drupal, от правки отмотаться не получится).
Показ разделов (категорий)
Меняем тип словаря Categories на Multiple Select/Required (вместо Tags), он перестает быть автопополняемым.
Переупорядочиваем разделы в нем в нужном порядке и в нужной иерархии.
Ставим модуль Taxonomy Block, подсовываем ему таксономию Category, размещаем в колонке навигации.
Показ облака тегов
Tagadelic полностью закрывает тему облака тегов, настройки тривиальны, не описываю.
Проба пера
Создаем запись с русским заголовком и русскими тегами (новыми, ранее не существовавшими). Она должна создаться с транслитерированным URL и нетранслитерированными тегами.Архивные URL
Архивные URL исходно казались гораздо страшнее, чем оказалось на самом деле:
- 95% работы делается через Views (и это занимает минут 5)
- Остальные 5% - модулем URL Alter (и это занимает гораздо больше времени, ибо хождение по минному полю).
Список месяцев с записями
Модуль Views содержит готовый View archive, который и делает всю работу:
- Определенная в нем страница archive/ при обращении без аргументов - показывает список месяцев, которые являются ссылкой на archive/YYYYMM. При обращении к archive/YYYYMM - показывается список записей, с листалкой и все такое.
- Определенный в рассматриваемом View блок - тоже выводит список месяцев.
URL вида /yyyy/mm/dd
Опять Views, но придется руками:
- Делаем новый View, допустим bydate.
- Делаем в нем Page с URL bydate
- Определяем три аргумента: год, месяц, день
- Определяем Row style (Node), сортировку (descending по дате), настраиваем листалку.
- bydate/YYYY - список записей за год.
- bydate/YYYY/MM - список за месяц.
- bydate/YYYY/MM/DD - список за день.
Переписывание URL
Я собирался переписать URL-и средствами nginx, но обнаружил более родное средство: модуль URL ALter. Точнее, сначала я нашел Subpath Alias, тот потребовал URL Alter, а в последнем оказалась вся нужная функциональность.
Ставим модуль, в его настройках пишем.
Inbound filter:
// Every ^\d\d\d\d$ should go to bydate/$1 $result=preg_replace("/^(\d\d\d\d)$/","bydate/$1",$result); #same for month $result=preg_replace("/^(\d\d\d\d\/\d\d)$/","bydate/$1",$result); # and same for day $result=preg_replace("/^(\d\d\d\d\/\d\d\/\d\d)$/","bydate/$1",$result);
Outbound filter
# remove leading bydate and archive in outbound urls $path=preg_replace("/^bydate\//","",$path); $path=preg_replace("/^archive\/(\d\d\d\d)(\d\d)$/","$1/$2",$path);
Собственно, все. Профит.
Важное замечание: правя настройки URL Alter вы ходите по минному полю. Но мы не саперы, поэтому можно/нужно открыть url_alter.module в редакторе и быть готовым закомментировать оба eval(), которые там есть. Без этого - вы скорее всего получите просто неработающий сайт и будет вам плохо.
Comments
В RSS ридере смотрится забавно, мне тоже на второй день знак
В RSS ридере смотрится забавно, мне тоже на второй день знакомства хотелось от друпала убежать куда-нибудь подальше ;)
"Миграция MovableType -> Drupal. День 1: постановка задачи "
"Миграция Drupal -> MovableType. День 2: миграция контента и URL"
Тьфу, пропасть. Спасибо.
Тьфу, пропасть.
Спасибо.