Merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/hero-tour-changes into lp:developer-ubuntu-com
- hero-tour-changes
- Merge into stable
Proposed by
Daniel Holbach
on 2016-03-08
| Status: | Superseded |
|---|---|
| Proposed branch: | lp:~developer-ubuntu-com-dev/developer-ubuntu-com/hero-tour-changes |
| Merge into: | lp:developer-ubuntu-com |
| Diff against target: |
744 lines (+344/-87) 10 files modified
md_importer/importer/__init__.py (+18/-0) md_importer/importer/article.py (+43/-24) md_importer/importer/process.py (+3/-1) md_importer/importer/publish.py (+66/-27) md_importer/importer/repo.py (+18/-8) md_importer/migrations/0002_hero_tour_changes.py (+24/-0) md_importer/models.py (+21/-0) md_importer/tests/test_branch_import.py (+106/-6) md_importer/tests/test_link_rewrite.py (+42/-20) md_importer/tests/utils.py (+3/-1) |
| To merge this branch: | bzr merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/hero-tour-changes |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Ubuntu App Developer site developers | 2016-03-08 | Pending | |
|
Review via email:
|
|||
This proposal has been superseded by a proposal from 2016-03-08.
Commit Message
Description of the Change
To post a comment you must log in.
lp:~developer-ubuntu-com-dev/developer-ubuntu-com/hero-tour-changes
updated
on 2016-03-22
- 219. By David Callé on 2016-03-08
-
Add first iteration of the snappy hero tour
- 220. By Daniel Holbach on 2016-03-09
-
fix unicode craziness
- 221. By Daniel Holbach on 2016-03-09
-
extend db_add_empty_page do you can define your own slug
- 222. By Daniel Holbach on 2016-03-10
-
add tests for hero tour IA
- 223. By Daniel Holbach on 2016-03-21
-
merge trunk
- 224. By Daniel Holbach on 2016-03-22
-
merge trunk
- 225. By Daniel Holbach on 2016-03-22
-
adapt url expectation to a crash David was seeing
- 226. By Daniel Holbach on 2016-03-22
-
make pep8/pyflakes happy
- 227. By Daniel Holbach on 2016-03-22
-
apply fix from David - thanks
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'md_importer/importer/__init__.py' |
| 2 | --- md_importer/importer/__init__.py 2016-01-12 11:44:04 +0000 |
| 3 | +++ md_importer/importer/__init__.py 2016-03-08 13:25:48 +0000 |
| 4 | @@ -1,5 +1,23 @@ |
| 5 | from developer_portal.settings import LANGUAGE_CODE |
| 6 | |
| 7 | +from md_importer.models import ExternalDocsBranchImportDirective |
| 8 | + |
| 9 | DEFAULT_LANG = LANGUAGE_CODE |
| 10 | HOME_PAGE_URL = '/{}/'.format(DEFAULT_LANG) |
| 11 | SUPPORTED_ARTICLE_TYPES = ['.md', '.html'] |
| 12 | + |
| 13 | +# Instead of just using pymdownx.github, we go with these because of |
| 14 | +# https://github.com/facelessuser/pymdown-extensions/issues/11 |
| 15 | +MARKDOWN_EXTENSIONS = [ |
| 16 | + 'markdown.extensions.tables', |
| 17 | + 'pymdownx.magiclink', |
| 18 | + 'pymdownx.betterem', |
| 19 | + 'pymdownx.tilde', |
| 20 | + 'pymdownx.githubemoji', |
| 21 | + 'pymdownx.tasklist', |
| 22 | + 'pymdownx.superfences', |
| 23 | +] |
| 24 | + |
| 25 | +model_info = ExternalDocsBranchImportDirective._meta |
| 26 | +TEMPLATE_CHOICES = model_info.get_field('template').choices |
| 27 | +DEFAULT_TEMPLATE = model_info.get_field('template').default |
| 28 | |
| 29 | === modified file 'md_importer/importer/article.py' |
| 30 | --- md_importer/importer/article.py 2016-01-15 13:56:34 +0000 |
| 31 | +++ md_importer/importer/article.py 2016-03-08 13:25:48 +0000 |
| 32 | @@ -8,9 +8,10 @@ |
| 33 | |
| 34 | from . import ( |
| 35 | DEFAULT_LANG, |
| 36 | + MARKDOWN_EXTENSIONS, |
| 37 | SUPPORTED_ARTICLE_TYPES, |
| 38 | ) |
| 39 | -from .publish import get_or_create_page, slugify |
| 40 | +from .publish import get_or_create_page, slugify, update_page |
| 41 | |
| 42 | if sys.version_info.major == 2: |
| 43 | from urlparse import urlparse |
| 44 | @@ -19,7 +20,7 @@ |
| 45 | |
| 46 | |
| 47 | class Article: |
| 48 | - def __init__(self, fn, write_to): |
| 49 | + def __init__(self, fn, write_to, advertise, template): |
| 50 | self.html = None |
| 51 | self.page = None |
| 52 | self.title = "" |
| 53 | @@ -27,18 +28,20 @@ |
| 54 | self.write_to = slugify(self.fn) |
| 55 | self.full_url = write_to |
| 56 | self.slug = os.path.basename(self.full_url) |
| 57 | + self.links_rewritten = False |
| 58 | + self.local_images = [] |
| 59 | + self.advertise = advertise |
| 60 | + self.template = template |
| 61 | |
| 62 | def _find_local_images(self): |
| 63 | '''Local images are currently not supported.''' |
| 64 | soup = BeautifulSoup(self.html, 'html5lib') |
| 65 | - local_images = [] |
| 66 | for img in soup.find_all('img'): |
| 67 | if img.has_attr('src'): |
| 68 | (scheme, netloc, path, params, query, fragment) = \ |
| 69 | urlparse(img.attrs['src']) |
| 70 | if scheme not in ['http', 'https']: |
| 71 | - local_images.extend([img.attrs['src']]) |
| 72 | - return local_images |
| 73 | + self.local_images.extend([img.attrs['src']]) |
| 74 | |
| 75 | def read(self): |
| 76 | if os.path.splitext(self.fn)[1] not in SUPPORTED_ARTICLE_TYPES: |
| 77 | @@ -50,13 +53,13 @@ |
| 78 | self.html = markdown.markdown( |
| 79 | f.read(), |
| 80 | output_format='html5', |
| 81 | - extensions=['pymdownx.github']) |
| 82 | + extensions=MARKDOWN_EXTENSIONS) |
| 83 | elif self.fn.endswith('.html'): |
| 84 | self.html = f.read() |
| 85 | - local_images = self._find_local_images() |
| 86 | - if local_images: |
| 87 | + self._find_local_images() |
| 88 | + if self.local_images: |
| 89 | logging.error('Found the following local image(s): {}'.format( |
| 90 | - ', '.join(local_images) |
| 91 | + ', '.join(self.local_images) |
| 92 | )) |
| 93 | return False |
| 94 | self.title = self._read_title() |
| 95 | @@ -73,10 +76,15 @@ |
| 96 | return slugify(self.fn).replace('-', ' ').title() |
| 97 | |
| 98 | def _remove_body_and_html_tags(self): |
| 99 | - self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, |
| 100 | - flags=re.MULTILINE) |
| 101 | - self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, |
| 102 | - flags=re.MULTILINE) |
| 103 | + for regex in [ |
| 104 | + # These are added by markdown.markdown |
| 105 | + r'\s*<html>\s*<body>\s*', |
| 106 | + r'\s*<\/body>\s*<\/html>\s*', |
| 107 | + # This is added by BeautifulSoup.prettify |
| 108 | + r'\s*<html>\s*<head>\s*<\/head>\s*<body>\s*', |
| 109 | + ]: |
| 110 | + self.html = re.sub(regex, '', self.html, |
| 111 | + flags=re.MULTILINE) |
| 112 | |
| 113 | def _use_developer_site_style(self): |
| 114 | begin = (u"<div class=\"row no-border\">" |
| 115 | @@ -92,7 +100,6 @@ |
| 116 | |
| 117 | def replace_links(self, titles, url_map): |
| 118 | soup = BeautifulSoup(self.html, 'html5lib') |
| 119 | - change = False |
| 120 | for link in soup.find_all('a'): |
| 121 | if not link.has_attr('class') or \ |
| 122 | 'headeranchor-link' not in link.attrs['class']: |
| 123 | @@ -100,25 +107,35 @@ |
| 124 | if title.endswith(link.attrs['href']) and \ |
| 125 | link.attrs['href'] != url_map[title].full_url: |
| 126 | link.attrs['href'] = url_map[title].full_url |
| 127 | - change = True |
| 128 | - if change: |
| 129 | + if not link.attrs['href'].startswith('/'): |
| 130 | + link.attrs['href'] = '/' + link.attrs['href'] |
| 131 | + self.links_rewritten = True |
| 132 | + if self.links_rewritten: |
| 133 | self.html = soup.prettify() |
| 134 | - return change |
| 135 | + self._remove_body_and_html_tags() |
| 136 | |
| 137 | def add_to_db(self): |
| 138 | '''Publishes pages in their branch alias namespace.''' |
| 139 | self.page = get_or_create_page( |
| 140 | title=self.title, full_url=self.full_url, menu_title=self.title, |
| 141 | - html=self.html) |
| 142 | + html=self.html, in_navigation=self.advertise, |
| 143 | + template=self.template) |
| 144 | if not self.page: |
| 145 | return False |
| 146 | - self.full_url = self.page.get_absolute_url() |
| 147 | + self.full_url = re.sub( |
| 148 | + r'^\/None\/', '/{}/'.format(DEFAULT_LANG), |
| 149 | + self.page.get_absolute_url()) |
| 150 | return True |
| 151 | |
| 152 | def publish(self): |
| 153 | + if self.links_rewritten: |
| 154 | + update_page(self.page, title=self.title, full_url=self.full_url, |
| 155 | + menu_title=self.title, html=self.html, |
| 156 | + in_navigation=self.advertise, template=self.template) |
| 157 | if self.page.is_dirty(DEFAULT_LANG): |
| 158 | self.page.publish(DEFAULT_LANG) |
| 159 | - self.page = self.page.get_public_object() |
| 160 | + if self.page.get_public_object(): |
| 161 | + self.page = self.page.get_public_object() |
| 162 | return self.page |
| 163 | |
| 164 | |
| 165 | @@ -128,14 +145,16 @@ |
| 166 | def read(self): |
| 167 | if not Article.read(self): |
| 168 | return False |
| 169 | - self.release_alias = re.findall(r'snappy/guides/(\S+?)/\S+?', |
| 170 | - self.full_url)[0] |
| 171 | + matches = re.findall(r'snappy/guides/(\S+?)/\S+?', |
| 172 | + self.full_url) |
| 173 | + if matches: |
| 174 | + self.release_alias = matches[0] |
| 175 | self._make_snappy_mods() |
| 176 | return True |
| 177 | |
| 178 | def _make_snappy_mods(self): |
| 179 | # Make sure the reader knows which documentation she is browsing |
| 180 | - if self.release_alias != 'current': |
| 181 | + if self.release_alias and self.release_alias != 'current': |
| 182 | before = (u"<div class=\"row no-border\">\n" |
| 183 | "<div class=\"eight-col\">\n") |
| 184 | after = (u"<div class=\"row no-border\">\n" |
| 185 | @@ -158,6 +177,6 @@ |
| 186 | redirect="/snappy/guides/current/{}".format(self.slug)) |
| 187 | if not page: |
| 188 | return False |
| 189 | - else: |
| 190 | + elif self.release_alias: |
| 191 | self.title += " (%s)" % (self.release_alias,) |
| 192 | return Article.add_to_db(self) |
| 193 | |
| 194 | === modified file 'md_importer/importer/process.py' |
| 195 | --- md_importer/importer/process.py 2016-01-16 00:36:39 +0000 |
| 196 | +++ md_importer/importer/process.py 2016-03-08 13:25:48 +0000 |
| 197 | @@ -22,7 +22,9 @@ |
| 198 | for directive in ExternalDocsBranchImportDirective.objects.filter( |
| 199 | external_docs_branch=branch): |
| 200 | repo.add_directive(directive.import_from, |
| 201 | - directive.write_to) |
| 202 | + directive.write_to, |
| 203 | + directive.advertise, |
| 204 | + directive.template) |
| 205 | if not repo.execute_import_directives(): |
| 206 | return False |
| 207 | if not repo.publish(): |
| 208 | |
| 209 | === modified file 'md_importer/importer/publish.py' |
| 210 | --- md_importer/importer/publish.py 2016-01-15 13:58:39 +0000 |
| 211 | +++ md_importer/importer/publish.py 2016-03-08 13:25:48 +0000 |
| 212 | @@ -1,14 +1,25 @@ |
| 213 | -from md_importer.importer import DEFAULT_LANG, HOME_PAGE_URL |
| 214 | +from md_importer.importer import ( |
| 215 | + DEFAULT_LANG, |
| 216 | + DEFAULT_TEMPLATE, |
| 217 | + HOME_PAGE_URL, |
| 218 | +) |
| 219 | |
| 220 | from cms.api import create_page, add_plugin |
| 221 | from cms.models import Title |
| 222 | from djangocms_text_ckeditor.html import clean_html |
| 223 | |
| 224 | +from bs4 import BeautifulSoup |
| 225 | import logging |
| 226 | import re |
| 227 | import os |
| 228 | |
| 229 | |
| 230 | +def _compare_html(html_a, html_b): |
| 231 | + soup_a = BeautifulSoup(html_a, 'html5lib') |
| 232 | + soup_b = BeautifulSoup(html_b, 'html5lib') |
| 233 | + return (clean_html(soup_a.prettify()) == clean_html(soup_b.prettify())) |
| 234 | + |
| 235 | + |
| 236 | def slugify(filename): |
| 237 | return os.path.basename(filename).replace('.md', '').replace('.html', '') |
| 238 | |
| 239 | @@ -32,44 +43,72 @@ |
| 240 | return parent_pages[0].page |
| 241 | |
| 242 | |
| 243 | +def find_text_plugin(page): |
| 244 | + # We create the page, so we know there's just one placeholder |
| 245 | + placeholder = page.placeholders.all()[0] |
| 246 | + if placeholder.get_plugins(): |
| 247 | + return ( |
| 248 | + placeholder, |
| 249 | + placeholder.get_plugins()[0].get_plugin_instance()[0] |
| 250 | + ) |
| 251 | + return (placeholder, None) |
| 252 | + |
| 253 | + |
| 254 | +def update_page(page, title, full_url, menu_title=None, |
| 255 | + in_navigation=True, redirect=None, html=None, template=None): |
| 256 | + if page.get_title() != title: |
| 257 | + page.title = title |
| 258 | + if page.get_menu_title() != menu_title: |
| 259 | + page.menu_title = menu_title |
| 260 | + if page.in_navigation != in_navigation: |
| 261 | + page.in_navigation = in_navigation |
| 262 | + if page.get_redirect() != redirect: |
| 263 | + page.redirect = redirect |
| 264 | + if page.template != template: |
| 265 | + page.template = template |
| 266 | + if html: |
| 267 | + update = True |
| 268 | + (placeholder, plugin) = find_text_plugin(page) |
| 269 | + if plugin: |
| 270 | + if _compare_html(html, plugin.body): |
| 271 | + update = False |
| 272 | + elif page.get_public_object(): |
| 273 | + (dummy, published_plugin) = \ |
| 274 | + find_text_plugin(page.get_public_object()) |
| 275 | + if published_plugin: |
| 276 | + if _compare_html(html, published_plugin.body): |
| 277 | + update = False |
| 278 | + if update: |
| 279 | + plugin.body = html |
| 280 | + plugin.save() |
| 281 | + else: |
| 282 | + # Reset draft |
| 283 | + page.get_draft_object().revert(DEFAULT_LANG) |
| 284 | + else: |
| 285 | + add_plugin( |
| 286 | + placeholder, 'RawHtmlPlugin', |
| 287 | + DEFAULT_LANG, body=html) |
| 288 | + |
| 289 | + |
| 290 | def get_or_create_page(title, full_url, menu_title=None, |
| 291 | - in_navigation=True, redirect=None, html=None): |
| 292 | + in_navigation=True, redirect=None, html=None, |
| 293 | + template=DEFAULT_TEMPLATE): |
| 294 | # First check if pages already exist. |
| 295 | pages = Title.objects.select_related('page').filter( |
| 296 | path__regex=full_url).filter(publisher_is_draft=True) |
| 297 | if pages: |
| 298 | page = pages[0].page |
| 299 | - if page.get_title() != title: |
| 300 | - page.title = title |
| 301 | - if page.get_menu_title() != menu_title: |
| 302 | - page.menu_title = menu_title |
| 303 | - if page.in_navigation != in_navigation: |
| 304 | - page.in_navigation = in_navigation |
| 305 | - if page.get_redirect() != redirect: |
| 306 | - page.redirect = redirect |
| 307 | - if html: |
| 308 | - # We create the page, so we know there's just one placeholder |
| 309 | - placeholder = page.placeholders.all()[0] |
| 310 | - if placeholder.get_plugins(): |
| 311 | - plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] |
| 312 | - if plugin.body != clean_html(html, full=False): |
| 313 | - plugin.body = html |
| 314 | - plugin.save() |
| 315 | - else: |
| 316 | - add_plugin( |
| 317 | - placeholder, 'RawHtmlPlugin', |
| 318 | - DEFAULT_LANG, body=html) |
| 319 | + update_page(page, title, full_url, menu_title, in_navigation, |
| 320 | + redirect, html, template) |
| 321 | else: |
| 322 | parent = _find_parent(full_url) |
| 323 | if not parent: |
| 324 | return None |
| 325 | slug = os.path.basename(full_url) |
| 326 | page = create_page( |
| 327 | - title, 'default.html', DEFAULT_LANG, slug=slug, parent=parent, |
| 328 | - menu_title=menu_title, in_navigation=in_navigation, |
| 329 | + title=title, template=template, language=DEFAULT_LANG, slug=slug, |
| 330 | + parent=parent, menu_title=menu_title, in_navigation=in_navigation, |
| 331 | position='last-child', redirect=redirect) |
| 332 | - placeholder = page.placeholders.get() |
| 333 | + placeholder = page.placeholders.all()[0] |
| 334 | add_plugin(placeholder, 'RawHtmlPlugin', DEFAULT_LANG, body=html) |
| 335 | - placeholder = page.placeholders.all()[0] |
| 336 | - plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] |
| 337 | return page |
| 338 | |
| 339 | === modified file 'md_importer/importer/repo.py' |
| 340 | --- md_importer/importer/repo.py 2016-01-15 18:54:50 +0000 |
| 341 | +++ md_importer/importer/repo.py 2016-03-08 13:25:48 +0000 |
| 342 | @@ -6,6 +6,8 @@ |
| 343 | from .publish import get_or_create_page, slugify |
| 344 | from .source import SourceCode |
| 345 | |
| 346 | +from md_importer.models import ExternalDocsBranchImportDirective |
| 347 | + |
| 348 | import glob |
| 349 | import logging |
| 350 | import os |
| 351 | @@ -56,12 +58,18 @@ |
| 352 | return 1 |
| 353 | return 0 |
| 354 | |
| 355 | - def add_directive(self, import_from, write_to): |
| 356 | + def add_directive(self, import_from, write_to, advertise=True, |
| 357 | + template=None): |
| 358 | + if template is None: |
| 359 | + model_info = ExternalDocsBranchImportDirective._meta |
| 360 | + template = model_info.get_field('template').default |
| 361 | self.directives += [ |
| 362 | { |
| 363 | 'import_from': os.path.join(self.checkout_location, |
| 364 | import_from), |
| 365 | - 'write_to': write_to |
| 366 | + 'write_to': write_to, |
| 367 | + 'advertise': advertise, |
| 368 | + 'template': template, |
| 369 | } |
| 370 | ] |
| 371 | |
| 372 | @@ -71,7 +79,8 @@ |
| 373 | for directive in [d for d in self.directives |
| 374 | if os.path.isfile(d['import_from'])]: |
| 375 | import_list += [ |
| 376 | - (directive['import_from'], directive['write_to']) |
| 377 | + (directive['import_from'], directive['write_to'], |
| 378 | + directive['advertise'], directive['template']) |
| 379 | ] |
| 380 | # Import directories next |
| 381 | for directive in [d for d in self.directives |
| 382 | @@ -79,7 +88,8 @@ |
| 383 | for fn in glob.glob('{}/*'.format(directive['import_from'])): |
| 384 | if fn not in [a[0] for a in import_list]: |
| 385 | import_list += [ |
| 386 | - (fn, os.path.join(directive['write_to'], slugify(fn))) |
| 387 | + (fn, os.path.join(directive['write_to'], slugify(fn)), |
| 388 | + directive['advertise'], directive['template']) |
| 389 | ] |
| 390 | # If we import into a namespace and don't have an index doc, |
| 391 | # we need to write one. |
| 392 | @@ -91,7 +101,8 @@ |
| 393 | return False |
| 394 | # The actual import |
| 395 | for entry in import_list: |
| 396 | - article = self._read_article(entry[0], entry[1]) |
| 397 | + article = self._read_article( |
| 398 | + entry[0], entry[1], entry[2], entry[3]) |
| 399 | if article: |
| 400 | self.imported_articles += [article] |
| 401 | self.titles[article.fn] = article.title |
| 402 | @@ -106,8 +117,8 @@ |
| 403 | self._write_fake_index_doc() |
| 404 | return True |
| 405 | |
| 406 | - def _read_article(self, fn, write_to): |
| 407 | - article = self.article_class(fn, write_to) |
| 408 | + def _read_article(self, fn, write_to, advertise, template): |
| 409 | + article = self.article_class(fn, write_to, advertise, template) |
| 410 | if article.read(): |
| 411 | return article |
| 412 | return None |
| 413 | @@ -118,7 +129,6 @@ |
| 414 | logging.error('Publishing of {} aborted.'.format(self.origin)) |
| 415 | return False |
| 416 | article.replace_links(self.titles, self.url_map) |
| 417 | - self.pages = [] |
| 418 | for article in self.imported_articles: |
| 419 | self.pages.extend([article.publish()]) |
| 420 | if self.index_page: |
| 421 | |
| 422 | === added file 'md_importer/migrations/0002_hero_tour_changes.py' |
| 423 | --- md_importer/migrations/0002_hero_tour_changes.py 1970-01-01 00:00:00 +0000 |
| 424 | +++ md_importer/migrations/0002_hero_tour_changes.py 2016-03-08 13:25:48 +0000 |
| 425 | @@ -0,0 +1,24 @@ |
| 426 | +# -*- coding: utf-8 -*- |
| 427 | +from __future__ import unicode_literals |
| 428 | + |
| 429 | +from django.db import migrations, models |
| 430 | + |
| 431 | + |
| 432 | +class Migration(migrations.Migration): |
| 433 | + |
| 434 | + dependencies = [ |
| 435 | + ('md_importer', '0001_initial'), |
| 436 | + ] |
| 437 | + |
| 438 | + operations = [ |
| 439 | + migrations.AddField( |
| 440 | + model_name='externaldocsbranchimportdirective', |
| 441 | + name='advertise', |
| 442 | + field=models.BooleanField(default=True, help_text='Should the imported articles be listed in the navigation? Default: yes.'), |
| 443 | + ), |
| 444 | + migrations.AddField( |
| 445 | + model_name='externaldocsbranchimportdirective', |
| 446 | + name='template', |
| 447 | + field=models.CharField(default=b'default.html', help_text='Django CMS template to use for the imported articles. Default: default.html', max_length=50, choices=[(b'default.html', b'Default'), (b'landing_page.html', b'Landing Page'), (b'no_subnav.html', b'Without Subnav'), (b'with_hero.html', b'With Hero')]), |
| 448 | + ), |
| 449 | + ] |
| 450 | |
| 451 | === modified file 'md_importer/models.py' |
| 452 | --- md_importer/models.py 2016-01-11 08:10:08 +0000 |
| 453 | +++ md_importer/models.py 2016-03-08 13:25:48 +0000 |
| 454 | @@ -1,9 +1,18 @@ |
| 455 | +from django.conf import settings |
| 456 | from django.db import models |
| 457 | from django.utils.translation import ugettext_lazy as _ |
| 458 | |
| 459 | from cms.models import Page |
| 460 | |
| 461 | |
| 462 | +if settings.CMS_TEMPLATES: |
| 463 | + cms_templates = settings.CMS_TEMPLATES |
| 464 | +else: |
| 465 | + cms_templates = ( |
| 466 | + ('default.html', 'Default'), |
| 467 | + ) |
| 468 | + |
| 469 | + |
| 470 | class ExternalDocsBranch(models.Model): |
| 471 | origin = models.CharField( |
| 472 | max_length=200, |
| 473 | @@ -43,6 +52,18 @@ |
| 474 | help_text=_('Article URL (for a specific file) or article namespace ' |
| 475 | 'for a directory or a set of files.'), |
| 476 | blank=True) |
| 477 | + advertise = models.BooleanField( |
| 478 | + default=True, |
| 479 | + help_text=_('Should the imported articles be listed in the ' |
| 480 | + 'navigation? Default: yes.'), |
| 481 | + ) |
| 482 | + template = models.CharField( |
| 483 | + max_length=50, |
| 484 | + default=cms_templates[0][0], |
| 485 | + choices=cms_templates, |
| 486 | + help_text=_('Django CMS template to use for the imported articles. ' |
| 487 | + 'Default: {}'.format(cms_templates[0][0])), |
| 488 | + ) |
| 489 | |
| 490 | def __str__(self): |
| 491 | return "{} -- {}".format(self.external_docs_branch, |
| 492 | |
| 493 | === modified file 'md_importer/tests/test_branch_import.py' |
| 494 | --- md_importer/tests/test_branch_import.py 2016-01-15 13:59:32 +0000 |
| 495 | +++ md_importer/tests/test_branch_import.py 2016-03-08 13:25:48 +0000 |
| 496 | @@ -2,9 +2,15 @@ |
| 497 | import pytz |
| 498 | import shutil |
| 499 | |
| 500 | -from cms.models import CMSPlugin, Page |
| 501 | +from cms.models import Page |
| 502 | |
| 503 | +from md_importer.importer import ( |
| 504 | + DEFAULT_TEMPLATE, |
| 505 | + TEMPLATE_CHOICES, |
| 506 | +) |
| 507 | from md_importer.importer.article import Article |
| 508 | +from md_importer.importer.publish import find_text_plugin |
| 509 | + |
| 510 | from .utils import TestLocalBranchImport |
| 511 | |
| 512 | |
| 513 | @@ -66,6 +72,71 @@ |
| 514 | self.assertEqual(page.parent_id, self.root.id) |
| 515 | |
| 516 | |
| 517 | +class TestArticleHTMLTagsAfterImport(TestLocalBranchImport): |
| 518 | + def runTest(self): |
| 519 | + self.create_repo('data/snapcraft-test') |
| 520 | + self.repo.add_directive('docs', '') |
| 521 | + self.assertEqual(len(self.repo.directives), 1) |
| 522 | + self.assertTrue(self.repo.execute_import_directives()) |
| 523 | + self.assertGreater(len(self.repo.imported_articles), 3) |
| 524 | + self.assertTrue(self.repo.publish()) |
| 525 | + pages = Page.objects.all() |
| 526 | + self.assertGreater(pages.count(), len(self.repo.imported_articles)) |
| 527 | + for article in self.repo.imported_articles: |
| 528 | + self.assertIsInstance(article, Article) |
| 529 | + self.assertNotIn('<body>', article.html) |
| 530 | + self.assertNotIn('<body>', article.html) |
| 531 | + |
| 532 | + |
| 533 | +class TestNoneInURLAfterImport(TestLocalBranchImport): |
| 534 | + def runTest(self): |
| 535 | + self.create_repo('data/snapcraft-test') |
| 536 | + self.repo.add_directive('docs', '') |
| 537 | + self.assertEqual(len(self.repo.directives), 1) |
| 538 | + self.assertTrue(self.repo.execute_import_directives()) |
| 539 | + self.assertGreater(len(self.repo.imported_articles), 3) |
| 540 | + self.assertTrue(self.repo.publish()) |
| 541 | + pages = Page.objects.all() |
| 542 | + self.assertGreater(pages.count(), len(self.repo.imported_articles)) |
| 543 | + for article in self.repo.imported_articles: |
| 544 | + self.assertIsInstance(article, Article) |
| 545 | + self.assertNotIn('/None/', article.full_url) |
| 546 | + for page in pages: |
| 547 | + self.assertIsNotNone(page.get_slug()) |
| 548 | + |
| 549 | + |
| 550 | +class TestAdvertiseImport(TestLocalBranchImport): |
| 551 | + '''Check if all imported articles are advertised in the navigation when |
| 552 | + using defaults.''' |
| 553 | + def runTest(self): |
| 554 | + self.create_repo('data/snapcraft-test') |
| 555 | + self.repo.add_directive('docs', '') |
| 556 | + self.assertTrue(self.repo.execute_import_directives()) |
| 557 | + for article in self.repo.imported_articles: |
| 558 | + self.assertTrue(article.advertise) |
| 559 | + self.assertTrue(self.repo.publish()) |
| 560 | + for page in Page.objects.filter(publisher_is_draft=False): |
| 561 | + if page.parent is not None: |
| 562 | + self.assertEqual(page.parent_id, self.root.id) |
| 563 | + self.assertTrue(page.in_navigation) |
| 564 | + |
| 565 | + |
| 566 | +class TestNoAdvertiseImport(TestLocalBranchImport): |
| 567 | + '''Check if all imported articles are advertised in the navigation when |
| 568 | + using defaults.''' |
| 569 | + def runTest(self): |
| 570 | + self.create_repo('data/snapcraft-test') |
| 571 | + self.repo.add_directive('docs', '', advertise=False) |
| 572 | + self.assertTrue(self.repo.execute_import_directives()) |
| 573 | + for article in self.repo.imported_articles: |
| 574 | + self.assertFalse(article.advertise) |
| 575 | + self.assertTrue(self.repo.publish()) |
| 576 | + for page in Page.objects.filter(publisher_is_draft=False): |
| 577 | + if page.parent is not None: |
| 578 | + self.assertEqual(page.parent_id, self.root.id) |
| 579 | + self.assertFalse(page.in_navigation) |
| 580 | + |
| 581 | + |
| 582 | class TestTwiceImport(TestLocalBranchImport): |
| 583 | '''Run import on the same contents twice, make sure we don't |
| 584 | add new pages over and over again.''' |
| 585 | @@ -101,9 +172,9 @@ |
| 586 | self.assertEqual( |
| 587 | Page.objects.filter(publisher_is_draft=False).count(), |
| 588 | len(self.repo.imported_articles)+1) # articles + root |
| 589 | + shutil.rmtree(self.tempdir) |
| 590 | # Take the time before publishing the second import |
| 591 | now = datetime.now(pytz.utc) |
| 592 | - shutil.rmtree(self.tempdir) |
| 593 | # Run second import |
| 594 | self.create_repo('data/snapcraft-test') |
| 595 | self.repo.add_directive('docs', '') |
| 596 | @@ -112,7 +183,36 @@ |
| 597 | self.assertTrue(self.repo.execute_import_directives()) |
| 598 | self.assertTrue(self.repo.publish()) |
| 599 | # Check the page's plugins |
| 600 | - for plugin_change in CMSPlugin.objects.filter( |
| 601 | - plugin_type='RawHtmlPlugin').order_by( |
| 602 | - '-changed_date'): |
| 603 | - self.assertGreater(now, plugin_change.changed_date) |
| 604 | + for page in Page.objects.filter(publisher_is_draft=False): |
| 605 | + if page != self.root: |
| 606 | + (dummy, plugin) = find_text_plugin(page) |
| 607 | + self.assertGreater(now, plugin.changed_date) |
| 608 | + |
| 609 | + |
| 610 | +class TestImportNoTemplateChange(TestLocalBranchImport): |
| 611 | + '''Check if all imported articles use the default template.''' |
| 612 | + def runTest(self): |
| 613 | + self.create_repo('data/snapcraft-test') |
| 614 | + self.repo.add_directive('docs', '') |
| 615 | + self.assertTrue(self.repo.execute_import_directives()) |
| 616 | + for article in self.repo.imported_articles: |
| 617 | + self.assertEqual(article.template, DEFAULT_TEMPLATE) |
| 618 | + self.assertTrue(self.repo.publish()) |
| 619 | + for page in Page.objects.filter(publisher_is_draft=False): |
| 620 | + if page.parent is not None: |
| 621 | + self.assertEqual(page.template, DEFAULT_TEMPLATE) |
| 622 | + |
| 623 | + |
| 624 | +class TestImportTemplateChange(TestLocalBranchImport): |
| 625 | + '''Check if all imported articles use the desired template.''' |
| 626 | + def runTest(self): |
| 627 | + self.create_repo('data/snapcraft-test') |
| 628 | + template_to_use = TEMPLATE_CHOICES[1][0] |
| 629 | + self.repo.add_directive('docs', '', template=template_to_use) |
| 630 | + self.assertTrue(self.repo.execute_import_directives()) |
| 631 | + for article in self.repo.imported_articles: |
| 632 | + self.assertEqual(article.template, template_to_use) |
| 633 | + self.assertTrue(self.repo.publish()) |
| 634 | + for page in Page.objects.filter(publisher_is_draft=False): |
| 635 | + if page.parent is not None: |
| 636 | + self.assertEqual(page.template, template_to_use) |
| 637 | |
| 638 | === modified file 'md_importer/tests/test_link_rewrite.py' |
| 639 | --- md_importer/tests/test_link_rewrite.py 2016-01-11 14:38:51 +0000 |
| 640 | +++ md_importer/tests/test_link_rewrite.py 2016-03-08 13:25:48 +0000 |
| 641 | @@ -30,6 +30,11 @@ |
| 642 | link.attrs['href'], |
| 643 | ', '.join([p.get_absolute_url() for p in pages]))) |
| 644 | self.assertIn(page, pages) |
| 645 | + if article.slug == 'file1': |
| 646 | + for link in soup.find_all('a'): |
| 647 | + if not link.has_attr('class') or \ |
| 648 | + 'headeranchor-link' not in link.attrs['class']: |
| 649 | + self.assertEqual(link.attrs['href'], '/file2') |
| 650 | |
| 651 | |
| 652 | class TestLinkBrokenRewrite(TestLocalBranchImport): |
| 653 | @@ -45,12 +50,33 @@ |
| 654 | self.assertEqual(article.page.parent, self.root) |
| 655 | soup = BeautifulSoup(article.html, 'html5lib') |
| 656 | for link in soup.find_all('a'): |
| 657 | - if link.has_attr('class') and \ |
| 658 | - 'headeranchor-link' in link.attrs['class']: |
| 659 | - break |
| 660 | - page = self.check_local_link(link.attrs['href']) |
| 661 | - self.assertIsNone(page) |
| 662 | - self.assertNotIn(page, pages) |
| 663 | + if not link.has_attr('class') or \ |
| 664 | + 'headeranchor-link' not in link.attrs['class']: |
| 665 | + page = self.check_local_link(link.attrs['href']) |
| 666 | + self.assertIsNone(page) |
| 667 | + self.assertNotIn(page, pages) |
| 668 | + |
| 669 | + |
| 670 | +class TestNoneNotInLinks(TestLocalBranchImport): |
| 671 | + def runTest(self): |
| 672 | + self.create_repo('data/snapcraft-test') |
| 673 | + snappy_page = db_add_empty_page('Snappy', self.root) |
| 674 | + self.assertFalse(snappy_page.publisher_is_draft) |
| 675 | + build_apps = db_add_empty_page('Build Apps', snappy_page) |
| 676 | + self.assertFalse(build_apps.publisher_is_draft) |
| 677 | + self.assertEqual( |
| 678 | + 3, Page.objects.filter(publisher_is_draft=False).count()) |
| 679 | + self.repo.add_directive('docs/intro.md', 'snappy/build-apps/current') |
| 680 | + self.repo.add_directive('docs', 'snappy/build-apps/current') |
| 681 | + self.assertTrue(self.repo.execute_import_directives()) |
| 682 | + self.assertTrue(self.repo.publish()) |
| 683 | + for article in self.repo.imported_articles: |
| 684 | + self.assertTrue(isinstance(article, Article)) |
| 685 | + self.assertGreater(len(article.html), 0) |
| 686 | + soup = BeautifulSoup(article.html, 'html5lib') |
| 687 | + for link in soup.find_all('a'): |
| 688 | + if is_local_link(link): |
| 689 | + self.assertFalse(link.attrs['href'].startswith('/None/')) |
| 690 | |
| 691 | |
| 692 | class TestSnapcraftLinkRewrite(TestLocalBranchImport): |
| 693 | @@ -62,25 +88,21 @@ |
| 694 | self.assertFalse(build_apps.publisher_is_draft) |
| 695 | self.assertEqual( |
| 696 | 3, Page.objects.filter(publisher_is_draft=False).count()) |
| 697 | - self.repo.add_directive('docs', 'snappy/build-apps/devel') |
| 698 | - self.repo.add_directive('README.md', 'snappy/build-apps/devel') |
| 699 | - self.repo.add_directive( |
| 700 | - 'HACKING.md', 'snappy/build-apps/devel/hacking') |
| 701 | + self.repo.add_directive('docs/intro.md', 'snappy/build-apps/current') |
| 702 | + self.repo.add_directive('docs', 'snappy/build-apps/current') |
| 703 | self.assertTrue(self.repo.execute_import_directives()) |
| 704 | self.assertTrue(self.repo.publish()) |
| 705 | pages = Page.objects.all() |
| 706 | for article in self.repo.imported_articles: |
| 707 | self.assertTrue(isinstance(article, Article)) |
| 708 | self.assertGreater(len(article.html), 0) |
| 709 | - for article in self.repo.imported_articles: |
| 710 | soup = BeautifulSoup(article.html, 'html5lib') |
| 711 | for link in soup.find_all('a'): |
| 712 | - if not is_local_link(link): |
| 713 | - break |
| 714 | - page = self.check_local_link(link.attrs['href']) |
| 715 | - self.assertIsNotNone( |
| 716 | - page, |
| 717 | - msg='Link {} not found. Available pages: {}'.format( |
| 718 | - link.attrs['href'], |
| 719 | - ', '.join([p.get_absolute_url() for p in pages]))) |
| 720 | - self.assertIn(page, pages) |
| 721 | + if is_local_link(link): |
| 722 | + page = self.check_local_link(link.attrs['href']) |
| 723 | + self.assertIsNotNone( |
| 724 | + page, |
| 725 | + msg='Link {} not found. Available pages: {}'.format( |
| 726 | + link.attrs['href'], |
| 727 | + ', '.join([p.get_absolute_url() for p in pages]))) |
| 728 | + self.assertIn(page, pages) |
| 729 | |
| 730 | === modified file 'md_importer/tests/utils.py' |
| 731 | --- md_importer/tests/utils.py 2016-01-11 14:38:51 +0000 |
| 732 | +++ md_importer/tests/utils.py 2016-03-08 13:25:48 +0000 |
| 733 | @@ -55,8 +55,10 @@ |
| 734 | self.assertEqual(self.fetch_retcode, 0) |
| 735 | |
| 736 | def check_local_link(self, url): |
| 737 | + if not url.startswith('/'): |
| 738 | + url = '/' + url |
| 739 | if not url.startswith('/{}/'.format(DEFAULT_LANG)): |
| 740 | - url = '/{}/{}/'.format(DEFAULT_LANG, url) |
| 741 | + url = '/{}'.format(DEFAULT_LANG) + url |
| 742 | request = self.get_request(url) |
| 743 | page = get_page_from_request(request) |
| 744 | return page |

