Merge lp:~dholbach/developer-ubuntu-com/1471160 into lp:developer-ubuntu-com
- 1471160
- Merge into stable
Proposed by
Daniel Holbach
Status: | Rejected |
---|---|
Rejected by: | Daniel Holbach |
Proposed branch: | lp:~dholbach/developer-ubuntu-com/1471160 |
Merge into: | lp:developer-ubuntu-com |
Diff against target: |
341 lines (+293/-0) 5 files modified
developer_portal/admin.py (+7/-0) developer_portal/management/commands/import-snappy-branches.py (+214/-0) developer_portal/migrations/0003_add_snappy_docs_branches.py (+60/-0) developer_portal/models.py (+10/-0) requirements.txt (+2/-0) |
To merge this branch: | bzr merge lp:~dholbach/developer-ubuntu-com/1471160 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu App Developer site developers | Pending | ||
Review via email: mp+264673@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote : | # |
- 133. By Daniel Holbach
-
fix BeautifulSoup initialisation warning
- 134. By Daniel Holbach
-
generate cms path from imported snappy file, add comment
- 135. By Daniel Holbach
-
fix admin view of SnappyDocsBranch
- 136. By Daniel Holbach
- 137. By Daniel Holbach
-
add FIXME entries
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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-07-16 13:46:03 +0000 | |||
4 | @@ -3,6 +3,8 @@ | |||
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 SnappyDocsBranch | ||
9 | 7 | |||
10 | 6 | __all__ = ( | 8 | __all__ = ( |
11 | 7 | ) | 9 | ) |
12 | 8 | 10 | ||
13 | @@ -22,3 +24,8 @@ | |||
14 | 22 | 24 | ||
15 | 23 | admin.site.register(Version, VersionAdmin) | 25 | admin.site.register(Version, VersionAdmin) |
16 | 24 | 26 | ||
17 | 27 | class SnappyDocsBranchAdmin(admin.ModelAdmin): | ||
18 | 28 | list_display = ('branch_origin', 'path_alias') | ||
19 | 29 | list_filter = ('branch_origin', 'path_alias') | ||
20 | 30 | |||
21 | 31 | admin.site.register(SnappyDocsBranch, SnappyDocsBranchAdmin) | ||
22 | 25 | 32 | ||
23 | === added file 'developer_portal/management/commands/import-snappy-branches.py' | |||
24 | --- developer_portal/management/commands/import-snappy-branches.py 1970-01-01 00:00:00 +0000 | |||
25 | +++ developer_portal/management/commands/import-snappy-branches.py 2015-07-16 13:46:03 +0000 | |||
26 | @@ -0,0 +1,214 @@ | |||
27 | 1 | from django.core.management.base import NoArgsCommand | ||
28 | 2 | |||
29 | 3 | from bs4 import BeautifulSoup | ||
30 | 4 | import codecs | ||
31 | 5 | import glob | ||
32 | 6 | import logging | ||
33 | 7 | import markdown | ||
34 | 8 | import os | ||
35 | 9 | import re | ||
36 | 10 | import shutil | ||
37 | 11 | import subprocess | ||
38 | 12 | import tempfile | ||
39 | 13 | |||
40 | 14 | from developer_portal.models import SnappyDocsBranch | ||
41 | 15 | |||
42 | 16 | RELEASE_PAGES = {} | ||
43 | 17 | |||
44 | 18 | |||
45 | 19 | class MarkdownFile(): | ||
46 | 20 | html = None | ||
47 | 21 | cms_path = '' | ||
48 | 22 | |||
49 | 23 | def __init__(self, fn): | ||
50 | 24 | self.fn = fn | ||
51 | 25 | self.slug = slugify(self.fn) | ||
52 | 26 | with codecs.open(self.fn, 'r', encoding='utf-8') as f: | ||
53 | 27 | self.html = markdown.markdown(f.read(), output_format="html5") | ||
54 | 28 | self.release_alias = self._get_release_alias() | ||
55 | 29 | self.title = self._read_title() | ||
56 | 30 | self._remove_body_and_html_tags() | ||
57 | 31 | self._use_developer_site_style() | ||
58 | 32 | |||
59 | 33 | def _get_release_alias(self): | ||
60 | 34 | alias = re.findall(r'/tmp/tmp\S+?/(\S+?)/docs/\S+?', self.fn) | ||
61 | 35 | return alias[0] | ||
62 | 36 | |||
63 | 37 | def _read_title(self): | ||
64 | 38 | soup = BeautifulSoup(self.html, 'html5lib') | ||
65 | 39 | if soup.title: | ||
66 | 40 | return soup.title.text | ||
67 | 41 | if soup.h1: | ||
68 | 42 | return soup.h1.text | ||
69 | 43 | return slugify(self.fn).replace('-', ' ').title() | ||
70 | 44 | |||
71 | 45 | def _remove_body_and_html_tags(self): | ||
72 | 46 | self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, | ||
73 | 47 | flags=re.MULTILINE) | ||
74 | 48 | self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, | ||
75 | 49 | flags=re.MULTILINE) | ||
76 | 50 | |||
77 | 51 | def _use_developer_site_style(self): | ||
78 | 52 | # Make sure the reader knows which documentation she is browsing | ||
79 | 53 | if self.release_alias != "current": | ||
80 | 54 | begin = (u"<div class=\"row no-border\">\n" | ||
81 | 55 | "<div class=\"box pull-three three-col\">" | ||
82 | 56 | "<p>You are browsing the Snappy <code>%s</code> " | ||
83 | 57 | "documentation.</p>" | ||
84 | 58 | "<p><a href=\"/snappy/guides/current/%s\">" | ||
85 | 59 | "Back to the latest stable release ›" | ||
86 | 60 | "</a></p></div>\n" | ||
87 | 61 | "<div class=\"eight-col\">\n") % (self.release_alias, | ||
88 | 62 | self.slug, ) | ||
89 | 63 | else: | ||
90 | 64 | begin = (u"<div class=\"row no-border\">" | ||
91 | 65 | "\n<div class=\"eight-col\">\n") | ||
92 | 66 | end = u"</div>\n</div>" | ||
93 | 67 | self.html = begin + self.html + end | ||
94 | 68 | self.html = self.html.replace( | ||
95 | 69 | "<pre><code>", | ||
96 | 70 | "</div><div class=\"twelve-col\"><pre><code>") | ||
97 | 71 | self.html = self.html.replace( | ||
98 | 72 | "</code></pre>", | ||
99 | 73 | "</code></pre></div><div class=\"eight-col\">") | ||
100 | 74 | |||
101 | 75 | def replace_links(self, titles): | ||
102 | 76 | for title in titles: | ||
103 | 77 | url = u"/snappy/guides/%s/%s" % ( | ||
104 | 78 | self.release_alias, slugify(title)) | ||
105 | 79 | link = u"<a href=\"%s\">%s</a>" % (url, titles[title]) | ||
106 | 80 | self.html = self.html.replace(os.path.basename(title), link) | ||
107 | 81 | |||
108 | 82 | def publish(self): | ||
109 | 83 | '''Publishes pages in their branch alias namespace.''' | ||
110 | 84 | from cms.api import create_page, add_plugin | ||
111 | 85 | |||
112 | 86 | page_title = self.title | ||
113 | 87 | |||
114 | 88 | if self.release_alias == "current": | ||
115 | 89 | # Add a guides/<page> redirect to guides/current/<page> | ||
116 | 90 | page = create_page( | ||
117 | 91 | self.title, "default.html", "en", | ||
118 | 92 | slug=self.slug, parent=RELEASE_PAGES['guides_page'], | ||
119 | 93 | in_navigation=True, position="last-child", | ||
120 | 94 | redirect="/snappy/guides/current/%s" % (self.slug)) | ||
121 | 95 | page.publish('en') | ||
122 | 96 | else: | ||
123 | 97 | page_title += " (%s)" % (self.release_alias,) | ||
124 | 98 | |||
125 | 99 | page = create_page( | ||
126 | 100 | page_title, "default.html", "en", slug=self.slug, | ||
127 | 101 | menu_title=self.title, parent=RELEASE_PAGES[self.release_alias], | ||
128 | 102 | in_navigation=True, position="last-child") | ||
129 | 103 | placeholder = page.placeholders.get() | ||
130 | 104 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=self.html) | ||
131 | 105 | page.publish('en') | ||
132 | 106 | |||
133 | 107 | |||
134 | 108 | def slugify(filename): | ||
135 | 109 | return os.path.basename(filename).replace('.md', '') | ||
136 | 110 | |||
137 | 111 | |||
138 | 112 | def get_branch_from_lp(origin, alias): | ||
139 | 113 | return subprocess.call([ | ||
140 | 114 | 'bzr', 'checkout', '--lightweight', origin, alias]) | ||
141 | 115 | |||
142 | 116 | |||
143 | 117 | class LocalBranch(): | ||
144 | 118 | titles = {} | ||
145 | 119 | |||
146 | 120 | def __init__(self, dirname): | ||
147 | 121 | self.dirname = dirname | ||
148 | 122 | self.docs_path = os.path.join(self.dirname, 'docs') | ||
149 | 123 | self.doc_fns = glob.glob(self.docs_path+'/*.md') | ||
150 | 124 | self.md_files = [] | ||
151 | 125 | |||
152 | 126 | def import_markdown(self): | ||
153 | 127 | for doc_fn in self.doc_fns: | ||
154 | 128 | md_file = MarkdownFile(doc_fn) | ||
155 | 129 | self.md_files += [md_file] | ||
156 | 130 | self.titles[md_file.fn] = md_file.title | ||
157 | 131 | for md_file in self.md_files: | ||
158 | 132 | md_file.replace_links(self.titles) | ||
159 | 133 | md_file.publish() | ||
160 | 134 | |||
161 | 135 | |||
162 | 136 | # FIXME: | ||
163 | 137 | # - we retrieve the old article somehow | ||
164 | 138 | # - then find the Raw HTML plugin and | ||
165 | 139 | # - replace the html in there | ||
166 | 140 | # - also: remove pages we don't need anymore | ||
167 | 141 | # - add new ones | ||
168 | 142 | # - make sure we can do that for different sets of docs with different pages | ||
169 | 143 | # | ||
170 | 144 | def remove_old_pages(): | ||
171 | 145 | '''Removes all pages in snappy/guides, created by the importer.''' | ||
172 | 146 | from cms.models import Title, Page | ||
173 | 147 | |||
174 | 148 | pages_to_remove = [] | ||
175 | 149 | for g in Title.objects.select_related('page__id').filter( | ||
176 | 150 | path__regex="snappy/guides/.*"): | ||
177 | 151 | pages_to_remove.append(g.page.id) | ||
178 | 152 | Page.objects.filter(id__in=pages_to_remove, created_by="script").delete() | ||
179 | 153 | |||
180 | 154 | |||
181 | 155 | def refresh_landing_page(release_alias): | ||
182 | 156 | '''Creates a branch page at snappy/guides/<branch alias>.''' | ||
183 | 157 | from cms.api import create_page | ||
184 | 158 | from cms.models import Title | ||
185 | 159 | |||
186 | 160 | guides_page = Title.objects.filter( | ||
187 | 161 | path="snappy/guides", published=True, | ||
188 | 162 | language="en", publisher_is_draft=True)[0] | ||
189 | 163 | RELEASE_PAGES['guides_page'] = guides_page.page | ||
190 | 164 | |||
191 | 165 | if release_alias == "current": | ||
192 | 166 | redirect = "/snappy/guides" | ||
193 | 167 | else: | ||
194 | 168 | redirect = None | ||
195 | 169 | new_release_page = create_page( | ||
196 | 170 | release_alias, "default.html", "en", slug=release_alias, | ||
197 | 171 | parent=RELEASE_PAGES['guides_page'], in_navigation=False, | ||
198 | 172 | position="last-child", redirect=redirect) | ||
199 | 173 | # FIXME Page needs content | ||
200 | 174 | # placeholder = new_release_page.placeholders.get() | ||
201 | 175 | # add_plugin(placeholder, 'RawHtmlPlugin', 'en', body="<html goes here>") | ||
202 | 176 | new_release_page.publish('en') | ||
203 | 177 | RELEASE_PAGES[release_alias] = new_release_page | ||
204 | 178 | |||
205 | 179 | |||
206 | 180 | def import_branches(): | ||
207 | 181 | if not SnappyDocsBranch.objects.count(): | ||
208 | 182 | logging.error('No Snappy branches registered in the ' | ||
209 | 183 | 'SnappyDocsBranch table yet.') | ||
210 | 184 | return | ||
211 | 185 | # FIXME: Do the removal part last. Else we might end up in situations | ||
212 | 186 | # where some code breaks and we stay in a state without articles. | ||
213 | 187 | remove_old_pages() | ||
214 | 188 | tempdir = tempfile.mkdtemp() | ||
215 | 189 | pwd = os.getcwd() | ||
216 | 190 | os.chdir(tempdir) | ||
217 | 191 | for branch in SnappyDocsBranch.objects.all(): | ||
218 | 192 | if get_branch_from_lp(branch.branch_origin, branch.path_alias) != 0: | ||
219 | 193 | logging.error( | ||
220 | 194 | 'Could not check out branch "%s".' % branch.branch_origin) | ||
221 | 195 | shutil.rmtree(os.path.join(tempdir, branch.path_alias)) | ||
222 | 196 | break | ||
223 | 197 | refresh_landing_page(branch.path_alias) | ||
224 | 198 | os.chdir(pwd) | ||
225 | 199 | for local_branch in [a for a in glob.glob(tempdir+'/*') | ||
226 | 200 | if os.path.isdir(a)]: | ||
227 | 201 | branch = LocalBranch(local_branch) | ||
228 | 202 | branch.import_markdown() | ||
229 | 203 | shutil.rmtree(tempdir) | ||
230 | 204 | |||
231 | 205 | |||
232 | 206 | class Command(NoArgsCommand): | ||
233 | 207 | help = "Import Snappy branches for documentation." | ||
234 | 208 | |||
235 | 209 | def handle_noargs(self, **options): | ||
236 | 210 | logging.basicConfig( | ||
237 | 211 | level=logging.DEBUG, | ||
238 | 212 | format='%(asctime)s %(levelname)-8s %(message)s', | ||
239 | 213 | datefmt='%F %T') | ||
240 | 214 | import_branches() | ||
241 | 0 | 215 | ||
242 | === added file 'developer_portal/migrations/0003_add_snappy_docs_branches.py' | |||
243 | --- developer_portal/migrations/0003_add_snappy_docs_branches.py 1970-01-01 00:00:00 +0000 | |||
244 | +++ developer_portal/migrations/0003_add_snappy_docs_branches.py 2015-07-16 13:46:03 +0000 | |||
245 | @@ -0,0 +1,60 @@ | |||
246 | 1 | # -*- coding: utf-8 -*- | ||
247 | 2 | from south.utils import datetime_utils as datetime | ||
248 | 3 | from south.db import db | ||
249 | 4 | from south.v2 import SchemaMigration | ||
250 | 5 | from django.db import models | ||
251 | 6 | |||
252 | 7 | |||
253 | 8 | class Migration(SchemaMigration): | ||
254 | 9 | |||
255 | 10 | def forwards(self, orm): | ||
256 | 11 | # Adding model 'SnappyDocsBranch' | ||
257 | 12 | db.create_table(u'developer_portal_snappydocsbranch', ( | ||
258 | 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
259 | 14 | ('branch_origin', self.gf('django.db.models.fields.CharField')(max_length=200)), | ||
260 | 15 | ('path_alias', self.gf('django.db.models.fields.CharField')(max_length=20)), | ||
261 | 16 | )) | ||
262 | 17 | db.send_create_signal(u'developer_portal', ['SnappyDocsBranch']) | ||
263 | 18 | |||
264 | 19 | |||
265 | 20 | def backwards(self, orm): | ||
266 | 21 | # Deleting model 'SnappyDocsBranch' | ||
267 | 22 | db.delete_table(u'developer_portal_snappydocsbranch') | ||
268 | 23 | |||
269 | 24 | |||
270 | 25 | models = { | ||
271 | 26 | 'cms.cmsplugin': { | ||
272 | 27 | 'Meta': {'object_name': 'CMSPlugin'}, | ||
273 | 28 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
274 | 29 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
275 | 30 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
276 | 31 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
277 | 32 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
278 | 33 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
279 | 34 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), | ||
280 | 35 | 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), | ||
281 | 36 | 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), | ||
282 | 37 | 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
283 | 38 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
284 | 39 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) | ||
285 | 40 | }, | ||
286 | 41 | 'cms.placeholder': { | ||
287 | 42 | 'Meta': {'object_name': 'Placeholder'}, | ||
288 | 43 | 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), | ||
289 | 44 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
290 | 45 | 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) | ||
291 | 46 | }, | ||
292 | 47 | u'developer_portal.rawhtml': { | ||
293 | 48 | 'Meta': {'object_name': 'RawHtml'}, | ||
294 | 49 | 'body': ('django.db.models.fields.TextField', [], {}), | ||
295 | 50 | u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) | ||
296 | 51 | }, | ||
297 | 52 | u'developer_portal.snappydocsbranch': { | ||
298 | 53 | 'Meta': {'object_name': 'SnappyDocsBranch'}, | ||
299 | 54 | 'branch_origin': ('django.db.models.fields.CharField', [], {'max_length': '200'}), | ||
300 | 55 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
301 | 56 | 'path_alias': ('django.db.models.fields.CharField', [], {'max_length': '20'}) | ||
302 | 57 | } | ||
303 | 58 | } | ||
304 | 59 | |||
305 | 60 | complete_apps = ['developer_portal'] | ||
306 | 0 | \ No newline at end of file | 61 | \ No newline at end of file |
307 | 1 | 62 | ||
308 | === modified file 'developer_portal/models.py' | |||
309 | --- developer_portal/models.py 2015-01-19 16:29:47 +0000 | |||
310 | +++ developer_portal/models.py 2015-07-16 13:46:03 +0000 | |||
311 | @@ -1,3 +1,5 @@ | |||
312 | 1 | from django.db import models | ||
313 | 2 | from django.utils.translation import ugettext_lazy as _ | ||
314 | 1 | 3 | ||
315 | 2 | from cms.models import CMSPlugin | 4 | from cms.models import CMSPlugin |
316 | 3 | from djangocms_text_ckeditor.html import extract_images | 5 | from djangocms_text_ckeditor.html import extract_images |
317 | @@ -13,3 +15,11 @@ | |||
318 | 13 | body = extract_images(body, self) | 15 | body = extract_images(body, self) |
319 | 14 | self.body = body | 16 | self.body = body |
320 | 15 | AbstractText.save(self, *args, **kwargs) | 17 | AbstractText.save(self, *args, **kwargs) |
321 | 18 | |||
322 | 19 | |||
323 | 20 | class SnappyDocsBranch(models.Model): | ||
324 | 21 | branch_origin = models.CharField(max_length=200, | ||
325 | 22 | help_text=_('Launchpad branch location, ie: lp:snappy/15.04')) | ||
326 | 23 | path_alias = models.CharField(max_length=20, | ||
327 | 24 | help_text=_('Path alias we want to use for the docs, ' | ||
328 | 25 | 'ie "15.04" or "latest", etc.')) | ||
329 | 16 | 26 | ||
330 | === modified file 'requirements.txt' | |||
331 | --- requirements.txt 2015-02-26 15:53:30 +0000 | |||
332 | +++ requirements.txt 2015-07-16 13:46:03 +0000 | |||
333 | @@ -1,6 +1,8 @@ | |||
334 | 1 | Django==1.6.8 | 1 | Django==1.6.8 |
335 | 2 | Markdown==2.6.2 | ||
336 | 2 | South==1.0.1 | 3 | South==1.0.1 |
337 | 3 | Pillow==2.6.1 | 4 | Pillow==2.6.1 |
338 | 5 | beautifulsoup4==4.4.0 | ||
339 | 4 | cmsplugin-zinnia==0.6 | 6 | cmsplugin-zinnia==0.6 |
340 | 5 | dj-database-url==0.3.0 | 7 | dj-database-url==0.3.0 |
341 | 6 | django-admin-enhancer==0.1.3.1 | 8 | django-admin-enhancer==0.1.3.1 |
First cut of importing snappy docs into the developer site.