Skip to content
This repository

Bug 730707 kumascript #164

Merged
merged 16 commits into from about 2 years ago

3 participants

Les Orchard luke crouch James Bennett
Les Orchard
Collaborator

Opening another PR for this, whether it's "done" or not. I've got quite a number of unreviewed commits piled up.

lmorchard added some commits
Les Orchard lmorchard bug 730707: progress checkpoint
* Switch back to out-of-box <% %> EJS escape sequences instead of {% %},
  since the ACE editor interferes with that less and the EJS docs make
  more sense that way.

  * TODO: Maybe need a schematic migration to convert existing templates
    in dev boxes at least:

      update wiki_document
      set html=replace(replace(html, '{%', '<%'), '%}', '%>')
      where slug like '%Template:%';

      update wiki_revision
      set content=replace(replace(content, '{%', '<%'), '%}', '%>')
      where slug like '%Template:%';

* Don't process views of Template:* pages through KumaScript

* Don't apply Bleach to Template:* source

* Reduce required length of page title to 2, so "en" and "CSS" can be
  edited.

* Whitelist a few more elements used in pages for Bleach.

* Add more elements with style attribute allowed by Bleach, whitelist a
  bunch of inline styles used in existing pages.

* Improvements to kumascript error display, broken out into an include.
  Includes edit / new links for Template documents behind scripts.

* Enable plain-text syntax highlighter brush

* KumaScript update
262ee17
Les Orchard lmorchard bug 730707: Rework section ID generation
* More closely match MindTouch for generating anchor IDs in pages, to
  help prevent breaking intra-wiki anchor links

* Honor an explicit autogenerated ID override where the `name` attribute
  is present. That should help break fewer page anchor links on existing
  pages.
716dd4a
Les Orchard lmorchard bug 730707: Bugfix for macro migration
* Bugfix for macro migration, where markup appears in the middle of a
  parameter. This happens in a lot of `note` and `warning` macros.
7f2f0d2
Les Orchard lmorchard bug 730707: Bugfix case-sensitivity in locale detection and migration 24f464d
Les Orchard lmorchard bug 730707: Title is not unique on documents.
* Remove vestigal title uniqueness enforcement that caused DB errors
d37b4c6
Les Orchard lmorchard bug 730707: Send doc metadata to kumascript
* Kumascript now accepts env vars as headers with base64/utf8/JSON
  encoded data structures

* Assemble some env vars from document metadata to send in the request
  to kumascript
395efcc
Les Orchard lmorchard Allow HEAD in document requests dfc3188
Les Orchard lmorchard Added more styles, to make CSS/CSS_References happier f3befb6
Les Orchard lmorchard Reintroducing some slugification in title -> slug tracking, mainly sp…
…aces to underscores
cf79314
Les Orchard lmorchard Remove unused category from document title view 90968e3
Les Orchard lmorchard Only prepopulate slug from title on new document page; disable AJAX t…
…itle/slug uniqueness check
e63f3a9
Les Orchard lmorchard Document title first in page title for edit page d2504ab
Les Orchard lmorchard Bugfix for non-ASCII chars in section IDs and edit links a567d83
Les Orchard lmorchard bug 730707: Accept ?include parameter on doc view to filter out class…
…="noinclude" blocks
04ef5eb
Les Orchard lmorchard bug 730707: Increase allowed document length from 100k to 300k 53809c2
Les Orchard lmorchard Kumascript update cb9588a
James Bennett ubernostrum commented on the diff
apps/wiki/content.py
@@ -102,6 +116,10 @@ def gen_id(self):
102 116 self.known_ids.add(id)
103 117 return id
104 118
  119 + def slugify(self, text):
  120 + """Turn the text content of a header into a slug for use in an ID"""
  121 + return (text.replace(' ', '_'))
2
James Bennett Collaborator

Is there any chance these IDs ever end up as part of a URL (not just a fragment identifier)? Looks like they do further down and if so, that's a potential Unicode issue -- we might want to do something like Django's own built-in slugify template filter, which has a little Unicode-normalization song-and-dance to produce a readable but URL-safe result.

Les Orchard Collaborator
lmorchard added a note

Yeah, these will probably end up in section editing URLs. :/ Need to look at this some more, because I want to make sure it matches up with existing anchor links from MindTouch. I don't think it quite does that all the way, either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
luke crouch groovecoder commented on the diff
apps/wiki/tests/test_views.py
@@ -459,6 +463,41 @@ def my_requests_get(url, headers=None, timeout=None):
459 463 for error in expected_errors['logs']:
460 464 ok_(error['message'] in response.content)
461 465
  466 + @mock.patch('requests.get')
  467 + def test_env_vars(self, mock_requests_get):
  468 + """Kumascript reports errors in HTTP headers, Kuma should display them"""
  469 +
  470 + # Now, trap the request from the view.
  471 + trap = {}
  472 + def my_requests_get(url, headers=None, timeout=None):
  473 + trap['headers'] = headers
  474 + return FakeResponse(
  475 + status_code=200,
  476 + body='HELLO WORLD',
  477 + headers={}
  478 + )
  479 + mock_requests_get.side_effect = my_requests_get
2
luke crouch Owner

why side_effect here?

Les Orchard Collaborator
lmorchard added a note

That's how my_requests_get gets called, and the headers trapped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
luke crouch groovecoder commented on the diff
media/js/wiki.js
@@ -39,7 +41,7 @@
39 41 initMetadataEditButton();
40 42 initSaveAndEditButtons();
41 43 initArticlePreview();
42   - initTitleAndSlugCheck();
  44 + // initTitleAndSlugCheck();
2
luke crouch Owner

just comment? should we delete altogether?

Les Orchard Collaborator
lmorchard added a note

Might be worth deleting... I just wanted to turn it off for now, and look into properly fixing it in the future. It's not updated for the Kuma world where locale + slug are unique rather than title + slug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
James Bennett ubernostrum commented on the diff
apps/wiki/content.py
((38 lines not shown))
  162 + # If this is a header, then scoop up the rest of the header and
  163 + # gather the text it contains.
  164 + start, text, tmp = token, [], []
  165 + while len(buffer):
  166 + token = buffer.pop(0)
  167 + tmp.append(token)
  168 + if token['type'] in ('Characters', 'SpaceCharacters'):
  169 + text.append(token['data'])
  170 + elif ('EndTag' == token['type'] and
  171 + start['name'] == token['name']):
  172 + # Note: This is naive, and doesn't track other
  173 + # start/end tags nested in the header. Odd things might
  174 + # happen in a case like <h1><h1></h1></h1>. But, that's
  175 + # invalid markup and the worst case should be a
  176 + # truncated ID because all the text wasn't accumulated.
  177 + break
3
James Bennett Collaborator

This may be a silly question, but the comment here made me think of it: is there any mechanism enforcing uniqueness of IDs within the document? What happens if IDs end up colliding?

Les Orchard Collaborator
lmorchard added a note

I kind of punted on that... There is a mechanism for uniqueness, but only for auto-generated IDs (eg. sect1, sect2, etc). For IDs based on element text or the name attribute, no uniqueness is enforced.

This is really a half-baked feature, ugh. :/

Les Orchard Collaborator
lmorchard added a note

FWIW, I just filed bug 747403 to remember to put more work into this feature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Les Orchard
Collaborator

Is there anything more to do or review here before merging?

luke crouch
Owner
luke crouch groovecoder merged commit d7f420a into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 16 unique commits by 1 author.

Apr 18, 2012
Les Orchard lmorchard bug 730707: progress checkpoint
* Switch back to out-of-box <% %> EJS escape sequences instead of {% %},
  since the ACE editor interferes with that less and the EJS docs make
  more sense that way.

  * TODO: Maybe need a schematic migration to convert existing templates
    in dev boxes at least:

      update wiki_document
      set html=replace(replace(html, '{%', '<%'), '%}', '%>')
      where slug like '%Template:%';

      update wiki_revision
      set content=replace(replace(content, '{%', '<%'), '%}', '%>')
      where slug like '%Template:%';

* Don't process views of Template:* pages through KumaScript

* Don't apply Bleach to Template:* source

* Reduce required length of page title to 2, so "en" and "CSS" can be
  edited.

* Whitelist a few more elements used in pages for Bleach.

* Add more elements with style attribute allowed by Bleach, whitelist a
  bunch of inline styles used in existing pages.

* Improvements to kumascript error display, broken out into an include.
  Includes edit / new links for Template documents behind scripts.

* Enable plain-text syntax highlighter brush

* KumaScript update
262ee17
Les Orchard lmorchard bug 730707: Rework section ID generation
* More closely match MindTouch for generating anchor IDs in pages, to
  help prevent breaking intra-wiki anchor links

* Honor an explicit autogenerated ID override where the `name` attribute
  is present. That should help break fewer page anchor links on existing
  pages.
716dd4a
Les Orchard lmorchard bug 730707: Bugfix for macro migration
* Bugfix for macro migration, where markup appears in the middle of a
  parameter. This happens in a lot of `note` and `warning` macros.
7f2f0d2
Les Orchard lmorchard bug 730707: Bugfix case-sensitivity in locale detection and migration 24f464d
Les Orchard lmorchard bug 730707: Title is not unique on documents.
* Remove vestigal title uniqueness enforcement that caused DB errors
d37b4c6
Les Orchard lmorchard bug 730707: Send doc metadata to kumascript
* Kumascript now accepts env vars as headers with base64/utf8/JSON
  encoded data structures

* Assemble some env vars from document metadata to send in the request
  to kumascript
395efcc
Les Orchard lmorchard Allow HEAD in document requests dfc3188
Les Orchard lmorchard Added more styles, to make CSS/CSS_References happier f3befb6
Les Orchard lmorchard Reintroducing some slugification in title -> slug tracking, mainly sp…
…aces to underscores
cf79314
Les Orchard lmorchard Remove unused category from document title view 90968e3
Les Orchard lmorchard Only prepopulate slug from title on new document page; disable AJAX t…
…itle/slug uniqueness check
e63f3a9
Les Orchard lmorchard Document title first in page title for edit page d2504ab
Les Orchard lmorchard Bugfix for non-ASCII chars in section IDs and edit links a567d83
Les Orchard lmorchard bug 730707: Accept ?include parameter on doc view to filter out class…
…="noinclude" blocks
04ef5eb
Les Orchard lmorchard bug 730707: Increase allowed document length from 100k to 300k 53809c2
Les Orchard lmorchard Kumascript update cb9588a
This page is out of date. Refresh to see the latest.
10 apps/dekicompat/management/commands/migrate_to_kuma_wiki.py
@@ -741,15 +741,15 @@ def convert_dekiscript_template(self, pt):
741 741 This is an incomplete process, but it tries to take care off as much as
742 742 it can so that human intervention is minimized."""
743 743
744   - # Many templates start with this prefix, which corresponds to {% in EJS
  744 + # Many templates start with this prefix, which corresponds to <% in EJS
745 745 pre = '<pre class="script">'
746 746 if pt.startswith(pre):
747   - pt = "{%%\n%s" % pt[len(pre):]
  747 + pt = "<%%\n%s" % pt[len(pre):]
748 748
749   - # Many templates end with this postfix, which corresponds to %} in EJS
  749 + # Many templates end with this postfix, which corresponds to %> in EJS
750 750 post = '</pre>'
751 751 if pt.endswith(post):
752   - pt = "%s\n%%}" % pt[:0-len(post)]
  752 + pt = "%s\n%%>" % pt[:0-len(post)]
753 753
754 754 # Template source is usually HTML encoded inside the <pre>
755 755 pt = (pt.replace('&amp;', '&')
@@ -817,7 +817,7 @@ def get_kuma_locale_and_slug_for_page(self, r):
817 817 if '/' in title:
818 818 # Treat the first part of the slug path as locale and snip it off.
819 819 mt_language, new_title = title.split('/', 1)
820   - if mt_language in MT_TO_KUMA_LOCALE_MAP:
  820 + if mt_language.lower() in MT_TO_KUMA_LOCALE_MAP:
821 821 # If it's a known language, then rebuild the slug
822 822 slug = '%s%s' % (ns_name, new_title)
823 823 else:
121 apps/wiki/content.py
... ... @@ -1,8 +1,12 @@
  1 +import logging
1 2 import re
2 3 from urllib import urlencode
3 4
  5 +from xml.sax.saxutils import quoteattr
  6 +
4 7 import html5lib
5 8 from html5lib.filters._base import Filter as html5lib_Filter
  9 +from pyquery import PyQuery as pq
6 10
7 11 from tower import ugettext as _
8 12
@@ -27,6 +31,16 @@ def parse(src):
27 31 return ContentSectionTool(src)
28 32
29 33
  34 +def filter_out_noinclude(src):
  35 + """Quick and dirty filter to remove <div class="noinclude"> blocks"""
  36 + # NOTE: This started as an html5lib filter, but it started getting really
  37 + # complex. Seems like pyquery works well enough without corrupting
  38 + # character encoding.
  39 + doc = pq(src)
  40 + doc.remove('*[class=noinclude]')
  41 + return doc.html()
  42 +
  43 +
30 44 class ContentSectionTool(object):
31 45
32 46 def __init__(self, src=None):
@@ -58,7 +72,7 @@ def parse(self, src):
58 72 def serialize(self, stream=None):
59 73 if stream is None:
60 74 stream = self.stream
61   - return "".join(self.serializer.serialize(stream))
  75 + return u"".join(self.serializer.serialize(stream))
62 76
63 77 def __unicode__(self):
64 78 return self.serialize()
@@ -102,6 +116,10 @@ def gen_id(self):
102 116 self.known_ids.add(id)
103 117 return id
104 118
  119 + def slugify(self, text):
  120 + """Turn the text content of a header into a slug for use in an ID"""
  121 + return (text.replace(' ', '_'))
  122 +
105 123 def __iter__(self):
106 124 input = html5lib_Filter.__iter__(self)
107 125
@@ -113,17 +131,63 @@ def __iter__(self):
113 131 attrs = dict(token['data'])
114 132 if 'id' in attrs:
115 133 self.known_ids.add(attrs['id'])
  134 + if 'name' in attrs:
  135 + self.known_ids.add(attrs['name'])
116 136
117   - # Pass 2: Sprinkle in IDs where they're missing
118   - for token in buffer:
119   - if ('StartTag' == token['type'] and
  137 + # Pass 2: Sprinkle in IDs where they're needed
  138 + while len(buffer):
  139 + token = buffer.pop(0)
  140 +
  141 + if not ('StartTag' == token['type'] and
120 142 token['name'] in SECTION_TAGS):
  143 + yield token
  144 + else:
121 145 attrs = dict(token['data'])
122   - id = attrs.get('id', None)
123   - if not id:
  146 +
  147 + # Treat a name attribute as a human-specified ID override
  148 + name = attrs.get('name', None)
  149 + if name:
  150 + attrs['id'] = name
  151 + token['data'] = attrs.items()
  152 + yield token
  153 + continue
  154 +
  155 + # If this is not a header, then generate a section ID.
  156 + if token['name'] not in HEAD_TAGS:
124 157 attrs['id'] = self.gen_id()
125 158 token['data'] = attrs.items()
126   - yield token
  159 + yield token
  160 + continue
  161 +
  162 + # If this is a header, then scoop up the rest of the header and
  163 + # gather the text it contains.
  164 + start, text, tmp = token, [], []
  165 + while len(buffer):
  166 + token = buffer.pop(0)
  167 + tmp.append(token)
  168 + if token['type'] in ('Characters', 'SpaceCharacters'):
  169 + text.append(token['data'])
  170 + elif ('EndTag' == token['type'] and
  171 + start['name'] == token['name']):
  172 + # Note: This is naive, and doesn't track other
  173 + # start/end tags nested in the header. Odd things might
  174 + # happen in a case like <h1><h1></h1></h1>. But, that's
  175 + # invalid markup and the worst case should be a
  176 + # truncated ID because all the text wasn't accumulated.
  177 + break
  178 +
  179 + # Slugify the text we found inside the header, generate an ID
  180 + # as a last resort.
  181 + slug = self.slugify(u''.join(text))
  182 + if not slug:
  183 + slug = self.gen_id()
  184 + attrs['id'] = slug
  185 + start['data'] = attrs.items()
  186 +
  187 + # Finally, emit the tokens we scooped up for the header.
  188 + yield start
  189 + for t in tmp:
  190 + yield t
127 191
128 192
129 193 class SectionEditLinkFilter(html5lib_Filter):
@@ -152,17 +216,18 @@ def __iter__(self):
152 216 'title': _('Edit section'),
153 217 'class': 'edit-section',
154 218 'data-section-id': id,
155   - 'data-section-src-url': '%s?%s' % (
  219 + 'data-section-src-url': u'%s?%s' % (
156 220 reverse('wiki.document',
157 221 args=[self.full_path],
158 222 locale=self.locale),
159   - urlencode({'section': id, 'raw': 'true'})
  223 + urlencode({'section': id.encode('utf-8'),
  224 + 'raw': 'true'})
160 225 ),
161   - 'href': '%s?%s' % (
  226 + 'href': u'%s?%s' % (
162 227 reverse('wiki.edit_document',
163 228 args=[self.full_path],
164 229 locale=self.locale),
165   - urlencode({'section': id,
  230 + urlencode({'section': id.encode('utf-8'),
166 231 'edit_links': 'true'})
167 232 )
168 233 }},
@@ -385,12 +450,26 @@ def __iter__(self):
385 450 continue
386 451
387 452 ds_call = []
388   - while len(buffer) and 'EndTag' != token['type']:
  453 + while len(buffer):
389 454 token = buffer.pop(0)
390   - if 'Characters' == token['type']:
  455 + if token['type'] in ('Characters', 'SpaceCharacters'):
391 456 ds_call.append(token['data'])
392   -
393   - ds_call = ''.join(ds_call).strip()
  457 + elif 'StartTag' == token['type']:
  458 + attrs = token['data']
  459 + if attrs:
  460 + a_out = (u' %s' % u' '.join(
  461 + (u'%s=%s' %
  462 + (name, quoteattr(val))
  463 + for name, val in attrs)))
  464 + else:
  465 + a_out = u''
  466 + ds_call.append(u'<%s%s>' % (token['name'], a_out))
  467 + elif 'EndTag' == token['type']:
  468 + if 'span' == token['name']:
  469 + break
  470 + ds_call.append('</%s>' % token['name'])
  471 +
  472 + ds_call = u''.join(ds_call).strip()
394 473
395 474 # Snip off any "template." prefixes
396 475 strip_prefixes = ('template.', 'wiki.')
@@ -417,7 +496,11 @@ def __iter__(self):
417 496 if m:
418 497 ds_call = '%s()' % (m.group(1))
419 498
420   - yield dict(
421   - type="Characters",
422   - data='{{ %s }}' % ds_call
423   - )
  499 + # HACK: This is dirty, but seems like the easiest way to
  500 + # reconstitute the token stream, including what gets parsed as
  501 + # markup in the middle of macro parameters.
  502 + #
  503 + # eg. {{ Note("This is <strong>strongly</strong> discouraged") }}
  504 + parsed = parse('{{ %s }}' % ds_call)
  505 + for token in parsed.stream:
  506 + yield token
11 apps/wiki/forms.py
@@ -49,7 +49,6 @@
49 49 COMMENT_LONG = _lazy(u'Please keep the length of the comment to '
50 50 u'%(limit_value)s characters or less. It is currently '
51 51 u'%(show_value)s characters.')
52   -TITLE_COLLIDES = _lazy(u'Another document with this title already exists.')
53 52 SLUG_COLLIDES = _lazy(u'Another document with this slug already exists.')
54 53 OTHER_COLLIDES = _lazy(u'Another document with this metadata already exists.')
55 54
@@ -162,7 +161,7 @@ def save(self, parent_doc, **kwargs):
162 161 class RevisionForm(forms.ModelForm):
163 162 """Form to create new revisions."""
164 163
165   - title = StrippedCharField(min_length=5, max_length=255,
  164 + title = StrippedCharField(min_length=2, max_length=255,
166 165 required=False,
167 166 widget=forms.TextInput(
168 167 attrs={'placeholder': TITLE_PLACEHOLDER}),
@@ -204,7 +203,7 @@ class RevisionForm(forms.ModelForm):
204 203 c in GROUPED_FIREFOX_VERSIONS]}
205 204
206 205 content = StrippedCharField(
207   - min_length=5, max_length=100000,
  206 + min_length=5, max_length=300000,
208 207 label=_lazy(u'Content:'),
209 208 widget=forms.Textarea(attrs={'data-showfor':
210 209 json.dumps(showfor_data)}),
@@ -274,8 +273,7 @@ def _clean_collidable(self, name):
274 273 # to them are ignored for an iframe submission
275 274 return getattr(self.instance.document, name)
276 275
277   - error_message = {'title': TITLE_COLLIDES,
278   - 'slug': SLUG_COLLIDES}.get(name, OTHER_COLLIDES)
  276 + error_message = {'slug': SLUG_COLLIDES}.get(name, OTHER_COLLIDES)
279 277 try:
280 278 existing_doc = Document.uncached.get(
281 279 locale=self.instance.document.locale,
@@ -297,9 +295,6 @@ def _clean_collidable(self, name):
297 295
298 296 return value
299 297
300   - def clean_title(self):
301   - return self._clean_collidable('title')
302   -
303 298 def clean_slug(self):
304 299 return self._clean_collidable('slug')
305 300
29 apps/wiki/models.py
@@ -36,7 +36,7 @@
36 36 ALLOWED_TAGS = bleach.ALLOWED_TAGS + [
37 37 'div', 'span', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
38 38 'pre', 'code',
39   - 'dl', 'dt', 'dd', 'small', 'sup',
  39 + 'dl', 'dt', 'dd', 'small', 'sup', 'u',
40 40 'img',
41 41 'input',
42 42 'table', 'tbody', 'thead', 'tr', 'th', 'td',
@@ -46,13 +46,14 @@
46 46 'address'
47 47 ]
48 48 ALLOWED_ATTRIBUTES = bleach.ALLOWED_ATTRIBUTES
49   -ALLOWED_ATTRIBUTES['div'] = ['class', 'id']
50   -ALLOWED_ATTRIBUTES['pre'] = ['class', 'id']
51   -ALLOWED_ATTRIBUTES['span'] = ['style', ]
  49 +ALLOWED_ATTRIBUTES['div'] = ['style', 'class', 'id']
  50 +ALLOWED_ATTRIBUTES['p'] = ['style', 'class', 'id']
  51 +ALLOWED_ATTRIBUTES['pre'] = ['style', 'class', 'id']
  52 +ALLOWED_ATTRIBUTES['span'] = ['style', 'title', ]
52 53 ALLOWED_ATTRIBUTES['img'] = ['src', 'id', 'align', 'alt', 'class', 'is',
53 54 'title', 'style']
54   -ALLOWED_ATTRIBUTES['a'] = ['id', 'class', 'href', 'title', ]
55   -ALLOWED_ATTRIBUTES.update(dict((x, ['style', ]) for x in
  55 +ALLOWED_ATTRIBUTES['a'] = ['style', 'id', 'class', 'href', 'title', ]
  56 +ALLOWED_ATTRIBUTES.update(dict((x, ['style', 'name', ]) for x in
56 57 ('h1', 'h2', 'h3', 'h4', 'h5', 'h6')))
57 58 ALLOWED_ATTRIBUTES.update(dict((x, ['id', ]) for x in (
58 59 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code', 'dl', 'dt', 'dd',
@@ -61,6 +62,16 @@
61 62 'progress', 'audio', 'video', 'details', 'datagrid', 'datalist', 'table',
62 63 'address'
63 64 )))
  65 +ALLOWED_STYLES = [
  66 + 'border', 'float', 'overflow', 'min-height', 'vertical-align',
  67 + 'white-space',
  68 + 'margin', 'margin-left', 'margin-top', 'margin-bottom', 'margin-right',
  69 + 'padding', 'padding-left', 'padding-top', 'padding-bottom', 'padding-right',
  70 + 'background', # TODO: Maybe not this one, it can load URLs
  71 + 'background-color',
  72 + 'font', 'font-size', 'font-weight', 'text-align', 'text-transform',
  73 + '-moz-column-width', '-webkit-columns', 'columns',
  74 +]
64 75
65 76 # Disruptiveness of edits to translated versions. Numerical magnitude indicate
66 77 # the relative severity.
@@ -549,10 +560,10 @@ def locale_and_slug_from_path(path, request=None):
549 560 if '/' in path:
550 561 locale, slug = path.split('/', 1)
551 562
552   - if locale in settings.MT_TO_KUMA_LOCALE_MAP:
  563 + if locale.lower() in settings.MT_TO_KUMA_LOCALE_MAP:
553 564 # If this looks like a MindTouch locale, remap it.
554 565 old_locale = locale
555   - locale = settings.MT_TO_KUMA_LOCALE_MAP[locale]
  566 + locale = settings.MT_TO_KUMA_LOCALE_MAP[locale.lower()]
556 567 # But, we only need a redirect if the locale actually changed.
557 568 needs_redirect = (locale != old_locale)
558 569
@@ -938,7 +949,7 @@ def content_cleaned(self):
938 949 return self.content
939 950 return bleach.clean(
940 951 self.content, attributes=ALLOWED_ATTRIBUTES, tags=ALLOWED_TAGS,
941   - strip_comments=False
  952 + styles=ALLOWED_STYLES, strip_comments=False
942 953 )
943 954
944 955 def get_previous(self):
18 apps/wiki/templates/wiki/document.html
... ... @@ -1,9 +1,7 @@
1 1 {# vim: set ts=2 et sts=2 sw=2: #}
2 2 {% extends "wiki/base.html" %}
3 3 {% from "wiki/includes/sidebar_modules.html" import document_tabs, document_notifications %}
4   -{# L10n: {t} is the title of the document. {c} is the category. #}
5   -{% set title = _('{t} | {c}')|f(t=document.title, c=document.get_category_display()) %}
6   -{% block title %}{{ page_title(title) }}{% endblock %}
  4 +{% block title %}{{ page_title(document.title) }}{% endblock %}
7 5 {% set classes = 'document' %}
8 6 {% block bodyclass %}document{% endblock %}
9 7 {% if document.parent %}
@@ -38,19 +36,7 @@ <h1 class="page-title">{{ document.title }}</h1>
38 36 {% endif %}
39 37 </ul>
40 38 {% if kumascript_errors %}
41   - <div class="warning" id="kumascript-errors">
42   - <p>{{ _("There are scripting errors on this page:") }}</p>
43   - <ul>
44   - {% for error in kumascript_errors %}
45   - <li class="error error-{{ error.level }}">
46   - {# <span class="level">{{ error.level }}</span> #}
47   - {% if error.args %}<span class="type">{{ error.args[0] }}</span>{% endif %}
48   - &#8212;
49   - <span class="message">{{ error.message }}</span>
50   - </li>
51   - {% endfor %}
52   - </ul>
53   - </div>
  39 + {% include 'wiki/includes/kumascript_errors.html' %}
54 40 {% endif %}
55 41 </header>
56 42 {% if redirected_from %}
2  apps/wiki/templates/wiki/edit_document.html
@@ -2,7 +2,7 @@
2 2 {% extends "wiki/base.html" %}
3 3 {% from "layout/errorlist.html" import errorlist %}
4 4 {% from "wiki/includes/sidebar_modules.html" import document_tabs %}
5   -{% set title = _('Edit Article | {document}')|f(document=document.title) %}
  5 +{% set title = _('{document} | Edit Article')|f(document=document.title) %}
6 6 {% block title %}{{ page_title(title) }}{% endblock %}
7 7 {# TODO: Change KB url to landing page when we have one #}
8 8 {% set crumbs = [(url('wiki.category', document.category), document.get_category_display()),
45 apps/wiki/templates/wiki/includes/kumascript_errors.html
... ... @@ -0,0 +1,45 @@
  1 +<div class="warning" id="kumascript-errors">
  2 +<p>{{ _("There are scripting errors on this page:") }}</p>
  3 +<ul>
  4 + {% for error in kumascript_errors %}
  5 + <li class="error error-{{ error.level }}">
  6 + {% if error.args %}
  7 + {% set err_type = error.args[0] %}
  8 + <strong class="type">{{ err_type }}</strong>
  9 + {% if err_type == 'TemplateExecutionError' %}
  10 + {% set options = error.args[2] %}
  11 + {% set token = options.token %}
  12 + {% set template_name = token.name %}
  13 + {% set template_args = token.args %}
  14 + {% set template_slug = 'Template:{name}' | f(name=template_name) %}
  15 + {% set template_path = ('{locale}/{slug}' | f(locale='en-US', slug=template_slug)) %}
  16 + {% set edit_url = url('wiki.edit_document', template_path) %}
  17 + <span>
  18 + at document offset {{ token['offset'] }}
  19 + in macro <code>{{ template_name }} ({{ template_args }})</code>
  20 + ( <a href="{{ edit_url }}">edit</a> ):
  21 + </span>
  22 + {% endif %}
  23 + {% if err_type == 'TemplateLoadingError' %}
  24 + {% set options = error.args[2] %}
  25 + {% set template_name = options.name %}
  26 + {% set template_slug = 'Template:{name}' | f(name=template_name) %}
  27 + {% set template_path = ('{locale}/{slug}' | f(locale='en-US', slug=template_slug)) %}
  28 + {% set edit_url = url('wiki.edit_document', template_path) %}
  29 + {% set new_url = url('wiki.new_document') %}
  30 + <span>
  31 + for <code>{{ template_slug }}</code> (
  32 + {% if 'status 404' in error.message %}
  33 + <a href="{{ new_url }}?slug={{ template_slug }}">new</a>
  34 + {% else %}
  35 + <a href="{{ edit_url }}">edit</a>
  36 + {% endif %}
  37 + ):
  38 + </span>
  39 + {% endif %}
  40 + {% endif %}
  41 + <pre class="message brush: text">{{ error.message }}</pre>
  42 + </li>
  43 + {% endfor %}
  44 +</ul>
  45 +</div>
48 apps/wiki/tests/test_content.py
... ... @@ -1,5 +1,6 @@
1 1 # This Python file uses the following encoding: utf-8
2 2 # see also: http://www.python.org/dev/peps/pep-0263/
  3 +import logging
3 4 from nose.tools import eq_, ok_
4 5 from nose.plugins.attrib import attr
5 6
@@ -19,18 +20,18 @@ class ContentSectionToolTests(TestCase):
19 20 def test_section_ids(self):
20 21
21 22 doc_src = """
22   - <h1>head</h1>
  23 + <h1 class="header1">Header One</h1>
23 24 <p>test</p>
24 25 <section>
25   - <h1>head</h1>
  26 + <h1 class="header2">Header Two</h1>
26 27 <p>test</p>
27 28 </section>
28   - <h2>head</h2>
  29 + <h2 name="Constants" class="hasname">This title does not match the name</h2>
29 30 <p>test</p>
30 31
31   - <h1 id="i-already-have-an-id" class="hasid">head</h1>
  32 + <h1 id="i-already-have-an-id" class="hasid">This text clobbers the ID</h1>
32 33
33   - <h1>head</h1>
  34 + <h1 class="header3">Header Three</h1>
34 35 <p>test</p>
35 36 """
36 37
@@ -40,8 +41,14 @@ def test_section_ids(self):
40 41 .serialize())
41 42 result_doc = pq(result_src)
42 43
43   - # First, ensure an existing ID hasn't been disturbed
44   - eq_('i-already-have-an-id', result_doc.find('.hasid').attr('id'))
  44 + expected = (
  45 + ('header1', 'Header_One'),
  46 + ('header2', 'Header_Two'),
  47 + ('hasname', 'Constants'),
  48 + ('hasid', 'This_text_clobbers_the_ID'),
  49 + )
  50 + for cls, id in expected:
  51 + eq_(id, result_doc.find('.%s' % cls).attr('id'))
45 52
46 53 # Then, ensure all elements in need of an ID now all have unique IDs.
47 54 ok_(len(SECTION_TAGS) > 0)
@@ -366,11 +373,12 @@ def test_generate_toc(self):
366 373 .filter(SectionTOCFilter).serialize())
367 374 eq_(normalize_html(expected), normalize_html(result))
368 375
369   - @attr('current')
370 376 def test_dekiscript_macro_conversion(self):
371 377 doc_src = u"""
372 378 <span>Just a span</span>
373 379 <span class="notascript">Hi there</span>
  380 + <li><span class="script">Warning("Performing synchronous IO on the main thread can cause serious performance problems. As a result, this method of modifying the database is <strong>strongly</strong> discouraged!")</span></li>
  381 + <li><span class="script">Note("Performing synchronous IO on the main thread can cause serious performance problems. As a result, this method of modifying the database is <strong class="important">strongly</strong> discouraged!")</span></li>
374 382 <li><span class="script">MixedCaseName('parameter1', 'parameter2')</span></li>
375 383 <li><span class="script">bug(689641)</span></li>
376 384 <li><span class="script">template.lowercasename('border')</span></li>
@@ -383,6 +391,8 @@ def test_dekiscript_macro_conversion(self):
383 391 expected = u"""
384 392 <span>Just a span</span>
385 393 <span class="notascript">Hi there</span>
  394 + <li>{{ Warning("Performing synchronous IO on the main thread can cause serious performance problems. As a result, this method of modifying the database is <strong>strongly</strong> discouraged!") }}</li>
  395 + <li>{{ Note("Performing synchronous IO on the main thread can cause serious performance problems. As a result, this method of modifying the database is <strong class="important">strongly</strong> discouraged!") }}</li>
386 396 <li>{{ MixedCaseName('parameter1', 'parameter2') }}</li>
387 397 <li>{{ bug("689641") }}</li>
388 398 <li>{{ lowercasename('border') }}</li>
@@ -408,6 +418,28 @@ def test_dekiscript_macro_conversion(self):
408 418 .filter(DekiscriptMacroFilter).serialize())
409 419 eq_(normalize_html(expected), normalize_html(result))
410 420
  421 + def test_noinclude(self):
  422 + doc_src = u"""
  423 + <div class="noinclude">{{ XULRefAttr() }}</div>
  424 + <dl>
  425 + <dt>{{ XULAttr(&quot;maxlength&quot;) }}</dt>
  426 + <dd>Type: <em>integer</em></dd>
  427 + <dd>Przykłady 例 예제 示例</dd>
  428 + </dl>
  429 + <div class="noinclude">
  430 + <p>{{ languages( { &quot;ja&quot;: &quot;ja/XUL/Attribute/maxlength&quot; } ) }}</p>
  431 + </div>
  432 + """
  433 + expected = u"""
  434 + <dl>
  435 + <dt>{{ XULAttr(&quot;maxlength&quot;) }}</dt>
  436 + <dd>Type: <em>integer</em></dd>
  437 + <dd>Przykłady 例 예제 示例</dd>
  438 + </dl>
  439 + """
  440 + result = (wiki.content.filter_out_noinclude(doc_src))
  441 + eq_(normalize_html(expected), normalize_html(result))
  442 +
411 443
412 444 class AllowedHTMLTests(TestCase):
413 445 simple_tags = (
20 apps/wiki/tests/test_forms.py
@@ -26,20 +26,20 @@ def test_form_loaded_with_section(self):
26 26 """RevisionForm given section_id should load initial content for only
27 27 one section"""
28 28 d, r = doc_rev("""
29   - <h1 id="s1">Head 1</h1>
  29 + <h1 id="s1">s1</h1>
30 30 <p>test</p>
31 31 <p>test</p>
32 32
33   - <h1 id="s2">Head 2</h1>
  33 + <h1 id="s2">s2</h1>
34 34 <p>test</p>
35 35 <p>test</p>
36 36
37   - <h1 id="s3">Head 3</h1>
  37 + <h1 id="s3">s3</h1>
38 38 <p>test</p>
39 39 <p>test</p>
40 40 """)
41 41 expected = """
42   - <h1 id="s2">Head 2</h1>
  42 + <h1 id="s2">s2</h1>
43 43 <p>test</p>
44 44 <p>test</p>
45 45 """
@@ -49,15 +49,15 @@ def test_form_loaded_with_section(self):
49 49
50 50 def test_form_save_section(self):
51 51 d, r = doc_rev("""
52   - <h1 id="s1">Head 1</h1>
  52 + <h1 id="s1">s1</h1>
53 53 <p>test</p>
54 54 <p>test</p>
55 55
56   - <h1 id="s2">Head 2</h1>
  56 + <h1 id="s2">s2</h1>
57 57 <p>test</p>
58 58 <p>test</p>
59 59
60   - <h1 id="s3">Head 3</h1>
  60 + <h1 id="s3">s3</h1>
61 61 <p>test</p>
62 62 <p>test</p>
63 63 """)
@@ -66,14 +66,14 @@ def test_form_save_section(self):
66 66 <p>new stuff</p>
67 67 """
68 68 expected = """
69   - <h1 id="s1">Head 1</h1>
  69 + <h1 id="s1">s1</h1>
70 70 <p>test</p>
71 71 <p>test</p>
72 72
73   - <h1 id="s2">New stuff</h1>
  73 + <h1 id="New_stuff">New stuff</h1>
74 74 <p>new stuff</p>
75 75
76   - <h1 id="s3">Head 3</h1>
  76 + <h1 id="s3">s3</h1>
77 77 <p>test</p>
78 78 <p>test</p>
79 79 """
160 apps/wiki/tests/test_views.py
... ... @@ -1,6 +1,9 @@
  1 +# This Python file uses the following encoding: utf-8
  2 +# see also: http://www.python.org/dev/peps/pep-0263/
1 3 import logging
2 4 import json
3 5 import base64
  6 +import time
4 7
5 8 from django.conf import settings
6 9 from django.contrib.sites.models import Site
@@ -229,6 +232,7 @@ def setUp(self):
229 232 super(KumascriptIntegrationTests, self).setUp()
230 233
231 234 self.d, self.r = doc_rev()
  235 + self.d.tags.set('foo', 'bar', 'baz')
232 236 self.url = reverse('wiki.document',
233 237 args=['%s/%s' % (self.d.locale, self.d.slug)],
234 238 locale=settings.WIKI_DEFAULT_LANGUAGE)
@@ -459,6 +463,41 @@ def my_requests_get(url, headers=None, timeout=None):
459 463 for error in expected_errors['logs']:
460 464 ok_(error['message'] in response.content)
461 465
  466 + @mock.patch('requests.get')
  467 + def test_env_vars(self, mock_requests_get):
  468 + """Kumascript reports errors in HTTP headers, Kuma should display them"""
  469 +
  470 + # Now, trap the request from the view.
  471 + trap = {}
  472 + def my_requests_get(url, headers=None, timeout=None):
  473 + trap['headers'] = headers
  474 + return FakeResponse(
  475 + status_code=200,
  476 + body='HELLO WORLD',
  477 + headers={}
  478 + )
  479 + mock_requests_get.side_effect = my_requests_get
  480 +
  481 + # Ensure kumascript is enabled
  482 + constance.config.KUMASCRIPT_TIMEOUT = 1.0
  483 + constance.config.KUMASCRIPT_MAX_AGE = 600
  484 +
  485 + # Fire off the request, and capture the env vars that would have been
  486 + # sent to kumascript
  487 + response = self.client.get(self.url)
  488 + pfx = 'x-kumascript-env-'
  489 + vars = dict(
  490 + (k[len(pfx):], json.loads(base64.b64decode(v)))
  491 + for k,v in trap['headers'].items()
  492 + if k.startswith(pfx))
  493 +
  494 + # Ensure the env vars intended for kumascript match expected values.
  495 + for n in ('title', 'slug', 'locale'):
  496 + eq_(getattr(self.d, n), vars[n])
  497 + eq_(self.d.get_absolute_url(), vars['path'])
  498 + eq_(time.mktime(self.d.modified.timetuple()), vars['modified'])
  499 + eq_(sorted([u'foo', u'bar', u'baz']), sorted(vars['tags']))
  500 +
462 501
463 502 class DocumentEditingTests(TestCaseBase):
464 503 """Tests for the document-editing view"""
@@ -484,50 +523,48 @@ def test_retitling(self):
484 523 locale=d.locale).title)
485 524 assert "REDIRECT" in Document.uncached.get(title=old_title).html
486 525
487   - def test_retitling_ignored_for_iframe(self):
  526 + def test_slug_change_ignored_for_iframe(self):
488 527 """When the title of an article is edited in an iframe, the change is
489 528 ignored."""
490 529 client = LocalizingClient()
491 530 client.login(username='admin', password='testpass')
492   - new_title = 'Some New Title'
  531 + new_slug = 'some_new_slug'
493 532 d, r = doc_rev()
494   - old_title = d.title
  533 + old_slug = d.slug
495 534 data = new_document_data()
496   - data.update({'title': new_title,
497   - 'slug': d.slug,
  535 + data.update({'title': d.title,
  536 + 'slug': new_slug,
498 537 'form': 'rev'})
499 538 client.post('%s?iframe=1' % reverse('wiki.edit_document',
500 539 args=[d.full_path]), data)
501   - eq_(old_title, Document.uncached.get(slug=d.slug,
502   - locale=d.locale).title)
503   - assert "REDIRECT" not in Document.uncached.get(title=old_title).html
  540 + eq_(old_slug, Document.uncached.get(slug=d.slug,
  541 + locale=d.locale).slug)
  542 + assert "REDIRECT" not in Document.uncached.get(slug=old_slug).html
504 543
505 544 @attr('clobber')
506   - def test_title_slug_collision_errors(self):
  545 + def test_slug_collision_errors(self):
507 546 """When an attempt is made to retitle an article and another with that
508 547 title already exists, there should be form errors"""
509 548 client = LocalizingClient()
510 549 client.login(username='admin', password='testpass')
511 550
512   - exist_title = "Existing doc"
513 551 exist_slug = "existing-doc"
514 552
515 553 # Create a new doc.
516 554 data = new_document_data()
517   - data.update({ "title": exist_title, "slug": exist_slug })
  555 + data.update({"slug": exist_slug})
518 556 resp = client.post(reverse('wiki.new_document'), data)
519 557 eq_(302, resp.status_code)
520 558
521 559 # Create another new doc.
522 560 data = new_document_data()
523   - data.update({ "title": 'Some new title', "slug": 'some-new-title' })
  561 + data.update({"slug": 'some-new-title'})
524 562 resp = client.post(reverse('wiki.new_document'), data)
525 563 eq_(302, resp.status_code)
526 564
527   - # Now, post an update with duplicate slug and title
  565 + # Now, post an update with duplicate slug
528 566 data.update({
529 567 'form': 'rev',
530   - 'title': exist_title,
531 568 'slug': exist_slug
532 569 })
533 570 resp = client.post(reverse('wiki.edit_document',
@@ -537,7 +574,6 @@ def test_title_slug_collision_errors(self):
537 574 p = pq(resp.content)
538 575
539 576 ok_(p.find('.errorlist').length > 0)
540   - ok_(p.find('.errorlist a[href="#id_title"]').length > 0)
541 577 ok_(p.find('.errorlist a[href="#id_slug"]').length > 0)
542 578
543 579 @attr('clobber')
@@ -926,28 +962,28 @@ def test_raw_source(self):
926 962 client = LocalizingClient()
927 963 client.login(username='admin', password='testpass')
928 964 d, r = doc_rev("""
929   - <h1 id="s1">Head 1</h1>
  965 + <h1 id="s1">s1</h1>
930 966 <p>test</p>
931 967 <p>test</p>
932 968
933   - <h1 id="s2">Head 2</h1>
  969 + <h1 id="s2">s2</h1>
934 970 <p>test</p>
935 971 <p>test</p>
936 972
937   - <h1 id="s3">Head 3</h1>
  973 + <h1 id="s3">s3</h1>
938 974 <p>test</p>
939 975 <p>test</p>
940 976 """)
941 977 expected = """
942   - <h1 id="s1">Head 1</h1>
  978 + <h1 id="s1">s1</h1>
943 979 <p>test</p>
944 980 <p>test</p>
945 981
946   - <h1 id="s2">Head 2</h1>
  982 + <h1 id="s2">s2</h1>
947 983 <p>test</p>
948 984 <p>test</p>
949 985
950   - <h1 id="s3">Head 3</h1>
  986 + <h1 id="s3">s3</h1>
951 987 <p>test</p>
952 988 <p>test</p>
953 989 """
@@ -962,26 +998,26 @@ def test_raw_with_editing_links_source(self):
962 998 client = LocalizingClient()
963 999 client.login(username='admin', password='testpass')
964 1000 d, r = doc_rev("""
965   - <h1 id="s1">Head 1</h1>
  1001 + <h1 id="s1">s1</h1>
966 1002 <p>test</p>
967 1003 <p>test</p>
968 1004
969   - <h1 id="s2">Head 2</h1>
  1005 + <h1 id="s2">s2</h1>
970 1006 <p>test</p>
971 1007 <p>test</p>
972 1008
973   - <h1 id="s3">Head 3</h1>
  1009 + <h1 id="s3">s3</h1>
974 1010 <p>test</p>
975 1011 <p>test</p>
976 1012 """)
977 1013 expected = """
978   - <h1 id="s1"><a class="edit-section" data-section-id="s1" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s1" href="/en-US/docs/%(full_path)s$edit?section=s1&amp;edit_links=true" title="Edit section">Edit</a>Head 1</h1>
  1014 + <h1 id="s1"><a class="edit-section" data-section-id="s1" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s1" href="/en-US/docs/%(full_path)s$edit?section=s1&amp;edit_links=true" title="Edit section">Edit</a>s1</h1>
979 1015 <p>test</p>
980 1016 <p>test</p>
981   - <h1 id="s2"><a class="edit-section" data-section-id="s2" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s2" href="/en-US/docs/%(full_path)s$edit?section=s2&amp;edit_links=true" title="Edit section">Edit</a>Head 2</h1>
  1017 + <h1 id="s2"><a class="edit-section" data-section-id="s2" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s2" href="/en-US/docs/%(full_path)s$edit?section=s2&amp;edit_links=true" title="Edit section">Edit</a>s2</h1>
982 1018 <p>test</p>
983 1019 <p>test</p>
984   - <h1 id="s3"><a class="edit-section" data-section-id="s3" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s3" href="/en-US/docs/%(full_path)s$edit?section=s3&amp;edit_links=true" title="Edit section">Edit</a>Head 3</h1>
  1020 + <h1 id="s3"><a class="edit-section" data-section-id="s3" data-section-src-url="/en-US/docs/%(full_path)s?raw=true&amp;section=s3" href="/en-US/docs/%(full_path)s$edit?section=s3&amp;edit_links=true" title="Edit section">Edit</a>s3</h1>
985 1021 <p>test</p>
986 1022 <p>test</p>
987 1023 """ % {'full_path': d.full_path}
@@ -995,20 +1031,20 @@ def test_raw_section_source(self):
995 1031 client = LocalizingClient()
996 1032 client.login(username='admin', password='testpass')
997 1033 d, r = doc_rev("""
998   - <h1 id="s1">Head 1</h1>
  1034 + <h1 id="s1">s1</h1>
999 1035 <p>test</p>
1000 1036 <p>test</p>
1001 1037
1002   - <h1 id="s2">Head 2</h1>
  1038 + <h1 id="s2">s2</h1>
1003 1039 <p>test</p>
1004 1040 <p>test</p>
1005 1041
1006   - <h1 id="s3">Head 3</h1>
  1042 + <h1 id="s3">s3</h1>
1007 1043 <p>test</p>
1008 1044 <p>test</p>
1009 1045 """)
1010 1046 expected = """
1011   - <h1 id="s2">Head 2</h1>
  1047 + <h1 id="s2">s2</h1>
1012 1048 <p>test</p>
1013 1049 <p>test</p>
1014 1050 """
@@ -1023,24 +1059,24 @@ def test_raw_section_edit(self):
1023 1059 client = LocalizingClient()
1024 1060 client.login(username='admin', password='testpass')
1025 1061 d, r = doc_rev("""
1026   - <h1 id="s1">Head 1</h1>
  1062 + <h1 id="s1">s1</h1>
1027 1063 <p>test</p>
1028 1064 <p>test</p>
1029 1065
1030   - <h1 id="s2">Head 2</h1>
  1066 + <h1 id="s2">s2</h1>
1031 1067 <p>test</p>
1032 1068 <p>test</p>
1033 1069
1034   - <h1 id="s3">Head 3</h1>
  1070 + <h1 id="s3">s3</h1>
1035 1071 <p>test</p>
1036 1072 <p>test</p>
1037 1073 """)
1038 1074 replace = """
1039   - <h1 id="s2">Replace</h1>
  1075 + <h1 id="s2">s2</h1>
1040 1076 <p>replace</p>
1041 1077 """
1042 1078 expected = """
1043   - <h1 id="s2">Replace</h1>
  1079 + <h1 id="s2">s2</h1>
1044 1080 <p>replace</p>
1045 1081 """
1046 1082 response = client.post('%s?section=s2&raw=true' %
@@ -1052,14 +1088,14 @@ def test_raw_section_edit(self):
1052 1088 normalize_html(response.content))
1053 1089
1054 1090 expected = """
1055   - <h1 id="s1">Head 1</h1>
  1091 + <h1 id="s1">s1</h1>
1056 1092 <p>test</p>
1057 1093 <p>test</p>
1058 1094
1059   - <h1 id="s2">Replace</h1>
  1095 + <h1 id="s2">s2</h1>
1060 1096 <p>replace</p>
1061 1097
1062   - <h1 id="s3">Head 3</h1>
  1098 + <h1 id="s3">s3</h1>
1063 1099 <p>test</p>
1064 1100 <p>test</p>
1065 1101 """
@@ -1077,34 +1113,34 @@ def test_midair_section_merge(self):
1077 1113 client.login(username='admin', password='testpass')
1078 1114
1079 1115 doc, rev = doc_rev("""
1080   - <h1 id="s1">Head 1</h1>
  1116 + <h1 id="s1">s1</h1>
1081 1117 <p>test</p>
1082 1118 <p>test</p>
1083 1119
1084   - <h1 id="s2">Head 2</h1>
  1120 + <h1 id="s2">s2</h1>
1085 1121 <p>test</p>
1086 1122 <p>test</p>
1087 1123
1088   - <h1 id="s3">Head 3</h1>
  1124 + <h1 id="s3">s3</h1>
1089 1125 <p>test</p>
1090 1126 <p>test</p>
1091 1127 """)
1092 1128 replace_1 = """
1093   - <h1 id="s1">replace</h1>
  1129 + <h1 id="s1">replace1</h1>
1094 1130 <p>replace</p>
1095 1131 """
1096 1132 replace_2 = """
1097   - <h1 id="s2">replace</h1>
  1133 + <h1 id="s2">replace2</h1>
1098 1134 <p>replace</p>
1099 1135 """
1100 1136 expected = """
1101   - <h1 id="s1">replace</h1>
  1137 + <h1 id="replace1">replace1</h1>
1102 1138 <p>replace</p>
1103 1139
1104   - <h1 id="s2">replace</h1>
  1140 + <h1 id="replace2">replace2</h1>
1105 1141 <p>replace</p>
1106 1142
1107   - <h1 id="s3">Head 3</h1>
  1143 + <h1 id="s3">s3</h1>
1108 1144 <p>test</p>
1109 1145 <p>test</p>
1110 1146 """
@@ -1169,15 +1205,15 @@ def test_midair_section_collision(self):
1169 1205 client.login(username='admin', password='testpass')
1170 1206
1171 1207 doc, rev = doc_rev("""
1172   - <h1 id="s1">Head 1</h1>
  1208 + <h1 id="s1">s1</h1>
1173 1209 <p>test</p>
1174 1210 <p>test</p>
1175 1211
1176   - <h1 id="s2">Head 2</h1>
  1212 + <h1 id="s2">s2</h1>
1177 1213 <p>test</p>
1178 1214 <p>test</p>
1179 1215
1180   - <h1 id="s3">Head 3</h1>
  1216 + <h1 id="s3">s3</h1>
1181 1217 <p>test</p>
1182 1218 <p>test</p>
1183 1219 """)
@@ -1229,6 +1265,30 @@ def test_midair_section_collision(self):
1229 1265 # With the raw API, we should get a 409 Conflict on collision.
1230 1266 eq_(409, resp.status_code)
1231 1267
  1268 + def test_raw_include_option(self):
  1269 + doc_src = u"""
  1270 + <div class="noinclude">{{ XULRefAttr() }}</div>
  1271 + <dl>
  1272 + <dt>{{ XULAttr(&quot;maxlength&quot;) }}</dt>
  1273 + <dd>Type: <em>integer</em></dd>
  1274 + <dd>Przykłady 例 예제 示例</dd>
  1275 + </dl>
  1276 + <div class="noinclude">
  1277 + <p>{{ languages( { &quot;ja&quot;: &quot;ja/XUL/Attribute/maxlength&quot; } ) }}</p>
  1278 + </div>
  1279 + """
  1280 + doc, rev = doc_rev(doc_src)
  1281 + expected = u"""
  1282 + <dl>
  1283 + <dt>{{ XULAttr(&quot;maxlength&quot;) }}</dt>
  1284 + <dd>Type: <em>integer</em></dd>
  1285 + <dd>Przykłady 例 예제 示例</dd>
  1286 + </dl>
  1287 + """
  1288 + client = LocalizingClient()
  1289 + resp = client.get('%s?raw&include' % reverse('wiki.document', args=[doc.full_path]))
  1290 + eq_(normalize_html(expected), normalize_html(resp.content.decode('utf-8')))
  1291 +
1232 1292 @attr('kumawiki')
1233 1293 def test_kumawiki_waffle_flag(self):
1234 1294
39 apps/wiki/views.py
... ... @@ -1,4 +1,5 @@
1 1 from datetime import datetime
  2 +import time
2 3 import json
3 4 from collections import defaultdict
4 5 import base64
@@ -47,7 +48,8 @@
47 48 OPERATING_SYSTEMS, GROUPED_OPERATING_SYSTEMS,
48 49 FIREFOX_VERSIONS, GROUPED_FIREFOX_VERSIONS,
49 50 REVIEW_FLAG_TAGS_DEFAULT, ALLOWED_ATTRIBUTES,
50   - ALLOWED_TAGS, get_current_or_latest_revision)
  51 + ALLOWED_TAGS, ALLOWED_STYLES,
  52 + get_current_or_latest_revision)
51 53 from wiki.tasks import send_reviewed_notification, schedule_rebuild_kb
52 54 import wiki.content
53 55
@@ -127,7 +129,7 @@ def process(request, document_path=None, *args, **kwargs):
127 129