Merge lp:~dholbach/developer-ubuntu-com/index_page into lp:~developer-ubuntu-com-dev/developer-ubuntu-com/snappy-docs-import

Proposed by Daniel Holbach on 2015-08-10
Status: Merged
Merged at revision: 158
Proposed branch: lp:~dholbach/developer-ubuntu-com/index_page
Merge into: lp:~developer-ubuntu-com-dev/developer-ubuntu-com/snappy-docs-import
Diff against target: 337 lines (+118/-78)
4 files modified
developer_portal/admin.py (+1/-1)
developer_portal/management/commands/import-snappy-branches.py (+108/-76)
developer_portal/migrations/0003_add_external_docs_branches.py (+2/-0)
developer_portal/models.py (+7/-1)
To merge this branch: bzr merge lp:~dholbach/developer-ubuntu-com/index_page
Reviewer Review Type Date Requested Status
David Callé 2015-08-10 Approve on 2015-08-10
Review via email: mp+267533@code.launchpad.net

Commit Message

- Add functionality to specify an (optional) index doc.
- Stop recreating articles entries in the database for every run.

To post a comment you must log in.
Daniel Holbach (dholbach) wrote :

Try with these external docs branches added:

  LP Branch Docs namespace Index doc
  -----------------------------------------------------
  lp:snappy snappy/guides/devel -
  lp:snappy/15.04 snappy/guides/current -
  lp:snapcraft snappy/snapcraft intro.md

David Callé (davidc3) wrote :

++1

review: Approve

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 2015-07-28 09:13:13 +0000
3+++ developer_portal/admin.py 2015-08-10 15:40:56 +0000
4@@ -12,7 +12,7 @@
5
6 def manual_snappy_doc_import(modeladmin, request, queryset):
7 for branch in queryset:
8- call_command('import-snappy-branches', branch.path_alias)
9+ call_command('import-snappy-branches', branch.docs_namespace)
10 manual_snappy_doc_import.short_description = "Import selected branches"
11
12
13
14=== modified file 'developer_portal/management/commands/import-snappy-branches.py'
15--- developer_portal/management/commands/import-snappy-branches.py 2015-07-31 12:58:32 +0000
16+++ developer_portal/management/commands/import-snappy-branches.py 2015-08-10 15:40:56 +0000
17@@ -1,7 +1,8 @@
18 from django.core.management.base import BaseCommand
19
20 from cms.api import create_page, add_plugin
21-from cms.models import Title
22+from cms.models import Page, Title
23+from cms.utils import page_resolver
24
25 from bs4 import BeautifulSoup
26 import codecs
27@@ -12,20 +13,25 @@
28 import re
29 import shutil
30 import subprocess
31+import sys
32 import tempfile
33
34 from developer_portal.models import ExternalDocsBranch
35
36 DOCS_DIRNAME = 'docs'
37-RELEASE_PAGES = {}
38
39
40 class MarkdownFile:
41 html = None
42
43- def __init__(self, fn):
44+ def __init__(self, fn, docs_namespace, slug_override=None):
45 self.fn = fn
46- self.slug = slugify(self.fn)
47+ self.docs_namespace = docs_namespace
48+ if slug_override:
49+ self.slug = slug_override
50+ else:
51+ self.slug = slugify(self.fn)
52+ self.full_url = os.path.join(self.docs_namespace, self.slug)
53 with codecs.open(self.fn, 'r', encoding='utf-8') as f:
54 self.html = markdown.markdown(
55 f.read(),
56@@ -76,20 +82,15 @@
57
58 def publish(self):
59 '''Publishes pages in their branch alias namespace.'''
60- from cms.api import create_page, add_plugin
61-
62- page = create_page(
63- self.title, "default.html", "en", slug=self.slug,
64- menu_title=self.title, parent=RELEASE_PAGES[self.release_alias],
65- in_navigation=True, position="last-child")
66- placeholder = page.placeholders.get()
67- add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=self.html)
68+ page = get_or_create_page(
69+ self.title, full_url=self.full_url, menu_title=self.title,
70+ html=self.html)
71 page.publish('en')
72
73
74 class SnappyMarkdownFile(MarkdownFile):
75- def __init__(self, fn):
76- MarkdownFile.__init__(self, fn)
77+ def __init__(self, fn, docs_namespace):
78+ MarkdownFile.__init__(self, fn, docs_namespace)
79 self._make_snappy_mods()
80
81 def _make_snappy_mods(self):
82@@ -111,10 +112,8 @@
83 def publish(self):
84 if self.release_alias == "current":
85 # Add a guides/<page> redirect to guides/current/<page>
86- page = create_page(
87- self.title, "default.html", "en",
88- slug=self.slug, parent=RELEASE_PAGES['guides_page'],
89- in_navigation=True, position="last-child",
90+ page = get_or_create_page(
91+ self.title, full_url=self.full_url.replace('/current', ''),
92 redirect="/snappy/guides/current/%s" % (self.slug))
93 page.publish('en')
94 else:
95@@ -142,90 +141,126 @@
96 self.external_branch = external_branch
97 self.docs_namespace = self.external_branch.docs_namespace
98 self.release_alias = os.path.basename(self.docs_namespace)
99- self.overview_page_title = self.release_alias.title()
100+ self.index_doc_title = self.release_alias.title()
101+ self.index_doc = self.external_branch.index_doc
102 self.markdown_class = MarkdownFile
103
104 def import_markdown(self):
105 for doc_fn in self.doc_fns:
106- md_file = self.markdown_class(doc_fn)
107+ if self.index_doc and os.path.basename(doc_fn) == self.index_doc:
108+ md_file = self.markdown_class(
109+ doc_fn,
110+ os.path.dirname(self.docs_namespace),
111+ slug_override=os.path.basename(self.docs_namespace))
112+ else:
113+ md_file = self.markdown_class(doc_fn, self.docs_namespace)
114 self.md_files += [md_file]
115 self.titles[md_file.fn] = md_file.title
116+ if not self.index_doc:
117+ self._create_fake_index_doc()
118+
119+ def remove_old_pages(self):
120+ imported_page_urls = set([md_file.full_url
121+ for md_file in self.md_files])
122+ index_doc = page_resolver.get_page_queryset_from_path(
123+ self.docs_namespace)[0]
124+ # All pages in this namespace currently in the database
125+ db_pages = index_doc.get_descendants().all()
126+ delete_pages = []
127+ for db_page in db_pages:
128+ still_relevant = False
129+ for url in imported_page_urls:
130+ if url in db_page.get_absolute_url():
131+ still_relevant = True
132+ break
133+ # At this point we know that there's no match and the page
134+ # can be deleted.
135+ if not still_relevant:
136+ delete_pages += [db_page.id]
137+ # Only remove pages created by a script!
138+ Page.objects.filter(id__in=delete_pages, created_by="script").delete()
139
140 def publish(self):
141 for md_file in self.md_files:
142 md_file.publish()
143
144- def refresh_landing_page(self):
145- '''Creates a branch page at snappy/guides/<branch alias>.'''
146-
147- guides_page = Title.objects.filter(
148- path="snappy/guides", published=True,
149- language="en", publisher_is_draft=True)[0]
150- RELEASE_PAGES['guides_page'] = guides_page.page
151+ def _create_fake_index_doc(self):
152+ '''Creates a fake index page at the top of the branches
153+ docs namespace.'''
154
155 if self.docs_namespace == "current":
156 redirect = "/snappy/guides"
157 else:
158 redirect = None
159- new_release_page = create_page(
160- self.overview_page_title, "default.html", "en",
161- slug=self.docs_namespace, parent=RELEASE_PAGES['guides_page'],
162- in_navigation=False, position="last-child", redirect=redirect)
163- placeholder = new_release_page.placeholders.get()
164- landing = (u"<div class=\"row\"><div class=\"eight-col\">\n"
165- "<p>This section contains documentation for the "
166- "<code>%s</code> Snappy branch.</p>"
167- "<p>Auto-imported from <a "
168- "href=\"https://code.launchpad.net/snappy\">%s</a>.</p>\n"
169- "</div></div>") % (self.docs_namespace,
170- self.external_branch.lp_origin)
171- add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=landing)
172+
173+ in_navigation = False
174+ menu_title = None
175+ landing = (
176+ u"<div class=\"row\"><div class=\"eight-col\">\n"
177+ "<p>This section contains documentation for the "
178+ "<code>%s</code> Snappy branch.</p>"
179+ "<p>Auto-imported from <a "
180+ "href=\"https://code.launchpad.net/snappy\">%s</a>.</p>\n"
181+ "</div></div>") % (self.docs_namespace,
182+ self.external_branch.lp_origin)
183+ new_release_page = get_or_create_page(
184+ self.index_doc_title, full_url=self.docs_namespace,
185+ in_navigation=in_navigation, redirect=redirect, html=landing,
186+ menu_title=menu_title)
187 new_release_page.publish('en')
188- RELEASE_PAGES[self.release_alias] = new_release_page
189+
190
191 class SnappyLocalBranch(LocalBranch):
192 def __init__(self, dirname, external_branch):
193 LocalBranch.__init__(self, dirname, external_branch)
194 self.markdown_class = SnappyMarkdownFile
195- self.overview_page_title = 'Snappy'
196+ self.index_doc_title = 'Snappy'
197 if self.release_alias != 'current':
198- self.overview_page_title += ' (%s)' % self.release_alias
199+ self.index_doc_title += ' (%s)' % self.release_alias
200
201 def import_markdown(self):
202 LocalBranch.import_markdown(self)
203 for md_file in self.md_files:
204 md_file.replace_links(self.titles)
205
206-def remove_old_pages(selection):
207- # FIXME:
208- # - we retrieve the old article somehow
209- # - then find the Raw HTML plugin and
210- # - replace the html in there
211- # - also: remove pages we don't need anymore
212- # - add new ones
213- # - make sure we can do that for different sets of docs with
214- # different pages
215-
216- '''Removes all pages in snappy/guides, created by the importer.'''
217- from cms.models import Title, Page
218-
219- pages_to_remove = []
220- aliases = "|".join(
221- ExternalDocsBranch.objects.values_list('docs_namespace', flat=True))
222- if selection == "current":
223- # Select all pages that are not in other aliases paths, this allows
224- # removing existing redirections to current and current itself
225- regex = "snappy/guides/(?!%s|.*/.*)|snappy/guides/current.*" % \
226- (aliases)
227+
228+def get_or_create_page(title, full_url, menu_title=None,
229+ in_navigation=True, redirect=None, html=None):
230+ # First check if pages already exist.
231+ pages = Title.objects.select_related('page').filter(path__regex=full_url)
232+ if pages:
233+ page = pages[0].page
234+ page.title = title
235+ page.publisher_is_draft = True
236+ page.menu_title = menu_title
237+ page.in_navigation = in_navigation
238+ page.redirect = redirect
239+ if html:
240+ # We create the page, so we know there's just one placeholder
241+ placeholder = page.placeholders.all()[0]
242+ if placeholder.get_plugins():
243+ plugin = placeholder.get_plugins()[0].get_plugin_instance()[0]
244+ plugin.body = html
245+ plugin.save()
246+ else:
247+ add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)
248 else:
249- # Select pages that are in the selected alias path
250- regex = "snappy/guides/%s.*" % (selection,)
251+ parent_pages = Title.objects.select_related('page').filter(
252+ path__regex=os.path.dirname(full_url))
253+ if not parent_pages:
254+ print('Parent %s not found.' % os.path.dirname(full_url))
255+ sys.exit(1)
256+ parent = parent_pages[0].page
257
258- for g in Title.objects.select_related('page__id').filter(
259- path__regex=regex):
260- pages_to_remove.append(g.page.id)
261- # Only remove pages created by a script!
262- Page.objects.filter(id__in=pages_to_remove, created_by="script").delete()
263+ slug = os.path.basename(full_url)
264+ page = create_page(
265+ title, "default.html", "en", slug=slug, parent=parent,
266+ menu_title=menu_title, in_navigation=in_navigation,
267+ position="last-child", redirect=redirect)
268+ if html:
269+ placeholder = page.placeholders.get()
270+ add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)
271+ return page
272
273
274 def import_branches(selection):
275@@ -233,9 +268,6 @@
276 logging.error('No branches registered in the '
277 'ExternalDocsBranch table yet.')
278 return
279- # FIXME: Do the removal part last. Else we might end up in situations
280- # where some code breaks and we stay in a state without articles.
281- remove_old_pages(selection)
282 tempdir = tempfile.mkdtemp()
283 for branch in ExternalDocsBranch.objects.filter(
284 docs_namespace__regex=selection):
285@@ -250,14 +282,14 @@
286 local_branch = SnappyLocalBranch(checkout_location, branch)
287 else:
288 local_branch = LocalBranch(checkout_location, branch)
289- local_branch.refresh_landing_page()
290 local_branch.import_markdown()
291 local_branch.publish()
292+ local_branch.remove_old_pages()
293 shutil.rmtree(tempdir)
294
295
296 class Command(BaseCommand):
297- help = "Import Snappy branches for documentation."
298+ help = "Import external branches for documentation."
299
300 def handle(*args, **options):
301 logging.basicConfig(
302
303=== modified file 'developer_portal/migrations/0003_add_external_docs_branches.py'
304--- developer_portal/migrations/0003_add_external_docs_branches.py 2015-07-27 14:46:14 +0000
305+++ developer_portal/migrations/0003_add_external_docs_branches.py 2015-08-10 15:40:56 +0000
306@@ -13,6 +13,7 @@
307 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
308 ('lp_origin', self.gf('django.db.models.fields.CharField')(max_length=200)),
309 ('docs_namespace', self.gf('django.db.models.fields.CharField')(max_length=120)),
310+ ('index_doc', self.gf('django.db.models.fields.CharField')(max_length=120, blank=True)),
311 ))
312 db.send_create_signal(u'developer_portal', ['ExternalDocsBranch'])
313
314@@ -48,6 +49,7 @@
315 'Meta': {'object_name': 'ExternalDocsBranch'},
316 'docs_namespace': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
317 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318+ 'index_doc': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
319 'lp_origin': ('django.db.models.fields.CharField', [], {'max_length': '200'})
320 },
321 u'developer_portal.rawhtml': {
322
323=== modified file 'developer_portal/models.py'
324--- developer_portal/models.py 2015-07-27 14:44:23 +0000
325+++ developer_portal/models.py 2015-08-10 15:40:56 +0000
326@@ -25,4 +25,10 @@
327 docs_namespace = models.CharField(
328 max_length=120,
329 help_text=_('Path alias we want to use for the docs, '
330- 'ie "snappy/guides/15.04" or "snappy/guides/latest", etc.'))
331+ 'ie "snappy/guides/15.04" or '
332+ '"snappy/guides/latest", etc.'))
333+ index_doc = models.CharField(
334+ max_length=120,
335+ help_text=_('File name of doc to be used as index document, '
336+ 'ie "intro.md"'),
337+ blank=True)

Subscribers

People subscribed via source and target branches