Statistics
| Branch: | Tag: | Revision:

root / cloudcms / forms.py @ 71b9f1ac

History | View | Annotate | Download (10.4 kB)

1
# -*- coding: utf-8 -*-
2

    
3
import zipfile
4
import tempfile
5
import os
6
import glob
7
import logging
8
import StringIO
9

    
10
from django import forms
11
from django.conf import settings
12
from django.db import transaction
13
from django.core.files import File
14

    
15
from feincms.module.medialibrary.models import Category as MediaCategory, \
16
        MediaFile
17

    
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
23

    
24
logger = logging.getLogger('cloudcms.rstimport')
25

    
26
# base filename to service slug map
27
DEFAULT_SERVICE_MAP = {
28
        'cyclades':'cyclades',
29
        'okeanos':'okeanos',
30
        'pithos':'pithos'
31
}
32

    
33
DEFAULT_REGION_MAP = {
34
        'faq':'main',
35
        'userguide':'main',
36
}
37

    
38
DEFAULT_RESIZE_GEOMETRY = (400, 400)
39

    
40
SERVICE_MAP = getattr(settings, 'CMS_RST_IMPORT_SERVICE_FILE_MAP',
41
        DEFAULT_SERVICE_MAP)
42
REGIONS_MAP = getattr(settings, 'CMS_RST_IMPORT_REGIONS_MAP',
43
        DEFAULT_REGION_MAP)
44
RESIZE_GEOMETRY = getattr(settings, 'CMS_RST_IMPORT_RESIZE_GEOMETRY',
45
    DEFAULT_RESIZE_GEOMETRY)
46

    
47
def service_from_filename(rst):
48
    fname = os.path.basename(rst).replace(".rst","")
49
    service_slug = DEFAULT_SERVICE_MAP.get(fname, None)
50
    if not service_slug:
51
        return None
52

    
53
    try:
54
        return Service.objects.filter(translations__slug=service_slug)[0]
55
    except IndexError:
56
        return None
57
    except Service.DoesNotExist:
58
        return None
59

    
60
    return None
61

    
62

    
63
def get_media_category(slug):
64
    return MediaCategory.objects.get(slug=slug)
65

    
66

    
67
CATEGORIES_CHOICES = (('faq', 'FAQs'), ('user-guide', 'User guide'))
68

    
69
class RstZipImportForm(forms.Form):
70

    
71
    FAQ_MEDIA_CATEGORY = 'faq-images'
72
    GUIDE_MEDIA_CATEGORY = 'user-guide-images'
73

    
74
    clean_data = forms.BooleanField(initial=False, required=False)
75
    dry_run = forms.BooleanField(initial=True, required=False,
76
            widget=forms.HiddenInput)
77
    import_file = forms.FileField(required=True)
78

    
79
    def __init__(self, *args, **kwargs):
80
        super(RstZipImportForm, self).__init__(*args, **kwargs)
81
        self.log_data = ""
82

    
83
    def log(self, msg):
84
        self.log_data += "\n" + msg
85

    
86
    def get_tmp_file(self, f):
87
        tmp = tempfile.mktemp('cloudcms-sphinx-import')
88
        f.file.reset()
89
        fd = file(tmp, 'w')
90
        fd.write(f.read())
91
        fd.close()
92
        return tmp
93

    
94
    def clean_import_file(self):
95
        f = self.cleaned_data['import_file']
96
        tmpfile = self.get_tmp_file(f)
97
        if not zipfile.is_zipfile(tmpfile):
98
            raise forms.ValidationError("Invalid zip file")
99
        return f
100

    
101
    def clean(self, *args, **kwargs):
102
        data = super(RstZipImportForm, self).clean(*args, **kwargs)
103
        return data
104

    
105
    def clean_existing_data(self):
106
        logger.warning("Removing all FAQ questions")
107
        Question.objects.all().delete()
108
        logger.warning("Removing all User Guide entries")
109
        UserGuideEntry.objects.all().delete()
110
        logger.warning("Removing all media files in categories %s", [self.FAQ_MEDIA_CATEGORY,
111
            self.GUIDE_MEDIA_CATEGORY])
112
        MediaFile.objects.filter(categories__slug__in=[self.FAQ_MEDIA_CATEGORY, \
113
            self.GUIDE_MEDIA_CATEGORY]).delete()
114

    
115
    def save(self, user, use_dir=None):
116
        stream = StringIO.StringIO()
117
        stream_handler = logging.StreamHandler(stream)
118
        stream_handler.setFormatter(logging.Formatter('<div class="log-entry %(levelname)s"><em>%(levelname)s</em>'
119
            '<pre>%(message)s</pre></div>'))
120
        logger.addHandler(stream_handler)
121
        old_level = logger.level
122
        logger.setLevel(logging.DEBUG)
123

    
124
        dry_run = self.cleaned_data.get('dry_run')
125
        clean_data = self.cleaned_data.get('clean_data')
126
        import_file = self.cleaned_data.get('import_file')
127

    
128
        if not use_dir:
129
            zipdir = tempfile.mkdtemp('cloudcms-sphinx-exports')
130
            zipdatafile = self.get_tmp_file(import_file)
131
            zipf = zipfile.ZipFile(file(zipdatafile))
132
            zipf.extractall(zipdir)
133
        else:
134
            zipdir = use_dir
135

    
136
        subdirs = os.listdir(zipdir)
137
        if len(subdirs) == 1 and os.path.isdir(os.path.join(zipdir, \
138
                subdirs[0])) and subdirs[0] != 'source':
139
            zipdir = os.path.join(zipdir, subdirs[0])
140

    
141
        sid = transaction.savepoint()
142

    
143
        if clean_data:
144
            try:
145
                logger.warning("Removing exising entries")
146
                self.clean_existing_data()
147
            except Exception, e:
148
                transaction.savepoint_rollback(sid)
149
                logger.exception("Failed to clean existing data")
150
                logger.removeHandler(stream_handler)
151
                logger.setLevel(old_level)
152
                return False, stream.getvalue()
153

    
154
        ret = ""
155
        try:
156
            logger.warning("Parsing contents of '%s'", zipdir)
157
            for data_type, rst, category, slug, title, html_content, \
158
                    images, stderr in generate_rst_contents_from_dir(zipdir):
159

    
160
                if stderr:
161
                    logger.error("Docutils error output for '%s'\n: %s" % (rst, stderr, ))
162

    
163
                service = service_from_filename(rst)
164
                if not service:
165
                    logger.info("Skipping entry for file '%s'. No category found" % rst)
166
                    continue
167

    
168
                logger.info("Processing, '%s'" % (rst, ))
169

    
170

    
171
                # first save media files
172
                cat = False
173
                newimages = []
174
                if data_type == 'userguide':
175
                    cat = get_media_category(self.GUIDE_MEDIA_CATEGORY)
176
                if data_type == 'faq':
177
                    cat = get_media_category(self.FAQ_MEDIA_CATEGORY)
178

    
179
                if not cat:
180
                    logger.info("Skipping %s, no media category found for '%s'",
181
                            rst, data_type)
182
                    continue
183

    
184
                for imgname, imgpath, imgabspath in images:
185
                    logger.debug("Checking image (%s, %s, %s)", imgname, imgpath, imgabspath)
186
                    newalt, newpath = create_or_update_media_file(cat, \
187
                            imgname, imgabspath)
188

    
189
                    html_content = html_content.replace(imgpath, newpath)
190

    
191
                # now html contains correct image links, we are ready to save
192
                # the faq/guide content
193
                if data_type == 'faq':
194
                    logger.info('Processing FAQ entry, %s, %s, %s', service, slug, title)
195
                    cat = add_or_update_faq_category(unicode(category[0]),
196
                            unicode(category[1]))
197
                    question = add_or_update_faq_question(user, service, cat, slug, \
198
                            title, html_content)
199

    
200
                if data_type == 'userguide':
201
                    logger.info('Processing USER GUIDE entry, %s, %s, %s', service, slug, title)
202
                    guide_entry = add_or_update_guide_entry(user, service, slug, \
203
                            title, html_content)
204

    
205

    
206
        except Exception, e:
207
            print e
208
            logger.exception("RST import failed")
209
            logger.removeHandler(stream_handler)
210
            logger.setLevel(old_level)
211
            transaction.savepoint_rollback(sid)
212
            return False, stream.getvalue()
213

    
214
        if dry_run:
215
            transaction.savepoint_rollback(sid)
216
        else:
217
            transaction.savepoint_commit(sid)
218

    
219
        return True, stream.getvalue()
220

    
221

    
222
def create_or_update_media_file(category, name, path):
223
    logger.info("Create or update media file, %s, %s, [category: %s]", name,
224
            path, category)
225
    name = category.title + " " + name
226

    
227
    try:
228
        # TODO: Check language ?????
229
        mf = MediaFile.objects.get(categories__in=[category], translations__caption=name)
230
        logger.info("Media file found")
231
    except MediaFile.DoesNotExist:
232
        logger.info("Media file not found, creating...")
233
        mf = MediaFile()
234
        mf.file = File(open(path))
235
        mf.save()
236
        mf.translations.create(caption=name)
237
        mf.categories.clear()
238
        mf.categories = [category]
239
        mf.save()
240

    
241
    # TODO: Check language ?????
242
    return mf.translations.all()[0].caption, mf.get_absolute_url()
243

    
244

    
245
def add_or_update_faq_category(slug, name):
246
    logger.info("Create or update faq subcategory, %s, %s", slug,
247
            name)
248
    try:
249
        category = FaqCategory.objects.get(translations__slug=slug)
250
        logger.info("FAQ category found")
251
    except FaqCategory.DoesNotExist:
252
        logger.info("FAQ category not found, creating...")
253
        category = FaqCategory()
254
        category.save()
255
        category.translations.create(slug=slug, title=name)
256

    
257
    return category
258

    
259

    
260
def add_or_update_faq_question(author, service, category, slug,\
261
        title, html_content):
262
    logger.info("Create or update faq question, %s, %s, %s, %s", service,
263
            category, slug, title)
264

    
265
    try:
266
        q = Question.objects.get(slug=slug)
267
        logger.info("Question found, updating...")
268
    except:
269
        logger.info("Question not found, creating...")
270
        q = Question()
271

    
272
    q.author = author
273
    q.is_active = True
274
    q.category = category
275
    q.service = service
276
    q.slug = slug
277
    q.title = unicode(title)
278
    q.save()
279
    q.application = [Application.current()]
280
    q.save()
281

    
282
    RawContentModel = Question.content_type_for(RawContent)
283
    try:
284
        content = q.rawcontent_set.filter()[0]
285
    except:
286
        content = q.rawcontent_set.create(region=REGIONS_MAP['faq'])
287

    
288
    content.text = html_content
289
    content.save()
290

    
291
    return q
292

    
293
def add_or_update_guide_entry(author, service, slug, title, html_content):
294
    logger.info("Create or update user guide entry, %s, %s, %s", service,
295
            slug, title)
296
    try:
297
        logger.info("Guide entry found, updating...")
298
        guide = UserGuideEntry.objects.get(slug=slug)
299
    except:
300
        logger.info("Guide entry not found, creating...")
301
        guide = UserGuideEntry()
302

    
303
    guide.author = author
304
    guide.is_active = True
305
    guide.service = service
306
    guide.slug = slug
307
    guide.title = unicode(title)
308
    guide.save()
309

    
310
    RawContentModel = UserGuideEntry.content_type_for(RawContent)
311
    try:
312
        content = guide.rawcontent_set.filter()[0]
313
    except:
314
        content = guide.rawcontent_set.create(region=REGIONS_MAP['userguide'])
315

    
316
    content.text = html_content
317
    content.save()
318

    
319
    return guide
320

    
321