1 # -*- coding: utf-8 -*-
10 from django import forms
11 from django.conf import settings
12 from django.db import transaction
13 from django.core.files import File
15 from feincms.module.medialibrary.models import Category as MediaCategory, \
18 from cloudcmsguide.models import UserGuideEntry
19 from cloudcmsfaq.models import Category as FaqCategory, Question
20 from cloudcms.rstutils import generate_rst_contents_from_dir
21 from cloudcms.models import Service, ServiceTranslation, Application
22 from feincms.content.raw.models import RawContent
24 logger = logging.getLogger('cloudcms.rstimport')
29 # base filename to service slug map
30 DEFAULT_SERVICE_MAP = {
31 'cyclades':'cyclades',
36 DEFAULT_REGION_MAP = {
41 DEFAULT_RESIZE_GEOMETRY = (400, 400)
43 SERVICE_MAP = getattr(settings, 'CMS_RST_IMPORT_SERVICE_FILE_MAP',
45 REGIONS_MAP = getattr(settings, 'CMS_RST_IMPORT_REGIONS_MAP',
47 RESIZE_GEOMETRY = getattr(settings, 'CMS_RST_IMPORT_RESIZE_GEOMETRY',
48 DEFAULT_RESIZE_GEOMETRY)
50 def service_from_filename(rst):
51 fname = os.path.basename(rst).replace(".rst","")
52 service_slug = DEFAULT_SERVICE_MAP.get(fname, None)
57 return Service.objects.filter(translations__slug=service_slug)[0]
60 except Service.DoesNotExist:
66 def get_media_category(slug):
67 return MediaCategory.objects.get(slug=slug)
70 CATEGORIES_CHOICES = (('faq', 'FAQs'), ('user-guide', 'User guide'))
72 class RstZipImportForm(forms.Form):
74 FAQ_MEDIA_CATEGORY = 'faq-images'
75 GUIDE_MEDIA_CATEGORY = 'user-guide-images'
77 clean_data = forms.BooleanField(initial=False, required=False)
78 dry_run = forms.BooleanField(initial=True, required=False,
79 widget=forms.HiddenInput)
80 import_file = forms.FileField(required=True)
82 def __init__(self, *args, **kwargs):
83 super(RstZipImportForm, self).__init__(*args, **kwargs)
87 self.log_data += "\n" + msg
89 def get_tmp_file(self, f):
90 tmp = tempfile.mktemp('cloudcms-sphinx-import')
97 def clean_import_file(self):
98 f = self.cleaned_data['import_file']
99 tmpfile = self.get_tmp_file(f)
100 if not zipfile.is_zipfile(tmpfile):
101 raise forms.ValidationError("Invalid zip file")
104 def clean(self, *args, **kwargs):
105 data = super(RstZipImportForm, self).clean(*args, **kwargs)
108 def clean_existing_data(self):
109 logger.warning("Removing all FAQ questions")
110 Question.objects.all().delete()
111 logger.warning("Removing all User Guide entries")
112 UserGuideEntry.objects.all().delete()
113 logger.warning("Removing all media files in categories %s", [self.FAQ_MEDIA_CATEGORY,
114 self.GUIDE_MEDIA_CATEGORY])
115 MediaFile.objects.filter(categories__slug__in=[self.FAQ_MEDIA_CATEGORY, \
116 self.GUIDE_MEDIA_CATEGORY]).delete()
118 def save(self, user, use_dir=None):
119 stream = StringIO.StringIO()
120 stream_handler = logging.StreamHandler(stream)
121 stream_handler.setFormatter(logging.Formatter('<div class="log-entry %(levelname)s"><em>%(levelname)s</em>'
122 '<pre>%(message)s</pre></div>'))
123 logger.addHandler(stream_handler)
124 old_level = logger.level
125 logger.setLevel(logging.DEBUG)
127 dry_run = self.cleaned_data.get('dry_run')
128 clean_data = self.cleaned_data.get('clean_data')
129 import_file = self.cleaned_data.get('import_file')
132 zipdir = tempfile.mkdtemp('cloudcms-sphinx-exports')
133 zipdatafile = self.get_tmp_file(import_file)
134 zipf = zipfile.ZipFile(file(zipdatafile))
135 zipf.extractall(zipdir)
139 subdirs = os.listdir(zipdir)
140 if len(subdirs) == 1 and os.path.isdir(os.path.join(zipdir, \
141 subdirs[0])) and subdirs[0] != 'source':
142 zipdir = os.path.join(zipdir, subdirs[0])
144 sid = transaction.savepoint()
148 logger.warning("Removing exising entries")
149 self.clean_existing_data()
152 transaction.savepoint_rollback(sid)
153 transaction.rollback()
155 logger.exception("Can not rollback")
156 logger.exception("Failed to clean existing data")
157 logger.removeHandler(stream_handler)
158 logger.setLevel(old_level)
159 return False, stream.getvalue()
163 logger.warning("Parsing contents of '%s'", zipdir)
164 for data_type, rst, category, slug, title, html_content, \
165 images, stderr in generate_rst_contents_from_dir(zipdir):
168 logger.error("Docutils error output for '%s'\n: %s" % (rst, stderr, ))
170 service = service_from_filename(rst)
172 logger.info("Skipping entry for file '%s'. No category found" % rst)
175 logger.info("Processing, '%s'" % (rst, ))
178 # first save media files
181 if data_type == 'userguide':
182 cat = get_media_category(self.GUIDE_MEDIA_CATEGORY)
183 if data_type == 'faq':
184 cat = get_media_category(self.FAQ_MEDIA_CATEGORY)
187 logger.info("Skipping %s, no media category found for '%s'",
191 for imgname, imgpath, imgabspath in images:
192 logger.debug("Checking image (%s, %s, %s)", imgname, imgpath, imgabspath)
193 newalt, newpath = create_or_update_media_file(cat, \
196 html_content = html_content.replace(imgpath, newpath)
198 # now html contains correct image links, we are ready to save
199 # the faq/guide content
200 if data_type == 'faq':
201 logger.info('Processing FAQ entry, %s, %s, %s', service, slug, title)
202 cat = add_or_update_faq_category(unicode(category[0]),
203 unicode(category[1]))
204 question = add_or_update_faq_question(user, service, cat, slug, \
207 if data_type == 'userguide':
208 logger.info('Processing USER GUIDE entry, %s, %s, %s', service, slug, title)
209 guide_entry = add_or_update_guide_entry(user, service, slug, \
215 logger.exception("RST import failed")
216 logger.removeHandler(stream_handler)
217 logger.setLevel(old_level)
219 transaction.savepoint_rollback(sid)
220 transaction.rollback()
222 logger.exception("Can not rollback")
224 return False, stream.getvalue()
228 transaction.savepoint_rollback(sid)
229 transaction.rollback()
231 logger.exception("Can not rollback")
234 transaction.savepoint_commit(sid)
236 return True, stream.getvalue()
239 def create_or_update_media_file(category, name, path):
240 logger.info("Create or update media file, %s, %s, [category: %s]", name,
242 name = category.title + " " + name
245 # TODO: Check language ?????
246 mf = MediaFile.objects.get(categories__in=[category], translations__caption=name)
247 logger.info("Media file found")
248 except MediaFile.DoesNotExist:
249 logger.info("Media file not found, creating...")
251 mf.file = File(open(path))
253 mf.translations.create(caption=name)
254 mf.categories.clear()
255 mf.categories = [category]
258 # TODO: Check language ?????
259 return mf.translations.all()[0].caption, mf.get_absolute_url()
262 def add_or_update_faq_category(slug, name):
263 logger.info("Create or update faq subcategory, %s, %s", slug,
266 category = FaqCategory.objects.get(translations__slug=slug)
267 logger.info("FAQ category found")
268 except FaqCategory.DoesNotExist:
269 logger.info("FAQ category not found, creating...")
270 category = FaqCategory()
272 category.translations.create(slug=slug, title=name)
277 def add_or_update_faq_question(author, service, category, slug,\
278 title, html_content):
279 logger.info("Create or update faq question, %s, %s, %s, %s", service,
280 category, slug, title)
283 q = Question.objects.get(slug=slug)
284 logger.info("Question found, updating...")
286 logger.info("Question not found, creating...")
291 q.category = category
293 q.slug = slugify(slug)
294 q.title = unicode(title)
297 q.application = [Application.current()]
300 RawContentModel = Question.content_type_for(RawContent)
302 content = q.rawcontent_set.filter()[0]
304 content = q.rawcontent_set.create(region=REGIONS_MAP['faq'])
306 content.text = html_content
311 def add_or_update_guide_entry(author, service, slug, title, html_content):
312 logger.info("Create or update user guide entry, %s, %s, %s", service,
315 logger.info("Guide entry found, updating...")
316 guide = UserGuideEntry.objects.get(slug=slug)
318 logger.info("Guide entry not found, creating...")
319 guide = UserGuideEntry()
321 guide.author = author
322 guide.is_active = True
323 guide.service = service
324 guide.slug = slugify(slug)
325 guide.title = unicode(title)
328 RawContentModel = UserGuideEntry.content_type_for(RawContent)
330 content = guide.rawcontent_set.filter()[0]
332 content = guide.rawcontent_set.create(region=REGIONS_MAP['userguide'])
334 content.text = html_content