Merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/snappy-docs-import into lp:developer-ubuntu-com
- snappy-docs-import
- Merge into stable
Status: | Merged |
---|---|
Approved by: | Daniel Holbach |
Approved revision: | 162 |
Merged at revision: | 126 |
Proposed branch: | lp:~developer-ubuntu-com-dev/developer-ubuntu-com/snappy-docs-import |
Merge into: | lp:developer-ubuntu-com |
Diff against target: |
507 lines (+422/-2) 6 files modified
developer_portal/admin.py (+18/-0) developer_portal/management/commands/import-external-docs-branches.py (+309/-0) developer_portal/migrations/0003_add_external_docs_branches.py (+62/-0) developer_portal/models.py (+20/-1) locale/developer_portal.pot (+11/-1) requirements.txt (+2/-0) |
To merge this branch: | bzr merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/snappy-docs-import |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
David Callé | Pending | ||
Review via email: mp+265004@code.launchpad.net |
Commit message
Add functionality to import external markdown docs (like snappy and snapcraft) automatically into developer site.
Description of the change
Daniel Holbach (dholbach) wrote : | # |
Daniel Holbach (dholbach) wrote : | # |
David Callé noticed that SnappyMarkdownF
Daniel Holbach (dholbach) wrote : | # |
<davidcalle> dholbach, well, I'm not sure about the "Snappy" name for landing pages. I haven't found anything else. Oh, maybe the script itself should be renamed to not have snappy in its name.
David Callé (davidc3) wrote : | # |
Added a few changes.
Lots of testing done with a dummy branch on the whole publication, page removal, doc name changes, etc. processes. Everything works great.
Daniel Holbach (dholbach) wrote : | # |
<dholbach> davidcalle, I read the code again and I think I'm fine with it
parts of it could be a bit simpler I feel, but for now I think it's good enough
Daniel Holbach (dholbach) wrote : | # |
<davidcalle> dholbach, same for me. Let's merge it :) I'll try the staging deploy this afternoon.
Preview Diff
1 | === modified file 'developer_portal/admin.py' | |||
2 | --- developer_portal/admin.py 2014-12-01 15:11:25 +0000 | |||
3 | +++ developer_portal/admin.py 2015-08-11 12:06:30 +0000 | |||
4 | @@ -3,10 +3,20 @@ | |||
5 | 3 | from reversion.models import Revision, Version | 3 | from reversion.models import Revision, Version |
6 | 4 | from reversion.admin import VersionAdmin | 4 | from reversion.admin import VersionAdmin |
7 | 5 | 5 | ||
8 | 6 | from .models import ExternalDocsBranch | ||
9 | 7 | from django.core.management import call_command | ||
10 | 8 | |||
11 | 6 | __all__ = ( | 9 | __all__ = ( |
12 | 7 | ) | 10 | ) |
13 | 8 | 11 | ||
14 | 9 | 12 | ||
15 | 13 | def import_selected_external_docs_branches(modeladmin, request, queryset): | ||
16 | 14 | for branch in queryset: | ||
17 | 15 | call_command('import-external-docs-branches', branch.docs_namespace) | ||
18 | 16 | import_selected_external_docs_branches.short_description = \ | ||
19 | 17 | "Import selected branches" | ||
20 | 18 | |||
21 | 19 | |||
22 | 10 | class RevisionAdmin(admin.ModelAdmin): | 20 | class RevisionAdmin(admin.ModelAdmin): |
23 | 11 | list_display = ('date_created', 'user', 'comment') | 21 | list_display = ('date_created', 'user', 'comment') |
24 | 12 | list_display_links = ('date_created', ) | 22 | list_display_links = ('date_created', ) |
25 | @@ -15,6 +25,7 @@ | |||
26 | 15 | 25 | ||
27 | 16 | admin.site.register(Revision, RevisionAdmin) | 26 | admin.site.register(Revision, RevisionAdmin) |
28 | 17 | 27 | ||
29 | 28 | |||
30 | 18 | class VersionAdmin(admin.ModelAdmin): | 29 | class VersionAdmin(admin.ModelAdmin): |
31 | 19 | list_display = ('content_type', 'object_id') | 30 | list_display = ('content_type', 'object_id') |
32 | 20 | list_display_links = ('object_id', ) | 31 | list_display_links = ('object_id', ) |
33 | @@ -22,3 +33,10 @@ | |||
34 | 22 | 33 | ||
35 | 23 | admin.site.register(Version, VersionAdmin) | 34 | admin.site.register(Version, VersionAdmin) |
36 | 24 | 35 | ||
37 | 36 | |||
38 | 37 | class ExternalDocsBranchAdmin(admin.ModelAdmin): | ||
39 | 38 | list_display = ('lp_origin', 'docs_namespace') | ||
40 | 39 | list_filter = ('lp_origin', 'docs_namespace') | ||
41 | 40 | actions = [import_selected_external_docs_branches] | ||
42 | 41 | |||
43 | 42 | admin.site.register(ExternalDocsBranch, ExternalDocsBranchAdmin) | ||
44 | 25 | 43 | ||
45 | === added file 'developer_portal/management/commands/import-external-docs-branches.py' | |||
46 | --- developer_portal/management/commands/import-external-docs-branches.py 1970-01-01 00:00:00 +0000 | |||
47 | +++ developer_portal/management/commands/import-external-docs-branches.py 2015-08-11 12:06:30 +0000 | |||
48 | @@ -0,0 +1,309 @@ | |||
49 | 1 | from django.core.management.base import BaseCommand | ||
50 | 2 | |||
51 | 3 | from cms.api import create_page, add_plugin | ||
52 | 4 | from cms.models import Page, Title | ||
53 | 5 | from cms.utils import page_resolver | ||
54 | 6 | |||
55 | 7 | from bs4 import BeautifulSoup | ||
56 | 8 | import codecs | ||
57 | 9 | import glob | ||
58 | 10 | import logging | ||
59 | 11 | import markdown | ||
60 | 12 | import os | ||
61 | 13 | import re | ||
62 | 14 | import shutil | ||
63 | 15 | import subprocess | ||
64 | 16 | import sys | ||
65 | 17 | import tempfile | ||
66 | 18 | |||
67 | 19 | from developer_portal.models import ExternalDocsBranch | ||
68 | 20 | |||
69 | 21 | DOCS_DIRNAME = 'docs' | ||
70 | 22 | |||
71 | 23 | |||
72 | 24 | class MarkdownFile: | ||
73 | 25 | html = None | ||
74 | 26 | |||
75 | 27 | def __init__(self, fn, docs_namespace, slug_override=None): | ||
76 | 28 | self.fn = fn | ||
77 | 29 | self.docs_namespace = docs_namespace | ||
78 | 30 | if slug_override: | ||
79 | 31 | self.slug = slug_override | ||
80 | 32 | else: | ||
81 | 33 | self.slug = slugify(self.fn) | ||
82 | 34 | self.full_url = os.path.join(self.docs_namespace, self.slug) | ||
83 | 35 | with codecs.open(self.fn, 'r', encoding='utf-8') as f: | ||
84 | 36 | self.html = markdown.markdown( | ||
85 | 37 | f.read(), | ||
86 | 38 | output_format="html5", | ||
87 | 39 | extensions=['markdown.extensions.tables']) | ||
88 | 40 | self.release_alias = self._get_release_alias() | ||
89 | 41 | self.title = self._read_title() | ||
90 | 42 | self._remove_body_and_html_tags() | ||
91 | 43 | self._use_developer_site_style() | ||
92 | 44 | |||
93 | 45 | def _get_release_alias(self): | ||
94 | 46 | alias = re.findall(r'/tmp/tmp\S+?/(\S+?)/%s/\S+?' % DOCS_DIRNAME, | ||
95 | 47 | self.fn) | ||
96 | 48 | return alias[0] | ||
97 | 49 | |||
98 | 50 | def _read_title(self): | ||
99 | 51 | soup = BeautifulSoup(self.html, 'html5lib') | ||
100 | 52 | if soup.title: | ||
101 | 53 | return soup.title.text | ||
102 | 54 | if soup.h1: | ||
103 | 55 | return soup.h1.text | ||
104 | 56 | return slugify(self.fn).replace('-', ' ').title() | ||
105 | 57 | |||
106 | 58 | def _remove_body_and_html_tags(self): | ||
107 | 59 | self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, | ||
108 | 60 | flags=re.MULTILINE) | ||
109 | 61 | self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, | ||
110 | 62 | flags=re.MULTILINE) | ||
111 | 63 | |||
112 | 64 | def _use_developer_site_style(self): | ||
113 | 65 | begin = (u"<div class=\"row no-border\">" | ||
114 | 66 | "\n<div class=\"eight-col\">\n") | ||
115 | 67 | end = u"</div>\n</div>" | ||
116 | 68 | self.html = begin + self.html + end | ||
117 | 69 | self.html = self.html.replace( | ||
118 | 70 | "<pre><code>", | ||
119 | 71 | "</div><div class=\"twelve-col\"><pre><code>") | ||
120 | 72 | self.html = self.html.replace( | ||
121 | 73 | "</code></pre>", | ||
122 | 74 | "</code></pre></div><div class=\"eight-col\">") | ||
123 | 75 | |||
124 | 76 | def replace_links(self, titles): | ||
125 | 77 | for title in titles: | ||
126 | 78 | url = u"/snappy/guides/%s/%s" % ( | ||
127 | 79 | self.release_alias, slugify(title)) | ||
128 | 80 | link = u"<a href=\"%s\">%s</a>" % (url, titles[title]) | ||
129 | 81 | self.html = self.html.replace(os.path.basename(title), link) | ||
130 | 82 | |||
131 | 83 | def publish(self): | ||
132 | 84 | '''Publishes pages in their branch alias namespace.''' | ||
133 | 85 | page = get_or_create_page( | ||
134 | 86 | self.title, full_url=self.full_url, menu_title=self.title, | ||
135 | 87 | html=self.html) | ||
136 | 88 | page.publish('en') | ||
137 | 89 | |||
138 | 90 | |||
139 | 91 | class SnappyMarkdownFile(MarkdownFile): | ||
140 | 92 | def __init__(self, fn, docs_namespace): | ||
141 | 93 | MarkdownFile.__init__(self, fn, docs_namespace) | ||
142 | 94 | self._make_snappy_mods() | ||
143 | 95 | |||
144 | 96 | def _make_snappy_mods(self): | ||
145 | 97 | # Make sure the reader knows which documentation she is browsing | ||
146 | 98 | if self.release_alias != 'current': | ||
147 | 99 | before = (u"<div class=\"row no-border\">\n" | ||
148 | 100 | "<div class=\"eight-col\">\n") | ||
149 | 101 | after = (u"<div class=\"row no-border\">\n" | ||
150 | 102 | "<div class=\"box pull-three three-col\">" | ||
151 | 103 | "<p>You are browsing the Snappy <code>%s</code> " | ||
152 | 104 | "documentation.</p>" | ||
153 | 105 | "<p><a href=\"/snappy/guides/current/%s\">" | ||
154 | 106 | "Back to the latest stable release ›" | ||
155 | 107 | "</a></p></div>\n" | ||
156 | 108 | "<div class=\"eight-col\">\n") % (self.release_alias, | ||
157 | 109 | self.slug, ) | ||
158 | 110 | self.html = self.html.replace(before, after) | ||
159 | 111 | |||
160 | 112 | def publish(self): | ||
161 | 113 | if self.release_alias == "current": | ||
162 | 114 | # Add a guides/<page> redirect to guides/current/<page> | ||
163 | 115 | page = get_or_create_page( | ||
164 | 116 | self.title, full_url=self.full_url.replace('/current', ''), | ||
165 | 117 | redirect="/snappy/guides/current/%s" % (self.slug)) | ||
166 | 118 | page.publish('en') | ||
167 | 119 | else: | ||
168 | 120 | self.title += " (%s)" % (self.release_alias,) | ||
169 | 121 | MarkdownFile.publish(self) | ||
170 | 122 | |||
171 | 123 | |||
172 | 124 | def slugify(filename): | ||
173 | 125 | return os.path.basename(filename).replace('.md', '') | ||
174 | 126 | |||
175 | 127 | |||
176 | 128 | def get_branch_from_lp(origin, alias): | ||
177 | 129 | return subprocess.call([ | ||
178 | 130 | 'bzr', 'checkout', '--lightweight', origin, alias]) | ||
179 | 131 | |||
180 | 132 | |||
181 | 133 | class LocalBranch: | ||
182 | 134 | titles = {} | ||
183 | 135 | |||
184 | 136 | def __init__(self, dirname, external_branch): | ||
185 | 137 | self.dirname = dirname | ||
186 | 138 | self.docs_path = os.path.join(self.dirname, DOCS_DIRNAME) | ||
187 | 139 | self.doc_fns = glob.glob(self.docs_path+'/*.md') | ||
188 | 140 | self.md_files = [] | ||
189 | 141 | self.external_branch = external_branch | ||
190 | 142 | self.docs_namespace = self.external_branch.docs_namespace | ||
191 | 143 | self.release_alias = os.path.basename(self.docs_namespace) | ||
192 | 144 | self.index_doc_title = self.release_alias.title() | ||
193 | 145 | self.index_doc = self.external_branch.index_doc | ||
194 | 146 | self.markdown_class = MarkdownFile | ||
195 | 147 | |||
196 | 148 | def import_markdown(self): | ||
197 | 149 | for doc_fn in self.doc_fns: | ||
198 | 150 | if self.index_doc and os.path.basename(doc_fn) == self.index_doc: | ||
199 | 151 | md_file = self.markdown_class( | ||
200 | 152 | doc_fn, | ||
201 | 153 | os.path.dirname(self.docs_namespace), | ||
202 | 154 | slug_override=os.path.basename(self.docs_namespace)) | ||
203 | 155 | self.md_files.insert(0, md_file) | ||
204 | 156 | else: | ||
205 | 157 | md_file = self.markdown_class(doc_fn, self.docs_namespace) | ||
206 | 158 | self.md_files += [md_file] | ||
207 | 159 | self.titles[md_file.fn] = md_file.title | ||
208 | 160 | if not self.index_doc: | ||
209 | 161 | self._create_fake_index_doc() | ||
210 | 162 | |||
211 | 163 | def remove_old_pages(self): | ||
212 | 164 | imported_page_urls = set([md_file.full_url | ||
213 | 165 | for md_file in self.md_files]) | ||
214 | 166 | index_doc = page_resolver.get_page_queryset_from_path( | ||
215 | 167 | self.docs_namespace)[0] | ||
216 | 168 | # All pages in this namespace currently in the database | ||
217 | 169 | db_pages = index_doc.get_descendants().all() | ||
218 | 170 | delete_pages = [] | ||
219 | 171 | for db_page in db_pages: | ||
220 | 172 | still_relevant = False | ||
221 | 173 | for url in imported_page_urls: | ||
222 | 174 | if url in db_page.get_absolute_url(): | ||
223 | 175 | still_relevant = True | ||
224 | 176 | break | ||
225 | 177 | # At this point we know that there's no match and the page | ||
226 | 178 | # can be deleted. | ||
227 | 179 | if not still_relevant: | ||
228 | 180 | delete_pages += [db_page.id] | ||
229 | 181 | # Only remove pages created by a script! | ||
230 | 182 | Page.objects.filter(id__in=delete_pages, created_by="script").delete() | ||
231 | 183 | |||
232 | 184 | def publish(self): | ||
233 | 185 | for md_file in self.md_files: | ||
234 | 186 | md_file.publish() | ||
235 | 187 | |||
236 | 188 | def _create_fake_index_doc(self): | ||
237 | 189 | '''Creates a fake index page at the top of the branches | ||
238 | 190 | docs namespace.''' | ||
239 | 191 | |||
240 | 192 | if self.docs_namespace == "current": | ||
241 | 193 | redirect = "/snappy/guides" | ||
242 | 194 | else: | ||
243 | 195 | redirect = None | ||
244 | 196 | |||
245 | 197 | in_navigation = False | ||
246 | 198 | menu_title = None | ||
247 | 199 | list_pages = "" | ||
248 | 200 | for page in self.md_files: | ||
249 | 201 | list_pages += "<li><a href=\"%s\">%s</a></li>" \ | ||
250 | 202 | % (os.path.basename(page.full_url), page.title) | ||
251 | 203 | landing = ( | ||
252 | 204 | u"<div class=\"row\"><div class=\"eight-col\">\n" | ||
253 | 205 | "<p>This section contains documentation for the " | ||
254 | 206 | "<code>%s</code> Snappy branch.</p>" | ||
255 | 207 | "<p><ul class=\"list-ubuntu\">%s</ul></p>\n" | ||
256 | 208 | "<p>Auto-imported from <a " | ||
257 | 209 | "href=\"https://code.launchpad.net/snappy\">%s</a>.</p>\n" | ||
258 | 210 | "</div></div>") % (self.release_alias, list_pages, | ||
259 | 211 | self.external_branch.lp_origin) | ||
260 | 212 | new_release_page = get_or_create_page( | ||
261 | 213 | self.index_doc_title, full_url=self.docs_namespace, | ||
262 | 214 | in_navigation=in_navigation, redirect=redirect, html=landing, | ||
263 | 215 | menu_title=menu_title) | ||
264 | 216 | new_release_page.publish('en') | ||
265 | 217 | |||
266 | 218 | |||
267 | 219 | class SnappyLocalBranch(LocalBranch): | ||
268 | 220 | def __init__(self, dirname, external_branch): | ||
269 | 221 | LocalBranch.__init__(self, dirname, external_branch) | ||
270 | 222 | self.markdown_class = SnappyMarkdownFile | ||
271 | 223 | self.index_doc_title = 'Snappy documentation' | ||
272 | 224 | if self.release_alias != 'current': | ||
273 | 225 | self.index_doc_title += ' (%s)' % self.release_alias | ||
274 | 226 | |||
275 | 227 | def import_markdown(self): | ||
276 | 228 | LocalBranch.import_markdown(self) | ||
277 | 229 | for md_file in self.md_files: | ||
278 | 230 | md_file.replace_links(self.titles) | ||
279 | 231 | |||
280 | 232 | |||
281 | 233 | def get_or_create_page(title, full_url, menu_title=None, | ||
282 | 234 | in_navigation=True, redirect=None, html=None): | ||
283 | 235 | # First check if pages already exist. | ||
284 | 236 | pages = Title.objects.select_related('page').filter(path__regex=full_url) | ||
285 | 237 | if pages: | ||
286 | 238 | page = pages[0].page | ||
287 | 239 | page.title = title | ||
288 | 240 | page.publisher_is_draft = True | ||
289 | 241 | page.menu_title = menu_title | ||
290 | 242 | page.in_navigation = in_navigation | ||
291 | 243 | page.redirect = redirect | ||
292 | 244 | if html: | ||
293 | 245 | # We create the page, so we know there's just one placeholder | ||
294 | 246 | placeholder = page.placeholders.all()[0] | ||
295 | 247 | if placeholder.get_plugins(): | ||
296 | 248 | plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] | ||
297 | 249 | plugin.body = html | ||
298 | 250 | plugin.save() | ||
299 | 251 | else: | ||
300 | 252 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
301 | 253 | else: | ||
302 | 254 | parent_pages = Title.objects.select_related('page').filter( | ||
303 | 255 | path__regex=os.path.dirname(full_url)) | ||
304 | 256 | if not parent_pages: | ||
305 | 257 | print('Parent %s not found.' % os.path.dirname(full_url)) | ||
306 | 258 | sys.exit(1) | ||
307 | 259 | parent = parent_pages[0].page | ||
308 | 260 | |||
309 | 261 | slug = os.path.basename(full_url) | ||
310 | 262 | page = create_page( | ||
311 | 263 | title, "default.html", "en", slug=slug, parent=parent, | ||
312 | 264 | menu_title=menu_title, in_navigation=in_navigation, | ||
313 | 265 | position="last-child", redirect=redirect) | ||
314 | 266 | if html: | ||
315 | 267 | placeholder = page.placeholders.get() | ||
316 | 268 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
317 | 269 | return page | ||
318 | 270 | |||
319 | 271 | |||
320 | 272 | def import_branches(selection): | ||
321 | 273 | if not ExternalDocsBranch.objects.count(): | ||
322 | 274 | logging.error('No branches registered in the ' | ||
323 | 275 | 'ExternalDocsBranch table yet.') | ||
324 | 276 | return | ||
325 | 277 | tempdir = tempfile.mkdtemp() | ||
326 | 278 | for branch in ExternalDocsBranch.objects.filter( | ||
327 | 279 | docs_namespace__regex=selection): | ||
328 | 280 | checkout_location = os.path.join( | ||
329 | 281 | tempdir, os.path.basename(branch.docs_namespace)) | ||
330 | 282 | if get_branch_from_lp(branch.lp_origin, checkout_location) != 0: | ||
331 | 283 | logging.error( | ||
332 | 284 | 'Could not check out branch "%s".' % branch.lp_origin) | ||
333 | 285 | shutil.rmtree(checkout_location) | ||
334 | 286 | break | ||
335 | 287 | if branch.lp_origin.startswith('lp:snappy'): | ||
336 | 288 | local_branch = SnappyLocalBranch(checkout_location, branch) | ||
337 | 289 | else: | ||
338 | 290 | local_branch = LocalBranch(checkout_location, branch) | ||
339 | 291 | local_branch.import_markdown() | ||
340 | 292 | local_branch.publish() | ||
341 | 293 | local_branch.remove_old_pages() | ||
342 | 294 | shutil.rmtree(tempdir) | ||
343 | 295 | |||
344 | 296 | |||
345 | 297 | class Command(BaseCommand): | ||
346 | 298 | help = "Import external branches for documentation." | ||
347 | 299 | |||
348 | 300 | def handle(*args, **options): | ||
349 | 301 | logging.basicConfig( | ||
350 | 302 | level=logging.ERROR, | ||
351 | 303 | format='%(asctime)s %(levelname)-8s %(message)s', | ||
352 | 304 | datefmt='%F %T') | ||
353 | 305 | if len(args) < 2 or args[1] == "all": | ||
354 | 306 | selection = '.*' | ||
355 | 307 | else: | ||
356 | 308 | selection = args[1] | ||
357 | 309 | import_branches(selection) | ||
358 | 0 | 310 | ||
359 | === added file 'developer_portal/migrations/0003_add_external_docs_branches.py' | |||
360 | --- developer_portal/migrations/0003_add_external_docs_branches.py 1970-01-01 00:00:00 +0000 | |||
361 | +++ developer_portal/migrations/0003_add_external_docs_branches.py 2015-08-11 12:06:30 +0000 | |||
362 | @@ -0,0 +1,62 @@ | |||
363 | 1 | # -*- coding: utf-8 -*- | ||
364 | 2 | from south.utils import datetime_utils as datetime | ||
365 | 3 | from south.db import db | ||
366 | 4 | from south.v2 import SchemaMigration | ||
367 | 5 | from django.db import models | ||
368 | 6 | |||
369 | 7 | |||
370 | 8 | class Migration(SchemaMigration): | ||
371 | 9 | |||
372 | 10 | def forwards(self, orm): | ||
373 | 11 | # Adding model 'ExternalDocsBranch' | ||
374 | 12 | db.create_table(u'developer_portal_externaldocsbranch', ( | ||
375 | 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
376 | 14 | ('lp_origin', self.gf('django.db.models.fields.CharField')(max_length=200)), | ||
377 | 15 | ('docs_namespace', self.gf('django.db.models.fields.CharField')(max_length=120)), | ||
378 | 16 | ('index_doc', self.gf('django.db.models.fields.CharField')(max_length=120, blank=True)), | ||
379 | 17 | )) | ||
380 | 18 | db.send_create_signal(u'developer_portal', ['ExternalDocsBranch']) | ||
381 | 19 | |||
382 | 20 | |||
383 | 21 | def backwards(self, orm): | ||
384 | 22 | # Deleting model 'ExternalDocsBranch' | ||
385 | 23 | db.delete_table(u'developer_portal_externaldocsbranch') | ||
386 | 24 | |||
387 | 25 | |||
388 | 26 | models = { | ||
389 | 27 | 'cms.cmsplugin': { | ||
390 | 28 | 'Meta': {'object_name': 'CMSPlugin'}, | ||
391 | 29 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
392 | 30 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
393 | 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
394 | 32 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
395 | 33 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
396 | 34 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
397 | 35 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), | ||
398 | 36 | 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), | ||
399 | 37 | 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), | ||
400 | 38 | 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
401 | 39 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
402 | 40 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) | ||
403 | 41 | }, | ||
404 | 42 | 'cms.placeholder': { | ||
405 | 43 | 'Meta': {'object_name': 'Placeholder'}, | ||
406 | 44 | 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), | ||
407 | 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
408 | 46 | 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) | ||
409 | 47 | }, | ||
410 | 48 | u'developer_portal.externaldocsbranch': { | ||
411 | 49 | 'Meta': {'object_name': 'ExternalDocsBranch'}, | ||
412 | 50 | 'docs_namespace': ('django.db.models.fields.CharField', [], {'max_length': '120'}), | ||
413 | 51 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
414 | 52 | 'index_doc': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), | ||
415 | 53 | 'lp_origin': ('django.db.models.fields.CharField', [], {'max_length': '200'}) | ||
416 | 54 | }, | ||
417 | 55 | u'developer_portal.rawhtml': { | ||
418 | 56 | 'Meta': {'object_name': 'RawHtml'}, | ||
419 | 57 | 'body': ('django.db.models.fields.TextField', [], {}), | ||
420 | 58 | u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) | ||
421 | 59 | } | ||
422 | 60 | } | ||
423 | 61 | |||
424 | 62 | complete_apps = ['developer_portal'] | ||
425 | 0 | \ No newline at end of file | 63 | \ No newline at end of file |
426 | 1 | 64 | ||
427 | === modified file 'developer_portal/models.py' | |||
428 | --- developer_portal/models.py 2015-01-19 16:29:47 +0000 | |||
429 | +++ developer_portal/models.py 2015-08-11 12:06:30 +0000 | |||
430 | @@ -1,10 +1,13 @@ | |||
431 | 1 | from django.db import models | ||
432 | 2 | from django.utils.translation import ugettext_lazy as _ | ||
433 | 1 | 3 | ||
434 | 2 | from cms.models import CMSPlugin | 4 | from cms.models import CMSPlugin |
435 | 3 | from djangocms_text_ckeditor.html import extract_images | 5 | from djangocms_text_ckeditor.html import extract_images |
436 | 4 | from djangocms_text_ckeditor.models import AbstractText | 6 | from djangocms_text_ckeditor.models import AbstractText |
437 | 5 | 7 | ||
438 | 8 | |||
439 | 6 | class RawHtml(AbstractText): | 9 | class RawHtml(AbstractText): |
441 | 7 | 10 | ||
442 | 8 | class Meta: | 11 | class Meta: |
443 | 9 | abstract = False | 12 | abstract = False |
444 | 10 | 13 | ||
445 | @@ -13,3 +16,19 @@ | |||
446 | 13 | body = extract_images(body, self) | 16 | body = extract_images(body, self) |
447 | 14 | self.body = body | 17 | self.body = body |
448 | 15 | AbstractText.save(self, *args, **kwargs) | 18 | AbstractText.save(self, *args, **kwargs) |
449 | 19 | |||
450 | 20 | |||
451 | 21 | class ExternalDocsBranch(models.Model): | ||
452 | 22 | lp_origin = models.CharField( | ||
453 | 23 | max_length=200, | ||
454 | 24 | help_text=_('Launchpad branch location, ie: lp:snappy/15.04')) | ||
455 | 25 | docs_namespace = models.CharField( | ||
456 | 26 | max_length=120, | ||
457 | 27 | help_text=_('Path alias we want to use for the docs, ' | ||
458 | 28 | 'ie "snappy/guides/15.04" or ' | ||
459 | 29 | '"snappy/guides/latest", etc.')) | ||
460 | 30 | index_doc = models.CharField( | ||
461 | 31 | max_length=120, | ||
462 | 32 | help_text=_('File name of doc to be used as index document, ' | ||
463 | 33 | 'ie "intro.md"'), | ||
464 | 34 | blank=True) | ||
465 | 16 | 35 | ||
466 | === modified file 'locale/developer_portal.pot' | |||
467 | --- locale/developer_portal.pot 2015-05-11 20:00:59 +0000 | |||
468 | +++ locale/developer_portal.pot 2015-08-11 12:06:30 +0000 | |||
469 | @@ -8,7 +8,7 @@ | |||
470 | 8 | msgstr "" | 8 | msgstr "" |
471 | 9 | "Project-Id-Version: PACKAGE VERSION\n" | 9 | "Project-Id-Version: PACKAGE VERSION\n" |
472 | 10 | "Report-Msgid-Bugs-To: \n" | 10 | "Report-Msgid-Bugs-To: \n" |
474 | 11 | "POT-Creation-Date: 2015-04-24 09:17+0000\n" | 11 | "POT-Creation-Date: 2015-07-27 14:41+0000\n" |
475 | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
476 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
477 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
478 | @@ -21,6 +21,16 @@ | |||
479 | 21 | msgid "Raw HTML" | 21 | msgid "Raw HTML" |
480 | 22 | msgstr "" | 22 | msgstr "" |
481 | 23 | 23 | ||
482 | 24 | #: developer_portal/models.py:24 | ||
483 | 25 | msgid "Launchpad branch location, ie: lp:snappy/15.04" | ||
484 | 26 | msgstr "" | ||
485 | 27 | |||
486 | 28 | #: developer_portal/models.py:27 | ||
487 | 29 | msgid "" | ||
488 | 30 | "Path alias we want to use for the docs, ie \"snappy/guides/15.04\" or " | ||
489 | 31 | "\"snappy/guides/latest\", etc." | ||
490 | 32 | msgstr "" | ||
491 | 33 | |||
492 | 24 | #: developer_portal/settings.py:187 developer_portal/settings.py:195 | 34 | #: developer_portal/settings.py:187 developer_portal/settings.py:195 |
493 | 25 | msgid "English" | 35 | msgid "English" |
494 | 26 | msgstr "" | 36 | msgstr "" |
495 | 27 | 37 | ||
496 | === modified file 'requirements.txt' | |||
497 | --- requirements.txt 2015-02-26 15:53:30 +0000 | |||
498 | +++ requirements.txt 2015-08-11 12:06:30 +0000 | |||
499 | @@ -1,6 +1,8 @@ | |||
500 | 1 | Django==1.6.8 | 1 | Django==1.6.8 |
501 | 2 | Markdown==2.6.2 | ||
502 | 2 | South==1.0.1 | 3 | South==1.0.1 |
503 | 3 | Pillow==2.6.1 | 4 | Pillow==2.6.1 |
504 | 5 | beautifulsoup4==4.4.0 | ||
505 | 4 | cmsplugin-zinnia==0.6 | 6 | cmsplugin-zinnia==0.6 |
506 | 5 | dj-database-url==0.3.0 | 7 | dj-database-url==0.3.0 |
507 | 6 | django-admin-enhancer==0.1.3.1 | 8 | django-admin-enhancer==0.1.3.1 |
This is still WIP.