Statistics
| Branch: | Tag: | Revision:

root / cloudcms / forms.py @ 66b454b7

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