Merge lp:~dholbach/developer-ubuntu-com/rework-importer into lp:developer-ubuntu-com

Proposed by Daniel Holbach
Status: Superseded
Proposed branch: lp:~dholbach/developer-ubuntu-com/rework-importer
Merge into: lp:developer-ubuntu-com
Prerequisite: lp:~developer-ubuntu-com-dev/developer-ubuntu-com/1470715
Diff against target: 886 lines (+636/-75) (has conflicts)
12 files modified
TODO (+7/-0)
developer_portal/admin.py (+22/-4)
developer_portal/management/commands/import-external-docs-branches.py (+42/-56)
developer_portal/management/importer/article.py (+126/-0)
developer_portal/management/importer/local_branch.py (+132/-0)
developer_portal/management/importer/publish.py (+60/-0)
developer_portal/management/importer/source.py (+51/-0)
developer_portal/migrations/0002_rework_importer.py (+59/-0)
developer_portal/migrations/0003_track_imported_articles.py (+24/-0)
developer_portal/models.py (+46/-15)
developer_portal/settings.py (+20/-0)
requirements.txt (+47/-0)
Text conflict in developer_portal/admin.py
Text conflict in developer_portal/management/commands/import-external-docs-branches.py
Text conflict in developer_portal/settings.py
Text conflict in requirements.txt
To merge this branch: bzr merge lp:~dholbach/developer-ubuntu-com/rework-importer
Reviewer Review Type Date Requested Status
Ubuntu App Developer site developers Pending
Review via email: mp+277722@code.launchpad.net
To post a comment you must log in.
183. By Daniel Holbach

make admin interface for ExternalDocsBranch* usable

184. By Daniel Holbach

first cut of importer reorg
 - break up into individual files
 - simplify API a bit, make it more easily extendable and testable
 - next steps: implement removal of old articles, fix links

185. By Daniel Holbach

we haven't implemented globs yet

186. By Daniel Holbach

merged from trunk

187. By Daniel Holbach

add TODO

188. By Daniel Holbach

merge trunk

189. By Daniel Holbach

merged lp:~dholbach/developer-ubuntu-com/django-cms-update

190. By Daniel Holbach

some more package updates

191. By Daniel Holbach

revert reversion upgrade

192. By Daniel Holbach

update migations locations

193. By Daniel Holbach

rename branch_origin to origin, add branch_name and add functionatlity so users can specify which branch of a git branch to import form

194. By Daniel Holbach

make ExternalDocsBranch.__str__ more revealing

195. By Daniel Holbach

update SnappyLocalBranch c'tor to be inline with the added branch_name field

196. By Daniel Holbach

make sure we run 'checkout <branch>' in the right directory

197. By Daniel Holbach

update member name

198. By Daniel Holbach

fix imports, return published articles

199. By Daniel Holbach

update TODO

200. By Daniel Holbach

make pep8 happy, use .format()

201. By Daniel Holbach

allow us to track which pages were imported from which branch

202. By Daniel Holbach

make pep8 happy

203. By Daniel Holbach

first cut at tracking docs-branch/page in a separate table

204. By Daniel Holbach

fix check if directory-import would import already file-imported articles

205. By Daniel Holbach

safe datetime of last_import, fix removal condition of old articles

206. By Daniel Holbach

remove unused import

207. By Daniel Holbach

merge from lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3

208. By Daniel Holbach

add check if the last change was made by our script

209. By Daniel Holbach

merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3 again

210. By Daniel Holbach

make __str__ representations of ExternalDocsBranchImportDirective and ExternalDocsBranch more readable

211. By Daniel Holbach

fix syntax error

212. By Daniel Holbach

every branch gets its own tempdir

213. By Daniel Holbach

help with paths starting with '/'

214. By Daniel Holbach

every branch gets its own tempdir

215. By Daniel Holbach

add fenced_code markdown extension - only change links if they are in our url_map

216. By Daniel Holbach

allow github style markdown

217. By Daniel Holbach

remove url_map checks, they are useless

218. By Daniel Holbach

update TODO

219. By Daniel Holbach

support local repositories - useful for testing

220. By Daniel Holbach

add first tests

221. By Daniel Holbach

add another test

222. By Daniel Holbach

pep8 fix

223. By Daniel Holbach

rename tests to be clearer, rewrite tests, so we can reuse a repository we already checked out

224. By Daniel Holbach

rename local_branch to repo - makes more sense these days

225. By Daniel Holbach

add test for post_checkout_command, fix post_checkout_command logic by chdir()ing correctly, make pep8+pyflakes happy

226. By Daniel Holbach

make create_home_page return home, test page db activities

227. By Daniel Holbach

check if all imported articles have 'home' as parent

228. By Daniel Holbach

add comment, pep8

229. By Daniel Holbach

add function to reset a repo - only useful for tests, where we reuse the same repo to avoid redownloading, add test where only one file is imported and we check the article tree

230. By Daniel Holbach

filter on publisher_is_draft=True

231. By Daniel Holbach

clean up after the test

232. By Daniel Holbach

make index_doc_title available everywhere

233. By Daniel Holbach

add db_add_empty_page convenience function, add simple articletree test, add test for the import of snappy devel

234. By Daniel Holbach

make git quiet

235. By Daniel Holbach

add create_repo convenience function

236. By Daniel Holbach

use create_repo function, add assertions for SnappyRepo vs Repo instantiation

237. By Daniel Holbach

add SnappyTestRepo singleton for re-use, rename variables

238. By Daniel Holbach

add assertions if imported articles have the correct type - drop useless tempdir creation

239. By Daniel Holbach

function article.get was renamed to article.read ages ago...

240. By Daniel Holbach

add test for 'current' snappy import (with check for redirects)

241. By Daniel Holbach

remove superfluous .get() call, add assertion that fetching worked fine

242. By Daniel Holbach

move snappy import tests into their own class

243. By Daniel Holbach

reset snappy test repos at the beginning of the test too

244. By Daniel Holbach

add assertion if snappy repo produces snappy articles

245. By Daniel Holbach

add reset() SnappyRepo to as well, rename variables and function names to be clearer, produce index_doc first, then add other pages, then refer to them in index_doc - this makes sure the parent (index_doc) is in the db first

246. By Daniel Holbach

merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3 - resolve conflicts

247. By Daniel Holbach

<ojii|work> because publishing etc will change a pages PK
<ojii|work> Page().publish() creates a new copy of the page (and deletes the old one)

248. By Daniel Holbach

improve variable usage

249. By Daniel Holbach

introduce repo.pages to distinguish from .imported_articles

250. By Daniel Holbach

fix tests

251. By Daniel Holbach

check for both 'python-api' and 'script'

252. By Daniel Holbach

break up tests into individual files

253. By Daniel Holbach

update TODO

254. By Daniel Holbach

improve create_repo helper function

255. By Daniel Holbach

allow SourceCode._get_branch to use local docs (ie for testing)

256. By Daniel Holbach

first cut at testing local links

257. By Daniel Holbach

fix simple link rewrite functionality and test

258. By Daniel Holbach

move md_importer into its own app

259. By Daniel Holbach

add migration for md_importer

260. By Daniel Holbach

merge lp:~dholbach/developer-ubuntu-com/django-cms-update

261. By Daniel Holbach

make directory structure clearer

262. By Daniel Holbach

adapt to new name of management command

263. By Daniel Holbach

add more readable error message

264. By Daniel Holbach

add more local test data (imported just the docs from snappy's and snapcraft's git master branches)

265. By Daniel Holbach

WIP commit:
- move and update TODO file
- define some global values for the importer centrally
- don't sys.exit() when adding pages to the db
- add debug messages
- break _find_parent() into its own function
- mostly make use of local test data
- drop singleton, use separate test classes instead
- add more assertions in tests

266. By Daniel Holbach

remove debug prints, make Repo class variables actual members

267. By Daniel Holbach

- make pep8 happy, remove unnecessary imports
- use TestLocalBranchImport as base class for almost everything,
  reduces a lot of duplication

268. By Daniel Holbach

remove unnecessary code, fix publishing of pages in tests

269. By Daniel Holbach

fix test_link_rewrite by fixing the URL

270. By Daniel Holbach

make Article class variables actual members

271. By Daniel Holbach

add new test to check links in snapcraft import, move link checking function into TestLocalBranchImport

272. By Daniel Holbach

merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3

273. By Daniel Holbach

allow empty strings in import directives

274. By Daniel Holbach

update migration

275. By Daniel Holbach

check if articles were added below home

276. By Daniel Holbach

remove unnecessary call to set a page's publisher_is_draft bit

277. By Daniel Holbach

give article a .publish() method which gives back the page's public_object

278. By Daniel Holbach

rename home_page to root_page, use cms.test_utils.testcases.CMSTestCase, make sure we use public_object wherever possible

279. By Daniel Holbach

update tests accordingly

280. By Daniel Holbach

disregard anchors, make link checking more flexible (ie not only check for intro.md, but also things like docs/intro.md

281. By Daniel Holbach

make URL work, even if LANG is already part of it

282. By Daniel Holbach

links look like they're working now

283. By Daniel Holbach

add test with a broken link, add convenience function is_local_link, modify tests

284. By Daniel Holbach

import doc fix from https://bugs.launchpad.net/developer-ubuntu-com/+bug/1531200

285. By Daniel Holbach

bug fixed

286. By Daniel Holbach

stop import if local images are found, add tests

287. By Daniel Holbach

update TODO

288. By Daniel Holbach

add test to see if importing the same content twice results in the same number of pages

289. By Daniel Holbach

when replacing links, only update HTML if things actually change, only publish if page is dirty

290. By Daniel Holbach

only update page attributes if they actually change, only update the text plugin if the html actually changes, veryify with djangocms_text_ckeditor.html.clean_html, add a text plugin to a page, even if html is empty

291. By Daniel Holbach

add test to check if running an import twice will update the articles in question

292. By Daniel Holbach

update TODO

293. By Daniel Holbach

add misc tests - forgot to 'bzr add'

294. By Daniel Holbach

make update-mtemplate work again, simplify it

295. By Daniel Holbach

update .pot file

296. By Daniel Holbach

add actual page object to repo.pages

297. By Daniel Holbach

use UTC for ImportedArticle.last_import, simplify ImportedArticle cleanup

298. By Daniel Holbach

use repo instead of branch consistently

299. By Daniel Holbach

break out the process of importing a branch into its own module, add a first simple test for it

300. By Daniel Holbach

fix clean up of imported articles, add a test

301. By Daniel Holbach

update TODO

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'TODO'
--- TODO 1970-01-01 00:00:00 +0000
+++ TODO 2015-12-09 15:41:23 +0000
@@ -0,0 +1,7 @@
1- import pictures (LP: #1511677)
2- unpublished docs
3- add comment at top of RawHTML plugins, so users know not to edit them,
4 currently blocked on LP: #1523925
5- when deleting pages, check if last rev was made by python-api
6
7-
08
=== modified file 'developer_portal/admin.py'
--- developer_portal/admin.py 2015-12-04 11:49:10 +0000
+++ developer_portal/admin.py 2015-12-09 15:41:23 +0000
@@ -4,7 +4,10 @@
4from reversion.admin import VersionAdmin4from reversion.admin import VersionAdmin
55
6from cms.extensions import TitleExtensionAdmin6from cms.extensions import TitleExtensionAdmin
7from .models import ExternalDocsBranch, SEOExtension7from .models import (
8 ExternalDocsBranch, ExternalDocsBranchImportDirective,
9 ImportedArticle, SEOExtension
10)
8from django.core.management import call_command11from django.core.management import call_command
912
10__all__ = (13__all__ = (
@@ -13,7 +16,12 @@
1316
14def import_selected_external_docs_branches(modeladmin, request, queryset):17def import_selected_external_docs_branches(modeladmin, request, queryset):
15 for branch in queryset:18 for branch in queryset:
19<<<<<<< TREE
16 call_command('import-external-docs-branches', branch.docs_namespace)20 call_command('import-external-docs-branches', branch.docs_namespace)
21=======
22 branches.append(branch.origin)
23 call_command('import-external-docs-branches', *branches)
24>>>>>>> MERGE-SOURCE
17 import_selected_external_docs_branches.short_description = \25 import_selected_external_docs_branches.short_description = \
18 "Import selected branches"26 "Import selected branches"
1927
@@ -35,12 +43,22 @@
35admin.site.register(Version, VersionAdmin)43admin.site.register(Version, VersionAdmin)
3644
3745
46@admin.register(ExternalDocsBranch)
38class ExternalDocsBranchAdmin(admin.ModelAdmin):47class ExternalDocsBranchAdmin(admin.ModelAdmin):
39 list_display = ('lp_origin', 'docs_namespace')48 list_display = ('origin', 'post_checkout_command', 'branch_name',)
40 list_filter = ('lp_origin', 'docs_namespace')49 list_filter = ('origin', 'post_checkout_command', 'branch_name',)
41 actions = [import_selected_external_docs_branches]50 actions = [import_selected_external_docs_branches]
4251
43admin.site.register(ExternalDocsBranch, ExternalDocsBranchAdmin)52
53@admin.register(ExternalDocsBranchImportDirective)
54class ExternalDocsBranchImportDirectiveAdmin(admin.ModelAdmin):
55 pass
56
57
58@admin.register(ImportedArticle)
59class ImportedArticleAdmin(admin.ModelAdmin):
60 pass
61
4462
45class SEOExtensionAdmin(TitleExtensionAdmin):63class SEOExtensionAdmin(TitleExtensionAdmin):
46 pass64 pass
4765
=== modified file 'developer_portal/management/commands/import-external-docs-branches.py'
--- developer_portal/management/commands/import-external-docs-branches.py 2015-12-04 11:49:10 +0000
+++ developer_portal/management/commands/import-external-docs-branches.py 2015-12-09 15:41:23 +0000
@@ -1,23 +1,14 @@
1from django.core.management.base import BaseCommand1from django.core.management.base import BaseCommand
2from django.core.management import call_command2from django.core.management import call_command
3from django.db import transaction3
44from ..importer.local_branch import LocalBranch, SnappyLocalBranch
5from cms.api import create_page, add_plugin5
6from cms.models import Page, Title6import datetime
7from cms.utils import page_resolver
8
9from bs4 import BeautifulSoup
10import codecs
11import glob
12import logging7import logging
13import markdown
14import os
15import re
16import shutil8import shutil
17import subprocess
18import sys
19import tempfile9import tempfile
2010
11<<<<<<< TREE
21from developer_portal.models import ExternalDocsBranch12from developer_portal.models import ExternalDocsBranch
2213
23DOCS_DIRNAME = 'docs'14DOCS_DIRNAME = 'docs'
@@ -295,6 +286,13 @@
295 placeholder = page.placeholders.get()286 placeholder = page.placeholders.get()
296 add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)287 add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)
297 return page288 return page
289=======
290from developer_portal.models import (
291 ExternalDocsBranch,
292 ExternalDocsBranchImportDirective,
293 ImportedArticle,
294)
295>>>>>>> MERGE-SOURCE
298296
299297
300def import_branches(selection):298def import_branches(selection):
@@ -303,51 +301,39 @@
303 'ExternalDocsBranch table yet.')301 'ExternalDocsBranch table yet.')
304 return302 return
305 tempdir = tempfile.mkdtemp()303 tempdir = tempfile.mkdtemp()
306 db_actions = DBActions()
307 for branch in ExternalDocsBranch.objects.filter(304 for branch in ExternalDocsBranch.objects.filter(
308 docs_namespace__regex=selection):305 origin__regex=selection, active=True):
309 checkout_location = os.path.join(306 url = branch.origin
310 tempdir, os.path.basename(branch.docs_namespace))307 if url.startswith('lp:snappy') or \
311 sourcecode = SourceCode(branch.lp_origin, checkout_location)308 'snappy' in url.split(':')[1].split('.git')[0].split('/'):
312 if sourcecode.get() != 0:309 branch_class = SnappyLocalBranch
313 logging.error(310 else:
314 'Could not check out branch "%s".' % branch.lp_origin)311 branch_class = LocalBranch
315 if os.path.exists(checkout_location):312 local_branch = branch_class(tempdir, branch.origin, branch.branch_name,
316 shutil.rmtree(checkout_location)313 branch.post_checkout_command)
314 if local_branch.get() != 0:
317 break315 break
318 if branch.lp_origin.startswith('lp:snappy') or \316 for directive in ExternalDocsBranchImportDirective.objects.filter(
319 'snappy' in branch.lp_origin.split(':')[1].split('.git')[0].split('/'):317 external_docs_branch=branch):
320 local_branch = SnappyLocalBranch(checkout_location, branch,318 local_branch.add_directive(directive.import_from,
321 db_actions)319 directive.write_to)
322 else:320 local_branch.execute_import_directives()
323 local_branch = LocalBranch(checkout_location, branch, db_actions)321 imported_articles = local_branch.publish()
324 local_branch.import_markdown()322 for imported_article in imported_articles:
325 local_branch.publish()323 ImportedArticle.objects.get_or_create(
326 local_branch.remove_old_pages()324 branch=branch,
325 page=imported_article.page,
326 last_import=datetime.datetime.now())
327
328 # The import is done, now let's clean up.
329 for old_article in ImportedArticle.objects.filter(branch=branch):
330 if old_article.page not in [a.page for a in imported_articles]:
331 # XXX: check if last revision was made by 'python-api'?
332 old_article.page.delete()
327 shutil.rmtree(tempdir)333 shutil.rmtree(tempdir)
328 db_actions.run()334
329335 # https://stackoverflow.com/questions/33284171/
330336 call_command('cms', 'fix-tree')
331class SourceCode():
332 def __init__(self, branch_origin, checkout_location):
333 self.branch_origin = branch_origin
334 self.checkout_location = checkout_location
335
336 def get(self):
337 if self.branch_origin.startswith('lp:') and \
338 os.path.exists('/usr/bin/bzr'):
339 return subprocess.call([
340 'bzr', 'checkout', '--lightweight', self.branch_origin,
341 self.checkout_location])
342 if self.branch_origin.startswith('https://github.com') and \
343 self.branch_origin.endswith('.git') and \
344 os.path.exists('/usr/bin/git'):
345 return subprocess.call([
346 'git', 'clone', '-q', self.branch_origin,
347 self.checkout_location])
348 logging.error(
349 'Branch format "{}" not understood.'.format(self.branch_origin))
350 return 1
351337
352338
353class Command(BaseCommand):339class Command(BaseCommand):
354340
=== added directory 'developer_portal/management/importer'
=== added file 'developer_portal/management/importer/__init__.py'
=== added file 'developer_portal/management/importer/article.py'
--- developer_portal/management/importer/article.py 1970-01-01 00:00:00 +0000
+++ developer_portal/management/importer/article.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,126 @@
1from bs4 import BeautifulSoup
2import codecs
3import logging
4import markdown
5import os
6import re
7
8from .publish import get_or_create_page, slugify
9
10
11class Article:
12 html = None
13 page = None
14 title = ""
15
16 def __init__(self, fn, write_to):
17 self.fn = fn
18 self.write_to = slugify(self.fn)
19 self.full_url = write_to
20 self.slug = os.path.basename(self.full_url)
21
22 def read(self):
23 with codecs.open(self.fn, 'r', encoding='utf-8') as f:
24 if self.fn.endswith('.md'):
25 self.html = markdown.markdown(
26 f.read(),
27 output_format='html5',
28 extensions=['markdown.extensions.tables'])
29 elif self.fn.endswith('.html'):
30 self.html = f.read()
31 else:
32 logging.error("Don't know how to interpret '{}'.".format(
33 self.fn))
34 return False
35 self.title = self._read_title()
36 self._remove_body_and_html_tags()
37 self._use_developer_site_style()
38 return True
39
40 def _read_title(self):
41 soup = BeautifulSoup(self.html, 'html5lib')
42 if soup.title:
43 return soup.title.text
44 if soup.h1:
45 return soup.h1.text
46 return slugify(self.fn).replace('-', ' ').title()
47
48 def _remove_body_and_html_tags(self):
49 self.html = re.sub(r"<html>\n\s<body>\n", "", self.html,
50 flags=re.MULTILINE)
51 self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html,
52 flags=re.MULTILINE)
53
54 def _use_developer_site_style(self):
55 begin = (u"<div class=\"row no-border\">"
56 "\n<div class=\"eight-col\">\n")
57 end = u"</div>\n</div>"
58 self.html = begin + self.html + end
59 self.html = self.html.replace(
60 "<pre><code>",
61 "</div><div class=\"twelve-col\"><pre><code>")
62 self.html = self.html.replace(
63 "</code></pre>",
64 "</code></pre></div><div class=\"eight-col\">")
65
66 def replace_links(self, titles, url_map):
67 for title in titles:
68 local_md_fn = os.path.basename(title)
69 url = u'/'+url_map[title]
70 # Replace links of the form <a href="/path/somefile.md"> first
71 href = u"<a href=\"{}\">".format(url)
72 md_href = u"<a href=\"{}\">".format(local_md_fn)
73 self.html = self.html.replace(md_href, href)
74
75 # Now we can replace free-standing "somefile.md" references in
76 # the HTML
77 link = href + u"{}</a>".format(titles[title])
78 self.html = self.html.replace(local_md_fn, link)
79
80 def publish(self):
81 '''Publishes pages in their branch alias namespace.'''
82 page = get_or_create_page(
83 title=self.title, full_url=self.full_url, menu_title=self.title,
84 html=self.html)
85 self.page = page
86 self.page.publish('en')
87
88
89class SnappyArticle(Article):
90 release_alias = None
91
92 def get(self):
93 if not Article.get(self):
94 return False
95 self.release_alias = re.findall(r'snappy/guides/(\S+?)/\S+?',
96 self.full_url)[0]
97 self._make_snappy_mods()
98 return True
99
100 def _make_snappy_mods(self):
101 # Make sure the reader knows which documentation she is browsing
102 if self.release_alias != 'current':
103 before = (u"<div class=\"row no-border\">\n"
104 "<div class=\"eight-col\">\n")
105 after = (u"<div class=\"row no-border\">\n"
106 "<div class=\"box pull-three three-col\">"
107 "<p>You are browsing the Snappy <code>%s</code> "
108 "documentation.</p>"
109 "<p><a href=\"/snappy/guides/current/%s\">"
110 "Back to the latest stable release &rsaquo;"
111 "</a></p></div>\n"
112 "<div class=\"eight-col\">\n") % (self.release_alias,
113 self.slug, )
114 self.html = self.html.replace(before, after)
115
116 def publish(self):
117 if self.release_alias == "current":
118 # Add a guides/<page> redirect to guides/current/<page>
119 page = get_or_create_page(
120 title=self.title,
121 full_url=self.full_url.replace('/current', ''),
122 redirect="/snappy/guides/current/{}".format(self.slug))
123 page.publish('en')
124 else:
125 self.title += " (%s)" % (self.release_alias,)
126 Article.publish(self)
0127
=== added file 'developer_portal/management/importer/local_branch.py'
--- developer_portal/management/importer/local_branch.py 1970-01-01 00:00:00 +0000
+++ developer_portal/management/importer/local_branch.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,132 @@
1from .article import Article, SnappyArticle
2from .publish import get_or_create_page, slugify
3from .source import SourceCode
4
5import glob
6import logging
7import os
8import shutil
9
10
11class LocalBranch:
12 titles = {}
13 url_map = {}
14 index_doc_title = None
15 index_doc = None
16 release_alias = None
17
18 def __init__(self, tempdir, origin, branch_name, post_checkout_command):
19 self.origin = origin
20 self.branch_name = branch_name
21 self.post_checkout_command = post_checkout_command
22 self.checkout_location = os.path.join(
23 tempdir,
24 os.path.basename(self.origin.replace('.git', '')))
25 self.article_class = Article
26 self.directives = []
27 self.imported_articles = []
28
29 def get(self):
30 sourcecode = SourceCode(self.origin, self.checkout_location,
31 self.branch_name, self.post_checkout_command)
32 if sourcecode.get() != 0:
33 logging.error(
34 'Could not check out branch "{}".'.format(self.origin))
35 shutil.rmtree(self.checkout_location)
36 return 1
37 return 0
38
39 def add_directive(self, import_from, write_to):
40 self.directives += [
41 {
42 'import_from': os.path.join(self.checkout_location,
43 import_from),
44 'write_to': write_to
45 }
46 ]
47
48 def execute_import_directives(self):
49 import_list = []
50 # Import single files first
51 for directive in [d for d in self.directives
52 if os.path.isfile(d['import_from'])]:
53 import_list += [
54 (directive['import_from'], directive['write_to'])
55 ]
56 # Import directories next
57 for directive in [d for d in self.directives
58 if os.path.isdir(d['import_from'])]:
59 for fn in glob.glob('{}/*'.format(directive['import_from'])):
60 if fn not in [a[0] for a in import_list]:
61 import_list += [
62 (fn, os.path.join(directive['write_to'], slugify(fn)))
63 ]
64 # If we import into a namespace and don't have an index doc,
65 # we need to write one.
66 if directive['write_to'] not in [x[1] for x in import_list]:
67 self.index_doc = directive['write_to']
68 # The actual import
69 for entry in import_list:
70 article = self._read_article(entry[0], entry[1])
71 if article:
72 self.imported_articles += [article]
73 self.titles[article.fn] = article.title
74 self.url_map[article.fn] = article.full_url
75 for article in self.imported_articles:
76 article.replace_links(self.titles, self.url_map)
77 if self.index_doc:
78 self._create_fake_index_docs()
79
80 def _read_article(self, fn, write_to):
81 article = self.article_class(fn, write_to)
82 if article.read():
83 return article
84 return None
85
86 def publish(self):
87 for article in self.imported_articles:
88 article.publish()
89 return self.imported_articles
90
91 def _create_fake_index_docs(self):
92 '''Creates a fake index page at the top of the branches
93 docs namespace.'''
94
95 if self.index_doc.endswith('current'):
96 redirect = '/snappy/guides'
97 else:
98 redirect = None
99 list_pages = ''
100 for article in [a for a
101 in self.imported_articles
102 if a.full_url.startswith(self.index_doc)]:
103 list_pages += '<li><a href=\"{}\">{}</a></li>'.format(
104 os.path.basename(article.full_url), article.title)
105 landing = (
106 u'<div class=\"row\"><div class=\"eight-col\">\n'
107 '<p>This section contains documentation for the '
108 '<code>{}</code> Snappy branch.</p>'
109 '<p><ul class=\"list-ubuntu\">{}</ul></p>\n'
110 '<p>Auto-imported from <a '
111 'href=\"https://github.com/ubuntu-core/snappy\">%s</a>.</p>\n'
112 '</div></div>'.format(self.release_alias, list_pages,
113 self.origin))
114 page = get_or_create_page(
115 title=self.index_doc_title, full_url=self.index_doc,
116 in_navigation=False, redirect=redirect, html=landing,
117 menu_title=None)
118 page.publish('en')
119
120
121class SnappyLocalBranch(LocalBranch):
122 def __init__(self, tempdir, origin, branch_name, post_checkout_command):
123 LocalBranch.__init__(self, tempdir, origin, branch_name,
124 post_checkout_command)
125 self.article_class = SnappyArticle
126 self.index_doc_title = 'Snappy documentation'
127
128 def _create_fake_index_docs(self):
129 self.release_alias = os.path.basename(self.index_doc)
130 if not self.index_doc.endswith('current'):
131 self.index_doc_title += ' ({})'.format(self.release_alias)
132 LocalBranch._create_fake_index_docs(self)
0133
=== added file 'developer_portal/management/importer/publish.py'
--- developer_portal/management/importer/publish.py 1970-01-01 00:00:00 +0000
+++ developer_portal/management/importer/publish.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,60 @@
1from cms.api import create_page, add_plugin
2from cms.models import Title
3
4import logging
5import os
6import sys
7
8# XXX: use this once we RawHTML plugins don't strip comments (LP: #1523925)
9START_TEXT = """
10<!--
11branch id: {}
12
13THIS PAGE IS AUTOMATICALLY UPDATED.
14DON'T EDIT IT - CHANGES WILL BE OVERWRITTEN.
15-->
16"""
17
18
19def slugify(filename):
20 return os.path.basename(filename).replace('.md', '').replace('.html', '')
21
22
23def get_or_create_page(title, full_url, menu_title=None,
24 in_navigation=True, redirect=None, html=None):
25 # First check if pages already exist.
26 pages = Title.objects.select_related('page').filter(path__regex=full_url)
27 if pages:
28 page = pages[0].page
29 page.title = title
30 page.publisher_is_draft = True
31 page.menu_title = menu_title
32 page.in_navigation = in_navigation
33 page.redirect = redirect
34 if html:
35 # We create the page, so we know there's just one placeholder
36 placeholder = page.placeholders.all()[0]
37 if placeholder.get_plugins():
38 plugin = placeholder.get_plugins()[0].get_plugin_instance()[0]
39 plugin.body = html
40 plugin.save()
41 else:
42 add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)
43 else:
44 parent_pages = Title.objects.select_related('page').filter(
45 path__regex=os.path.dirname(full_url))
46 if not parent_pages:
47 logging.error('Parent {} not found.'.format(
48 os.path.dirname(full_url)))
49 sys.exit(1)
50 parent = parent_pages[0].page
51
52 slug = os.path.basename(full_url)
53 page = create_page(
54 title, "default.html", "en", slug=slug, parent=parent,
55 menu_title=menu_title, in_navigation=in_navigation,
56 position="last-child", redirect=redirect)
57 if html:
58 placeholder = page.placeholders.get()
59 add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html)
60 return page
061
=== added file 'developer_portal/management/importer/source.py'
--- developer_portal/management/importer/source.py 1970-01-01 00:00:00 +0000
+++ developer_portal/management/importer/source.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,51 @@
1import logging
2import os
3import subprocess
4
5
6class SourceCode():
7 def __init__(self, origin, checkout_location, branch_name,
8 post_checkout_command):
9 self.origin = origin
10 self.checkout_location = checkout_location
11 self.branch_name = branch_name
12 self.post_checkout_command = post_checkout_command
13
14 def get(self):
15 res = self._get_branch()
16 if res == 0 and self.post_checkout_command:
17 res = self._post_checkout()
18 return res
19 return res
20
21 def _get_branch(self):
22 if self.origin.startswith('lp:') and \
23 os.path.exists('/usr/bin/bzr'):
24 return subprocess.call([
25 'bzr', 'checkout', '--lightweight', self.origin,
26 self.checkout_location])
27 if self.origin.startswith('https://github.com') and \
28 self.origin.endswith('.git') and \
29 os.path.exists('/usr/bin/git'):
30 retcode = subprocess.call([
31 'git', 'clone', '-q', self.origin,
32 self.checkout_location])
33 if retcode == 0 and self.branch_name:
34 pwd = os.getcwd()
35 os.chdir(self.checkout_location)
36 retcode = subprocess.call(['git', 'checkout',
37 self.branch_name])
38 os.chdir(pwd)
39 return retcode
40 logging.error(
41 'Branch format "{}" not understood.'.format(self.origin))
42 return 1
43
44 def _post_checkout(self):
45 process = subprocess.Popen(self.post_checkout_command.split(),
46 stdout=subprocess.PIPE)
47 (out, err) = process.communicate()
48 retcode = process.wait()
49 if retcode != 0:
50 logging.error(out)
51 return retcode
052
=== added file 'developer_portal/migrations/0002_rework_importer.py'
--- developer_portal/migrations/0002_rework_importer.py 1970-01-01 00:00:00 +0000
+++ developer_portal/migrations/0002_rework_importer.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,59 @@
1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3
4from django.db import migrations, models
5
6
7class Migration(migrations.Migration):
8
9 dependencies = [
10 ('developer_portal', '0001_initial'),
11 ]
12
13 operations = [
14 migrations.CreateModel(
15 name='ExternalDocsBranchImportDirective',
16 fields=[
17 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18 ('import_from', models.CharField(help_text='File or directory to import from the branch. Ie: "docs/intro.md" (file) or "docs" (complete directory), etc.', max_length=150)),
19 ('write_to', models.CharField(help_text='Article URL (for a specific file) or article namespace for a directory or a set of files.', max_length=150)),
20 ],
21 ),
22 migrations.AlterModelOptions(
23 name='externaldocsbranch',
24 options={'verbose_name': 'external docs branch', 'verbose_name_plural': 'external docs branches'},
25 ),
26 migrations.RenameField(
27 model_name='externaldocsbranch',
28 old_name='lp_origin',
29 new_name='origin',
30 ),
31 migrations.RemoveField(
32 model_name='externaldocsbranch',
33 name='docs_namespace',
34 ),
35 migrations.RemoveField(
36 model_name='externaldocsbranch',
37 name='index_doc',
38 ),
39 migrations.AddField(
40 model_name='externaldocsbranch',
41 name='active',
42 field=models.BooleanField(default=True),
43 ),
44 migrations.AddField(
45 model_name='externaldocsbranch',
46 name='branch_name',
47 field=models.CharField(help_text='For use with git branches, ie: "master" or "15.04" or "1.x".', max_length=200, blank=True),
48 ),
49 migrations.AddField(
50 model_name='externaldocsbranch',
51 name='post_checkout_command',
52 field=models.CharField(help_text='Command to run after checkout of the branch.', max_length=100, blank=True),
53 ),
54 migrations.AddField(
55 model_name='externaldocsbranchimportdirective',
56 name='external_docs_branch',
57 field=models.ForeignKey(to='developer_portal.ExternalDocsBranch'),
58 ),
59 ]
060
=== added file 'developer_portal/migrations/0003_track_imported_articles.py'
--- developer_portal/migrations/0003_track_imported_articles.py 1970-01-01 00:00:00 +0000
+++ developer_portal/migrations/0003_track_imported_articles.py 2015-12-09 15:41:23 +0000
@@ -0,0 +1,24 @@
1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3
4from django.db import migrations, models
5
6
7class Migration(migrations.Migration):
8
9 dependencies = [
10 ('cms', '0013_urlconfrevision'),
11 ('developer_portal', '0002_rework_importer'),
12 ]
13
14 operations = [
15 migrations.CreateModel(
16 name='ImportedArticle',
17 fields=[
18 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19 ('last_import', models.DateTimeField(help_text='Datetime of last import.', verbose_name='Datetime')),
20 ('branch', models.ForeignKey(to='developer_portal.ExternalDocsBranch')),
21 ('page', models.ForeignKey(to='cms.Page')),
22 ],
23 ),
24 ]
025
=== modified file 'developer_portal/models.py'
--- developer_portal/models.py 2015-11-02 16:47:26 +0000
+++ developer_portal/models.py 2015-12-09 15:41:23 +0000
@@ -1,9 +1,9 @@
1from django.db import models1from django.db import models
2from django.utils.translation import ugettext_lazy as _2from django.utils.translation import ugettext_lazy as _
33
4from cms.models import CMSPlugin
5from cms.extensions import TitleExtension4from cms.extensions import TitleExtension
6from cms.extensions.extension_pool import extension_pool5from cms.extensions.extension_pool import extension_pool
6from cms.models import Page
7from djangocms_text_ckeditor.html import extract_images7from djangocms_text_ckeditor.html import extract_images
8from djangocms_text_ckeditor.models import AbstractText8from djangocms_text_ckeditor.models import AbstractText
99
@@ -21,23 +21,54 @@
2121
2222
23class ExternalDocsBranch(models.Model):23class ExternalDocsBranch(models.Model):
24 # We originally assumed that branches would also live in LP,24 origin = models.CharField(
25 # well, we were wrong, but let's keep the name around. It's
26 # no use having a schema/data migration just for this.
27 lp_origin = models.CharField(
28 max_length=200,25 max_length=200,
29 help_text=_('External branch location, ie: lp:snappy/15.04 or '26 help_text=_('External branch location, ie: lp:snappy/15.04 or '
30 'https://github.com/ubuntu-core/snappy.git'))27 'https://github.com/ubuntu-core/snappy.git'))
31 docs_namespace = models.CharField(28 branch_name = models.CharField(
32 max_length=120,29 max_length=200,
33 help_text=_('Path alias we want to use for the docs, '30 help_text=_('For use with git branches, ie: "master" or "15.04" '
34 'ie "snappy/guides/15.04" or '31 'or "1.x".'),
35 '"snappy/guides/latest", etc.'))32 blank=True)
36 index_doc = models.CharField(33 post_checkout_command = models.CharField(
37 max_length=120,34 max_length=100,
38 help_text=_('File name of doc to be used as index document, '35 help_text=_('Command to run after checkout of the branch.'),
39 'ie "intro.md"'),36 blank=True)
40 blank=True)37 active = models.BooleanField(default=True)
38
39 def __str__(self):
40 if self.branch_name:
41 return "{} {}".format(self.origin, self.branch_name)
42 return "{}".format(self.origin)
43
44 class Meta:
45 verbose_name = "external docs branch"
46 verbose_name_plural = "external docs branches"
47
48
49class ExternalDocsBranchImportDirective(models.Model):
50 external_docs_branch = models.ForeignKey(ExternalDocsBranch)
51 import_from = models.CharField(
52 max_length=150,
53 help_text=_('File or directory to import from the branch. '
54 'Ie: "docs/intro.md" (file) or '
55 '"docs" (complete directory), etc.'))
56 write_to = models.CharField(
57 max_length=150,
58 help_text=_('Article URL (for a specific file) or article namespace '
59 'for a directory or a set of files.'))
60
61 def __str__(self):
62 return "{} -- {}".format(self.external_docs_branch.origin,
63 self.import_from)
64
65
66class ImportedArticle(models.Model):
67 page = models.ForeignKey(Page)
68 branch = models.ForeignKey(ExternalDocsBranch)
69 last_import = models.DateTimeField(
70 _('Datetime'), help_text=_('Datetime of last import.'))
71
4172
42class SEOExtension(TitleExtension):73class SEOExtension(TitleExtension):
43 keywords = models.CharField(max_length=256)74 keywords = models.CharField(max_length=256)
4475
=== modified file 'developer_portal/settings.py'
--- developer_portal/settings.py 2015-12-04 16:09:27 +0000
+++ developer_portal/settings.py 2015-12-09 15:41:23 +0000
@@ -318,6 +318,26 @@
318 #'PAGINATE_BY': 10,318 #'PAGINATE_BY': 10,
319}319}
320320
321<<<<<<< TREE
322=======
323MIGRATION_MODULES = {
324 'cms': 'cms.migrations',
325 'cmsplugin_zinnia': 'cmsplugin_zinnia.migrations',
326 'djangocms_link': 'djangocms_link.migrations',
327 'djangocms_picture': 'djangocms_picture.migrations',
328 'djangocms_snippet': 'djangocms_snippet.migrations',
329 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations',
330 'djangocms_video': 'djangocms_video.migrations',
331 'django_comments': 'django_comments.migrations',
332 'menus': 'menus.migrations',
333 'rest_framework.authtoken': 'rest_framework.authtoken.migrations',
334 'reversion': 'reversion.migrations',
335 'tagging': 'tagging.migrations',
336 'taggit': 'taggit.migrations',
337 'zinnia': 'zinnia.migrations',
338}
339
340>>>>>>> MERGE-SOURCE
321LOGGING = {341LOGGING = {
322 'version': 1,342 'version': 1,
323 'disable_existing_loggers': False,343 'disable_existing_loggers': False,
324344
=== modified file 'requirements.txt'
--- requirements.txt 2015-12-04 11:49:10 +0000
+++ requirements.txt 2015-12-09 15:41:23 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1oslo.config==1.6.02oslo.config==1.6.0
2oslo.i18n==1.2.03oslo.i18n==1.2.0
3oslo.serialization==1.2.04oslo.serialization==1.2.0
@@ -8,12 +9,25 @@
8Pillow==2.6.19Pillow==2.6.1
9beautifulsoup4==4.4.010beautifulsoup4==4.4.0
10cmsplugin-zinnia==0.611cmsplugin-zinnia==0.6
12=======
13Django==1.8.7
14django-template-debug==0.3.5
15oslo.config==3.0.0
16oslo.i18n==2.7.0
17oslo.serialization==2.0.0
18oslo.utils==2.8.0
19Pillow==2.9.0
20cmsplugin-zinnia==0.8
21Markdown==2.6.5
22beautifulsoup4==4.4.1
23>>>>>>> MERGE-SOURCE
11dj-database-url==0.3.024dj-database-url==0.3.0
12django-admin-enhancer==0.1.3.125django-admin-enhancer==0.1.3.1
13django-appconf==0.626django-appconf==0.6
14django-blog-zinnia==0.14.227django-blog-zinnia==0.14.2
15django-ckeditor==4.4.728django-ckeditor==4.4.7
16django-ckeditor-updated==4.4.429django-ckeditor-updated==4.4.4
30<<<<<<< TREE
17django-classy-tags==0.5.131django-classy-tags==0.5.1
18django-cms==3.0.632django-cms==3.0.6
19django-contrib-comments==1.533django-contrib-comments==1.5
@@ -25,23 +39,56 @@
25django-polymorphic==0.639django-polymorphic==0.6
26django-reversion==1.8.440django-reversion==1.8.4
27django-sekizai==0.741django-sekizai==0.7
42=======
43django-classy-tags==0.6.2
44django-cms==3.2.0
45django-contrib-comments==1.6.1
46django-meta==0.3.1
47django-meta-mixin==0.2.1
48django-missing==0.1.15
49django-parler==1.5.1
50django-polymorphic==0.7.2
51django-reversion==1.9.3
52django-sekizai==0.8.2
53>>>>>>> MERGE-SOURCE
28django-swiftstorage==1.1.054django-swiftstorage==1.1.0
55<<<<<<< TREE
29django-tagging==0.3.356django-tagging==0.3.3
30django-taggit==0.12.257django-taggit==0.12.2
31django-taggit-autosuggest==0.2.558django-taggit-autosuggest==0.2.5
59=======
60django-tagging==0.4
61django-taggit==0.17.5
62django-taggit-autosuggest==0.2.8
63>>>>>>> MERGE-SOURCE
32django-taggit-templatetags==0.2.564django-taggit-templatetags==0.2.5
33django-templatetag-sugar==1.065django-templatetag-sugar==1.0
34django-xmlrpc==0.1.566django-xmlrpc==0.1.5
67<<<<<<< TREE
35djangocms-admin-style==0.2.268djangocms-admin-style==0.2.2
36djangocms-link==1.569djangocms-link==1.5
37djangocms-picture==0.170djangocms-picture==0.1
38djangocms-snippet==1.371djangocms-snippet==1.3
39djangocms-text-ckeditor==2.4.272djangocms-text-ckeditor==2.4.2
73=======
74djangocms-admin-style==1.0.7
75djangocms-link==1.7.1
76djangocms-picture==0.2.0
77djangocms-snippet==1.7.1
78djangocms-text-ckeditor==2.8.0
79>>>>>>> MERGE-SOURCE
40djangocms-utils==0.9.580djangocms-utils==0.9.5
81<<<<<<< TREE
41djangocms-video==0.182djangocms-video==0.1
42python-keystoneclient==0.11.283python-keystoneclient==0.11.2
43python-swiftclient==2.3.184python-swiftclient==2.3.1
44pytz==2014.785pytz==2014.7
86=======
87djangocms-video==0.2.0
88python-keystoneclient==1.3.3
89python-swiftclient==2.6.0
90pytz==2015.7
91>>>>>>> MERGE-SOURCE
45simple-translation==0.8.692simple-translation==0.8.6
46simplejson==3.6.593simplejson==3.6.5
47wsgiref==0.1.294wsgiref==0.1.2

Subscribers

People subscribed via source and target branches