Statistics
| Branch: | Tag: | Revision:

root / cloudcms / forms.py @ d240ebcb

History | View | Annotate | Download (10.3 kB)

1 deb708bf Kostas Papadimitriou
# -*- coding: utf-8 -*-
2 deb708bf Kostas Papadimitriou
3 deb708bf Kostas Papadimitriou
import zipfile
4 deb708bf Kostas Papadimitriou
import tempfile
5 deb708bf Kostas Papadimitriou
import os
6 deb708bf Kostas Papadimitriou
import glob
7 deb708bf Kostas Papadimitriou
import logging
8 58b9c196 Kostas Papadimitriou
import StringIO
9 deb708bf Kostas Papadimitriou
10 deb708bf Kostas Papadimitriou
from django import forms
11 deb708bf Kostas Papadimitriou
from django.conf import settings
12 deb708bf Kostas Papadimitriou
from django.db import transaction
13 deb708bf Kostas Papadimitriou
from django.core.files import File
14 deb708bf Kostas Papadimitriou
15 deb708bf Kostas Papadimitriou
from feincms.module.medialibrary.models import Category as MediaCategory, \
16 deb708bf Kostas Papadimitriou
        MediaFile
17 deb708bf Kostas Papadimitriou
18 deb708bf Kostas Papadimitriou
from cloudcmsguide.models import UserGuideEntry
19 deb708bf Kostas Papadimitriou
from cloudcmsfaq.models import Category as FaqCategory, Question
20 deb708bf Kostas Papadimitriou
from cloudcms.rstutils import generate_rst_contents_from_dir
21 deb708bf Kostas Papadimitriou
from cloudcms.models import Service, ServiceTranslation, Application
22 deb708bf Kostas Papadimitriou
from feincms.content.raw.models import RawContent
23 deb708bf Kostas Papadimitriou
24 deb708bf Kostas Papadimitriou
logger = logging.getLogger('cloudcms.rstimport')
25 deb708bf Kostas Papadimitriou
26 deb708bf Kostas Papadimitriou
# base filename to service slug map
27 deb708bf Kostas Papadimitriou
DEFAULT_SERVICE_MAP = {
28 deb708bf Kostas Papadimitriou
        'cyclades':'cyclades',
29 deb708bf Kostas Papadimitriou
        'okeanos':'okeanos',
30 deb708bf Kostas Papadimitriou
        'pithos':'pithos'
31 deb708bf Kostas Papadimitriou
}
32 deb708bf Kostas Papadimitriou
33 deb708bf Kostas Papadimitriou
DEFAULT_REGION_MAP = {
34 deb708bf Kostas Papadimitriou
        'faq':'main',
35 deb708bf Kostas Papadimitriou
        'userguide':'main',
36 deb708bf Kostas Papadimitriou
}
37 deb708bf Kostas Papadimitriou
38 deb708bf Kostas Papadimitriou
DEFAULT_RESIZE_GEOMETRY = (400, 400)
39 deb708bf Kostas Papadimitriou
40 deb708bf Kostas Papadimitriou
SERVICE_MAP = getattr(settings, 'CMS_RST_IMPORT_SERVICE_FILE_MAP',
41 deb708bf Kostas Papadimitriou
        DEFAULT_SERVICE_MAP)
42 deb708bf Kostas Papadimitriou
REGIONS_MAP = getattr(settings, 'CMS_RST_IMPORT_REGIONS_MAP',
43 deb708bf Kostas Papadimitriou
        DEFAULT_REGION_MAP)
44 deb708bf Kostas Papadimitriou
RESIZE_GEOMETRY = getattr(settings, 'CMS_RST_IMPORT_RESIZE_GEOMETRY',
45 deb708bf Kostas Papadimitriou
    DEFAULT_RESIZE_GEOMETRY)
46 deb708bf Kostas Papadimitriou
47 deb708bf Kostas Papadimitriou
def service_from_filename(rst):
48 deb708bf Kostas Papadimitriou
    fname = os.path.basename(rst).replace(".rst","")
49 deb708bf Kostas Papadimitriou
    service_slug = DEFAULT_SERVICE_MAP.get(fname, None)
50 deb708bf Kostas Papadimitriou
    if not service_slug:
51 deb708bf Kostas Papadimitriou
        return None
52 deb708bf Kostas Papadimitriou
53 deb708bf Kostas Papadimitriou
    try:
54 deb708bf Kostas Papadimitriou
        return Service.objects.filter(translations__slug=service_slug)[0]
55 deb708bf Kostas Papadimitriou
    except IndexError:
56 deb708bf Kostas Papadimitriou
        return None
57 deb708bf Kostas Papadimitriou
    except Service.DoesNotExist:
58 deb708bf Kostas Papadimitriou
        return None
59 deb708bf Kostas Papadimitriou
60 deb708bf Kostas Papadimitriou
    return None
61 deb708bf Kostas Papadimitriou
62 deb708bf Kostas Papadimitriou
63 deb708bf Kostas Papadimitriou
def get_media_category(slug):
64 deb708bf Kostas Papadimitriou
    return MediaCategory.objects.get(slug=slug)
65 deb708bf Kostas Papadimitriou
66 deb708bf Kostas Papadimitriou
67 58b9c196 Kostas Papadimitriou
CATEGORIES_CHOICES = (('faq', 'FAQs'), ('user-guide', 'User guide'))
68 deb708bf Kostas Papadimitriou
69 deb708bf Kostas Papadimitriou
class RstZipImportForm(forms.Form):
70 deb708bf Kostas Papadimitriou
71 deb708bf Kostas Papadimitriou
    FAQ_MEDIA_CATEGORY = 'faq-images'
72 deb708bf Kostas Papadimitriou
    GUIDE_MEDIA_CATEGORY = 'user-guide-images'
73 deb708bf Kostas Papadimitriou
74 deb708bf Kostas Papadimitriou
    clean_data = forms.BooleanField(initial=False, required=False)
75 deb708bf Kostas Papadimitriou
    dry_run = forms.BooleanField(initial=True, required=False,
76 deb708bf Kostas Papadimitriou
            widget=forms.HiddenInput)
77 deb708bf Kostas Papadimitriou
    import_file = forms.FileField(required=True)
78 deb708bf Kostas Papadimitriou
79 deb708bf Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
80 deb708bf Kostas Papadimitriou
        super(RstZipImportForm, self).__init__(*args, **kwargs)
81 deb708bf Kostas Papadimitriou
        self.log_data = ""
82 deb708bf Kostas Papadimitriou
83 deb708bf Kostas Papadimitriou
    def log(self, msg):
84 deb708bf Kostas Papadimitriou
        self.log_data += "\n" + msg
85 deb708bf Kostas Papadimitriou
86 deb708bf Kostas Papadimitriou
    def get_tmp_file(self, f):
87 deb708bf Kostas Papadimitriou
        tmp = tempfile.mktemp('cloudcms-sphinx-import')
88 deb708bf Kostas Papadimitriou
        f.file.reset()
89 deb708bf Kostas Papadimitriou
        fd = file(tmp, 'w')
90 deb708bf Kostas Papadimitriou
        fd.write(f.read())
91 deb708bf Kostas Papadimitriou
        fd.close()
92 deb708bf Kostas Papadimitriou
        return tmp
93 deb708bf Kostas Papadimitriou
94 deb708bf Kostas Papadimitriou
    def clean_import_file(self):
95 deb708bf Kostas Papadimitriou
        f = self.cleaned_data['import_file']
96 deb708bf Kostas Papadimitriou
        tmpfile = self.get_tmp_file(f)
97 deb708bf Kostas Papadimitriou
        if not zipfile.is_zipfile(tmpfile):
98 deb708bf Kostas Papadimitriou
            raise forms.ValidationError("Invalid zip file")
99 deb708bf Kostas Papadimitriou
        return f
100 deb708bf Kostas Papadimitriou
101 deb708bf Kostas Papadimitriou
    def clean(self, *args, **kwargs):
102 deb708bf Kostas Papadimitriou
        data = super(RstZipImportForm, self).clean(*args, **kwargs)
103 deb708bf Kostas Papadimitriou
        return data
104 deb708bf Kostas Papadimitriou
105 deb708bf Kostas Papadimitriou
    def clean_existing_data(self):
106 58b9c196 Kostas Papadimitriou
        logger.warning("Removing all FAQ questions")
107 deb708bf Kostas Papadimitriou
        Question.objects.all().delete()
108 58b9c196 Kostas Papadimitriou
        logger.warning("Removing all User Guide entries")
109 deb708bf Kostas Papadimitriou
        UserGuideEntry.objects.all().delete()
110 58b9c196 Kostas Papadimitriou
        logger.warning("Removing all media files in categories %s", [self.FAQ_MEDIA_CATEGORY,
111 58b9c196 Kostas Papadimitriou
            self.GUIDE_MEDIA_CATEGORY])
112 deb708bf Kostas Papadimitriou
        MediaFile.objects.filter(categories__slug__in=[self.FAQ_MEDIA_CATEGORY, \
113 deb708bf Kostas Papadimitriou
            self.GUIDE_MEDIA_CATEGORY]).delete()
114 deb708bf Kostas Papadimitriou
115 deb708bf Kostas Papadimitriou
    def save(self, user, use_dir=None):
116 58b9c196 Kostas Papadimitriou
        stream = StringIO.StringIO()
117 58b9c196 Kostas Papadimitriou
        stream_handler = logging.StreamHandler(stream)
118 58b9c196 Kostas Papadimitriou
        stream_handler.setFormatter(logging.Formatter('<div class="log-entry %(levelname)s"><em>%(levelname)s</em>'
119 58b9c196 Kostas Papadimitriou
            '<pre>%(message)s</pre></div>'))
120 58b9c196 Kostas Papadimitriou
        logger.addHandler(stream_handler)
121 58b9c196 Kostas Papadimitriou
        old_level = logger.level
122 58b9c196 Kostas Papadimitriou
        logger.setLevel(logging.DEBUG)
123 58b9c196 Kostas Papadimitriou
124 deb708bf Kostas Papadimitriou
        dry_run = self.cleaned_data.get('dry_run')
125 deb708bf Kostas Papadimitriou
        clean_data = self.cleaned_data.get('clean_data')
126 deb708bf Kostas Papadimitriou
        import_file = self.cleaned_data.get('import_file')
127 deb708bf Kostas Papadimitriou
128 deb708bf Kostas Papadimitriou
        if not use_dir:
129 deb708bf Kostas Papadimitriou
            zipdir = tempfile.mkdtemp('cloudcms-sphinx-exports')
130 deb708bf Kostas Papadimitriou
            zipdatafile = self.get_tmp_file(import_file)
131 deb708bf Kostas Papadimitriou
            zipf = zipfile.ZipFile(file(zipdatafile))
132 deb708bf Kostas Papadimitriou
            zipf.extractall(zipdir)
133 deb708bf Kostas Papadimitriou
        else:
134 deb708bf Kostas Papadimitriou
            zipdir = use_dir
135 deb708bf Kostas Papadimitriou
136 deb708bf Kostas Papadimitriou
        subdirs = os.listdir(zipdir)
137 deb708bf Kostas Papadimitriou
        if len(subdirs) == 1 and os.path.isdir(os.path.join(zipdir, \
138 deb708bf Kostas Papadimitriou
                subdirs[0])) and subdirs[0] != 'source':
139 deb708bf Kostas Papadimitriou
            zipdir = os.path.join(zipdir, subdirs[0])
140 deb708bf Kostas Papadimitriou
141 58b9c196 Kostas Papadimitriou
        sid = transaction.savepoint()
142 deb708bf Kostas Papadimitriou
143 deb708bf Kostas Papadimitriou
        if clean_data:
144 deb708bf Kostas Papadimitriou
            try:
145 58b9c196 Kostas Papadimitriou
                logger.warning("Removing exising entries")
146 deb708bf Kostas Papadimitriou
                self.clean_existing_data()
147 deb708bf Kostas Papadimitriou
            except Exception, e:
148 58b9c196 Kostas Papadimitriou
                transaction.savepoint_rollback(sid)
149 58b9c196 Kostas Papadimitriou
                logger.exception("Failed to clean existing data")
150 58b9c196 Kostas Papadimitriou
                logger.removeHandler(stream_handler)
151 58b9c196 Kostas Papadimitriou
                logger.setLevel(old_level)
152 58b9c196 Kostas Papadimitriou
                return False, stream.getvalue()
153 deb708bf Kostas Papadimitriou
154 deb708bf Kostas Papadimitriou
        ret = ""
155 deb708bf Kostas Papadimitriou
        try:
156 58b9c196 Kostas Papadimitriou
            logger.warning("Parsing contents of '%s'", zipdir)
157 deb708bf Kostas Papadimitriou
            for data_type, rst, category, slug, title, html_content, \
158 deb708bf Kostas Papadimitriou
                    images, stderr in generate_rst_contents_from_dir(zipdir):
159 deb708bf Kostas Papadimitriou
160 deb708bf Kostas Papadimitriou
                if stderr:
161 58b9c196 Kostas Papadimitriou
                    logger.error("Docutils error output for '%s'\n: %s" % (rst, stderr, ))
162 deb708bf Kostas Papadimitriou
163 deb708bf Kostas Papadimitriou
                service = service_from_filename(rst)
164 deb708bf Kostas Papadimitriou
                if not service:
165 deb708bf Kostas Papadimitriou
                    logger.info("Skipping entry for file '%s'. No category found" % rst)
166 deb708bf Kostas Papadimitriou
                    continue
167 deb708bf Kostas Papadimitriou
168 58b9c196 Kostas Papadimitriou
                logger.info("Processing, '%s'" % (rst, ))
169 58b9c196 Kostas Papadimitriou
170 deb708bf Kostas Papadimitriou
171 deb708bf Kostas Papadimitriou
                # first save media files
172 58b9c196 Kostas Papadimitriou
                cat = False
173 deb708bf Kostas Papadimitriou
                newimages = []
174 deb708bf Kostas Papadimitriou
                if data_type == 'userguide':
175 deb708bf Kostas Papadimitriou
                    cat = get_media_category(self.GUIDE_MEDIA_CATEGORY)
176 deb708bf Kostas Papadimitriou
                if data_type == 'faq':
177 deb708bf Kostas Papadimitriou
                    cat = get_media_category(self.FAQ_MEDIA_CATEGORY)
178 deb708bf Kostas Papadimitriou
179 deb708bf Kostas Papadimitriou
                if not cat:
180 58b9c196 Kostas Papadimitriou
                    logger.info("Skipping %s, no media category found for '%s'",
181 58b9c196 Kostas Papadimitriou
                            rst, data_type)
182 deb708bf Kostas Papadimitriou
                    continue
183 deb708bf Kostas Papadimitriou
184 deb708bf Kostas Papadimitriou
                for imgname, imgpath, imgabspath in images:
185 58b9c196 Kostas Papadimitriou
                    logger.debug("Checking image (%s, %s, %s)", imgname, imgpath, imgabspath)
186 deb708bf Kostas Papadimitriou
                    newalt, newpath = create_or_update_media_file(cat, \
187 deb708bf Kostas Papadimitriou
                            imgname, imgabspath)
188 deb708bf Kostas Papadimitriou
189 deb708bf Kostas Papadimitriou
                    html_content = html_content.replace(imgpath, newpath)
190 deb708bf Kostas Papadimitriou
191 deb708bf Kostas Papadimitriou
                # now html contains correct image links, we are ready to save
192 deb708bf Kostas Papadimitriou
                # the faq/guide content
193 deb708bf Kostas Papadimitriou
                if data_type == 'faq':
194 58b9c196 Kostas Papadimitriou
                    logger.info('Processing FAQ entry, %s, %s, %s', service, slug, title)
195 deb708bf Kostas Papadimitriou
                    cat = add_or_update_faq_category(category[0], category[1])
196 deb708bf Kostas Papadimitriou
                    question = add_or_update_faq_question(user, service, cat, slug, \
197 deb708bf Kostas Papadimitriou
                            title, html_content)
198 deb708bf Kostas Papadimitriou
199 deb708bf Kostas Papadimitriou
                if data_type == 'userguide':
200 58b9c196 Kostas Papadimitriou
                    logger.info('Processing USER GUIDE entry, %s, %s, %s', service, slug, title)
201 deb708bf Kostas Papadimitriou
                    guide_entry = add_or_update_guide_entry(user, service, slug, \
202 deb708bf Kostas Papadimitriou
                            title, html_content)
203 deb708bf Kostas Papadimitriou
204 deb708bf Kostas Papadimitriou
205 deb708bf Kostas Papadimitriou
        except Exception, e:
206 deb708bf Kostas Papadimitriou
            logger.exception("RST import failed")
207 58b9c196 Kostas Papadimitriou
            logger.removeHandler(stream_handler)
208 58b9c196 Kostas Papadimitriou
            logger.setLevel(old_level)
209 58b9c196 Kostas Papadimitriou
            transaction.savepoint_rollback(sid)
210 58b9c196 Kostas Papadimitriou
            return False, stream.getvalue()
211 deb708bf Kostas Papadimitriou
212 deb708bf Kostas Papadimitriou
        if dry_run:
213 58b9c196 Kostas Papadimitriou
            transaction.savepoint_rollback(sid)
214 deb708bf Kostas Papadimitriou
        else:
215 58b9c196 Kostas Papadimitriou
            transaction.savepoint_commit(sid)
216 58b9c196 Kostas Papadimitriou
217 58b9c196 Kostas Papadimitriou
        return True, stream.getvalue()
218 deb708bf Kostas Papadimitriou
219 deb708bf Kostas Papadimitriou
220 deb708bf Kostas Papadimitriou
def create_or_update_media_file(category, name, path):
221 58b9c196 Kostas Papadimitriou
    logger.info("Create or update media file, %s, %s, [category: %s]", name,
222 58b9c196 Kostas Papadimitriou
            path, category)
223 deb708bf Kostas Papadimitriou
    name = category.title + " " + name
224 deb708bf Kostas Papadimitriou
225 deb708bf Kostas Papadimitriou
    try:
226 deb708bf Kostas Papadimitriou
        # TODO: Check language ?????
227 58b9c196 Kostas Papadimitriou
        mf = MediaFile.objects.get(categories__in=[category], translations__caption=name)
228 58b9c196 Kostas Papadimitriou
        logger.info("Media file found")
229 deb708bf Kostas Papadimitriou
    except MediaFile.DoesNotExist:
230 58b9c196 Kostas Papadimitriou
        logger.info("Media file not found, creating...")
231 deb708bf Kostas Papadimitriou
        mf = MediaFile()
232 deb708bf Kostas Papadimitriou
        mf.file = File(open(path))
233 deb708bf Kostas Papadimitriou
        mf.save()
234 deb708bf Kostas Papadimitriou
        mf.translations.create(caption=name)
235 58b9c196 Kostas Papadimitriou
        mf.categories.clear()
236 58b9c196 Kostas Papadimitriou
        mf.categories = [category]
237 58b9c196 Kostas Papadimitriou
        mf.save()
238 deb708bf Kostas Papadimitriou
239 deb708bf Kostas Papadimitriou
    # TODO: Check language ?????
240 deb708bf Kostas Papadimitriou
    return mf.translations.all()[0].caption, mf.get_absolute_url()
241 deb708bf Kostas Papadimitriou
242 deb708bf Kostas Papadimitriou
243 deb708bf Kostas Papadimitriou
def add_or_update_faq_category(slug, name):
244 58b9c196 Kostas Papadimitriou
    logger.info("Create or update faq subcategory, %s, %s", slug,
245 58b9c196 Kostas Papadimitriou
            name)
246 deb708bf Kostas Papadimitriou
    try:
247 deb708bf Kostas Papadimitriou
        category = FaqCategory.objects.get(translations__slug=slug)
248 58b9c196 Kostas Papadimitriou
        logger.info("FAQ category found")
249 deb708bf Kostas Papadimitriou
    except FaqCategory.DoesNotExist:
250 58b9c196 Kostas Papadimitriou
        logger.info("FAQ category not found, creating...")
251 deb708bf Kostas Papadimitriou
        category = FaqCategory()
252 deb708bf Kostas Papadimitriou
        category.save()
253 deb708bf Kostas Papadimitriou
        category.translations.create(slug=slug, title=name)
254 deb708bf Kostas Papadimitriou
255 deb708bf Kostas Papadimitriou
    return category
256 deb708bf Kostas Papadimitriou
257 58b9c196 Kostas Papadimitriou
258 deb708bf Kostas Papadimitriou
def add_or_update_faq_question(author, service, category, slug,\
259 deb708bf Kostas Papadimitriou
        title, html_content):
260 58b9c196 Kostas Papadimitriou
    logger.info("Create or update faq question, %s, %s, %s, %s", service,
261 58b9c196 Kostas Papadimitriou
            category, slug, title)
262 deb708bf Kostas Papadimitriou
263 deb708bf Kostas Papadimitriou
    try:
264 deb708bf Kostas Papadimitriou
        q = Question.objects.get(slug=slug)
265 58b9c196 Kostas Papadimitriou
        logger.info("Question found, updating...")
266 deb708bf Kostas Papadimitriou
    except:
267 58b9c196 Kostas Papadimitriou
        logger.info("Question not found, creating...")
268 deb708bf Kostas Papadimitriou
        q = Question()
269 deb708bf Kostas Papadimitriou
270 deb708bf Kostas Papadimitriou
    q.author = author
271 deb708bf Kostas Papadimitriou
    q.is_active = True
272 deb708bf Kostas Papadimitriou
    q.category = category
273 deb708bf Kostas Papadimitriou
    q.service = service
274 deb708bf Kostas Papadimitriou
    q.slug = slug
275 deb708bf Kostas Papadimitriou
    q.title = title
276 deb708bf Kostas Papadimitriou
    q.save()
277 deb708bf Kostas Papadimitriou
    q.application = [Application.current()]
278 deb708bf Kostas Papadimitriou
    q.save()
279 deb708bf Kostas Papadimitriou
280 deb708bf Kostas Papadimitriou
    RawContentModel = Question.content_type_for(RawContent)
281 deb708bf Kostas Papadimitriou
    try:
282 deb708bf Kostas Papadimitriou
        content = q.rawcontent_set.filter()[0]
283 deb708bf Kostas Papadimitriou
    except:
284 deb708bf Kostas Papadimitriou
        content = q.rawcontent_set.create(region=REGIONS_MAP['faq'])
285 deb708bf Kostas Papadimitriou
286 deb708bf Kostas Papadimitriou
    content.text = html_content
287 deb708bf Kostas Papadimitriou
    content.save()
288 deb708bf Kostas Papadimitriou
289 deb708bf Kostas Papadimitriou
    return q
290 deb708bf Kostas Papadimitriou
291 deb708bf Kostas Papadimitriou
def add_or_update_guide_entry(author, service, slug, title, html_content):
292 58b9c196 Kostas Papadimitriou
    logger.info("Create or update user guide entry, %s, %s, %s", service,
293 58b9c196 Kostas Papadimitriou
            slug, title)
294 deb708bf Kostas Papadimitriou
    try:
295 58b9c196 Kostas Papadimitriou
        logger.info("Guide entry found, updating...")
296 deb708bf Kostas Papadimitriou
        guide = UserGuideEntry.objects.get(slug=slug)
297 deb708bf Kostas Papadimitriou
    except:
298 58b9c196 Kostas Papadimitriou
        logger.info("Guide entry not found, creating...")
299 deb708bf Kostas Papadimitriou
        guide = UserGuideEntry()
300 deb708bf Kostas Papadimitriou
301 deb708bf Kostas Papadimitriou
    guide.author = author
302 deb708bf Kostas Papadimitriou
    guide.is_active = True
303 deb708bf Kostas Papadimitriou
    guide.service = service
304 deb708bf Kostas Papadimitriou
    guide.slug = slug
305 deb708bf Kostas Papadimitriou
    guide.title = title
306 deb708bf Kostas Papadimitriou
    guide.save()
307 deb708bf Kostas Papadimitriou
308 deb708bf Kostas Papadimitriou
    RawContentModel = UserGuideEntry.content_type_for(RawContent)
309 deb708bf Kostas Papadimitriou
    try:
310 deb708bf Kostas Papadimitriou
        content = guide.rawcontent_set.filter()[0]
311 deb708bf Kostas Papadimitriou
    except:
312 deb708bf Kostas Papadimitriou
        content = guide.rawcontent_set.create(region=REGIONS_MAP['userguide'])
313 deb708bf Kostas Papadimitriou
314 deb708bf Kostas Papadimitriou
    content.text = html_content
315 deb708bf Kostas Papadimitriou
    content.save()
316 deb708bf Kostas Papadimitriou
317 deb708bf Kostas Papadimitriou
    return guide
318 deb708bf Kostas Papadimitriou