# Copyright (c) 2011, 2012 Free Software Foundation
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
# This project incorporates work covered by the following copyright and permission notice:
# Copyright (c) 2009, Julien Fache
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of the author nor the names of other
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# Copyright (c) 2011, 2012 Free Software Foundation
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
"""Blogger to Gstudio command module
Based on Elijah Rutschman's code"""
import sys
from getpass import getpass
from datetime import datetime
from optparse import make_option
from django.utils.encoding import smart_str
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
from django.template.defaultfilters import slugify
from django.core.management.base import CommandError
from django.core.management.base import NoArgsCommand
from django.contrib.contenttypes.models import ContentType
from django.contrib.comments import get_model as get_comment_model
from gstudio import __version__
from gstudio.models import Nodetype
from gstudio.models import Metatype
from gstudio.managers import DRAFT, PUBLISHED
gdata_service = None
Comment = get_comment_model()
class Command(NoArgsCommand):
"""Command object for importing a Blogger blog
into Gstudio via Google's gdata API."""
help = 'Import a Blogger blog into Gstudio.'
option_list = NoArgsCommand.option_list + (
make_option('--blogger-username', dest='blogger_username', default='',
help='The username to login to Blogger with'),
make_option('--metatype-title', dest='metatype_title', default='',
help='The Gstudio metatype to import Blogger posts to'),
make_option('--blogger-blog-id', dest='blogger_blog_id', default='',
help='The id of the Blogger blog to import'),
make_option('--author', dest='author', default='',
help='All imported nodetypes belong to specified author')
)
SITE = Site.objects.get_current()
def __init__(self):
"""Init the Command and add custom styles"""
super(Command, self).__init__()
self.style.TITLE = self.style.SQL_FIELD
self.style.STEP = self.style.SQL_COLTYPE
self.style.ITEM = self.style.HTTP_INFO
def write_out(self, message, verbosity_level=1):
"""Convenient method for outputing"""
if self.verbosity and self.verbosity >= verbosity_level:
sys.stdout.write(smart_str(message))
sys.stdout.flush()
def handle_noargs(self, **options):
global gdata_service
try:
from gdata import service
gdata_service = service
except ImportError:
raise CommandError('You need to install the gdata ' \
'module to run this command.')
self.verbosity = int(options.get('verbosity', 1))
self.blogger_username = options.get('blogger_username')
self.metatype_title = options.get('metatype_title')
self.blogger_blog_id = options.get('blogger_blog_id')
self.write_out(self.style.TITLE(
'Starting migration from Blogger to Gstudio %s\n' % __version__))
if not self.blogger_username:
self.blogger_username = raw_input('Blogger username: ')
if not self.blogger_username:
raise CommandError('Invalid Blogger username')
self.blogger_password = getpass('Blogger password: ')
try:
self.blogger_manager = BloggerManager(self.blogger_username,
self.blogger_password)
except gdata_service.BadAuthentication:
raise CommandError('Incorrect Blogger username or password')
default_author = options.get('author')
if default_author:
try:
self.default_author = User.objects.get(username=default_author)
except User.DoesNotExist:
raise CommandError(
'Invalid Gstudio username for default author "%s"' % \
default_author)
else:
self.default_author = User.objects.all()[0]
if not self.blogger_blog_id:
self.select_blog_id()
if not self.metatype_title:
self.metatype_title = raw_input(
'Metatype title for imported nodetypes: ')
if not self.metatype_title:
raise CommandError('Invalid metatype title')
self.import_posts()
def select_blog_id(self):
self.write_out(self.style.STEP('- Requesting your weblogs\n'))
blogs_list = [blog for blog in self.blogger_manager.get_blogs()]
while True:
i = 0
blogs = {}
for blog in blogs_list:
i += 1
blogs[i] = blog
self.write_out('%s. %s (%s)' % (i, blog.title.text,
get_blog_id(blog)))
try:
blog_index = int(raw_input('\nSelect a blog to import: '))
blog = blogs[blog_index]
break
except (ValueError, KeyError):
self.write_out(self.style.ERROR(
'Please enter a valid blog number\n'))
self.blogger_blog_id = get_blog_id(blog)
def get_metatype(self):
metatype, created = Metatype.objects.get_or_create(
title=self.metatype_title,
slug=slugify(self.metatype_title)[:255])
if created:
metatype.save()
return metatype
def import_posts(self):
metatype = self.get_metatype()
self.write_out(self.style.STEP('- Importing nodetypes\n'))
for post in self.blogger_manager.get_posts(self.blogger_blog_id):
creation_date = convert_blogger_timestamp(post.published.text)
status = DRAFT if is_draft(post) else PUBLISHED
title = post.title.text or ''
content = post.content.text or ''
slug = slugify(post.title.text or get_post_id(post))[:255]
try:
nodetype = Nodetype.objects.get(creation_date=creation_date,
slug=slug)
output = self.style.NOTICE('> Skipped %s (already migrated)\n'
% nodetype)
except Nodetype.DoesNotExist:
nodetype = Nodetype(status=status, title=title, content=content,
creation_date=creation_date, slug=slug)
if self.default_author:
nodetype.author = self.default_author
nodetype.tags = ','.join([slugify(cat.term) for
cat in post.metatype])
nodetype.last_update = convert_blogger_timestamp(
post.updated.text)
nodetype.save()
nodetype.sites.add(self.SITE)
nodetype.metatypes.add(metatype)
nodetype.authors.add(self.default_author)
try:
self.import_comments(nodetype, post)
except gdata_service.RequestError:
# comments not available for this post
pass
output = self.style.ITEM('> Migrated %s + %s comments\n'
% (nodetype.title, len(Comment.objects.for_model(nodetype))))
self.write_out(output)
def import_comments(self, nodetype, post):
blog_id = self.blogger_blog_id
post_id = get_post_id(post)
comments = self.blogger_manager.get_comments(blog_id, post_id)
nodetype_content_type = ContentType.objects.get_for_model(Nodetype)
for comment in comments:
submit_date = convert_blogger_timestamp(comment.published.text)
content = comment.content.text
author = comment.author[0]
if author:
user_name = author.name.text if author.name else ''
user_email = author.email.text if author.email else ''
user_url = author.uri.text if author.uri else ''
else:
user_name = ''
user_email = ''
user_url = ''
com, created = Comment.objects.get_or_create(
content_type=nodetype_content_type,
object_pk=nodetype.pk,
comment=content,
submit_date=submit_date,
site=self.SITE,
user_name=user_name,
user_email=user_email,
user_url=user_url)
if created:
com.save()
def convert_blogger_timestamp(timestamp):
# parse 2010-12-19T15:37:00.003
date_string = timestamp[:-6]
return datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S.%f')
def is_draft(post):
if post.control:
if post.control.draft:
if post.control.draft.text == 'yes':
return True
return False
def get_blog_id(blog):
return blog.GetSelfLink().href.split('/')[-1]
def get_post_id(post):
return post.GetSelfLink().href.split('/')[-1]
class BloggerManager(object):
def __init__(self, username, password):
self.service = gdata_service.GDataService(username, password)
self.service.server = 'www.blogger.com'
self.service.service = 'blogger'
self.service.ProgrammaticLogin()
def get_blogs(self):
feed = self.service.Get('/feeds/default/blogs')
for blog in feed.nodetype:
yield blog
def get_posts(self, blog_id):
feed = self.service.Get('/feeds/%s/posts/default' % blog_id)
for post in feed.nodetype:
yield post
def get_comments(self, blog_id, post_id):
feed = self.service.Get('/feeds/%s/%s/comments/default' % \
(blog_id, post_id))
for comment in feed.nodetype:
yield comment