Merge branch 'newstyles' into userguide
authorKostas Papadimitriou <kpap@grnet.gr>
Thu, 12 Jul 2012 14:49:10 +0000 (17:49 +0300)
committerKostas Papadimitriou <kpap@grnet.gr>
Thu, 12 Jul 2012 14:49:10 +0000 (17:49 +0300)
63 files changed:
cloudcms/admin.py
cloudcms/cms.py
cloudcms/fixtures/cloudcms_default_services.json [new file with mode: 0644]
cloudcms/fixtures/cloudcms_media_categories.json [new file with mode: 0644]
cloudcms/forms.py [new file with mode: 0644]
cloudcms/migrate/cloudcmsguide/0001_initial.py [new file with mode: 0644]
cloudcms/migrate/cloudcmsguide/0002_auto__add_field_userguideentry_lft__add_field_userguideentry_rght__add.py [new file with mode: 0644]
cloudcms/migrate/cloudcmsguide/__init__.py [new file with mode: 0644]
cloudcms/rstutils.py [new file with mode: 0644]
cloudcms/synnefo_settings.py
cloudcms/templates/cms/admin_import_guide_faq.html [new file with mode: 0644]
cloudcms/templates/cms/pages/userguide.html [new file with mode: 0644]
cloudcms/templatetags/cloudcms_tags.py
cloudcms/test_settings.py [new file with mode: 0644]
cloudcms/tests/__init__.py [new file with mode: 0644]
cloudcms/tests/invalidzip.zip [new file with mode: 0644]
cloudcms/tests/okeanos-user-docs.zip [new file with mode: 0644]
cloudcms/tests/test_media/medialibrary/2012/07/image.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_1.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_2.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_3.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_4.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_5.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_6.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_7.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_8.png [new file with mode: 0755]
cloudcms/tests/test_media/medialibrary/2012/07/image_9.png [new file with mode: 0755]
cloudcms/tests/userdocs1.zip [new file with mode: 0644]
cloudcms/tests/userdocs1/source/faq/cyclades.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/faq/index.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/faq/okeanos.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/faq/pithos.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/images/cyclades/image.png [new file with mode: 0644]
cloudcms/tests/userdocs1/source/images/faq/image.png [new file with mode: 0644]
cloudcms/tests/userdocs1/source/images/image.png [new file with mode: 0644]
cloudcms/tests/userdocs1/source/images/pithos_guide/image.png [new file with mode: 0644]
cloudcms/tests/userdocs1/source/index.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/userguide/anotherfile.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/userguide/cyclades.rst [new file with mode: 0644]
cloudcms/tests/userdocs1/source/userguide/index.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/faq/cyclades.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/faq/index.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/faq/okeanos.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/faq/pithos.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/images/cyclades/image.png [new file with mode: 0644]
cloudcms/tests/userdocs2/source/images/faq/image.png [new file with mode: 0644]
cloudcms/tests/userdocs2/source/images/image.png [new file with mode: 0644]
cloudcms/tests/userdocs2/source/images/pithos_guide/image.png [new file with mode: 0644]
cloudcms/tests/userdocs2/source/index.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/userguide/anotherfile.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/userguide/cyclades.rst [new file with mode: 0644]
cloudcms/tests/userdocs2/source/userguide/index.rst [new file with mode: 0644]
cloudcms/urls.py
cloudcmsguide/__init__.py [new file with mode: 0644]
cloudcmsguide/admin.py [new file with mode: 0644]
cloudcmsguide/models.py [new file with mode: 0644]
cloudcmsguide/sitemap.py [new file with mode: 0644]
cloudcmsguide/templates/cloudcmsguide/archive.html [new file with mode: 0644]
cloudcmsguide/templates/cloudcmsguide/detail.html [new file with mode: 0644]
cloudcmsguide/tests.py [moved from cloudcms/tests.py with 83% similarity]
cloudcmsguide/urls.py [new file with mode: 0644]
cloudcmsguide/views.py [new file with mode: 0644]
setup.py

index a45317c..4d42eaf 100644 (file)
 
 
 from django.contrib import admin
+from django.conf.urls.defaults import patterns
+from django.views.generic.simple import direct_to_template
+from django.http import HttpResponse
+from django.shortcuts import redirect
+from django.contrib import messages
+
+
 from feincms.translations import admin_translationinline, short_language_code
 
 from cloudcms import models
@@ -63,3 +70,32 @@ admin.site.register(models.Application, ApplicationAdmin)
 admin.site.register(models.Client, ClientAdmin)
 admin.site.register(models.Service, ServiceAdmin)
 
+
+from cloudcms.forms import RstZipImportForm
+
+def import_from_sphinx(request):
+    if not request.user.is_superuser:
+        return HttpResponse(status=401)
+
+    context = {}
+    form = RstZipImportForm()
+
+    if request.method == 'POST':
+        form = RstZipImportForm(request.POST, request.FILES)
+        if form.is_valid():
+            try:
+                ret = form.save(request.user)
+                messages.add_message(request, messages.INFO, 'Form saved')
+                return redirect('/cmsmanage/sphinximport/')
+            except Exception, e:
+                context['exception'] = e
+
+        else:
+            context['error'] = True
+
+    context['form'] = form
+
+    return direct_to_template(request, 'cms/admin_import_guide_faq.html', context)
+
+sphinx_import = admin.site.admin_view(import_from_sphinx)
+
index 4308958..791b38c 100644 (file)
@@ -110,6 +110,16 @@ TEMPLATES = [{
         ),
     },
     {
+    'key': 'userguide',
+    'title': 'Userguide template',
+    'path': 'cms/pages/userguide.html',
+    'regions': (
+        ('top', 'Top region'),
+        ('bottom_left', 'Bottom left region'),
+        ('bottom_right', 'Bottom right region'),
+        ),
+    },
+    {
     'key': 'faq',
     'title': 'FAQ\'s template',
     'path': 'cms/pages/faq.html',
@@ -152,7 +162,9 @@ Page.create_content_type(MediaFileContent, TYPE_CHOICES=(
 ))
 Page.create_content_type(ApplicationContent, APPLICATIONS=(
     ('cloudcmsblog', 'Cloud blog', {'urls': 'cloudcmsblog.urls'}),
-    ('cloudcmsfaq', 'Cloud FAQ', {'urls': 'cloudcmsfaq.urls'}),))
+    ('cloudcmsfaq', 'Cloud FAQ', {'urls': 'cloudcmsfaq.urls'}),
+    ('cloudcmsguide', 'Cloud user guide', {'urls': 'cloudcmsguide.urls'}),)
+)
 
 # cloudcms specific content registration
 Page.create_content_type(LoginForm)
@@ -160,8 +172,6 @@ Page.create_content_type(AboutBlock)
 Page.create_content_type(ResourcesList)
 Page.create_content_type(BlockColor)
 
-
-
 # Extra cms applications
 EXTRA_CONTENT_MODELS = []
 
@@ -175,8 +185,8 @@ if 'cloudcmsfaq' in settings.INSTALLED_APPS:
     EXTRA_CONTENT_MODELS.append(Question)
 
 if 'cloudcmsguide' in settings.INSTALLED_APPS:
-    from cloudcmsguide.models import Section
-    EXTRA_CONTENT_MODELS.append(Section)
+    from cloudcmsguide.models import UserGuideEntry
+    EXTRA_CONTENT_MODELS.append(UserGuideEntry)
 
 for model in EXTRA_CONTENT_MODELS:
     # Feincms specific registrations for our blog entry model
diff --git a/cloudcms/fixtures/cloudcms_default_services.json b/cloudcms/fixtures/cloudcms_default_services.json
new file mode 100644 (file)
index 0000000..f5ff079
--- /dev/null
@@ -0,0 +1,74 @@
+[
+    {
+        "pk": 1, 
+        "model": "sites.site", 
+        "fields": {
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "cloudcms.application", 
+        "fields": {
+            "code": "cmsapp",
+            "title": "Cms app",
+            "site": 1
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "cloudcms.service", 
+        "fields": {
+            "ordering": 3
+        }
+    }, 
+    {
+        "pk": 2, 
+        "model": "cloudcms.service", 
+        "fields": {
+            "ordering": 2
+        }
+    }, 
+    {
+        "pk": 3, 
+        "model": "cloudcms.service", 
+        "fields": {
+            "ordering": 1
+        }
+    }, 
+    {
+        "pk": 2, 
+        "model": "cloudcms.servicetranslation", 
+        "fields": {
+            "description": "", 
+            "parent": 2, 
+            "title": "Cyclades", 
+            "cms_page": null, 
+            "language_code": "en", 
+            "slug": "cyclades"
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "cloudcms.servicetranslation", 
+        "fields": {
+            "description": "", 
+            "parent": 1, 
+            "title": "Okeanos", 
+            "cms_page": null, 
+            "language_code": "en", 
+            "slug": "okeanos"
+        }
+    }, 
+    {
+        "pk": 3, 
+        "model": "cloudcms.servicetranslation", 
+        "fields": {
+            "description": "", 
+            "parent": 3, 
+            "title": "Pithos", 
+            "cms_page": null, 
+            "language_code": "en", 
+            "slug": "pithos"
+        }
+    }
+]
diff --git a/cloudcms/fixtures/cloudcms_media_categories.json b/cloudcms/fixtures/cloudcms_media_categories.json
new file mode 100644 (file)
index 0000000..d89321b
--- /dev/null
@@ -0,0 +1,20 @@
+[
+    {
+        "pk": "1", 
+        "model": "medialibrary.category", 
+        "fields": {
+            "slug": "faq-images", 
+            "parent": null, 
+            "title": "FAQ images"
+        }
+    }, 
+    {
+        "pk": "2", 
+        "model": "medialibrary.category", 
+        "fields": {
+            "slug": "user-guide-images", 
+            "parent": null, 
+            "title": "User guide images"
+        }
+    }
+]
diff --git a/cloudcms/forms.py b/cloudcms/forms.py
new file mode 100644 (file)
index 0000000..0d127fd
--- /dev/null
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+
+import zipfile
+import tempfile
+import os
+import glob
+import logging
+
+from django import forms
+from django.conf import settings
+from django.db import transaction
+from django.core.files import File
+
+from feincms.module.medialibrary.models import Category as MediaCategory, \
+        MediaFile
+
+from cloudcmsguide.models import UserGuideEntry
+from cloudcmsfaq.models import Category as FaqCategory, Question
+from cloudcms.rstutils import generate_rst_contents_from_dir
+from cloudcms.models import Service, ServiceTranslation, Application
+from feincms.content.raw.models import RawContent
+
+logger = logging.getLogger('cloudcms.rstimport')
+
+# base filename to service slug map
+DEFAULT_SERVICE_MAP = {
+        'cyclades':'cyclades',
+        'okeanos':'okeanos',
+        'pithos':'pithos'
+}
+
+DEFAULT_REGION_MAP = {
+        'faq':'main',
+        'userguide':'main',
+}
+
+DEFAULT_RESIZE_GEOMETRY = (400, 400)
+
+SERVICE_MAP = getattr(settings, 'CMS_RST_IMPORT_SERVICE_FILE_MAP',
+        DEFAULT_SERVICE_MAP)
+REGIONS_MAP = getattr(settings, 'CMS_RST_IMPORT_REGIONS_MAP',
+        DEFAULT_REGION_MAP)
+RESIZE_GEOMETRY = getattr(settings, 'CMS_RST_IMPORT_RESIZE_GEOMETRY',
+    DEFAULT_RESIZE_GEOMETRY)
+
+def service_from_filename(rst):
+    fname = os.path.basename(rst).replace(".rst","")
+    service_slug = DEFAULT_SERVICE_MAP.get(fname, None)
+    if not service_slug:
+        return None
+
+    try:
+        return Service.objects.filter(translations__slug=service_slug)[0]
+    except IndexError:
+        return None
+    except Service.DoesNotExist:
+        return None
+
+    return None
+
+
+def get_media_category(slug):
+    return MediaCategory.objects.get(slug=slug)
+
+
+CATEGORIES_CHOICES = (('faqs', 'FAQs'), ('guide', 'User guide'))
+
+class RstZipImportForm(forms.Form):
+
+    FAQ_MEDIA_CATEGORY = 'faq-images'
+    GUIDE_MEDIA_CATEGORY = 'user-guide-images'
+
+    clean_data = forms.BooleanField(initial=False, required=False)
+    dry_run = forms.BooleanField(initial=True, required=False,
+            widget=forms.HiddenInput)
+    import_file = forms.FileField(required=True)
+
+    def __init__(self, *args, **kwargs):
+        super(RstZipImportForm, self).__init__(*args, **kwargs)
+        self.log_data = ""
+
+    def log(self, msg):
+        self.log_data += "\n" + msg
+
+    def get_tmp_file(self, f):
+        tmp = tempfile.mktemp('cloudcms-sphinx-import')
+        f.file.reset()
+        fd = file(tmp, 'w')
+        fd.write(f.read())
+        fd.close()
+        return tmp
+
+    def clean_import_file(self):
+        f = self.cleaned_data['import_file']
+        tmpfile = self.get_tmp_file(f)
+        if not zipfile.is_zipfile(tmpfile):
+            raise forms.ValidationError("Invalid zip file")
+        return f
+
+    def clean(self, *args, **kwargs):
+        data = super(RstZipImportForm, self).clean(*args, **kwargs)
+        return data
+
+    def clean_existing_data(self):
+        Question.objects.all().delete()
+        UserGuideEntry.objects.all().delete()
+        MediaFile.objects.filter(categories__slug__in=[self.FAQ_MEDIA_CATEGORY, \
+            self.GUIDE_MEDIA_CATEGORY]).delete()
+
+    def save(self, user, use_dir=None):
+        dry_run = self.cleaned_data.get('dry_run')
+        clean_data = self.cleaned_data.get('clean_data')
+        import_file = self.cleaned_data.get('import_file')
+
+        if not use_dir:
+            zipdir = tempfile.mkdtemp('cloudcms-sphinx-exports')
+            zipdatafile = self.get_tmp_file(import_file)
+            zipf = zipfile.ZipFile(file(zipdatafile))
+            zipf.extractall(zipdir)
+        else:
+            zipdir = use_dir
+
+        subdirs = os.listdir(zipdir)
+        if len(subdirs) == 1 and os.path.isdir(os.path.join(zipdir, \
+                subdirs[0])) and subdirs[0] != 'source':
+            zipdir = os.path.join(zipdir, subdirs[0])
+
+        #sid = transaction.savepoint()
+
+        if clean_data:
+            try:
+                self.clean_existing_data()
+            except Exception, e:
+                return False
+
+        ret = ""
+        try:
+            for data_type, rst, category, slug, title, html_content, \
+                    images, stderr in generate_rst_contents_from_dir(zipdir):
+
+                ret += stderr
+                #logger.info("Parsed %s" % (rst, ))
+                if stderr:
+                    pass
+                    #logger.info("Error output: %s" % (stderr, ))
+
+                service = service_from_filename(rst)
+                if not service:
+                    logger.info("Skipping entry for file '%s'. No category found" % rst)
+                    continue
+
+
+                # first save media files
+                newimages = []
+                if data_type == 'userguide':
+                    cat = get_media_category(self.GUIDE_MEDIA_CATEGORY)
+                if data_type == 'faq':
+                    cat = get_media_category(self.FAQ_MEDIA_CATEGORY)
+
+                if not cat:
+                    continue
+
+                for imgname, imgpath, imgabspath in images:
+                    newalt, newpath = create_or_update_media_file(cat, \
+                            imgname, imgabspath)
+
+                    html_content = html_content.replace(imgpath, newpath)
+
+                # now html contains correct image links, we are ready to save
+                # the faq/guide content
+                if data_type == 'faq':
+                    cat = add_or_update_faq_category(category[0], category[1])
+                    question = add_or_update_faq_question(user, service, cat, slug, \
+                            title, html_content)
+
+                if data_type == 'userguide':
+                    guide_entry = add_or_update_guide_entry(user, service, slug, \
+                            title, html_content)
+
+
+        except Exception, e:
+            logger.exception("RST import failed")
+            #transaction.savepoint_rollback(sid)
+            return False
+
+        if dry_run:
+            pass
+            #transaction.savepoint_rollback(sid)
+        else:
+            pass
+            #transaction.savepoint_commit(sid)
+
+
+def create_or_update_media_file(category, name, path):
+    name = category.title + " " + name
+
+    try:
+        # TODO: Check language ?????
+        mf = MediaFile.objects.get(categories=category, translations__caption=name)
+    except MediaFile.DoesNotExist:
+        mf = MediaFile()
+        mf.category = category
+        mf.file = File(open(path))
+        mf.save()
+        mf.translations.create(caption=name)
+
+    # TODO: Check language ?????
+    return mf.translations.all()[0].caption, mf.get_absolute_url()
+
+
+def add_or_update_faq_category(slug, name):
+    try:
+        category = FaqCategory.objects.get(translations__slug=slug)
+    except FaqCategory.DoesNotExist:
+        category = FaqCategory()
+        category.save()
+        category.translations.create(slug=slug, title=name)
+
+    return category
+
+def add_or_update_faq_question(author, service, category, slug,\
+        title, html_content):
+
+    try:
+        q = Question.objects.get(slug=slug)
+    except:
+        q = Question()
+
+    q.author = author
+    q.is_active = True
+    q.category = category
+    q.service = service
+    q.slug = slug
+    q.title = title
+    q.save()
+    q.application = [Application.current()]
+    q.save()
+
+    RawContentModel = Question.content_type_for(RawContent)
+    try:
+        content = q.rawcontent_set.filter()[0]
+    except:
+        content = q.rawcontent_set.create(region=REGIONS_MAP['faq'])
+
+    content.text = html_content
+    content.save()
+
+    return q
+
+def add_or_update_guide_entry(author, service, slug, title, html_content):
+    try:
+        guide = UserGuideEntry.objects.get(slug=slug)
+    except:
+        guide = UserGuideEntry()
+
+    guide.author = author
+    guide.is_active = True
+    guide.service = service
+    guide.slug = slug
+    guide.title = title
+    guide.save()
+
+    RawContentModel = UserGuideEntry.content_type_for(RawContent)
+    try:
+        content = guide.rawcontent_set.filter()[0]
+    except:
+        content = guide.rawcontent_set.create(region=REGIONS_MAP['userguide'])
+
+    content.text = html_content
+    content.save()
+
+    return guide
+
diff --git a/cloudcms/migrate/cloudcmsguide/0001_initial.py b/cloudcms/migrate/cloudcmsguide/0001_initial.py
new file mode 100644 (file)
index 0000000..2a6d93c
--- /dev/null
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'UserGuideEntry'
+        db.create_table('cloudcmsguide_userguideentry', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)),
+            ('is_featured', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=100)),
+            ('author', self.gf('django.db.models.fields.related.ForeignKey')(related_name='guide_pages', to=orm['auth.User'])),
+            ('language', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('published_on', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, null=True, blank=True)),
+            ('last_changed', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+            ('service', self.gf('django.db.models.fields.related.ForeignKey')(related_name='userguideentries', null=True, to=orm['cloudcms.Service'])),
+            ('creation_date', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('modification_date', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('meta_keywords', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('meta_description', self.gf('django.db.models.fields.TextField')(blank=True)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['UserGuideEntry'])
+
+        # Adding model 'RawContent'
+        db.create_table('cloudcmsguide_userguideentry_rawcontent', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('text', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='rawcontent_set', to=orm['cloudcmsguide.UserGuideEntry'])),
+            ('region', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')(default=0)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['RawContent'])
+
+        # Adding model 'TemplateContent'
+        db.create_table('cloudcmsguide_userguideentry_templatecontent', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('filename', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='templatecontent_set', to=orm['cloudcmsguide.UserGuideEntry'])),
+            ('region', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')(default=0)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['TemplateContent'])
+
+        # Adding model 'SectionContent'
+        db.create_table('cloudcmsguide_userguideentry_sectioncontent', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)),
+            ('richtext', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sectioncontent_set', to=orm['cloudcmsguide.UserGuideEntry'])),
+            ('region', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')(default=0)),
+            ('mediafile', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='cloudcmsguide_sectioncontent_set', null=True, to=orm['medialibrary.MediaFile'])),
+            ('type', self.gf('django.db.models.fields.CharField')(default='block', max_length=10)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['SectionContent'])
+
+        # Adding model 'RichTextContent'
+        db.create_table('cloudcmsguide_userguideentry_richtextcontent', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('text', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='richtextcontent_set', to=orm['cloudcmsguide.UserGuideEntry'])),
+            ('region', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')(default=0)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['RichTextContent'])
+
+        # Adding model 'ImageContent'
+        db.create_table('cloudcmsguide_userguideentry_imagecontent', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='imagecontent_set', to=orm['cloudcmsguide.UserGuideEntry'])),
+            ('region', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')(default=0)),
+            ('position', self.gf('django.db.models.fields.CharField')(default='default', max_length=10)),
+        ))
+        db.send_create_signal('cloudcmsguide', ['ImageContent'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'UserGuideEntry'
+        db.delete_table('cloudcmsguide_userguideentry')
+
+        # Deleting model 'RawContent'
+        db.delete_table('cloudcmsguide_userguideentry_rawcontent')
+
+        # Deleting model 'TemplateContent'
+        db.delete_table('cloudcmsguide_userguideentry_templatecontent')
+
+        # Deleting model 'SectionContent'
+        db.delete_table('cloudcmsguide_userguideentry_sectioncontent')
+
+        # Deleting model 'RichTextContent'
+        db.delete_table('cloudcmsguide_userguideentry_richtextcontent')
+
+        # Deleting model 'ImageContent'
+        db.delete_table('cloudcmsguide_userguideentry_imagecontent')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'cloudcms.service': {
+            'Meta': {'ordering': "['-ordering']", 'object_name': 'Service'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})
+        },
+        'cloudcmsguide.imagecontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ImageContent', 'db_table': "'cloudcmsguide_userguideentry_imagecontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imagecontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'position': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '10'}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'cloudcmsguide.rawcontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'RawContent', 'db_table': "'cloudcmsguide_userguideentry_rawcontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rawcontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'cloudcmsguide.richtextcontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'RichTextContent', 'db_table': "'cloudcmsguide_userguideentry_richtextcontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'richtextcontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'cloudcmsguide.sectioncontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'SectionContent', 'db_table': "'cloudcmsguide_userguideentry_sectioncontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mediafile': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'cloudcmsguide_sectioncontent_set'", 'null': 'True', 'to': "orm['medialibrary.MediaFile']"}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sectioncontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'richtext': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "'block'", 'max_length': '10'})
+        },
+        'cloudcmsguide.templatecontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'TemplateContent', 'db_table': "'cloudcmsguide_userguideentry_templatecontent'"},
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'templatecontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'cloudcmsguide.userguideentry': {
+            'Meta': {'ordering': "['service', '-published_on']", 'object_name': 'UserGuideEntry'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'guide_pages'", 'to': "orm['auth.User']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'language': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'last_changed': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'meta_keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'modification_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'published_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userguideentries'", 'null': 'True', 'to': "orm['cloudcms.Service']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'medialibrary.category': {
+            'Meta': {'ordering': "['parent__title', 'title']", 'object_name': 'Category'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['medialibrary.Category']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+        },
+        'medialibrary.mediafile': {
+            'Meta': {'object_name': 'MediaFile'},
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['medialibrary.Category']", 'null': 'True', 'blank': 'True'}),
+            'copyright': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
+        }
+    }
+
+    complete_apps = ['cloudcmsguide']
\ No newline at end of file
diff --git a/cloudcms/migrate/cloudcmsguide/0002_auto__add_field_userguideentry_lft__add_field_userguideentry_rght__add.py b/cloudcms/migrate/cloudcmsguide/0002_auto__add_field_userguideentry_lft__add_field_userguideentry_rght__add.py
new file mode 100644 (file)
index 0000000..f157b8c
--- /dev/null
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'UserGuideEntry.lft'
+        db.add_column('cloudcmsguide_userguideentry', 'lft',
+                      self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True),
+                      keep_default=False)
+
+        # Adding field 'UserGuideEntry.rght'
+        db.add_column('cloudcmsguide_userguideentry', 'rght',
+                      self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True),
+                      keep_default=False)
+
+        # Adding field 'UserGuideEntry.tree_id'
+        db.add_column('cloudcmsguide_userguideentry', 'tree_id',
+                      self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True),
+                      keep_default=False)
+
+        # Adding field 'UserGuideEntry.level'
+        db.add_column('cloudcmsguide_userguideentry', 'level',
+                      self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True),
+                      keep_default=False)
+
+        # Adding field 'UserGuideEntry.parent'
+        db.add_column('cloudcmsguide_userguideentry', 'parent',
+                      self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['cloudcmsguide.UserGuideEntry']),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'UserGuideEntry.lft'
+        db.delete_column('cloudcmsguide_userguideentry', 'lft')
+
+        # Deleting field 'UserGuideEntry.rght'
+        db.delete_column('cloudcmsguide_userguideentry', 'rght')
+
+        # Deleting field 'UserGuideEntry.tree_id'
+        db.delete_column('cloudcmsguide_userguideentry', 'tree_id')
+
+        # Deleting field 'UserGuideEntry.level'
+        db.delete_column('cloudcmsguide_userguideentry', 'level')
+
+        # Deleting field 'UserGuideEntry.parent'
+        db.delete_column('cloudcmsguide_userguideentry', 'parent_id')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'cloudcms.service': {
+            'Meta': {'ordering': "['-ordering']", 'object_name': 'Service'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})
+        },
+        'cloudcmsguide.imagecontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ImageContent', 'db_table': "'cloudcmsguide_userguideentry_imagecontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imagecontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'position': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '10'}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'cloudcmsguide.rawcontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'RawContent', 'db_table': "'cloudcmsguide_userguideentry_rawcontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rawcontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'cloudcmsguide.richtextcontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'RichTextContent', 'db_table': "'cloudcmsguide_userguideentry_richtextcontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'richtextcontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'cloudcmsguide.sectioncontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'SectionContent', 'db_table': "'cloudcmsguide_userguideentry_sectioncontent'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mediafile': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'cloudcmsguide_sectioncontent_set'", 'null': 'True', 'to': "orm['medialibrary.MediaFile']"}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sectioncontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'richtext': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "'block'", 'max_length': '10'})
+        },
+        'cloudcmsguide.templatecontent': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'TemplateContent', 'db_table': "'cloudcmsguide_userguideentry_templatecontent'"},
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'templatecontent_set'", 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'region': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'cloudcmsguide.userguideentry': {
+            'Meta': {'ordering': "['service', '-published_on']", 'object_name': 'UserGuideEntry'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'guide_pages'", 'to': "orm['auth.User']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'language': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'last_changed': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'meta_keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'modification_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['cloudcmsguide.UserGuideEntry']"}),
+            'published_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userguideentries'", 'null': 'True', 'to': "orm['cloudcms.Service']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'medialibrary.category': {
+            'Meta': {'ordering': "['parent__title', 'title']", 'object_name': 'Category'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['medialibrary.Category']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+        },
+        'medialibrary.mediafile': {
+            'Meta': {'object_name': 'MediaFile'},
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['medialibrary.Category']", 'null': 'True', 'blank': 'True'}),
+            'copyright': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
+        }
+    }
+
+    complete_apps = ['cloudcmsguide']
diff --git a/cloudcms/migrate/cloudcmsguide/__init__.py b/cloudcms/migrate/cloudcmsguide/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cloudcms/rstutils.py b/cloudcms/rstutils.py
new file mode 100644 (file)
index 0000000..642a269
--- /dev/null
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+
+"""
+Helper methods to parse rst documents and extract data appropriate for faq/guide
+entries creation.
+"""
+
+import os
+import sys
+import glob
+import StringIO
+
+from os.path import join
+from collections import defaultdict
+from docutils.core import publish_parts
+from lxml import html
+
+class SphinxImportException(Exception):
+    pass
+
+
+class SphinxImportValidationError(SphinxImportException):
+    pass
+
+
+def rst2html(data):
+    """
+    Use docutils publis_parts to convert rst to html. Return parts body and error
+    output tuple.
+    """
+    origstderr = sys.stderr
+    sys.stderr = StringIO.StringIO()
+
+    parts = publish_parts(data, writer_name='html')['body']
+    sys.stderr.seek(0)
+    output = sys.stderr.read()
+    sys.stderr = origstderr
+
+    return parts, output
+
+
+def parse_rst_data(data, data_type='faq'):
+    """
+    Parse given data from rst to html. Filter html and generate approriate
+    entries based on data_type provided.
+
+    Generated content:
+
+        - **category** (used for `faq` data type since each question belongs to a
+          specific category)
+        - **slug** the slug of the entry
+        - **title** the title of the entry
+        - **html_data** the html content of the entry
+        - **images** (img-alt, img-path) tuples list
+    """
+    html_data, output = rst2html(data)
+    doc = html.document_fromstring("<html><body>" + html_data + "</body></html>")
+
+    category_selectors = {
+        'faq': ".//div[h2][@class='section']",
+        'userguide': ".//div[h1][@class='section']",
+    }
+
+    # find first level sections
+    sections = doc.findall(category_selectors[data_type])
+    for section in sections:
+        entry_category = (None, None)
+
+        attrs = dict(section.items())
+        if not attrs.get('id', None):
+            continue
+
+        slug = attrs.get('id')
+        if data_type == 'userguide':
+            title = section.find('h1').text_content()
+            section.remove(section.find('h1'))
+        else:
+            title = section.find('h2').text_content()
+            section.remove(section.find('h2'))
+
+        image_els = section.findall('.//img')
+
+        if data_type == 'faq':
+            h1 = list(section.iterancestors())[0].find(".//h1")
+            el_with_id = dict(h1.getparent().items())
+            entry_category = (el_with_id.get('id', None), h1.text_content())
+
+
+        def get_img_el_data(img):
+            attrs = dict(img.items())
+            alt = attrs.get('alt', None)
+            if not alt:
+                alt = "okeanos iaas " + data_type + " image"
+            else:
+                if len(alt.split("/")) > 0:
+                    alt = data_type + " " + alt.split("/")[-1]
+                if len(alt.split(".")) > 0:
+                    alt = alt.split(".")[0]
+
+            img.set('alt', alt)
+
+            src = attrs.get('src')
+            if src.startswith("/images"):
+                src = src[1:]
+                img.set('src', src)
+
+            return attrs.get('alt', None), src
+
+        images = map(get_img_el_data, image_els)
+
+        html_data = ""
+        for child in section.getchildren():
+            html_data += html.tostring(child, pretty_print=True)
+
+        yield entry_category, slug, title, html_data, images, output
+
+
+def get_dir_rst_files(dirname):
+    """
+    Given a dir return the glob of *.rst files
+    """
+    for f in glob.glob(join(dirname, '*.rst')):
+        if f.startswith('index'):
+            continue
+        yield f
+
+
+def generate_rst_contents_from_dir(rstdir):
+    """
+    Handle directory contents and run ``parse_rst_data`` for each file we want
+    to parse.
+
+    Valid structure of the dir contents so that appropriate files can be parsed::
+
+        â”œâ”€â”€ README.rst
+        â””── source
+            â”œâ”€â”€ conf.py
+            â”œâ”€â”€ faq
+            â”‚   â”œâ”€â”€ cyclades.rst
+            â”‚   â”œâ”€â”€ index.rst
+            â”‚   â”œâ”€â”€ okeanos.rst
+            â”‚   â””── pithos.rst
+            â”œâ”€â”€ images
+            â”‚   â”œâ”€â”€ cyclades
+            â”‚   â”‚   â”œâ”€â”€ image10.png
+            â”‚   â”‚   â””── image9.png
+            â”‚   â”œâ”€â”€ faq
+            â”‚   â”‚   â””── faq_image1.png
+            â”‚   â”œâ”€â”€ intro_img_cyclades.png
+            â”‚   â””── pithos_guide
+            â”‚       â””── image2.png
+            â”œâ”€â”€ index.rst
+            â””── userguide
+                â”œâ”€â”€ cyclades.rst
+                â”œâ”€â”€ index.rst
+                â”œâ”€â”€ pithos.rst
+                â””── quick-intro.rst
+
+    Will generate a tuple of,
+
+        ['faq', 'userguide'], </abs/path/filename.rst> + *<generated tuple members of ``parse_rst_data``>
+
+    """
+
+    #rstdir = "/tmp/tmphsl6bicloudcms-sphinx-exports"
+
+    fpath = lambda x: join(rstdir, 'source', x)
+
+    images_dir = fpath('images')
+    guide_dir = fpath('userguide')
+    faq_dir = fpath('faq')
+
+    # validation
+    if not os.path.exists(images_dir) or not os.path.isdir(images_dir):
+        raise SphinxImportException('Cannot find images dir')
+
+    if not os.path.exists(guide_dir) or not os.path.isdir(guide_dir):
+        raise SphinxImportException('Cannot find guide dir')
+
+    if not os.path.exists(faq_dir) or not os.path.isdir(faq_dir):
+        raise SphinxImportException('Cannot find FAQs dir')
+
+    def fix_image_path(img):
+        # make image path absolute
+        img = list(img)
+        if img[1].startswith("/"):
+            img.append(fpath(img[1][1:]))
+        else:
+            img.append(fpath(img[1]))
+
+        return img
+
+    for d in ['userguide', 'faq']:
+        for f in get_dir_rst_files(fpath(d)):
+            for category, slug, title, html_data, \
+                    images, stderr in parse_rst_data(file(f).read(), d):
+                # absolute image paths
+                images = map(fix_image_path, images)
+                yield d, f, category, slug, title, html_data, images, stderr
+
+
+
index fac7f0f..5e6aa0e 100644 (file)
@@ -37,6 +37,8 @@ cloudcms_apps = [
     'cloudcmsblog',
     'cloudcmsfaq',
     'cloudcmsresources',
+    'cloudcmsguide',
+
     'pagination',
 
     'django.contrib.auth',
@@ -88,6 +90,7 @@ try:
         SOUTH_MIGRATION_MODULES = {
             'cloudcmsblog': 'cloudcms.migrate.cloudcmsblog',
             'cloudcmsfaq': 'cloudcms.migrate.cloudcmsfaq',
+            'cloudcmsguide': 'cloudcms.migrate.cloudcmsguide',
             'feincms.module.page': 'cloudcms.migrate.page',
             'feincms.module.medialibrary': 'cloudcms.migrate.medialibrary',
         }
@@ -95,6 +98,7 @@ try:
         SOUTH_MIGRATION_MODULES = {
             'cloudcmsblog': 'cloudcms.migrate.cloudcmsblog',
             'cloudcmsfaq': 'cloudcms.migrate.cloudcmsfaq',
+            'cloudcmsguide': 'cloudcms.migrate.cloudcmsguide',
             'page': 'cloudcms.migrate.page',
             'medialibrary': 'cloudcms.migrate.medialibrary',
         }
@@ -105,3 +109,14 @@ except:
 import os
 SITE_ID = int(os.environ.get('CLOUDCMS_SITE_ID', 1))
 
+loggers = {
+    'cloudcms.rstimport': {
+        'handlers': ['console'],
+        'level': 'DEBUG'
+    },
+    'cloudcms': {
+        'handlers': ['console'],
+        'level': 'DEBUG'
+    }
+}
+
diff --git a/cloudcms/templates/cms/admin_import_guide_faq.html b/cloudcms/templates/cms/admin_import_guide_faq.html
new file mode 100644 (file)
index 0000000..3d1d2b0
--- /dev/null
@@ -0,0 +1,44 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+
+
+{% block extrahead %}{{ block.super }}
+{% url admin:jsi18n as jsi18nurl %}
+<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
+{% endblock %}
+
+{% block coltype %}{% endblock %}
+
+{% block bodyclass %}change-form{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../">{% trans "Home" %}</a> &rsaquo;
+     <a href=".">{% trans "Import faq/guide from sphinx zip" %}</a> &rsaquo; 
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+
+<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+  <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+  {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+  </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form enctype="multipart/form-data" action="." method="post" id="import_form">{% csrf_token %}{% block form_top %}{% endblock %}
+    <div>
+        {{ form.non_field_errors }}
+        {{ form.as_p }}
+        <input type="submit" />
+    </div>
+</form></div>
+{% endblock %}
diff --git a/cloudcms/templates/cms/pages/userguide.html b/cloudcms/templates/cms/pages/userguide.html
new file mode 100644 (file)
index 0000000..fa5ce7b
--- /dev/null
@@ -0,0 +1,22 @@
+{% extends "cms/pages/basic1top2bottom.html" %}
+{% load applicationcontent_tags feincms_page_tags %}
+
+
+{% block page.bottom_right.content %}
+    {% if request|has_fragment:"maincol" %}
+        {% get_fragment request "maincol" %}
+    {% else %}
+        {{ block.super }}
+    {% endif %}
+{% endblock %}
+
+{% block page.bottom_left.content %}
+    {% if request|has_fragment:"sidecol" %}
+        {% get_fragment request "sidecol" %}
+    {% else %}
+        {{ block.super }}
+    {% endif %}
+{% endblock %}
+
+
+
index d46877d..986eab0 100644 (file)
@@ -17,5 +17,4 @@ def get_service_faqs(service):
 
         grouped[q.category].append(q)
 
-    print grouped
     return grouped.iteritems()
diff --git a/cloudcms/test_settings.py b/cloudcms/test_settings.py
new file mode 100644 (file)
index 0000000..441fa1d
--- /dev/null
@@ -0,0 +1,3 @@
+from synnefo.settings import *
+
+DATABASES['default']['name'] = ":memory:"
diff --git a/cloudcms/tests/__init__.py b/cloudcms/tests/__init__.py
new file mode 100644 (file)
index 0000000..1feac3c
--- /dev/null
@@ -0,0 +1,102 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+import os
+import tempfile
+
+from django.test import TestCase, TransactionTestCase
+from django.contrib.auth.models import User
+from django.core.files.uploadedfile import SimpleUploadedFile
+
+from feincms.module.medialibrary.models import Category as MediaCategory, \
+        MediaFile
+
+from cloudcmsguide.models import UserGuideEntry
+from cloudcmsfaq.models import Category, Question
+from cloudcms.rstutils import generate_rst_contents_from_dir
+from cloudcms.models import Service, ServiceTranslation
+from cloudcms.forms import RstZipImportForm
+
+from synnefo.lib.log import initialize_logging
+
+from django.conf import settings
+
+initialize_logging()
+
+TESTS_BASE = os.path.abspath(os.path.dirname(__file__))
+
+TEST_DIR1 = os.path.join(TESTS_BASE, 'userdocs1')
+TEST_DIR2 = os.path.join(TESTS_BASE, 'userdocs2')
+
+TEST_INVALID_ZIP = os.path.join(TESTS_BASE, 'invalidzip.zip')
+TEST_VALID_ZIP = os.path.join(TESTS_BASE, 'okeanos-user-docs.zip')
+TEST_VALID_ZIP = os.path.join(TESTS_BASE, 'userdocs1.zip')
+
+settings.MEDIA_ROOT = tempfile.mkdtemp("cms-tests")
+
+class TestSphinxImport(TransactionTestCase):
+
+    fixtures = ['cloudcms_media_categories', 'cloudcms_default_services']
+
+    def setUp(self):
+        c = Category.objects.create()
+        self.user = User.objects.create(username="admin", email="admin@admin.com")
+        self.user.is_superuser = True
+        self.user.set_password("test")
+        self.user.save()
+
+    def test_from_dir1(self):
+        zipfile = SimpleUploadedFile('file.zip', file(TEST_VALID_ZIP).read())
+        form = RstZipImportForm({'import_categories': ['faqs', 'guide']}, files={'import_file': zipfile})
+        form.is_valid()
+        form.save(self.user, TEST_DIR1)
+
+        self.assertEqual(MediaFile.objects.all().count(), 5)
+        self.assertEqual(Question.objects.all().count(), 4)
+        self.assertEqual(UserGuideEntry.objects.all().count(), 3)
+
+    def test_form(self):
+        zipfile = SimpleUploadedFile('file.zip', file(TEST_INVALID_ZIP).read())
+        form = RstZipImportForm({'import_categories': ['faqs', 'guide']}, files={'import_file': zipfile})
+        self.assertEqual(form.is_valid(), False)
+
+        zipfile = SimpleUploadedFile('file.zip', file(TEST_VALID_ZIP).read())
+        form = RstZipImportForm({'import_categories': ['faqs', 'guide']}, files={'import_file': zipfile})
+        self.assertEqual(form.is_valid(), True)
+
+        form.save(self.user)
+        self.assertEqual(MediaFile.objects.all().count(), 5)
+        self.assertEqual(Question.objects.all().count(), 4)
+        self.assertEqual(UserGuideEntry.objects.all().count(), 3)
+
diff --git a/cloudcms/tests/invalidzip.zip b/cloudcms/tests/invalidzip.zip
new file mode 100644 (file)
index 0000000..a13721a
--- /dev/null
@@ -0,0 +1 @@
+INVALID ZIP FILE
diff --git a/cloudcms/tests/okeanos-user-docs.zip b/cloudcms/tests/okeanos-user-docs.zip
new file mode 100644 (file)
index 0000000..168b3f0
Binary files /dev/null and b/cloudcms/tests/okeanos-user-docs.zip differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image.png b/cloudcms/tests/test_media/medialibrary/2012/07/image.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_1.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_1.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_1.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_2.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_2.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_2.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_3.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_3.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_3.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_4.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_4.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_4.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_5.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_5.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_5.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_6.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_6.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_6.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_7.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_7.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_7.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_8.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_8.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_8.png differ
diff --git a/cloudcms/tests/test_media/medialibrary/2012/07/image_9.png b/cloudcms/tests/test_media/medialibrary/2012/07/image_9.png
new file mode 100755 (executable)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/test_media/medialibrary/2012/07/image_9.png differ
diff --git a/cloudcms/tests/userdocs1.zip b/cloudcms/tests/userdocs1.zip
new file mode 100644 (file)
index 0000000..7e6bc9f
Binary files /dev/null and b/cloudcms/tests/userdocs1.zip differ
diff --git a/cloudcms/tests/userdocs1/source/faq/cyclades.rst b/cloudcms/tests/userdocs1/source/faq/cyclades.rst
new file mode 100644 (file)
index 0000000..72253cb
--- /dev/null
@@ -0,0 +1,19 @@
+.. _cyclades-questions:
+
+==================
+Cyclades questions
+==================
+
+General
+********
+
+Is there any limit to the number of VMs I can create?
+==============================================================================================================
+
+For the alpha2 phase of ~okeanos, the number of VMs is limited to 2 for each user.
+
+Can I have less VMs with more Resources? (e.g 1VM with 80GB of disk)
+====================================================================
+
+That option is not available. Maximum resources that can be used are the same for all the VMs.
+
diff --git a/cloudcms/tests/userdocs1/source/faq/index.rst b/cloudcms/tests/userdocs1/source/faq/index.rst
new file mode 100644 (file)
index 0000000..57439b1
--- /dev/null
@@ -0,0 +1,9 @@
+Frequently asked questions
+==========================
+
+.. toctree::
+   :maxdepth: 3
+   
+   okeanos
+   cyclades
+   pithos
diff --git a/cloudcms/tests/userdocs1/source/faq/okeanos.rst b/cloudcms/tests/userdocs1/source/faq/okeanos.rst
new file mode 100644 (file)
index 0000000..e11f83e
--- /dev/null
@@ -0,0 +1,46 @@
+.. _okeanos-faq:
+
+=================
+Okeanos questions
+=================
+
+Requirements
+**************
+
+What web browser should I use to access the ~okeanos service?
+========================================================================================================
+
+Mozilla Firefox, chrome Internet explorer, safari (To be confirmed) Mobile browsers? iPhone? (Dolphin, Opera mini, Safari mobile) it would be nice to check the status of the vm from a mobile device
+
+What are the requirements for my computer in order to use ~okeanos? 
+========================================================================================================
+
+~Okeanos does not have any specific requirements for your physical computer. The only thing that you will need is a compatible web browser and an Internet connection.   
+
+
+Data safety
+************
+
+Is there any chance of losing my data while using the service?
+========================================================================================================
+
+~Okeanos is currently on Alpha2 testing phase. As a result, the service might become unstable since it is still on its testing phase.
+
+Are the data that I store on my virtual machines available to other users of the service?
+========================================================================================================
+
+Only the user that has the credentials of the account can have access to the data stored on the virtual machines of that account. No other user of the service can access that data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cloudcms/tests/userdocs1/source/faq/pithos.rst b/cloudcms/tests/userdocs1/source/faq/pithos.rst
new file mode 100644 (file)
index 0000000..58341ea
--- /dev/null
@@ -0,0 +1,5 @@
+.. _pithos-faq:
+
+Pithos questions
+================
+
diff --git a/cloudcms/tests/userdocs1/source/images/cyclades/image.png b/cloudcms/tests/userdocs1/source/images/cyclades/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs1/source/images/cyclades/image.png differ
diff --git a/cloudcms/tests/userdocs1/source/images/faq/image.png b/cloudcms/tests/userdocs1/source/images/faq/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs1/source/images/faq/image.png differ
diff --git a/cloudcms/tests/userdocs1/source/images/image.png b/cloudcms/tests/userdocs1/source/images/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs1/source/images/image.png differ
diff --git a/cloudcms/tests/userdocs1/source/images/pithos_guide/image.png b/cloudcms/tests/userdocs1/source/images/pithos_guide/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs1/source/images/pithos_guide/image.png differ
diff --git a/cloudcms/tests/userdocs1/source/index.rst b/cloudcms/tests/userdocs1/source/index.rst
new file mode 100644 (file)
index 0000000..9d60b5f
--- /dev/null
@@ -0,0 +1,18 @@
+~okeanos documentation
+======================
+
+.. toctree::
+   :maxdepth: 3
+    
+   userguide/index
+   faq/index
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/cloudcms/tests/userdocs1/source/userguide/anotherfile.rst b/cloudcms/tests/userdocs1/source/userguide/anotherfile.rst
new file mode 100644 (file)
index 0000000..06e1867
--- /dev/null
@@ -0,0 +1,58 @@
+.. _quick-intro-guide:
+
+Quick intro
+***********
+
+Nam pretium tellus non massa vulputate nec sagittis libero pharetra. Nulla
+facilisi. Pellentesque habitant morbi tristique senectus et netus et malesuada
+fames ac turpis egestas. Aliquam blandit purus eget libero commodo faucibus.
+
+.. figure:: /images/okeanos_intro.png
+
+    ~okeanos screenshot
+
+Quisque semper felis nec erat consectetur non aliquet lacus tempus. Integer
+feugiat, metus scelerisque gravida convallis, nisl velit cursus mi, non rhoncus
+odio velit sed mi. Phasellus porta ullamcorper dui, eu venenatis lectus lacinia
+ut. Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus. Sed vulputate turpis nunc, ut varius tellus. 
+
+
+Title 1
+=======
+
+Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus.
+
+* This is a bulleted list.
+* It has two items, the second
+  item uses two lines.
+
+#. This is a numbered list.
+#. It has two items.
+
+1. This is a numbered list.
+2. It has two items too.
+
+
+
+Title 1.1
+---------
+
+Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus.
diff --git a/cloudcms/tests/userdocs1/source/userguide/cyclades.rst b/cloudcms/tests/userdocs1/source/userguide/cyclades.rst
new file mode 100644 (file)
index 0000000..d8feefd
--- /dev/null
@@ -0,0 +1,81 @@
+\r
+.. _cyclades-user-guide:\r
+\r
+Cyclades user guide\r
+*******************\r
+\r
+Welcome!\r
+~~~~~~~~\r
+\r
+In Cyclades, you can create your own virtual machines, manage them and connect them via private networks.\r
+When you first enter the Cyclades homepage, the main menu bar will appear in front of your screen:\r
+\r
+.. figure:: images/image.png\r
+\r
+    HELLO IMAGE\r
+\r
+\r
+How to create a VM?\r
+~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+\r
+Initially, as in most important things, you have to press the big bright button! (|image7_png|)\r
+\r
+\r
+**Step 1:**\r
+Select the operating system you want for your VM\r
+\r
+.. image:: images/image.png\r
+\r
+You can choose between Windows 2008 Server Edition or different distributions of Linux (with or without a GUI).\r
+\r
+\r
+**Step 2:**\r
+Depending on the OS you have selected, ~okeanos has preselected some of the VM's characteristics.\r
+If you want, you can change them now!\r
+\r
+.. image:: /images/image.png\r
+\r
+You can select how many cores your processor will have, how many GB of RAM your system will boast , or the size of your disk drive.\r
+Alternatively, you can simply choose the power scale of your VM, ie Small, Medium or Large.\r
+\r
+\r
+**Step 3:**\r
+Name your VM!\r
+\r
+.. image:: /images/image.png\r
+\r
+At this point, you can name your VM and give it a role (MailServer, Proxy, etc.).\r
+The above do not affect the VM you create. They merely help you with categorising your arsenal of VMs!\r
+\r
+As for the ssh keys, they can be regarded as your "fingerprint", large alphanumeric sequences that allow you to access remote machines without ever having to enter your username/password.\r
+More details about the process of creating these keys can be found in `How can I create SSH keys?`_.\r
+\r
+\r
+**Step 4:**\r
+Confirmation\r
+\r
+.. image:: images/cyclades/image.png\r
+\r
+At this point you will see a summary of your choices. If everything seems fine to you, by clicking the "create machine" button, your order will be processed and your VM will be available in no time!\r
+And of course, do not forget to save the password that will appear on your screen, since for security reasons there is absolutely no other way to retrieve it!\r
+\r
+.. _cyclades-connection-guide:\r
+\r
+How do I connect to a VM?\r
+~~~~~~~~~~~~~~~~~~~~~~~~~\r
+\r
+Now that you have created your machine, you can connect to it. We have created the following guides to help decompose the connecting process into a set of simple commmands and clicks-clicks, having the less computer savvy user in mind. Before we begin with the guides, we need to clarify some terms that might be new to an ~okeanos user, but are important to help choose what guide suits his/her needs:\r
+\r
+* **Host machine** is the computer you are using in your home i.e. your laptop, your PC\r
+* **Guest machine** is the target VM you want to connect to in ~okeanos\r
+* **Graphical connection** is your typical connection type, the one with the fancy graphics that you most propably use in your host machine. *(Not available for Debian Base and CentOS guest machines)* \r
+* **Command-line connection** is a less typical connection type, where navigating and using your VM consists of typing commands to a terminal. *(Not available for Windows guest machines)*\r
+\r
+With that clarified, you can jump right into the appropriate guide\r
+\r
+* `Windows host to Windows guest [Graphical]`_\r
+* `Windows/Linux host to Linux guest [Graphical]`_\r
+* `Windows/Linux host to Linux guest [Command-line]`_\r
+* `Linux host to Windows guest [Graphical]`_\r
+* `Connecting via console`_\r
+\r
diff --git a/cloudcms/tests/userdocs1/source/userguide/index.rst b/cloudcms/tests/userdocs1/source/userguide/index.rst
new file mode 100644 (file)
index 0000000..8044ee7
--- /dev/null
@@ -0,0 +1,9 @@
+User guide
+==========
+
+.. toctree::
+   :maxdepth: 3
+   
+   quick-intro
+   cyclades
+   pithos
diff --git a/cloudcms/tests/userdocs2/source/faq/cyclades.rst b/cloudcms/tests/userdocs2/source/faq/cyclades.rst
new file mode 100644 (file)
index 0000000..72253cb
--- /dev/null
@@ -0,0 +1,19 @@
+.. _cyclades-questions:
+
+==================
+Cyclades questions
+==================
+
+General
+********
+
+Is there any limit to the number of VMs I can create?
+==============================================================================================================
+
+For the alpha2 phase of ~okeanos, the number of VMs is limited to 2 for each user.
+
+Can I have less VMs with more Resources? (e.g 1VM with 80GB of disk)
+====================================================================
+
+That option is not available. Maximum resources that can be used are the same for all the VMs.
+
diff --git a/cloudcms/tests/userdocs2/source/faq/index.rst b/cloudcms/tests/userdocs2/source/faq/index.rst
new file mode 100644 (file)
index 0000000..57439b1
--- /dev/null
@@ -0,0 +1,9 @@
+Frequently asked questions
+==========================
+
+.. toctree::
+   :maxdepth: 3
+   
+   okeanos
+   cyclades
+   pithos
diff --git a/cloudcms/tests/userdocs2/source/faq/okeanos.rst b/cloudcms/tests/userdocs2/source/faq/okeanos.rst
new file mode 100644 (file)
index 0000000..e11f83e
--- /dev/null
@@ -0,0 +1,46 @@
+.. _okeanos-faq:
+
+=================
+Okeanos questions
+=================
+
+Requirements
+**************
+
+What web browser should I use to access the ~okeanos service?
+========================================================================================================
+
+Mozilla Firefox, chrome Internet explorer, safari (To be confirmed) Mobile browsers? iPhone? (Dolphin, Opera mini, Safari mobile) it would be nice to check the status of the vm from a mobile device
+
+What are the requirements for my computer in order to use ~okeanos? 
+========================================================================================================
+
+~Okeanos does not have any specific requirements for your physical computer. The only thing that you will need is a compatible web browser and an Internet connection.   
+
+
+Data safety
+************
+
+Is there any chance of losing my data while using the service?
+========================================================================================================
+
+~Okeanos is currently on Alpha2 testing phase. As a result, the service might become unstable since it is still on its testing phase.
+
+Are the data that I store on my virtual machines available to other users of the service?
+========================================================================================================
+
+Only the user that has the credentials of the account can have access to the data stored on the virtual machines of that account. No other user of the service can access that data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cloudcms/tests/userdocs2/source/faq/pithos.rst b/cloudcms/tests/userdocs2/source/faq/pithos.rst
new file mode 100644 (file)
index 0000000..58341ea
--- /dev/null
@@ -0,0 +1,5 @@
+.. _pithos-faq:
+
+Pithos questions
+================
+
diff --git a/cloudcms/tests/userdocs2/source/images/cyclades/image.png b/cloudcms/tests/userdocs2/source/images/cyclades/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs2/source/images/cyclades/image.png differ
diff --git a/cloudcms/tests/userdocs2/source/images/faq/image.png b/cloudcms/tests/userdocs2/source/images/faq/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs2/source/images/faq/image.png differ
diff --git a/cloudcms/tests/userdocs2/source/images/image.png b/cloudcms/tests/userdocs2/source/images/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs2/source/images/image.png differ
diff --git a/cloudcms/tests/userdocs2/source/images/pithos_guide/image.png b/cloudcms/tests/userdocs2/source/images/pithos_guide/image.png
new file mode 100644 (file)
index 0000000..936c744
Binary files /dev/null and b/cloudcms/tests/userdocs2/source/images/pithos_guide/image.png differ
diff --git a/cloudcms/tests/userdocs2/source/index.rst b/cloudcms/tests/userdocs2/source/index.rst
new file mode 100644 (file)
index 0000000..9d60b5f
--- /dev/null
@@ -0,0 +1,18 @@
+~okeanos documentation
+======================
+
+.. toctree::
+   :maxdepth: 3
+    
+   userguide/index
+   faq/index
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/cloudcms/tests/userdocs2/source/userguide/anotherfile.rst b/cloudcms/tests/userdocs2/source/userguide/anotherfile.rst
new file mode 100644 (file)
index 0000000..06e1867
--- /dev/null
@@ -0,0 +1,58 @@
+.. _quick-intro-guide:
+
+Quick intro
+***********
+
+Nam pretium tellus non massa vulputate nec sagittis libero pharetra. Nulla
+facilisi. Pellentesque habitant morbi tristique senectus et netus et malesuada
+fames ac turpis egestas. Aliquam blandit purus eget libero commodo faucibus.
+
+.. figure:: /images/okeanos_intro.png
+
+    ~okeanos screenshot
+
+Quisque semper felis nec erat consectetur non aliquet lacus tempus. Integer
+feugiat, metus scelerisque gravida convallis, nisl velit cursus mi, non rhoncus
+odio velit sed mi. Phasellus porta ullamcorper dui, eu venenatis lectus lacinia
+ut. Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus. Sed vulputate turpis nunc, ut varius tellus. 
+
+
+Title 1
+=======
+
+Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus.
+
+* This is a bulleted list.
+* It has two items, the second
+  item uses two lines.
+
+#. This is a numbered list.
+#. It has two items.
+
+1. This is a numbered list.
+2. It has two items too.
+
+
+
+Title 1.1
+---------
+
+Sed odio nunc, faucibus et faucibus congue, convallis non metus. Donec
+vulputate tristique nulla non mollis. Cum sociis natoque penatibus et magnis dis
+parturient montes, nascetur ridiculus mus. Phasellus eu augue risus. Nullam
+sodales, libero et tempor egestas, nunc neque imperdiet mauris, id cursus leo
+nisi nec tortor. Etiam eget massa faucibus lacus ornare lacinia vel sodales
+nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
+ridiculus mus.
diff --git a/cloudcms/tests/userdocs2/source/userguide/cyclades.rst b/cloudcms/tests/userdocs2/source/userguide/cyclades.rst
new file mode 100644 (file)
index 0000000..baa029a
--- /dev/null
@@ -0,0 +1,78 @@
+\r
+.. _cyclades-user-guide:\r
+\r
+Cyclades user guide\r
+*******************\r
+\r
+Welcome!\r
+~~~~~~~~\r
+\r
+In Cyclades, you can create your own virtual machines, manage them and connect them via private networks.\r
+When you first enter the Cyclades homepage, the main menu bar will appear in front of your screen:\r
+|image1_jpeg|\r
+\r
+\r
+How to create a VM?\r
+~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+\r
+Initially, as in most important things, you have to press the big bright button! (|image7_png|)\r
+\r
+\r
+**Step 1:**\r
+Select the operating system you want for your VM\r
+\r
+|image8_png|\r
+\r
+You can choose between Windows 2008 Server Edition or different distributions of Linux (with or without a GUI).\r
+\r
+\r
+**Step 2:**\r
+Depending on the OS you have selected, ~okeanos has preselected some of the VM's characteristics.\r
+If you want, you can change them now!\r
+\r
+|image9_png|\r
+\r
+You can select how many cores your processor will have, how many GB of RAM your system will boast , or the size of your disk drive.\r
+Alternatively, you can simply choose the power scale of your VM, ie Small, Medium or Large.\r
+\r
+\r
+**Step 3:**\r
+Name your VM!\r
+\r
+|image10_png|\r
+\r
+At this point, you can name your VM and give it a role (MailServer, Proxy, etc.).\r
+The above do not affect the VM you create. They merely help you with categorising your arsenal of VMs!\r
+\r
+As for the ssh keys, they can be regarded as your "fingerprint", large alphanumeric sequences that allow you to access remote machines without ever having to enter your username/password.\r
+More details about the process of creating these keys can be found in `How can I create SSH keys?`_.\r
+\r
+\r
+**Step 4:**\r
+Confirmation\r
+\r
+|image11_png|\r
+\r
+At this point you will see a summary of your choices. If everything seems fine to you, by clicking the "create machine" button, your order will be processed and your VM will be available in no time!\r
+And of course, do not forget to save the password that will appear on your screen, since for security reasons there is absolutely no other way to retrieve it!\r
+\r
+.. _cyclades-connection-guide:\r
+\r
+How do I connect to a VM?\r
+~~~~~~~~~~~~~~~~~~~~~~~~~\r
+\r
+Now that you have created your machine, you can connect to it. We have created the following guides to help decompose the connecting process into a set of simple commmands and clicks-clicks, having the less computer savvy user in mind. Before we begin with the guides, we need to clarify some terms that might be new to an ~okeanos user, but are important to help choose what guide suits his/her needs:\r
+\r
+* **Host machine** is the computer you are using in your home i.e. your laptop, your PC\r
+* **Guest machine** is the target VM you want to connect to in ~okeanos\r
+* **Graphical connection** is your typical connection type, the one with the fancy graphics that you most propably use in your host machine. *(Not available for Debian Base and CentOS guest machines)* \r
+* **Command-line connection** is a less typical connection type, where navigating and using your VM consists of typing commands to a terminal. *(Not available for Windows guest machines)*\r
+\r
+With that clarified, you can jump right into the appropriate guide\r
+\r
+* `Windows host to Windows guest [Graphical]`_\r
+* `Windows/Linux host to Linux guest [Graphical]`_\r
+* `Windows/Linux host to Linux guest [Command-line]`_\r
+* `Linux host to Windows guest [Graphical]`_\r
+* `Connecting via console`_\r
+\r
diff --git a/cloudcms/tests/userdocs2/source/userguide/index.rst b/cloudcms/tests/userdocs2/source/userguide/index.rst
new file mode 100644 (file)
index 0000000..8044ee7
--- /dev/null
@@ -0,0 +1,9 @@
+User guide
+==========
+
+.. toctree::
+   :maxdepth: 3
+   
+   quick-intro
+   cyclades
+   pithos
index c0cdfdd..8c449d5 100644 (file)
@@ -46,6 +46,7 @@ sitemaps = {'pages': PageSitemap, 'blog_posts': BlogSitemap }
 
 urlpatterns = patterns('',
     url(r'^feed/', BlogFeed(), name="blogfeed"),
+    url(r'^cmsmanage/sphinximport/$', 'cloudcms.admin.sphinx_import'),
     url(r'^cmsmanage/', include(admin.site.urls)),
     url(r'', include('feincms.urls')),
     url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps }),
diff --git a/cloudcmsguide/__init__.py b/cloudcmsguide/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cloudcmsguide/admin.py b/cloudcmsguide/admin.py
new file mode 100644 (file)
index 0000000..d0594f4
--- /dev/null
@@ -0,0 +1,75 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+from feincms.translations import admin_translationinline, short_language_code
+from feincms.admin import tree_editor, item_editor
+
+from cloudcmsguide.models import UserGuideEntry
+
+
+class UserGuideEntryAdmin(item_editor.ItemEditor, tree_editor.TreeEditor):
+
+    date_hierarchy = 'published_on'
+    list_display = ['title', 'is_active', 'published_on', 'service', 'author']
+    list_editable = ['is_active']
+    list_filter = ['is_active', 'is_featured', 'author']
+    search_fields = ['title', 'slug']
+    raw_id_fields = ['parent']
+
+    prepopulated_fields = {
+        'slug': ('title',),
+        }
+
+    fieldsets = [
+        [None, {
+            'fields': [
+                ('is_active', 'published_on'),
+                ('title', 'slug'),
+                'parent',
+                'service',
+                'author',
+            ]
+        }],
+        item_editor.FEINCMS_CONTENT_FIELDSET,
+    ]
+
+    def import_from_sphinx(self, request):
+        return import_from_sphinx(request)
+
+
+admin.site.register(UserGuideEntry, UserGuideEntryAdmin)
+
diff --git a/cloudcmsguide/models.py b/cloudcmsguide/models.py
new file mode 100644 (file)
index 0000000..60be618
--- /dev/null
@@ -0,0 +1,148 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+from datetime import datetime
+
+from django.db import models
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext_lazy as _, ugettext, ungettext
+from django.template.loader import render_to_string
+from django.core import urlresolvers
+
+from feincms import translations
+from feincms.models import Base
+from feincms.module.page.models import Page
+from feincms.content.application.models import reverse
+from feincms.content.richtext.models import RichTextContent
+from feincms.content.section.models import SectionContent
+from feincms.module.medialibrary.fields import MediaFileForeignKey
+from feincms.module.medialibrary.models import MediaFile
+from feincms.module.page.extensions.navigation import NavigationExtension
+from feincms.module.page.extensions.navigation import PagePretender
+from feincms.content.application.models import ApplicationContent
+from feincms.models import Base, create_base_model
+
+from cloudcms.cms_utils import get_app_page
+
+class UserGuideEntryManager(models.Manager):
+
+    def active(self):
+        return self.filter(is_active=True)
+
+    def latest(self, limit=3):
+        return self.filter()[:limit]
+
+
+def get_guide_page():
+    """
+    Returns Page model that has been associated with userguide application
+    """
+    return get_app_page(Page, "cloudcmsguide")
+
+
+try:
+    # MPTT 0.4
+    from mptt.models import MPTTModel
+    mptt_register = False
+    Base = create_base_model(MPTTModel)
+except ImportError:
+    # MPTT 0.3
+    mptt_register = True
+
+class UserGuideEntry(Base):
+    """
+    User guide entry
+    """
+    is_active = models.BooleanField(_('is active'), default=True)
+    is_featured = models.BooleanField(_('is featured'), default=False)
+
+    title = models.CharField(_('title'), max_length=100)
+    slug = models.SlugField(_('slug'), max_length=100, unique_for_date='published_on')
+    author = models.ForeignKey(User, related_name='guide_pages', verbose_name=_('author'))
+    language = models.CharField(max_length=255, choices=settings.LANGUAGES)
+
+    published_on = models.DateTimeField(_('published on'), blank=True, null=True, default=datetime.now,
+        help_text=_('Will be filled in automatically when entry gets published.'))
+    last_changed = models.DateTimeField(_('last change'), auto_now=True, editable=False)
+
+    service = models.ForeignKey('cloudcms.Service', verbose_name=_('service'),
+        related_name='userguideentries', null=True, blank=False)
+
+    parent = models.ForeignKey('self', verbose_name=_('Parent'), blank=True, null=True, related_name='children')
+
+    objects = UserGuideEntryManager()
+
+    class Meta:
+        get_latest_by = 'published_on'
+        ordering = ['service', '-published_on']
+        verbose_name = _('User guide entry')
+        verbose_name_plural = _('User guide entries')
+
+    def __unicode__(self):
+        return self.title
+
+    def get_absolute_url(self):
+        try:
+            r = reverse('cloudcmsguide_entry_detail', 'cloudcmsguide.urls', (),
+                    {
+                     'service': self.service.translation.slug,
+                     'slug': self.slug,
+                    })
+        except Exception, e:
+            print e
+            return ""
+
+        # ugly hack to fix proper application reverse url
+        GUIDE_URL = ""
+        try:
+            GUIDE_URL = get_guide_page().get_navigation_url()
+        except Exception, e:
+            pass
+
+        if r.startswith(GUIDE_URL):
+            return r
+        else:
+            return GUIDE_URL + r.lstrip('/')
+
+    def back_url(self):
+        return get_guide_page().get_navigation_url()
+
+if mptt_register: # MPTT 0.3 legacy support
+    mptt.register(Page)
+
+UserGuideEntry.register_extensions(
+    'changedate',
+    'seo'
+)
diff --git a/cloudcmsguide/sitemap.py b/cloudcmsguide/sitemap.py
new file mode 100644 (file)
index 0000000..45a2170
--- /dev/null
@@ -0,0 +1,43 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+from django.db.models import Max
+from django.contrib.sitemaps import Sitemap
+from cloudcmsguide.models import UserGuideEntry
+
+class UserGuideSitemap(Sitemap):
+
+    def items(self):
+        return UserGuideEntry.objects.filter(is_active=True)
+
diff --git a/cloudcmsguide/templates/cloudcmsguide/archive.html b/cloudcmsguide/templates/cloudcmsguide/archive.html
new file mode 100644 (file)
index 0000000..9b51662
--- /dev/null
@@ -0,0 +1,16 @@
+{% load applicationcontent_tags pagination_tags i18n %}
+
+<div class="guide">
+    {% for service in services %}
+    <div class="guide-category">
+        <h2>{{ service.translation.title|upper }}</h2>
+        <ul>
+            {% for e in service.userguideentries.active %}
+            <li {% if e == entry %}class="current"{% endif %}>
+                <a href="{{ e.get_absolute_url }}">{{ e.title }}</a>
+            </li>
+            {% endfor %}
+        </ul>
+    </div>
+    {% endfor %}
+</div>
diff --git a/cloudcmsguide/templates/cloudcmsguide/detail.html b/cloudcmsguide/templates/cloudcmsguide/detail.html
new file mode 100644 (file)
index 0000000..c420732
--- /dev/null
@@ -0,0 +1,19 @@
+{% load applicationcontent_tags i18n %}
+
+{% fragment request "sidecol" %}
+{% include "cloudcmsguide/archive.html" %}
+{% endfragment %}
+
+{% fragment request "maincol" %}
+<div class="user-guide-entry">
+    <h2>{{ entry.title|upper }}</h2>
+    <div class="content">
+        {% for content in entry.content.main %}
+            {{ content.render }}
+        {% endfor %}
+    </div>
+    <div class="bottom-content backlink">
+        <a href="{{ entry.back_url }}">{% trans "&lt; Back to User guide" %}</a>
+    </div>
+</div>
+{% endfragment %}
similarity index 83%
rename from cloudcms/tests.py
rename to cloudcmsguide/tests.py
index d4e0c30..ba67af7 100644 (file)
 
 
 """
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
 
-Replace this with more appropriate tests for your application.
+Replace these with more appropriate tests for your application.
 """
 
 from django.test import TestCase
 
-
 class SimpleTest(TestCase):
     def test_basic_addition(self):
         """
         Tests that 1 + 1 always equals 2.
         """
-        self.assertEqual(1 + 1, 2)
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/cloudcmsguide/urls.py b/cloudcmsguide/urls.py
new file mode 100644 (file)
index 0000000..9e3d07e
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+from django.conf.urls.defaults import patterns, include, url
+from django.conf import settings
+from django.contrib import admin
+
+admin.autodiscover()
+
+urlpatterns = patterns('cloudcmsguide.views',
+    url(r'^$', 'index', name='cloudcmsguide_entries_archive'),
+    url(r'^(?P<service>[\w]+)-(?P<slug>[-\w]+)/$',
+        'detail', name='cloudcmsguide_entry_detail'),
+)
+
diff --git a/cloudcmsguide/views.py b/cloudcmsguide/views.py
new file mode 100644 (file)
index 0000000..ea50662
--- /dev/null
@@ -0,0 +1,64 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``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 GRNET S.A 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.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+
+from django.conf import settings
+from django.views.generic.simple import direct_to_template
+from django.http import HttpResponseRedirect, Http404
+
+from cloudcms.models import Service
+from cloudcmsguide.models import UserGuideEntry
+
+def index(request):
+    return archive(request)
+
+def archive(request):
+    """
+    Display entries list
+    """
+    services = Service.objects.all()
+
+    return direct_to_template(request,
+            "cloudcmsguide/archive.html", {'services': services})
+
+def detail(request, service, slug):
+    """
+    Display detailed entry.
+    """
+    entry = UserGuideEntry.objects.get(slug=slug)
+    services = Service.objects.all()
+
+    return direct_to_template(request,
+            "cloudcmsguide/detail.html", {'entry': entry,
+                                        'services': services})
+
index afed716..2c4d886 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -177,8 +177,9 @@ setup(
          'web_apps = cloudcms.synnefo_settings:cloudcms_apps',
          'web_middleware = cloudcms.synnefo_settings:cloudcms_middlewares',
          'web_context_processors = cloudcms.synnefo_settings:cloudcms_context_processors',
-         'urls = cloudcms.urls:urls',
-         'web_static = cloudcms.synnefo_settings:cloudcms_staticfiles'
+         'urls = cloudcms.urls:urlpatterns',
+         'web_static = cloudcms.synnefo_settings:cloudcms_staticfiles',
+         'loggers = cloudcms.synnefo_settings:loggers'
          ]
     },