Merge lp:~dholbach/developer-ubuntu-com/rework-importer into lp:developer-ubuntu-com
- rework-importer
- Merge into stable
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 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu App Developer site developers | Pending | ||
Review via email: mp+277722@code.launchpad.net |
Commit message
Description of the change
- 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
- 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 ExternalDocsBra
nch.__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 ExternalDocsBra
nchImportDirect ive 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
- 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 TestLocalBranch
Import - 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
1 | === added file 'TODO' | |||
2 | --- TODO 1970-01-01 00:00:00 +0000 | |||
3 | +++ TODO 2015-12-09 15:41:23 +0000 | |||
4 | @@ -0,0 +1,7 @@ | |||
5 | 1 | - import pictures (LP: #1511677) | ||
6 | 2 | - unpublished docs | ||
7 | 3 | - add comment at top of RawHTML plugins, so users know not to edit them, | ||
8 | 4 | currently blocked on LP: #1523925 | ||
9 | 5 | - when deleting pages, check if last rev was made by python-api | ||
10 | 6 | |||
11 | 7 | - | ||
12 | 0 | 8 | ||
13 | === modified file 'developer_portal/admin.py' | |||
14 | --- developer_portal/admin.py 2015-12-04 11:49:10 +0000 | |||
15 | +++ developer_portal/admin.py 2015-12-09 15:41:23 +0000 | |||
16 | @@ -4,7 +4,10 @@ | |||
17 | 4 | from reversion.admin import VersionAdmin | 4 | from reversion.admin import VersionAdmin |
18 | 5 | 5 | ||
19 | 6 | from cms.extensions import TitleExtensionAdmin | 6 | from cms.extensions import TitleExtensionAdmin |
21 | 7 | from .models import ExternalDocsBranch, SEOExtension | 7 | from .models import ( |
22 | 8 | ExternalDocsBranch, ExternalDocsBranchImportDirective, | ||
23 | 9 | ImportedArticle, SEOExtension | ||
24 | 10 | ) | ||
25 | 8 | from django.core.management import call_command | 11 | from django.core.management import call_command |
26 | 9 | 12 | ||
27 | 10 | __all__ = ( | 13 | __all__ = ( |
28 | @@ -13,7 +16,12 @@ | |||
29 | 13 | 16 | ||
30 | 14 | def import_selected_external_docs_branches(modeladmin, request, queryset): | 17 | def import_selected_external_docs_branches(modeladmin, request, queryset): |
31 | 15 | for branch in queryset: | 18 | for branch in queryset: |
32 | 19 | <<<<<<< TREE | ||
33 | 16 | call_command('import-external-docs-branches', branch.docs_namespace) | 20 | call_command('import-external-docs-branches', branch.docs_namespace) |
34 | 21 | ======= | ||
35 | 22 | branches.append(branch.origin) | ||
36 | 23 | call_command('import-external-docs-branches', *branches) | ||
37 | 24 | >>>>>>> MERGE-SOURCE | ||
38 | 17 | import_selected_external_docs_branches.short_description = \ | 25 | import_selected_external_docs_branches.short_description = \ |
39 | 18 | "Import selected branches" | 26 | "Import selected branches" |
40 | 19 | 27 | ||
41 | @@ -35,12 +43,22 @@ | |||
42 | 35 | admin.site.register(Version, VersionAdmin) | 43 | admin.site.register(Version, VersionAdmin) |
43 | 36 | 44 | ||
44 | 37 | 45 | ||
45 | 46 | @admin.register(ExternalDocsBranch) | ||
46 | 38 | class ExternalDocsBranchAdmin(admin.ModelAdmin): | 47 | class ExternalDocsBranchAdmin(admin.ModelAdmin): |
49 | 39 | list_display = ('lp_origin', 'docs_namespace') | 48 | list_display = ('origin', 'post_checkout_command', 'branch_name',) |
50 | 40 | list_filter = ('lp_origin', 'docs_namespace') | 49 | list_filter = ('origin', 'post_checkout_command', 'branch_name',) |
51 | 41 | actions = [import_selected_external_docs_branches] | 50 | actions = [import_selected_external_docs_branches] |
52 | 42 | 51 | ||
54 | 43 | admin.site.register(ExternalDocsBranch, ExternalDocsBranchAdmin) | 52 | |
55 | 53 | @admin.register(ExternalDocsBranchImportDirective) | ||
56 | 54 | class ExternalDocsBranchImportDirectiveAdmin(admin.ModelAdmin): | ||
57 | 55 | pass | ||
58 | 56 | |||
59 | 57 | |||
60 | 58 | @admin.register(ImportedArticle) | ||
61 | 59 | class ImportedArticleAdmin(admin.ModelAdmin): | ||
62 | 60 | pass | ||
63 | 61 | |||
64 | 44 | 62 | ||
65 | 45 | class SEOExtensionAdmin(TitleExtensionAdmin): | 63 | class SEOExtensionAdmin(TitleExtensionAdmin): |
66 | 46 | pass | 64 | pass |
67 | 47 | 65 | ||
68 | === modified file 'developer_portal/management/commands/import-external-docs-branches.py' | |||
69 | --- developer_portal/management/commands/import-external-docs-branches.py 2015-12-04 11:49:10 +0000 | |||
70 | +++ developer_portal/management/commands/import-external-docs-branches.py 2015-12-09 15:41:23 +0000 | |||
71 | @@ -1,23 +1,14 @@ | |||
72 | 1 | from django.core.management.base import BaseCommand | 1 | from django.core.management.base import BaseCommand |
73 | 2 | from django.core.management import call_command | 2 | from django.core.management import call_command |
83 | 3 | from django.db import transaction | 3 | |
84 | 4 | 4 | from ..importer.local_branch import LocalBranch, SnappyLocalBranch | |
85 | 5 | from cms.api import create_page, add_plugin | 5 | |
86 | 6 | from cms.models import Page, Title | 6 | import datetime |
78 | 7 | from cms.utils import page_resolver | ||
79 | 8 | |||
80 | 9 | from bs4 import BeautifulSoup | ||
81 | 10 | import codecs | ||
82 | 11 | import glob | ||
87 | 12 | import logging | 7 | import logging |
88 | 13 | import markdown | ||
89 | 14 | import os | ||
90 | 15 | import re | ||
91 | 16 | import shutil | 8 | import shutil |
92 | 17 | import subprocess | ||
93 | 18 | import sys | ||
94 | 19 | import tempfile | 9 | import tempfile |
95 | 20 | 10 | ||
96 | 11 | <<<<<<< TREE | ||
97 | 21 | from developer_portal.models import ExternalDocsBranch | 12 | from developer_portal.models import ExternalDocsBranch |
98 | 22 | 13 | ||
99 | 23 | DOCS_DIRNAME = 'docs' | 14 | DOCS_DIRNAME = 'docs' |
100 | @@ -295,6 +286,13 @@ | |||
101 | 295 | placeholder = page.placeholders.get() | 286 | placeholder = page.placeholders.get() |
102 | 296 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | 287 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) |
103 | 297 | return page | 288 | return page |
104 | 289 | ======= | ||
105 | 290 | from developer_portal.models import ( | ||
106 | 291 | ExternalDocsBranch, | ||
107 | 292 | ExternalDocsBranchImportDirective, | ||
108 | 293 | ImportedArticle, | ||
109 | 294 | ) | ||
110 | 295 | >>>>>>> MERGE-SOURCE | ||
111 | 298 | 296 | ||
112 | 299 | 297 | ||
113 | 300 | def import_branches(selection): | 298 | def import_branches(selection): |
114 | @@ -303,51 +301,39 @@ | |||
115 | 303 | 'ExternalDocsBranch table yet.') | 301 | 'ExternalDocsBranch table yet.') |
116 | 304 | return | 302 | return |
117 | 305 | tempdir = tempfile.mkdtemp() | 303 | tempdir = tempfile.mkdtemp() |
118 | 306 | db_actions = DBActions() | ||
119 | 307 | for branch in ExternalDocsBranch.objects.filter( | 304 | for branch in ExternalDocsBranch.objects.filter( |
129 | 308 | docs_namespace__regex=selection): | 305 | origin__regex=selection, active=True): |
130 | 309 | checkout_location = os.path.join( | 306 | url = branch.origin |
131 | 310 | tempdir, os.path.basename(branch.docs_namespace)) | 307 | if url.startswith('lp:snappy') or \ |
132 | 311 | sourcecode = SourceCode(branch.lp_origin, checkout_location) | 308 | 'snappy' in url.split(':')[1].split('.git')[0].split('/'): |
133 | 312 | if sourcecode.get() != 0: | 309 | branch_class = SnappyLocalBranch |
134 | 313 | logging.error( | 310 | else: |
135 | 314 | 'Could not check out branch "%s".' % branch.lp_origin) | 311 | branch_class = LocalBranch |
136 | 315 | if os.path.exists(checkout_location): | 312 | local_branch = branch_class(tempdir, branch.origin, branch.branch_name, |
137 | 316 | shutil.rmtree(checkout_location) | 313 | branch.post_checkout_command) |
138 | 314 | if local_branch.get() != 0: | ||
139 | 317 | break | 315 | break |
149 | 318 | if branch.lp_origin.startswith('lp:snappy') or \ | 316 | for directive in ExternalDocsBranchImportDirective.objects.filter( |
150 | 319 | 'snappy' in branch.lp_origin.split(':')[1].split('.git')[0].split('/'): | 317 | external_docs_branch=branch): |
151 | 320 | local_branch = SnappyLocalBranch(checkout_location, branch, | 318 | local_branch.add_directive(directive.import_from, |
152 | 321 | db_actions) | 319 | directive.write_to) |
153 | 322 | else: | 320 | local_branch.execute_import_directives() |
154 | 323 | local_branch = LocalBranch(checkout_location, branch, db_actions) | 321 | imported_articles = local_branch.publish() |
155 | 324 | local_branch.import_markdown() | 322 | for imported_article in imported_articles: |
156 | 325 | local_branch.publish() | 323 | ImportedArticle.objects.get_or_create( |
157 | 326 | local_branch.remove_old_pages() | 324 | branch=branch, |
158 | 325 | page=imported_article.page, | ||
159 | 326 | last_import=datetime.datetime.now()) | ||
160 | 327 | |||
161 | 328 | # The import is done, now let's clean up. | ||
162 | 329 | for old_article in ImportedArticle.objects.filter(branch=branch): | ||
163 | 330 | if old_article.page not in [a.page for a in imported_articles]: | ||
164 | 331 | # XXX: check if last revision was made by 'python-api'? | ||
165 | 332 | old_article.page.delete() | ||
166 | 327 | shutil.rmtree(tempdir) | 333 | shutil.rmtree(tempdir) |
190 | 328 | db_actions.run() | 334 | |
191 | 329 | 335 | # https://stackoverflow.com/questions/33284171/ | |
192 | 330 | 336 | call_command('cms', 'fix-tree') | |
170 | 331 | class SourceCode(): | ||
171 | 332 | def __init__(self, branch_origin, checkout_location): | ||
172 | 333 | self.branch_origin = branch_origin | ||
173 | 334 | self.checkout_location = checkout_location | ||
174 | 335 | |||
175 | 336 | def get(self): | ||
176 | 337 | if self.branch_origin.startswith('lp:') and \ | ||
177 | 338 | os.path.exists('/usr/bin/bzr'): | ||
178 | 339 | return subprocess.call([ | ||
179 | 340 | 'bzr', 'checkout', '--lightweight', self.branch_origin, | ||
180 | 341 | self.checkout_location]) | ||
181 | 342 | if self.branch_origin.startswith('https://github.com') and \ | ||
182 | 343 | self.branch_origin.endswith('.git') and \ | ||
183 | 344 | os.path.exists('/usr/bin/git'): | ||
184 | 345 | return subprocess.call([ | ||
185 | 346 | 'git', 'clone', '-q', self.branch_origin, | ||
186 | 347 | self.checkout_location]) | ||
187 | 348 | logging.error( | ||
188 | 349 | 'Branch format "{}" not understood.'.format(self.branch_origin)) | ||
189 | 350 | return 1 | ||
193 | 351 | 337 | ||
194 | 352 | 338 | ||
195 | 353 | class Command(BaseCommand): | 339 | class Command(BaseCommand): |
196 | 354 | 340 | ||
197 | === added directory 'developer_portal/management/importer' | |||
198 | === added file 'developer_portal/management/importer/__init__.py' | |||
199 | === added file 'developer_portal/management/importer/article.py' | |||
200 | --- developer_portal/management/importer/article.py 1970-01-01 00:00:00 +0000 | |||
201 | +++ developer_portal/management/importer/article.py 2015-12-09 15:41:23 +0000 | |||
202 | @@ -0,0 +1,126 @@ | |||
203 | 1 | from bs4 import BeautifulSoup | ||
204 | 2 | import codecs | ||
205 | 3 | import logging | ||
206 | 4 | import markdown | ||
207 | 5 | import os | ||
208 | 6 | import re | ||
209 | 7 | |||
210 | 8 | from .publish import get_or_create_page, slugify | ||
211 | 9 | |||
212 | 10 | |||
213 | 11 | class Article: | ||
214 | 12 | html = None | ||
215 | 13 | page = None | ||
216 | 14 | title = "" | ||
217 | 15 | |||
218 | 16 | def __init__(self, fn, write_to): | ||
219 | 17 | self.fn = fn | ||
220 | 18 | self.write_to = slugify(self.fn) | ||
221 | 19 | self.full_url = write_to | ||
222 | 20 | self.slug = os.path.basename(self.full_url) | ||
223 | 21 | |||
224 | 22 | def read(self): | ||
225 | 23 | with codecs.open(self.fn, 'r', encoding='utf-8') as f: | ||
226 | 24 | if self.fn.endswith('.md'): | ||
227 | 25 | self.html = markdown.markdown( | ||
228 | 26 | f.read(), | ||
229 | 27 | output_format='html5', | ||
230 | 28 | extensions=['markdown.extensions.tables']) | ||
231 | 29 | elif self.fn.endswith('.html'): | ||
232 | 30 | self.html = f.read() | ||
233 | 31 | else: | ||
234 | 32 | logging.error("Don't know how to interpret '{}'.".format( | ||
235 | 33 | self.fn)) | ||
236 | 34 | return False | ||
237 | 35 | self.title = self._read_title() | ||
238 | 36 | self._remove_body_and_html_tags() | ||
239 | 37 | self._use_developer_site_style() | ||
240 | 38 | return True | ||
241 | 39 | |||
242 | 40 | def _read_title(self): | ||
243 | 41 | soup = BeautifulSoup(self.html, 'html5lib') | ||
244 | 42 | if soup.title: | ||
245 | 43 | return soup.title.text | ||
246 | 44 | if soup.h1: | ||
247 | 45 | return soup.h1.text | ||
248 | 46 | return slugify(self.fn).replace('-', ' ').title() | ||
249 | 47 | |||
250 | 48 | def _remove_body_and_html_tags(self): | ||
251 | 49 | self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, | ||
252 | 50 | flags=re.MULTILINE) | ||
253 | 51 | self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, | ||
254 | 52 | flags=re.MULTILINE) | ||
255 | 53 | |||
256 | 54 | def _use_developer_site_style(self): | ||
257 | 55 | begin = (u"<div class=\"row no-border\">" | ||
258 | 56 | "\n<div class=\"eight-col\">\n") | ||
259 | 57 | end = u"</div>\n</div>" | ||
260 | 58 | self.html = begin + self.html + end | ||
261 | 59 | self.html = self.html.replace( | ||
262 | 60 | "<pre><code>", | ||
263 | 61 | "</div><div class=\"twelve-col\"><pre><code>") | ||
264 | 62 | self.html = self.html.replace( | ||
265 | 63 | "</code></pre>", | ||
266 | 64 | "</code></pre></div><div class=\"eight-col\">") | ||
267 | 65 | |||
268 | 66 | def replace_links(self, titles, url_map): | ||
269 | 67 | for title in titles: | ||
270 | 68 | local_md_fn = os.path.basename(title) | ||
271 | 69 | url = u'/'+url_map[title] | ||
272 | 70 | # Replace links of the form <a href="/path/somefile.md"> first | ||
273 | 71 | href = u"<a href=\"{}\">".format(url) | ||
274 | 72 | md_href = u"<a href=\"{}\">".format(local_md_fn) | ||
275 | 73 | self.html = self.html.replace(md_href, href) | ||
276 | 74 | |||
277 | 75 | # Now we can replace free-standing "somefile.md" references in | ||
278 | 76 | # the HTML | ||
279 | 77 | link = href + u"{}</a>".format(titles[title]) | ||
280 | 78 | self.html = self.html.replace(local_md_fn, link) | ||
281 | 79 | |||
282 | 80 | def publish(self): | ||
283 | 81 | '''Publishes pages in their branch alias namespace.''' | ||
284 | 82 | page = get_or_create_page( | ||
285 | 83 | title=self.title, full_url=self.full_url, menu_title=self.title, | ||
286 | 84 | html=self.html) | ||
287 | 85 | self.page = page | ||
288 | 86 | self.page.publish('en') | ||
289 | 87 | |||
290 | 88 | |||
291 | 89 | class SnappyArticle(Article): | ||
292 | 90 | release_alias = None | ||
293 | 91 | |||
294 | 92 | def get(self): | ||
295 | 93 | if not Article.get(self): | ||
296 | 94 | return False | ||
297 | 95 | self.release_alias = re.findall(r'snappy/guides/(\S+?)/\S+?', | ||
298 | 96 | self.full_url)[0] | ||
299 | 97 | self._make_snappy_mods() | ||
300 | 98 | return True | ||
301 | 99 | |||
302 | 100 | def _make_snappy_mods(self): | ||
303 | 101 | # Make sure the reader knows which documentation she is browsing | ||
304 | 102 | if self.release_alias != 'current': | ||
305 | 103 | before = (u"<div class=\"row no-border\">\n" | ||
306 | 104 | "<div class=\"eight-col\">\n") | ||
307 | 105 | after = (u"<div class=\"row no-border\">\n" | ||
308 | 106 | "<div class=\"box pull-three three-col\">" | ||
309 | 107 | "<p>You are browsing the Snappy <code>%s</code> " | ||
310 | 108 | "documentation.</p>" | ||
311 | 109 | "<p><a href=\"/snappy/guides/current/%s\">" | ||
312 | 110 | "Back to the latest stable release ›" | ||
313 | 111 | "</a></p></div>\n" | ||
314 | 112 | "<div class=\"eight-col\">\n") % (self.release_alias, | ||
315 | 113 | self.slug, ) | ||
316 | 114 | self.html = self.html.replace(before, after) | ||
317 | 115 | |||
318 | 116 | def publish(self): | ||
319 | 117 | if self.release_alias == "current": | ||
320 | 118 | # Add a guides/<page> redirect to guides/current/<page> | ||
321 | 119 | page = get_or_create_page( | ||
322 | 120 | title=self.title, | ||
323 | 121 | full_url=self.full_url.replace('/current', ''), | ||
324 | 122 | redirect="/snappy/guides/current/{}".format(self.slug)) | ||
325 | 123 | page.publish('en') | ||
326 | 124 | else: | ||
327 | 125 | self.title += " (%s)" % (self.release_alias,) | ||
328 | 126 | Article.publish(self) | ||
329 | 0 | 127 | ||
330 | === added file 'developer_portal/management/importer/local_branch.py' | |||
331 | --- developer_portal/management/importer/local_branch.py 1970-01-01 00:00:00 +0000 | |||
332 | +++ developer_portal/management/importer/local_branch.py 2015-12-09 15:41:23 +0000 | |||
333 | @@ -0,0 +1,132 @@ | |||
334 | 1 | from .article import Article, SnappyArticle | ||
335 | 2 | from .publish import get_or_create_page, slugify | ||
336 | 3 | from .source import SourceCode | ||
337 | 4 | |||
338 | 5 | import glob | ||
339 | 6 | import logging | ||
340 | 7 | import os | ||
341 | 8 | import shutil | ||
342 | 9 | |||
343 | 10 | |||
344 | 11 | class LocalBranch: | ||
345 | 12 | titles = {} | ||
346 | 13 | url_map = {} | ||
347 | 14 | index_doc_title = None | ||
348 | 15 | index_doc = None | ||
349 | 16 | release_alias = None | ||
350 | 17 | |||
351 | 18 | def __init__(self, tempdir, origin, branch_name, post_checkout_command): | ||
352 | 19 | self.origin = origin | ||
353 | 20 | self.branch_name = branch_name | ||
354 | 21 | self.post_checkout_command = post_checkout_command | ||
355 | 22 | self.checkout_location = os.path.join( | ||
356 | 23 | tempdir, | ||
357 | 24 | os.path.basename(self.origin.replace('.git', ''))) | ||
358 | 25 | self.article_class = Article | ||
359 | 26 | self.directives = [] | ||
360 | 27 | self.imported_articles = [] | ||
361 | 28 | |||
362 | 29 | def get(self): | ||
363 | 30 | sourcecode = SourceCode(self.origin, self.checkout_location, | ||
364 | 31 | self.branch_name, self.post_checkout_command) | ||
365 | 32 | if sourcecode.get() != 0: | ||
366 | 33 | logging.error( | ||
367 | 34 | 'Could not check out branch "{}".'.format(self.origin)) | ||
368 | 35 | shutil.rmtree(self.checkout_location) | ||
369 | 36 | return 1 | ||
370 | 37 | return 0 | ||
371 | 38 | |||
372 | 39 | def add_directive(self, import_from, write_to): | ||
373 | 40 | self.directives += [ | ||
374 | 41 | { | ||
375 | 42 | 'import_from': os.path.join(self.checkout_location, | ||
376 | 43 | import_from), | ||
377 | 44 | 'write_to': write_to | ||
378 | 45 | } | ||
379 | 46 | ] | ||
380 | 47 | |||
381 | 48 | def execute_import_directives(self): | ||
382 | 49 | import_list = [] | ||
383 | 50 | # Import single files first | ||
384 | 51 | for directive in [d for d in self.directives | ||
385 | 52 | if os.path.isfile(d['import_from'])]: | ||
386 | 53 | import_list += [ | ||
387 | 54 | (directive['import_from'], directive['write_to']) | ||
388 | 55 | ] | ||
389 | 56 | # Import directories next | ||
390 | 57 | for directive in [d for d in self.directives | ||
391 | 58 | if os.path.isdir(d['import_from'])]: | ||
392 | 59 | for fn in glob.glob('{}/*'.format(directive['import_from'])): | ||
393 | 60 | if fn not in [a[0] for a in import_list]: | ||
394 | 61 | import_list += [ | ||
395 | 62 | (fn, os.path.join(directive['write_to'], slugify(fn))) | ||
396 | 63 | ] | ||
397 | 64 | # If we import into a namespace and don't have an index doc, | ||
398 | 65 | # we need to write one. | ||
399 | 66 | if directive['write_to'] not in [x[1] for x in import_list]: | ||
400 | 67 | self.index_doc = directive['write_to'] | ||
401 | 68 | # The actual import | ||
402 | 69 | for entry in import_list: | ||
403 | 70 | article = self._read_article(entry[0], entry[1]) | ||
404 | 71 | if article: | ||
405 | 72 | self.imported_articles += [article] | ||
406 | 73 | self.titles[article.fn] = article.title | ||
407 | 74 | self.url_map[article.fn] = article.full_url | ||
408 | 75 | for article in self.imported_articles: | ||
409 | 76 | article.replace_links(self.titles, self.url_map) | ||
410 | 77 | if self.index_doc: | ||
411 | 78 | self._create_fake_index_docs() | ||
412 | 79 | |||
413 | 80 | def _read_article(self, fn, write_to): | ||
414 | 81 | article = self.article_class(fn, write_to) | ||
415 | 82 | if article.read(): | ||
416 | 83 | return article | ||
417 | 84 | return None | ||
418 | 85 | |||
419 | 86 | def publish(self): | ||
420 | 87 | for article in self.imported_articles: | ||
421 | 88 | article.publish() | ||
422 | 89 | return self.imported_articles | ||
423 | 90 | |||
424 | 91 | def _create_fake_index_docs(self): | ||
425 | 92 | '''Creates a fake index page at the top of the branches | ||
426 | 93 | docs namespace.''' | ||
427 | 94 | |||
428 | 95 | if self.index_doc.endswith('current'): | ||
429 | 96 | redirect = '/snappy/guides' | ||
430 | 97 | else: | ||
431 | 98 | redirect = None | ||
432 | 99 | list_pages = '' | ||
433 | 100 | for article in [a for a | ||
434 | 101 | in self.imported_articles | ||
435 | 102 | if a.full_url.startswith(self.index_doc)]: | ||
436 | 103 | list_pages += '<li><a href=\"{}\">{}</a></li>'.format( | ||
437 | 104 | os.path.basename(article.full_url), article.title) | ||
438 | 105 | landing = ( | ||
439 | 106 | u'<div class=\"row\"><div class=\"eight-col\">\n' | ||
440 | 107 | '<p>This section contains documentation for the ' | ||
441 | 108 | '<code>{}</code> Snappy branch.</p>' | ||
442 | 109 | '<p><ul class=\"list-ubuntu\">{}</ul></p>\n' | ||
443 | 110 | '<p>Auto-imported from <a ' | ||
444 | 111 | 'href=\"https://github.com/ubuntu-core/snappy\">%s</a>.</p>\n' | ||
445 | 112 | '</div></div>'.format(self.release_alias, list_pages, | ||
446 | 113 | self.origin)) | ||
447 | 114 | page = get_or_create_page( | ||
448 | 115 | title=self.index_doc_title, full_url=self.index_doc, | ||
449 | 116 | in_navigation=False, redirect=redirect, html=landing, | ||
450 | 117 | menu_title=None) | ||
451 | 118 | page.publish('en') | ||
452 | 119 | |||
453 | 120 | |||
454 | 121 | class SnappyLocalBranch(LocalBranch): | ||
455 | 122 | def __init__(self, tempdir, origin, branch_name, post_checkout_command): | ||
456 | 123 | LocalBranch.__init__(self, tempdir, origin, branch_name, | ||
457 | 124 | post_checkout_command) | ||
458 | 125 | self.article_class = SnappyArticle | ||
459 | 126 | self.index_doc_title = 'Snappy documentation' | ||
460 | 127 | |||
461 | 128 | def _create_fake_index_docs(self): | ||
462 | 129 | self.release_alias = os.path.basename(self.index_doc) | ||
463 | 130 | if not self.index_doc.endswith('current'): | ||
464 | 131 | self.index_doc_title += ' ({})'.format(self.release_alias) | ||
465 | 132 | LocalBranch._create_fake_index_docs(self) | ||
466 | 0 | 133 | ||
467 | === added file 'developer_portal/management/importer/publish.py' | |||
468 | --- developer_portal/management/importer/publish.py 1970-01-01 00:00:00 +0000 | |||
469 | +++ developer_portal/management/importer/publish.py 2015-12-09 15:41:23 +0000 | |||
470 | @@ -0,0 +1,60 @@ | |||
471 | 1 | from cms.api import create_page, add_plugin | ||
472 | 2 | from cms.models import Title | ||
473 | 3 | |||
474 | 4 | import logging | ||
475 | 5 | import os | ||
476 | 6 | import sys | ||
477 | 7 | |||
478 | 8 | # XXX: use this once we RawHTML plugins don't strip comments (LP: #1523925) | ||
479 | 9 | START_TEXT = """ | ||
480 | 10 | <!-- | ||
481 | 11 | branch id: {} | ||
482 | 12 | |||
483 | 13 | THIS PAGE IS AUTOMATICALLY UPDATED. | ||
484 | 14 | DON'T EDIT IT - CHANGES WILL BE OVERWRITTEN. | ||
485 | 15 | --> | ||
486 | 16 | """ | ||
487 | 17 | |||
488 | 18 | |||
489 | 19 | def slugify(filename): | ||
490 | 20 | return os.path.basename(filename).replace('.md', '').replace('.html', '') | ||
491 | 21 | |||
492 | 22 | |||
493 | 23 | def get_or_create_page(title, full_url, menu_title=None, | ||
494 | 24 | in_navigation=True, redirect=None, html=None): | ||
495 | 25 | # First check if pages already exist. | ||
496 | 26 | pages = Title.objects.select_related('page').filter(path__regex=full_url) | ||
497 | 27 | if pages: | ||
498 | 28 | page = pages[0].page | ||
499 | 29 | page.title = title | ||
500 | 30 | page.publisher_is_draft = True | ||
501 | 31 | page.menu_title = menu_title | ||
502 | 32 | page.in_navigation = in_navigation | ||
503 | 33 | page.redirect = redirect | ||
504 | 34 | if html: | ||
505 | 35 | # We create the page, so we know there's just one placeholder | ||
506 | 36 | placeholder = page.placeholders.all()[0] | ||
507 | 37 | if placeholder.get_plugins(): | ||
508 | 38 | plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] | ||
509 | 39 | plugin.body = html | ||
510 | 40 | plugin.save() | ||
511 | 41 | else: | ||
512 | 42 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
513 | 43 | else: | ||
514 | 44 | parent_pages = Title.objects.select_related('page').filter( | ||
515 | 45 | path__regex=os.path.dirname(full_url)) | ||
516 | 46 | if not parent_pages: | ||
517 | 47 | logging.error('Parent {} not found.'.format( | ||
518 | 48 | os.path.dirname(full_url))) | ||
519 | 49 | sys.exit(1) | ||
520 | 50 | parent = parent_pages[0].page | ||
521 | 51 | |||
522 | 52 | slug = os.path.basename(full_url) | ||
523 | 53 | page = create_page( | ||
524 | 54 | title, "default.html", "en", slug=slug, parent=parent, | ||
525 | 55 | menu_title=menu_title, in_navigation=in_navigation, | ||
526 | 56 | position="last-child", redirect=redirect) | ||
527 | 57 | if html: | ||
528 | 58 | placeholder = page.placeholders.get() | ||
529 | 59 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
530 | 60 | return page | ||
531 | 0 | 61 | ||
532 | === added file 'developer_portal/management/importer/source.py' | |||
533 | --- developer_portal/management/importer/source.py 1970-01-01 00:00:00 +0000 | |||
534 | +++ developer_portal/management/importer/source.py 2015-12-09 15:41:23 +0000 | |||
535 | @@ -0,0 +1,51 @@ | |||
536 | 1 | import logging | ||
537 | 2 | import os | ||
538 | 3 | import subprocess | ||
539 | 4 | |||
540 | 5 | |||
541 | 6 | class SourceCode(): | ||
542 | 7 | def __init__(self, origin, checkout_location, branch_name, | ||
543 | 8 | post_checkout_command): | ||
544 | 9 | self.origin = origin | ||
545 | 10 | self.checkout_location = checkout_location | ||
546 | 11 | self.branch_name = branch_name | ||
547 | 12 | self.post_checkout_command = post_checkout_command | ||
548 | 13 | |||
549 | 14 | def get(self): | ||
550 | 15 | res = self._get_branch() | ||
551 | 16 | if res == 0 and self.post_checkout_command: | ||
552 | 17 | res = self._post_checkout() | ||
553 | 18 | return res | ||
554 | 19 | return res | ||
555 | 20 | |||
556 | 21 | def _get_branch(self): | ||
557 | 22 | if self.origin.startswith('lp:') and \ | ||
558 | 23 | os.path.exists('/usr/bin/bzr'): | ||
559 | 24 | return subprocess.call([ | ||
560 | 25 | 'bzr', 'checkout', '--lightweight', self.origin, | ||
561 | 26 | self.checkout_location]) | ||
562 | 27 | if self.origin.startswith('https://github.com') and \ | ||
563 | 28 | self.origin.endswith('.git') and \ | ||
564 | 29 | os.path.exists('/usr/bin/git'): | ||
565 | 30 | retcode = subprocess.call([ | ||
566 | 31 | 'git', 'clone', '-q', self.origin, | ||
567 | 32 | self.checkout_location]) | ||
568 | 33 | if retcode == 0 and self.branch_name: | ||
569 | 34 | pwd = os.getcwd() | ||
570 | 35 | os.chdir(self.checkout_location) | ||
571 | 36 | retcode = subprocess.call(['git', 'checkout', | ||
572 | 37 | self.branch_name]) | ||
573 | 38 | os.chdir(pwd) | ||
574 | 39 | return retcode | ||
575 | 40 | logging.error( | ||
576 | 41 | 'Branch format "{}" not understood.'.format(self.origin)) | ||
577 | 42 | return 1 | ||
578 | 43 | |||
579 | 44 | def _post_checkout(self): | ||
580 | 45 | process = subprocess.Popen(self.post_checkout_command.split(), | ||
581 | 46 | stdout=subprocess.PIPE) | ||
582 | 47 | (out, err) = process.communicate() | ||
583 | 48 | retcode = process.wait() | ||
584 | 49 | if retcode != 0: | ||
585 | 50 | logging.error(out) | ||
586 | 51 | return retcode | ||
587 | 0 | 52 | ||
588 | === added file 'developer_portal/migrations/0002_rework_importer.py' | |||
589 | --- developer_portal/migrations/0002_rework_importer.py 1970-01-01 00:00:00 +0000 | |||
590 | +++ developer_portal/migrations/0002_rework_importer.py 2015-12-09 15:41:23 +0000 | |||
591 | @@ -0,0 +1,59 @@ | |||
592 | 1 | # -*- coding: utf-8 -*- | ||
593 | 2 | from __future__ import unicode_literals | ||
594 | 3 | |||
595 | 4 | from django.db import migrations, models | ||
596 | 5 | |||
597 | 6 | |||
598 | 7 | class Migration(migrations.Migration): | ||
599 | 8 | |||
600 | 9 | dependencies = [ | ||
601 | 10 | ('developer_portal', '0001_initial'), | ||
602 | 11 | ] | ||
603 | 12 | |||
604 | 13 | operations = [ | ||
605 | 14 | migrations.CreateModel( | ||
606 | 15 | name='ExternalDocsBranchImportDirective', | ||
607 | 16 | fields=[ | ||
608 | 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
609 | 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)), | ||
610 | 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)), | ||
611 | 20 | ], | ||
612 | 21 | ), | ||
613 | 22 | migrations.AlterModelOptions( | ||
614 | 23 | name='externaldocsbranch', | ||
615 | 24 | options={'verbose_name': 'external docs branch', 'verbose_name_plural': 'external docs branches'}, | ||
616 | 25 | ), | ||
617 | 26 | migrations.RenameField( | ||
618 | 27 | model_name='externaldocsbranch', | ||
619 | 28 | old_name='lp_origin', | ||
620 | 29 | new_name='origin', | ||
621 | 30 | ), | ||
622 | 31 | migrations.RemoveField( | ||
623 | 32 | model_name='externaldocsbranch', | ||
624 | 33 | name='docs_namespace', | ||
625 | 34 | ), | ||
626 | 35 | migrations.RemoveField( | ||
627 | 36 | model_name='externaldocsbranch', | ||
628 | 37 | name='index_doc', | ||
629 | 38 | ), | ||
630 | 39 | migrations.AddField( | ||
631 | 40 | model_name='externaldocsbranch', | ||
632 | 41 | name='active', | ||
633 | 42 | field=models.BooleanField(default=True), | ||
634 | 43 | ), | ||
635 | 44 | migrations.AddField( | ||
636 | 45 | model_name='externaldocsbranch', | ||
637 | 46 | name='branch_name', | ||
638 | 47 | field=models.CharField(help_text='For use with git branches, ie: "master" or "15.04" or "1.x".', max_length=200, blank=True), | ||
639 | 48 | ), | ||
640 | 49 | migrations.AddField( | ||
641 | 50 | model_name='externaldocsbranch', | ||
642 | 51 | name='post_checkout_command', | ||
643 | 52 | field=models.CharField(help_text='Command to run after checkout of the branch.', max_length=100, blank=True), | ||
644 | 53 | ), | ||
645 | 54 | migrations.AddField( | ||
646 | 55 | model_name='externaldocsbranchimportdirective', | ||
647 | 56 | name='external_docs_branch', | ||
648 | 57 | field=models.ForeignKey(to='developer_portal.ExternalDocsBranch'), | ||
649 | 58 | ), | ||
650 | 59 | ] | ||
651 | 0 | 60 | ||
652 | === added file 'developer_portal/migrations/0003_track_imported_articles.py' | |||
653 | --- developer_portal/migrations/0003_track_imported_articles.py 1970-01-01 00:00:00 +0000 | |||
654 | +++ developer_portal/migrations/0003_track_imported_articles.py 2015-12-09 15:41:23 +0000 | |||
655 | @@ -0,0 +1,24 @@ | |||
656 | 1 | # -*- coding: utf-8 -*- | ||
657 | 2 | from __future__ import unicode_literals | ||
658 | 3 | |||
659 | 4 | from django.db import migrations, models | ||
660 | 5 | |||
661 | 6 | |||
662 | 7 | class Migration(migrations.Migration): | ||
663 | 8 | |||
664 | 9 | dependencies = [ | ||
665 | 10 | ('cms', '0013_urlconfrevision'), | ||
666 | 11 | ('developer_portal', '0002_rework_importer'), | ||
667 | 12 | ] | ||
668 | 13 | |||
669 | 14 | operations = [ | ||
670 | 15 | migrations.CreateModel( | ||
671 | 16 | name='ImportedArticle', | ||
672 | 17 | fields=[ | ||
673 | 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
674 | 19 | ('last_import', models.DateTimeField(help_text='Datetime of last import.', verbose_name='Datetime')), | ||
675 | 20 | ('branch', models.ForeignKey(to='developer_portal.ExternalDocsBranch')), | ||
676 | 21 | ('page', models.ForeignKey(to='cms.Page')), | ||
677 | 22 | ], | ||
678 | 23 | ), | ||
679 | 24 | ] | ||
680 | 0 | 25 | ||
681 | === modified file 'developer_portal/models.py' | |||
682 | --- developer_portal/models.py 2015-11-02 16:47:26 +0000 | |||
683 | +++ developer_portal/models.py 2015-12-09 15:41:23 +0000 | |||
684 | @@ -1,9 +1,9 @@ | |||
685 | 1 | from django.db import models | 1 | from django.db import models |
686 | 2 | from django.utils.translation import ugettext_lazy as _ | 2 | from django.utils.translation import ugettext_lazy as _ |
687 | 3 | 3 | ||
688 | 4 | from cms.models import CMSPlugin | ||
689 | 5 | from cms.extensions import TitleExtension | 4 | from cms.extensions import TitleExtension |
690 | 6 | from cms.extensions.extension_pool import extension_pool | 5 | from cms.extensions.extension_pool import extension_pool |
691 | 6 | from cms.models import Page | ||
692 | 7 | from djangocms_text_ckeditor.html import extract_images | 7 | from djangocms_text_ckeditor.html import extract_images |
693 | 8 | from djangocms_text_ckeditor.models import AbstractText | 8 | from djangocms_text_ckeditor.models import AbstractText |
694 | 9 | 9 | ||
695 | @@ -21,23 +21,54 @@ | |||
696 | 21 | 21 | ||
697 | 22 | 22 | ||
698 | 23 | class ExternalDocsBranch(models.Model): | 23 | class ExternalDocsBranch(models.Model): |
703 | 24 | # We originally assumed that branches would also live in LP, | 24 | origin = models.CharField( |
700 | 25 | # well, we were wrong, but let's keep the name around. It's | ||
701 | 26 | # no use having a schema/data migration just for this. | ||
702 | 27 | lp_origin = models.CharField( | ||
704 | 28 | max_length=200, | 25 | max_length=200, |
705 | 29 | help_text=_('External branch location, ie: lp:snappy/15.04 or ' | 26 | help_text=_('External branch location, ie: lp:snappy/15.04 or ' |
706 | 30 | 'https://github.com/ubuntu-core/snappy.git')) | 27 | 'https://github.com/ubuntu-core/snappy.git')) |
717 | 31 | docs_namespace = models.CharField( | 28 | branch_name = models.CharField( |
718 | 32 | max_length=120, | 29 | max_length=200, |
719 | 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" ' |
720 | 34 | 'ie "snappy/guides/15.04" or ' | 31 | 'or "1.x".'), |
721 | 35 | '"snappy/guides/latest", etc.')) | 32 | blank=True) |
722 | 36 | index_doc = models.CharField( | 33 | post_checkout_command = models.CharField( |
723 | 37 | max_length=120, | 34 | max_length=100, |
724 | 38 | help_text=_('File name of doc to be used as index document, ' | 35 | help_text=_('Command to run after checkout of the branch.'), |
725 | 39 | 'ie "intro.md"'), | 36 | blank=True) |
726 | 40 | blank=True) | 37 | active = models.BooleanField(default=True) |
727 | 38 | |||
728 | 39 | def __str__(self): | ||
729 | 40 | if self.branch_name: | ||
730 | 41 | return "{} {}".format(self.origin, self.branch_name) | ||
731 | 42 | return "{}".format(self.origin) | ||
732 | 43 | |||
733 | 44 | class Meta: | ||
734 | 45 | verbose_name = "external docs branch" | ||
735 | 46 | verbose_name_plural = "external docs branches" | ||
736 | 47 | |||
737 | 48 | |||
738 | 49 | class ExternalDocsBranchImportDirective(models.Model): | ||
739 | 50 | external_docs_branch = models.ForeignKey(ExternalDocsBranch) | ||
740 | 51 | import_from = models.CharField( | ||
741 | 52 | max_length=150, | ||
742 | 53 | help_text=_('File or directory to import from the branch. ' | ||
743 | 54 | 'Ie: "docs/intro.md" (file) or ' | ||
744 | 55 | '"docs" (complete directory), etc.')) | ||
745 | 56 | write_to = models.CharField( | ||
746 | 57 | max_length=150, | ||
747 | 58 | help_text=_('Article URL (for a specific file) or article namespace ' | ||
748 | 59 | 'for a directory or a set of files.')) | ||
749 | 60 | |||
750 | 61 | def __str__(self): | ||
751 | 62 | return "{} -- {}".format(self.external_docs_branch.origin, | ||
752 | 63 | self.import_from) | ||
753 | 64 | |||
754 | 65 | |||
755 | 66 | class ImportedArticle(models.Model): | ||
756 | 67 | page = models.ForeignKey(Page) | ||
757 | 68 | branch = models.ForeignKey(ExternalDocsBranch) | ||
758 | 69 | last_import = models.DateTimeField( | ||
759 | 70 | _('Datetime'), help_text=_('Datetime of last import.')) | ||
760 | 71 | |||
761 | 41 | 72 | ||
762 | 42 | class SEOExtension(TitleExtension): | 73 | class SEOExtension(TitleExtension): |
763 | 43 | keywords = models.CharField(max_length=256) | 74 | keywords = models.CharField(max_length=256) |
764 | 44 | 75 | ||
765 | === modified file 'developer_portal/settings.py' | |||
766 | --- developer_portal/settings.py 2015-12-04 16:09:27 +0000 | |||
767 | +++ developer_portal/settings.py 2015-12-09 15:41:23 +0000 | |||
768 | @@ -318,6 +318,26 @@ | |||
769 | 318 | #'PAGINATE_BY': 10, | 318 | #'PAGINATE_BY': 10, |
770 | 319 | } | 319 | } |
771 | 320 | 320 | ||
772 | 321 | <<<<<<< TREE | ||
773 | 322 | ======= | ||
774 | 323 | MIGRATION_MODULES = { | ||
775 | 324 | 'cms': 'cms.migrations', | ||
776 | 325 | 'cmsplugin_zinnia': 'cmsplugin_zinnia.migrations', | ||
777 | 326 | 'djangocms_link': 'djangocms_link.migrations', | ||
778 | 327 | 'djangocms_picture': 'djangocms_picture.migrations', | ||
779 | 328 | 'djangocms_snippet': 'djangocms_snippet.migrations', | ||
780 | 329 | 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations', | ||
781 | 330 | 'djangocms_video': 'djangocms_video.migrations', | ||
782 | 331 | 'django_comments': 'django_comments.migrations', | ||
783 | 332 | 'menus': 'menus.migrations', | ||
784 | 333 | 'rest_framework.authtoken': 'rest_framework.authtoken.migrations', | ||
785 | 334 | 'reversion': 'reversion.migrations', | ||
786 | 335 | 'tagging': 'tagging.migrations', | ||
787 | 336 | 'taggit': 'taggit.migrations', | ||
788 | 337 | 'zinnia': 'zinnia.migrations', | ||
789 | 338 | } | ||
790 | 339 | |||
791 | 340 | >>>>>>> MERGE-SOURCE | ||
792 | 321 | LOGGING = { | 341 | LOGGING = { |
793 | 322 | 'version': 1, | 342 | 'version': 1, |
794 | 323 | 'disable_existing_loggers': False, | 343 | 'disable_existing_loggers': False, |
795 | 324 | 344 | ||
796 | === modified file 'requirements.txt' | |||
797 | --- requirements.txt 2015-12-04 11:49:10 +0000 | |||
798 | +++ requirements.txt 2015-12-09 15:41:23 +0000 | |||
799 | @@ -1,3 +1,4 @@ | |||
800 | 1 | <<<<<<< TREE | ||
801 | 1 | oslo.config==1.6.0 | 2 | oslo.config==1.6.0 |
802 | 2 | oslo.i18n==1.2.0 | 3 | oslo.i18n==1.2.0 |
803 | 3 | oslo.serialization==1.2.0 | 4 | oslo.serialization==1.2.0 |
804 | @@ -8,12 +9,25 @@ | |||
805 | 8 | Pillow==2.6.1 | 9 | Pillow==2.6.1 |
806 | 9 | beautifulsoup4==4.4.0 | 10 | beautifulsoup4==4.4.0 |
807 | 10 | cmsplugin-zinnia==0.6 | 11 | cmsplugin-zinnia==0.6 |
808 | 12 | ======= | ||
809 | 13 | Django==1.8.7 | ||
810 | 14 | django-template-debug==0.3.5 | ||
811 | 15 | oslo.config==3.0.0 | ||
812 | 16 | oslo.i18n==2.7.0 | ||
813 | 17 | oslo.serialization==2.0.0 | ||
814 | 18 | oslo.utils==2.8.0 | ||
815 | 19 | Pillow==2.9.0 | ||
816 | 20 | cmsplugin-zinnia==0.8 | ||
817 | 21 | Markdown==2.6.5 | ||
818 | 22 | beautifulsoup4==4.4.1 | ||
819 | 23 | >>>>>>> MERGE-SOURCE | ||
820 | 11 | dj-database-url==0.3.0 | 24 | dj-database-url==0.3.0 |
821 | 12 | django-admin-enhancer==0.1.3.1 | 25 | django-admin-enhancer==0.1.3.1 |
822 | 13 | django-appconf==0.6 | 26 | django-appconf==0.6 |
823 | 14 | django-blog-zinnia==0.14.2 | 27 | django-blog-zinnia==0.14.2 |
824 | 15 | django-ckeditor==4.4.7 | 28 | django-ckeditor==4.4.7 |
825 | 16 | django-ckeditor-updated==4.4.4 | 29 | django-ckeditor-updated==4.4.4 |
826 | 30 | <<<<<<< TREE | ||
827 | 17 | django-classy-tags==0.5.1 | 31 | django-classy-tags==0.5.1 |
828 | 18 | django-cms==3.0.6 | 32 | django-cms==3.0.6 |
829 | 19 | django-contrib-comments==1.5 | 33 | django-contrib-comments==1.5 |
830 | @@ -25,23 +39,56 @@ | |||
831 | 25 | django-polymorphic==0.6 | 39 | django-polymorphic==0.6 |
832 | 26 | django-reversion==1.8.4 | 40 | django-reversion==1.8.4 |
833 | 27 | django-sekizai==0.7 | 41 | django-sekizai==0.7 |
834 | 42 | ======= | ||
835 | 43 | django-classy-tags==0.6.2 | ||
836 | 44 | django-cms==3.2.0 | ||
837 | 45 | django-contrib-comments==1.6.1 | ||
838 | 46 | django-meta==0.3.1 | ||
839 | 47 | django-meta-mixin==0.2.1 | ||
840 | 48 | django-missing==0.1.15 | ||
841 | 49 | django-parler==1.5.1 | ||
842 | 50 | django-polymorphic==0.7.2 | ||
843 | 51 | django-reversion==1.9.3 | ||
844 | 52 | django-sekizai==0.8.2 | ||
845 | 53 | >>>>>>> MERGE-SOURCE | ||
846 | 28 | django-swiftstorage==1.1.0 | 54 | django-swiftstorage==1.1.0 |
847 | 55 | <<<<<<< TREE | ||
848 | 29 | django-tagging==0.3.3 | 56 | django-tagging==0.3.3 |
849 | 30 | django-taggit==0.12.2 | 57 | django-taggit==0.12.2 |
850 | 31 | django-taggit-autosuggest==0.2.5 | 58 | django-taggit-autosuggest==0.2.5 |
851 | 59 | ======= | ||
852 | 60 | django-tagging==0.4 | ||
853 | 61 | django-taggit==0.17.5 | ||
854 | 62 | django-taggit-autosuggest==0.2.8 | ||
855 | 63 | >>>>>>> MERGE-SOURCE | ||
856 | 32 | django-taggit-templatetags==0.2.5 | 64 | django-taggit-templatetags==0.2.5 |
857 | 33 | django-templatetag-sugar==1.0 | 65 | django-templatetag-sugar==1.0 |
858 | 34 | django-xmlrpc==0.1.5 | 66 | django-xmlrpc==0.1.5 |
859 | 67 | <<<<<<< TREE | ||
860 | 35 | djangocms-admin-style==0.2.2 | 68 | djangocms-admin-style==0.2.2 |
861 | 36 | djangocms-link==1.5 | 69 | djangocms-link==1.5 |
862 | 37 | djangocms-picture==0.1 | 70 | djangocms-picture==0.1 |
863 | 38 | djangocms-snippet==1.3 | 71 | djangocms-snippet==1.3 |
864 | 39 | djangocms-text-ckeditor==2.4.2 | 72 | djangocms-text-ckeditor==2.4.2 |
865 | 73 | ======= | ||
866 | 74 | djangocms-admin-style==1.0.7 | ||
867 | 75 | djangocms-link==1.7.1 | ||
868 | 76 | djangocms-picture==0.2.0 | ||
869 | 77 | djangocms-snippet==1.7.1 | ||
870 | 78 | djangocms-text-ckeditor==2.8.0 | ||
871 | 79 | >>>>>>> MERGE-SOURCE | ||
872 | 40 | djangocms-utils==0.9.5 | 80 | djangocms-utils==0.9.5 |
873 | 81 | <<<<<<< TREE | ||
874 | 41 | djangocms-video==0.1 | 82 | djangocms-video==0.1 |
875 | 42 | python-keystoneclient==0.11.2 | 83 | python-keystoneclient==0.11.2 |
876 | 43 | python-swiftclient==2.3.1 | 84 | python-swiftclient==2.3.1 |
877 | 44 | pytz==2014.7 | 85 | pytz==2014.7 |
878 | 86 | ======= | ||
879 | 87 | djangocms-video==0.2.0 | ||
880 | 88 | python-keystoneclient==1.3.3 | ||
881 | 89 | python-swiftclient==2.6.0 | ||
882 | 90 | pytz==2015.7 | ||
883 | 91 | >>>>>>> MERGE-SOURCE | ||
884 | 45 | simple-translation==0.8.6 | 92 | simple-translation==0.8.6 |
885 | 46 | simplejson==3.6.5 | 93 | simplejson==3.6.5 |
886 | 47 | wsgiref==0.1.2 | 94 | wsgiref==0.1.2 |