RkBlog

Hardware, programming and astronomy tutorials and reviews.

Migracja danych z punBB do MyghtyBoard

Migracja danych do Django (przenoszenie kont użytkowników oraz innych danych).

Szybka i elastyczna migracja danych ze skryptu opartego na PHP do Django jest możliwa. Do tego celu możemy wykorzystać ORM Django tak by migrowane dane można było dodać do bazy innego typu, a także by nie mieć problemów z kodowaniem.

Forum MyghtyBoard ma model kategorii:
# Forum Categories
class Category(models.Model):
	cat_name = models.CharField(maxlength=255, verbose_name=_("Category Name")) # name of the category
	cat_order = models.PositiveSmallIntegerField(default=0, verbose_name=_("Order")) # order of categories on the forum-categories list
	class Meta:
		verbose_name = _("Category")
		verbose_name_plural = _("Categories")
		db_table = 'rk_category' + str(settings.SITE_ID)
	class Admin:
		list_display = ('cat_name','cat_order')
	def __str__(self):
		return self.cat_name
Naszym celem będzie stworzenie skryptu pythona dodającego przez ORM Django migrowane dane. Tak oto wyglądałby przykładowy skrypt dodający kilka kategorii:
# -*- coding: utf-8 -*-
from os import environ
environ['DJANGO_SETTINGS_MODULE'] = 'settings'

from settings import *
from myghtyboard.models import *

p = Category(cat_name='''Fora towarzyskie''', cat_order='''90''')
p.save()

p = Category(cat_name='''Rodzina BSD''', cat_order='''60''')
p.save()

p = Category(cat_name='''GNU/Linux''', cat_order='''10''')
p.save()

p = Category(cat_name='''jakilinux.org''', cat_order='''120''')
p.save()
Skrypt należy umieścić w katalogu z projektem django i wykonać. Pierwsza jego część to import potrzebnych modułów, druga to import modeli aplikacji, do których chcemy dodać dane, a trzecia to dodanie samych danych. Teraz pozostaje kwestia jak wygenerować taki skrypt. W tym przykładzie użyjemy punBB. Stworzymy migrator danych z punBB do MyghtyBoard. punBB posiada tabelę kategorii zawierającą trzy kolumny: id, cat_name (nazwa kategorii) i disp_position (kolejność). Sytuacja wręcz idealna - wszystkie dane są na miejscu, wystarczy je przerzucić. Możemy napisać skrypt w PHP lub w Pythonie, który pobierze dane i w pętli je "wyświetli" - a dokładniej dla każdej kategorii ma wyświetlić coś takiego:
p = Category(cat_name='''NAZWA_KATEGORII''', cat_order='''KOLEJNOŚĆ''')
p.save()
W przypadku pisania tego w pythonie musimy użyć mysql-python (lub odpowiednio innego moduły dla innego typu bazy danych), a w przypadku PHP - jednego z API dla MySQL. Dla PHP i z wykorzystaniem punBB nasz skrypt wyglądałby tak:
<?php
<?PHP
define('PUN_ROOT', './'); // ścieżka do skryptu forum
require PUN_ROOT.'include/common.php';
ob_start();
echo '# -*- coding: utf-8 -*-
from os import environ
environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from settings import *
';

IF($result = $db->query("SELECT * FROM ".$db->prefix."categories"))
	{
	echo "

".'from myghtyboard.models import *',"
".'Category.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
echo 'mc = Category(cat_name=''''.$row['cat_name'].'''', cat_order=''''.$row['disp_position'].'''')
mc.save()'."
";
		}
	}

$wynik = ob_get_contents();
ob_end_clean();
file_put_contents('install.py', $wynik);
echo '<h1>install.py should be ready</h1>';
Powyższy skrypt należy umieścić w katalogu punBB i otworzyć w przeglądarce. Stworzy on w katalogu forum plik "install.py" zawierający gotowy skrypt pythona/Django z pobranymi danymi.

Kolejnym modelem jest "Forum" przetrzymujące fora. Nowym elementem jest tutaj zależność do kategorii.
# Forums
class Forum(models.Model):
	forum_category = models.ForeignKey(Category, verbose_name=_("Forum Category")) # Forum category
	forum_name = models.CharField(maxlength=255, verbose_name=_("Forum Name")) # name of the forum
	forum_description = models.CharField(maxlength=255, verbose_name=_("Forum Description")) # desc of the forum
	forum_topics = models.PositiveIntegerField(default='0', blank=True, verbose_name=_("Topics")) # number of topics
	forum_posts = models.PositiveIntegerField(default='0', blank=True, verbose_name=_("Posts")) # number of posts
	forum_lastpost = models.CharField(maxlength=255, verbose_name=_("Last Post"), blank=True, default='', null=True) # last poster info etc.
	forum_order = models.PositiveSmallIntegerField(default=0) # order of forums on the category list
Tabela punBB zawiera numer ID tabeli kategorii, lecz w naszym przypadku nie możemy podać tego numeru. Musimy zwrócić obiekt - Category.objects.get(id=NUMER_ID). Dodatkowo, jeżeli kategorie w punBB nie byłyby numerowane po kolei od 1 to musielibyśmy w kodzie importującym kategorie podawać również id danej kategorii. Oto kod dla importu for:
<?php
IF($result = $db->query("SELECT * FROM ".$db->prefix."forums"))
	{
	echo "

".'Forum.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
echo 'mf = Forum(id = ''''.$row['id'].'''', forum_category = Category.objects.get(id='.$row['cat_id'].'), forum_name = ''''.$row['forum_name'].'''', forum_description =''''.$row['forum_desc'].'''', forum_order=''''.$row['disp_position'].'''', forum_posts=''''.$row['num_posts'].'''', forum_topics = ''''.$row['num_topics'].'''')
mf.save()'."
";
		}
	}
Zastosowaliśmy obiekt kategorii, a także podajemy ID danego wpisu forum, tak by w imporcie tematów nie było problemów (gdy fora nie mają ciągłej numeracji od 1).

Ciekawym problemem jest też import użytkowników, a dokładniej migracja haseł. Django zapisuje hasło jako:
"sha1$SÓL$HASZ"
Gdzie sha1 oznacza funkcję haszującą, SÓL - losowy ciąg znaków dołączony do hasła przed stworzeniem HASZa. W przypadku punBB mamy problem z głowy, gdyż domyślnie używa on funkcji sha1. Nie używa "soli", lecz to nie problem podać django pustą "sól":
"sha1$$HASZ"
Kod PHP odpowiedzialny za migrację wyglądałby tak:
<?php
IF($result = $db->query("SELECT * FROM ".$db->prefix."users WHERE id > 2 AND num_posts > 10 ORDER BY last_post DESC"))
	{
	echo 'User.objects.filter(id__gt=1).delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
		echo 'us = User(username="'.$row['username'].'", first_name="'.$row['username'].'", last_name="'.$db->escape($row['username']).'", email="'.$row['email'].'", password="sha1$$'.$row['password'].'", is_staff="0", is_active="1", is_superuser="0", last_login="2006-08-29 20:00:34", date_joined="2006-08-29 20:00:34")'."
";
		echo 'us.save()'."
";
		}
	}
Data ostatniego logowania i data rejestracji są statyczne - "aby były".

Efekty

Pełen kod migratora z punBB:
<?php
<?PHP
define('PUN_ROOT', './'); // ścieżka do skryptu forum
require PUN_ROOT.'include/common.php';
ob_start();
echo '# -*- coding: utf-8 -*-
from os import environ
environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from django.contrib.auth.models import User, Group
from settings import *
';
IF($result = $db->query("SELECT * FROM ".$db->prefix."users WHERE id > 2 AND num_posts > 10 ORDER BY last_post DESC"))
	{
	echo 'User.objects.filter(id__gt=1).delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
		echo 'us = User(username="'.$row['username'].'", first_name="'.$row['username'].'", last_name="'.$db->escape($row['username']).'", email="'.$row['email'].'", password="sha1$$'.$row['password'].'", is_staff="0", is_active="1", is_superuser="0", last_login="2006-08-29 20:00:34", date_joined="2006-08-29 20:00:34")'."
";
		echo 'us.save()'."
";
		}
	}


IF($result = $db->query("SELECT * FROM ".$db->prefix."categories"))
	{
	echo "

".'from myghtyboard.models import *',"
".'Category.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
echo 'mc = Category(cat_name=''''.$row['cat_name'].'''', cat_order=''''.$row['disp_position'].'''')
mc.save()'."
";
		}
	}

IF($result = $db->query("SELECT * FROM ".$db->prefix."forums"))
	{
	echo "

".'Forum.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
echo 'mf = Forum(id = ''''.$row['id'].'''', forum_category = Category.objects.get(id='.$row['cat_id'].'), forum_name = ''''.$row['forum_name'].'''', forum_description =''''.$row['forum_desc'].'''', forum_order=''''.$row['disp_position'].'''', forum_posts=''''.$row['num_posts'].'''', forum_topics = ''''.$row['num_topics'].'''')
mf.save()'."
";
		}
	}
	
IF($result = $db->query("SELECT * FROM ".$db->prefix."topics"))
	{
	echo "

".'Topic.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
echo 'mt = Topic(id = ''''.$row['id'].'''', topic_forum = Forum.objects.get(id='.$row['forum_id'].'), topic_name = ''''.$row['subject'].'''', topic_author =  ''''.$row['poster'].'''', topic_posts =  ''''.$row['num_replies'].'''', topic_modification_date =  ''''.date("Y-m-d H:i:s", $res['posted']).'''')
mt.save()'."
";
		}
	}

IF($result = $db->query("SELECT * FROM ".$db->prefix."posts"))
	{
	echo "

".'Post.objects.all().delete()'."
";
	while ($row = $db->fetch_assoc($result))
		{
		$row['message'] = nl2br($row['message']);
		$row['message'] = str_replace("
", '', $row['message']);
		$row['message'] = $db->escape(str_replace("

", '', $row['message']));
echo 'mp = Post(post_topic = Topic.objects.get(id='.$row['topic_id'].'), post_text = ''''.$row['message'].'''', post_author = ''''.$row['poster'].'''', post_date = ''''.date("Y-m-d H:i:s", $res['posted']).'''', post_ip = '1.2.3.4')
mp.save()'."
";
		}
	}
$wynik = ob_get_contents();
ob_end_clean();
file_put_contents('install_1.py', $wynik);
echo '<h1>install_1.py should be ready</h1>';
?>
djpun1
przykładowe punBB
djpun2
zmigrowane dane w MyghtyBoard
RkBlog

Diamanda - Aplikacje Django, 14 July 2008,

Comment article