Left: | ||
Right: |
OLD | NEW |
---|---|
1 # Copyright: 2012 MoinMoin:CheerXiao | 1 # Copyright: 2012 MoinMoin:CheerXiao |
2 # Copyright: 2009 MoinMoin:ThomasWaldmann | 2 # Copyright: 2009 MoinMoin:ThomasWaldmann |
3 # Copyright: 2009-2011 MoinMoin:ReimarBauer | 3 # Copyright: 2009-2011 MoinMoin:ReimarBauer |
4 # Copyright: 2009 MoinMoin:ChristopherDenter | 4 # Copyright: 2009 MoinMoin:ChristopherDenter |
5 # Copyright: 2008,2009 MoinMoin:BastianBlank | 5 # Copyright: 2008,2009 MoinMoin:BastianBlank |
6 # Copyright: 2010 MoinMoin:ValentinJaniaut | 6 # Copyright: 2010 MoinMoin:ValentinJaniaut |
7 # Copyright: 2010 MoinMoin:DiogenesAugusto | 7 # Copyright: 2010 MoinMoin:DiogenesAugusto |
8 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details. | 8 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details. |
9 | 9 |
Reimar Bauer
2012/07/23 21:40:57
sometimes we have itemtype and sometimes item_type
xiaq
2012/07/24 02:50:06
content_type was inherited from old code, but i sh
| |
10 """ | 10 """ |
11 MoinMoin - misc. mimetype items | 11 MoinMoin - highlevel items |
12 | 12 |
13 While MoinMoin.storage cares for backend storage of items, | 13 While MoinMoin.storage cares for backend storage of items, |
14 this module cares for more high-level, frontend items, | 14 this module cares for more high-level, frontend items, |
15 e.g. showing, editing, etc. of wiki items. | 15 e.g. showing, editing, etc. of wiki items. |
16 | |
17 Each class in this module corresponds to an itemtype. | |
16 """ | 18 """ |
17 # TODO: split this huge module into multiple ones after code has stabilized | |
18 | 19 |
19 import os, re, time, datetime, base64 | 20 import re, time |
20 import tarfile | |
21 import zipfile | |
22 import tempfile | |
23 import itertools | 21 import itertools |
24 from StringIO import StringIO | 22 from StringIO import StringIO |
25 from array import array | |
26 | 23 |
27 from flatland import Form, String, Integer, Boolean, Enum | 24 from flatland import Form |
28 from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLVa lidator, Converted | 25 from flatland.validation import Validator |
29 | 26 |
30 from whoosh.query import Term, And, Prefix | 27 from whoosh.query import Term, And, Prefix |
31 | 28 |
32 from MoinMoin.forms import RequiredText, OptionalText, File, Submit | 29 from MoinMoin.forms import RequiredText, OptionalText, OptionalMultilineText, Ta gs, Submit |
33 | 30 |
34 from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid | 31 from MoinMoin.security.textcha import TextCha, TextChaizedForm |
35 from MoinMoin.signalling import item_modified | 32 from MoinMoin.signalling import item_modified |
36 from MoinMoin.util.mimetype import MimeType | 33 from MoinMoin.util.mime import Type |
37 from MoinMoin.util.mime import Type, type_moin_document | |
38 from MoinMoin.util.tree import moin_page, html, xlink, docbook | |
39 from MoinMoin.util.iri import Iri | |
40 from MoinMoin.util.crypto import cache_key | |
41 from MoinMoin.storage.middleware.protecting import AccessDenied | 34 from MoinMoin.storage.middleware.protecting import AccessDenied |
42 | 35 |
43 try: | |
44 import PIL | |
45 from PIL import Image as PILImage | |
46 from PIL.ImageChops import difference as PILdiff | |
47 except ImportError: | |
48 PIL = None | |
49 | |
50 from MoinMoin import log | 36 from MoinMoin import log |
51 logging = log.getLogger(__name__) | 37 logging = log.getLogger(__name__) |
52 | 38 |
53 try: | 39 try: |
54 import json | 40 import json |
55 except ImportError: | 41 except ImportError: |
56 import simplejson as json | 42 import simplejson as json |
57 | 43 |
58 from flask import current_app as app | 44 from flask import current_app as app |
59 from flask import g as flaskg | 45 from flask import g as flaskg |
60 | 46 |
61 from flask import request, url_for, flash, Response, redirect, abort, escape | 47 from flask import request, Response, redirect, abort, escape |
62 | 48 |
63 from werkzeug import is_resource_modified | 49 from werkzeug import is_resource_modified |
64 from jinja2 import Markup | |
65 | 50 |
66 from MoinMoin.i18n import _, L_, N_ | 51 from MoinMoin.i18n import L_ |
67 from MoinMoin.themes import render_template | 52 from MoinMoin.themes import render_template |
68 from MoinMoin import wikiutil, config, user | |
69 from MoinMoin.util.send_file import send_file | |
70 from MoinMoin.util.interwiki import url_for_item | 53 from MoinMoin.util.interwiki import url_for_item |
71 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, Storage Error | 54 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, Storage Error |
72 from MoinMoin.config import NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, REVERTE D_TO, ACL, \ | 55 from MoinMoin.util.registry import RegistryBase |
73 IS_SYSITEM, SYSITEM_VERSION, USERGROUP, SOMEDICT, \ | 56 from MoinMoin.constants.keys import ( |
74 CONTENTTYPE, SIZE, LANGUAGE, ITEMLINKS, ITEMTRANSCLU SIONS, \ | 57 NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE, |
75 TAGS, ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMM ENT, \ | 58 CONTENTTYPE, SIZE, TAGS, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT, |
76 HASH_ALGORITHM, CONTENTTYPE_GROUPS, ITEMID, REVID, D ATAID, \ | 59 HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID |
77 CURRENT, PARENTID | 60 ) |
61 from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_GROUPS | |
62 | |
63 #from .content import content_registry | |
64 | |
78 | 65 |
79 COLS = 80 | 66 COLS = 80 |
Reimar Bauer
2012/07/23 21:40:57
constant?
| |
80 ROWS_DATA = 20 | |
81 ROWS_META = 10 | 67 ROWS_META = 10 |
Reimar Bauer
2012/07/23 21:40:57
constants?
xiaq
2012/07/24 02:50:06
see comments below
| |
82 | 68 |
83 | 69 |
84 from ..util.registry import RegistryBase | |
85 | |
86 | |
87 class RegistryItem(RegistryBase): | 70 class RegistryItem(RegistryBase): |
88 class Entry(object): | 71 class Entry(object): |
89 def __init__(self, factory, content_type, priority): | 72 def __init__(self, factory, item_type, priority): |
90 self.factory = factory | 73 self.factory = factory |
91 self.content_type = content_type | 74 self.item_type = item_type |
92 self.priority = priority | 75 self.priority = priority |
93 | 76 |
94 def __call__(self, name, content_type, kw): | 77 def __call__(self, name, item_type, kw): |
95 if self.content_type.issupertype(content_type): | 78 if self.item_type == item_type: |
96 return self.factory(name, content_type, **kw) | 79 return self.factory(name, item_type, **kw) |
97 | 80 |
98 def __eq__(self, other): | 81 def __eq__(self, other): |
99 if isinstance(other, self.__class__): | 82 if isinstance(other, self.__class__): |
100 return (self.factory == other.factory and | 83 return (self.factory == other.factory and |
101 self.content_type == other.content_type and | 84 self.item_type == other.item_type and |
102 self.priority == other.priority) | 85 self.priority == other.priority) |
103 return NotImplemented | 86 return NotImplemented |
104 | 87 |
105 def __lt__(self, other): | 88 def __lt__(self, other): |
106 if isinstance(other, self.__class__): | 89 if isinstance(other, self.__class__): |
107 if self.priority < other.priority: | 90 if self.priority < other.priority: |
108 return True | 91 return True |
109 if self.content_type != other.content_type: | 92 return self.item_type == other.item_type |
110 return other.content_type.issupertype(self.content_type) | |
111 return False | |
112 return NotImplemented | 93 return NotImplemented |
113 | 94 |
114 def __repr__(self): | 95 def __repr__(self): |
115 return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__ , | 96 return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__ , |
116 self.content_type, | 97 self.item_type, |
117 self.priority, | 98 self.priority, |
118 self.factory) | 99 self.factory) |
119 | 100 |
120 def get(self, name, content_type, **kw): | 101 def get(self, name, item_type, **kw): |
121 for entry in self._entries: | 102 for entry in self._entries: |
122 item = entry(name, content_type, kw) | 103 item = entry(name, item_type, kw) |
123 if item is not None: | 104 if item is not None: |
124 return item | 105 return item |
125 | 106 |
126 def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MID DLE): | 107 def register(self, factory, item_type, priority=RegistryBase.PRIORITY_MIDDLE ): |
127 """ | 108 """ |
128 Register a factory | 109 Register a factory |
129 | 110 |
130 :param factory: Factory to register. Callable, must return an object. | 111 :param factory: Factory to register. Callable, must return an object. |
131 """ | 112 """ |
132 return self._register(self.Entry(factory, content_type, priority)) | 113 return self._register(self.Entry(factory, item_type, priority)) |
133 | 114 |
134 | 115 |
135 item_registry = RegistryItem() | 116 item_registry = RegistryItem() |
136 | 117 |
137 | 118 |
138 def conv_serialize(doc, namespaces, method='polyglot'): | |
139 out = array('u') | |
140 flaskg.clock.start('conv_serialize') | |
141 doc.write(out.fromunicode, namespaces=namespaces, method=method) | |
142 out = out.tounicode() | |
143 flaskg.clock.stop('conv_serialize') | |
144 return out | |
145 | |
146 | |
147 class DummyRev(dict): | 119 class DummyRev(dict): |
148 """ if we have no stored Revision, we use this dummy """ | 120 """ if we have no stored Revision, we use this dummy """ |
149 def __init__(self, item, contenttype): | 121 def __init__(self, item, contenttype): |
150 self.item = item | 122 self.item = item |
151 self.meta = {CONTENTTYPE: contenttype} | 123 self.meta = {CONTENTTYPE: contenttype} |
152 self.data = StringIO('') | 124 self.data = StringIO('') |
153 self.revid = None | 125 self.revid = None |
154 | 126 |
155 | 127 |
156 class DummyItem(object): | 128 class DummyItem(object): |
157 """ if we have no stored Item, we use this dummy """ | 129 """ if we have no stored Item, we use this dummy """ |
158 def __init__(self, name): | 130 def __init__(self, name): |
159 self.name = name | 131 self.name = name |
160 def list_revisions(self): | 132 def list_revisions(self): |
161 return [] # same as an empty Item | 133 return [] # same as an empty Item |
162 def destroy_all_revisions(self): | 134 def destroy_all_revisions(self): |
163 return True | 135 return True |
164 | 136 |
165 | 137 |
138 class BaseChangeForm(TextChaizedForm): | |
139 comment = OptionalText.using(label=L_('Comment')).with_properties(placeholde r=L_("Comment about your change")) | |
140 submit = Submit | |
141 | |
142 | |
143 class BaseMetaForm(Form): | |
144 wikiname = OptionalText.using(label=L_("Wiki name")).with_properties(placeho lder=L_("Wiki name")) | |
145 contenttype = RequiredText.using(label=L_("Content type")).with_properties(p laceholder=L_("Content type")) | |
146 summary = OptionalText.using(label=L_("Summary")).with_properties(placeholde r=L_("One-line summary of the item")) | |
147 tags = Tags | |
148 | |
149 | |
150 class ValidJSON(Validator): | |
151 """Validator for JSON | |
152 """ | |
153 invalid_json_msg = L_('Invalid JSON.') | |
154 | |
155 def validate(self, element, state): | |
156 try: | |
157 json.loads(element.value) | |
158 except: | |
159 return self.note_error(element, state, 'invalid_json_msg') | |
160 return True | |
161 | |
162 | |
166 class Item(object): | 163 class Item(object): |
167 """ Highlevel (not storage) Item """ | 164 """ Highlevel (not storage) Item, wraps around a storage Revision""" |
168 @classmethod | 165 @classmethod |
169 def _factory(cls, name=u'', contenttype=None, **kw): | 166 def _factory(cls, name=u'', itemtype=None, **kw): |
170 return cls(name, contenttype=unicode(contenttype), **kw) | 167 return cls(name, **kw) |
171 | 168 |
172 @classmethod | 169 @classmethod |
173 def create(cls, name=u'', contenttype=None, rev_id=CURRENT, item=None): | 170 def create(cls, name=u'', itemtype=None, contenttype=None, rev_id=CURRENT, i tem=None): |
171 """ | |
172 Create a highlevel Item by looking up :name or directly wrapping | |
173 :item and extract the Revision designated by :rev_id revision. | |
174 | |
175 The highlevel Item is created by creating an instance of Content | |
176 subclass according to the item's contenttype metadata entry; The | |
177 :contenttype argument can be used to override contenttype. It is used | |
178 only when handling +convert (when deciding the contenttype of target | |
179 item), +modify (when creating a new item whose contenttype is not yet | |
180 decided), +diff and +diffraw (to coerce the Content to a common | |
181 super-contenttype of both revisions). | |
182 | |
183 After that the Content instance, an instance of Item subclass is | |
184 created according to the item's itemtype metadata entry, and the | |
185 previously created Content instance is assigned to its content | |
186 property. | |
187 """ | |
174 if contenttype is None: | 188 if contenttype is None: |
175 contenttype = u'application/x-nonexistent' | 189 contenttype = u'application/x-nonexistent' |
176 | 190 if itemtype is None: |
191 itemtype = u'nonexistent' | |
177 if 1: # try: | 192 if 1: # try: |
178 if item is None: | 193 if item is None: |
179 item = flaskg.storage[name] | 194 item = flaskg.storage[name] |
180 else: | 195 else: |
181 name = item.name | 196 name = item.name |
182 if not item: # except NoSuchItemError: | 197 if not item: # except NoSuchItemError: |
183 logging.debug("No such item: {0!r}".format(name)) | 198 logging.debug("No such item: {0!r}".format(name)) |
184 item = DummyItem(name) | 199 item = DummyItem(name) |
185 rev = DummyRev(item, contenttype) | 200 rev = DummyRev(item, contenttype) |
186 logging.debug("Item {0!r}, created dummy revision with contenttype { 1!r}".format(name, contenttype)) | 201 logging.debug("Item {0!r}, created dummy revision with contenttype { 1!r}".format(name, contenttype)) |
187 else: | 202 else: |
188 logging.debug("Got item: {0!r}".format(name)) | 203 logging.debug("Got item: {0!r}".format(name)) |
189 try: | 204 try: |
190 rev = item.get_revision(rev_id) | 205 rev = item.get_revision(rev_id) |
191 contenttype = u'application/octet-stream' # it exists | 206 contenttype = u'application/octet-stream' # it exists |
207 itemtype = u'default' # default itemtype to u'default' for compa tibility | |
192 except KeyError: # NoSuchRevisionError: | 208 except KeyError: # NoSuchRevisionError: |
193 try: | 209 try: |
194 rev = item.get_revision(CURRENT) # fall back to current revi sion | 210 rev = item.get_revision(CURRENT) # fall back to current revi sion |
195 # XXX add some message about invalid revision | 211 # XXX add some message about invalid revision |
196 except KeyError: # NoSuchRevisionError: | 212 except KeyError: # NoSuchRevisionError: |
197 logging.debug("Item {0!r} has no revisions.".format(name)) | 213 logging.debug("Item {0!r} has no revisions.".format(name)) |
198 rev = DummyRev(item, contenttype) | 214 rev = DummyRev(item, contenttype) |
199 logging.debug("Item {0!r}, created dummy revision with conte nttype {1!r}".format(name, contenttype)) | 215 logging.debug("Item {0!r}, created dummy revision with conte nttype {1!r}".format(name, contenttype)) |
200 logging.debug("Got item {0!r}, revision: {1!r}".format(name, rev_id) ) | 216 logging.debug("Got item {0!r}, revision: {1!r}".format(name, rev_id) ) |
201 contenttype = rev.meta.get(CONTENTTYPE) or contenttype # use contenttype in case our metadata does not provide CONTENTTYPE | 217 contenttype = rev.meta.get(CONTENTTYPE) or contenttype # use contenttype in case our metadata does not provide CONTENTTYPE |
202 logging.debug("Item {0!r}, got contenttype {1!r} from revision meta".for mat(name, contenttype)) | 218 logging.debug("Item {0!r}, got contenttype {1!r} from revision meta".for mat(name, contenttype)) |
203 #logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta))) | 219 #logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta))) |
204 | 220 |
205 item = item_registry.get(name, Type(contenttype), rev=rev) | 221 content = content_registry.get(name, Type(contenttype), rev=rev) |
206 logging.debug("ItemClass {0!r} handles {1!r}".format(item.__class__, con tenttype)) | 222 logging.debug("Content class {0!r} handles {1!r}".format(content.__class __, contenttype)) |
223 | |
224 itemtype = rev.meta.get(ITEMTYPE) or itemtype | |
225 logging.debug("Item {0!r}, got itemtype {1!r} from revision meta".format (name, itemtype)) | |
226 | |
227 item = item_registry.get(name, itemtype, rev=rev, content=content) | |
228 logging.debug("Item class {0!r} handles {1!r}".format(item.__class__, it emtype)) | |
229 | |
207 return item | 230 return item |
208 | 231 |
209 def __init__(self, name, rev=None, contenttype=None): | 232 def __init__(self, name, rev=None, content=None): |
210 self.name = name | 233 self.name = name |
211 self.rev = rev | 234 self.rev = rev |
212 self.contenttype = contenttype | 235 self.content = content |
213 | 236 |
214 def get_meta(self): | 237 def get_meta(self): |
215 return self.rev.meta | 238 return self.rev.meta |
216 meta = property(fget=get_meta) | 239 meta = property(fget=get_meta) |
217 | 240 |
241 @property | |
242 def contenttype(self): | |
243 return self.content.contenttype if self.content else None | |
244 | |
245 def do_get(self, force_attachment=False, mimetype=None): | |
246 hash = self.rev.meta.get(HASH_ALGORITHM) | |
247 if is_resource_modified(request.environ, hash): # use hash as etag | |
248 return self._do_get_modified(hash, force_attachment=force_attachment , mimetype=mimetype) | |
249 else: | |
250 return Response(status=304) | |
251 | |
218 def _render_meta(self): | 252 def _render_meta(self): |
219 # override this in child classes | 253 return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False))) |
220 return '' | |
221 | |
222 def internal_representation(self, converters=['smiley']): | |
223 """ | |
224 Return the internal representation of a document using a DOM Tree | |
225 """ | |
226 flaskg.clock.start('conv_in_dom') | |
227 hash_name = HASH_ALGORITHM | |
228 hash_hexdigest = self.rev.meta.get(hash_name) | |
229 if hash_hexdigest: | |
230 cid = cache_key(usage="internal_representation", | |
231 hash_name=hash_name, | |
232 hash_hexdigest=hash_hexdigest) | |
233 doc = app.cache.get(cid) | |
234 else: | |
235 # likely a non-existing item | |
236 doc = cid = None | |
237 if doc is None: | |
238 # We will see if we can perform the conversion: | |
239 # FROM_mimetype --> DOM | |
240 # if so we perform the transformation, otherwise we don't | |
241 from MoinMoin.converter import default_registry as reg | |
242 input_conv = reg.get(Type(self.contenttype), type_moin_document) | |
243 if not input_conv: | |
244 raise TypeError("We cannot handle the conversion from {0} to the DOM tree".format(self.contenttype)) | |
245 smiley_conv = reg.get(type_moin_document, type_moin_document, | |
246 icon='smiley') | |
247 | |
248 # We can process the conversion | |
249 links = Iri(scheme='wiki', authority='', path='/' + self.name) | |
250 doc = input_conv(self.rev, self.contenttype) | |
251 # XXX is the following assuming that the top element of the doc tree | |
252 # is a moin_page.page element? if yes, this is the wrong place to do that | |
253 # as not every doc will have that element (e.g. for images, we just get | |
254 # moin_page.object, for a tar item, we get a moin_page.table): | |
255 doc.set(moin_page.page_href, unicode(links)) | |
256 for conv in converters: | |
257 if conv == 'smiley': | |
258 doc = smiley_conv(doc) | |
259 if cid: | |
260 app.cache.set(cid, doc) | |
261 flaskg.clock.stop('conv_in_dom') | |
262 return doc | |
263 | |
264 def _expand_document(self, doc): | |
265 from MoinMoin.converter import default_registry as reg | |
266 include_conv = reg.get(type_moin_document, type_moin_document, includes= 'expandall') | |
267 macro_conv = reg.get(type_moin_document, type_moin_document, macros='exp andall') | |
268 link_conv = reg.get(type_moin_document, type_moin_document, links='exter n') | |
269 flaskg.clock.start('conv_include') | |
270 doc = include_conv(doc) | |
271 flaskg.clock.stop('conv_include') | |
272 flaskg.clock.start('conv_macro') | |
273 doc = macro_conv(doc) | |
274 flaskg.clock.stop('conv_macro') | |
275 flaskg.clock.start('conv_link') | |
276 doc = link_conv(doc) | |
277 flaskg.clock.stop('conv_link') | |
278 return doc | |
279 | |
280 def _render_data(self): | |
281 from MoinMoin.converter import default_registry as reg | |
282 # TODO: Real output format | |
283 doc = self.internal_representation() | |
284 doc = self._expand_document(doc) | |
285 flaskg.clock.start('conv_dom_html') | |
286 html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-p age')) | |
287 doc = html_conv(doc) | |
288 flaskg.clock.stop('conv_dom_html') | |
289 rendered_data = conv_serialize(doc, {html.namespace: ''}) | |
290 return rendered_data | |
291 | |
292 def _render_data_xml(self): | |
293 doc = self.internal_representation() | |
294 return conv_serialize(doc, | |
295 {moin_page.namespace: '', | |
296 xlink.namespace: 'xlink', | |
297 html.namespace: 'html', | |
298 }, | |
299 'xml') | |
300 | |
301 def _render_data_highlight(self): | |
302 # override this in child classes | |
303 return '' | |
304 | |
305 def _do_modify_show_templates(self): | |
306 # call this if the item is still empty | |
307 rev_ids = [] | |
308 item_templates = self.get_templates(self.contenttype) | |
309 return render_template('modify_show_template_selection.html', | |
310 item_name=self.name, | |
311 rev=self.rev, | |
312 contenttype=self.contenttype, | |
313 templates=item_templates, | |
314 first_rev_id=rev_ids and rev_ids[0], | |
315 last_rev_id=rev_ids and rev_ids[-1], | |
316 meta_rendered='', | |
317 data_rendered='', | |
318 ) | |
319 | 254 |
320 def meta_filter(self, meta): | 255 def meta_filter(self, meta): |
321 """ kill metadata entries that we set automatically when saving """ | 256 """ kill metadata entries that we set automatically when saving """ |
322 kill_keys = [# shall not get copied from old rev to new rev | 257 kill_keys = [# shall not get copied from old rev to new rev |
323 SYSITEM_VERSION, | 258 SYSITEM_VERSION, |
324 NAME_OLD, | 259 NAME_OLD, |
325 # are automatically implanted when saving | 260 # are automatically implanted when saving |
326 NAME, | 261 NAME, |
327 ITEMID, REVID, DATAID, | 262 ITEMID, REVID, DATAID, |
328 HASH_ALGORITHM, | 263 HASH_ALGORITHM, |
(...skipping 23 matching lines...) Expand all Loading... | |
352 """ | 287 """ |
353 transform the meta dict of the current revision into a meta dict | 288 transform the meta dict of the current revision into a meta dict |
354 that can be used for savind next revision (after "modify"). | 289 that can be used for savind next revision (after "modify"). |
355 """ | 290 """ |
356 meta = dict(meta) | 291 meta = dict(meta) |
357 revid = meta.pop(REVID, None) | 292 revid = meta.pop(REVID, None) |
358 if revid is not None: | 293 if revid is not None: |
359 meta[PARENTID] = revid | 294 meta[PARENTID] = revid |
360 return meta | 295 return meta |
361 | 296 |
362 def get_data(self): | |
363 return '' # TODO create a better method for binary stuff | |
364 data = property(fget=get_data) | |
365 | |
366 def _write_stream(self, content, new_rev, bufsize=8192): | |
367 written = 0 | |
368 if hasattr(content, "read"): | |
369 while True: | |
370 buf = content.read(bufsize) | |
371 if not buf: | |
372 break | |
373 new_rev.data.write(buf) | |
374 written += len(buf) | |
375 elif isinstance(content, str): | |
376 new_rev.data.write(content) | |
377 written += len(content) | |
378 else: | |
379 raise StorageError("unsupported content object: {0!r}".format(conten t)) | |
380 return written | |
381 | |
382 def _rename(self, name, comment, action): | 297 def _rename(self, name, comment, action): |
383 self._save(self.meta, self.data, name=name, action=action, comment=comme nt) | 298 self._save(self.meta, self.content.data, name=name, action=action, comme nt=comment) |
384 for child in self.get_index(): | 299 for child in self.get_index(): |
385 item = Item.create(child[0]) | 300 item = Item.create(child[0]) |
386 item._save(item.meta, item.data, name='/'.join((name, child[1])), ac tion=action, comment=comment) | 301 item._save(item.meta, item.content.data, name='/'.join((name, child[ 1])), action=action, comment=comment) |
387 | 302 |
388 def rename(self, name, comment=u''): | 303 def rename(self, name, comment=u''): |
389 """ | 304 """ |
390 rename this item to item <name> | 305 rename this item to item <name> |
391 """ | 306 """ |
392 return self._rename(name, comment, action=u'RENAME') | 307 return self._rename(name, comment, action=u'RENAME') |
393 | 308 |
394 def delete(self, comment=u''): | 309 def delete(self, comment=u''): |
395 """ | 310 """ |
396 delete this item | 311 delete this item |
397 """ | 312 """ |
398 trash_prefix = u'Trash/' # XXX move to config | 313 trash_prefix = u'Trash/' # XXX move to config |
399 now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) | 314 now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) |
400 # make trash name unique by including timestamp: | 315 # make trash name unique by including timestamp: |
401 trashname = u'{0}{1} ({2} UTC)'.format(trash_prefix, self.name, now) | 316 trashname = u'{0}{1} ({2} UTC)'.format(trash_prefix, self.name, now) |
402 return self._rename(trashname, comment, action=u'TRASH') | 317 return self._rename(trashname, comment, action=u'TRASH') |
403 | 318 |
404 def revert(self, comment=u''): | 319 def revert(self, comment=u''): |
405 return self._save(self.meta, self.data, action=u'REVERT', comment=commen t) | 320 return self._save(self.meta, self.content.data, action=u'REVERT', commen t=comment) |
406 | 321 |
407 def destroy(self, comment=u'', destroy_item=False): | 322 def destroy(self, comment=u'', destroy_item=False): |
408 # called from destroy UI/POST | 323 # called from destroy UI/POST |
409 if destroy_item: | 324 if destroy_item: |
410 # destroy complete item with all revisions, metadata, etc. | 325 # destroy complete item with all revisions, metadata, etc. |
411 self.rev.item.destroy_all_revisions() | 326 self.rev.item.destroy_all_revisions() |
412 else: | 327 else: |
413 # just destroy this revision | 328 # just destroy this revision |
414 self.rev.item.destroy_revision(self.rev.revid) | 329 self.rev.item.destroy_revision(self.rev.revid) |
415 | 330 |
416 def modify(self, meta, data, comment=u'', contenttype_guessed=None, contentt ype_qs=None): | 331 def modify(self, meta, data, comment=u'', contenttype_guessed=None, contentt ype_qs=None): |
417 if contenttype_qs: | 332 if contenttype_qs: |
418 # we use querystring param to FORCE content type | 333 # we use querystring param to FORCE content type |
419 meta[CONTENTTYPE] = contenttype_qs | 334 meta[CONTENTTYPE] = contenttype_qs |
420 | 335 |
421 return self._save(meta, data, contenttype_guessed=contenttype_guessed, c omment=comment) | 336 return self._save(meta, data, contenttype_guessed=contenttype_guessed, c omment=comment) |
422 | 337 |
338 class _ModifyForm(BaseChangeForm): | |
339 """Base class for ModifyForm of Item subclasses.""" | |
340 meta_form = BaseMetaForm | |
341 extra_meta_text = OptionalMultilineText.using(label=L_("Extra MetaData ( JSON)")).with_properties(rows=ROWS_META, cols=COLS).validated_by(ValidJSON()) | |
342 | |
343 def _load(self, item): | |
344 meta = item.prepare_meta_for_modify(item.meta) | |
345 self['meta_form'].set(meta, 'duck') | |
346 for k in self['meta_form'].field_schema_mapping.keys(): | |
347 meta.pop(k, None) | |
348 self['extra_meta_text'].set(item.meta_dict_to_text(meta)) | |
349 self['content_form']._load(item.content) | |
350 | |
351 def _dump(self, item): | |
352 meta = self['meta_form'].value.copy() | |
353 meta['tags'] = list(self['meta_form']['tags']) | |
354 meta.update(item.meta_text_to_dict(self['extra_meta_text'].value)) | |
355 data, contenttype_guessed = self['content_form']._dump(item.content) | |
356 comment = self['comment'].value | |
357 return meta, data, contenttype_guessed, comment | |
358 | |
359 @classmethod | |
360 def from_item(cls, item): | |
361 form = cls.from_defaults() | |
362 TextCha(form).amend_form() | |
363 form._load(item) | |
364 return form | |
365 | |
366 @classmethod | |
367 def from_request(cls, request): | |
368 form = cls.from_flat(request.form.items() + request.files.items()) | |
369 TextCha(form).amend_form() | |
370 return form | |
371 | |
372 def do_modify(self, template_name): | |
373 """ | |
374 Handle +modify requests, both GET and POST. | |
375 | |
376 This method should be overridden in subclasses, providing polymorphic | |
377 behavior for the +modify view. | |
378 """ | |
379 raise NotImplementedError | |
380 | |
423 def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_gues sed=None, comment=u'', overwrite=False): | 381 def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_gues sed=None, comment=u'', overwrite=False): |
424 backend = flaskg.storage | 382 backend = flaskg.storage |
425 storage_item = backend[self.name] | 383 storage_item = backend[self.name] |
426 try: | 384 try: |
427 currentrev = storage_item.get_revision(CURRENT) | 385 currentrev = storage_item.get_revision(CURRENT) |
428 rev_id = currentrev.revid | 386 rev_id = currentrev.revid |
429 contenttype_current = currentrev.meta.get(CONTENTTYPE) | 387 contenttype_current = currentrev.meta.get(CONTENTTYPE) |
430 except KeyError: # XXX was: NoSuchRevisionError: | 388 except KeyError: # XXX was: NoSuchRevisionError: |
431 currentrev = None | 389 currentrev = None |
432 rev_id = None | 390 rev_id = None |
(...skipping 19 matching lines...) Expand all Loading... | |
452 | 410 |
453 if data is None: | 411 if data is None: |
454 if currentrev is not None: | 412 if currentrev is not None: |
455 # we don't have (new) data, just copy the old one. | 413 # we don't have (new) data, just copy the old one. |
456 # a valid usecase of this is to just edit metadata. | 414 # a valid usecase of this is to just edit metadata. |
457 data = currentrev.data | 415 data = currentrev.data |
458 else: | 416 else: |
459 data = '' | 417 data = '' |
460 | 418 |
461 if isinstance(data, unicode): | 419 if isinstance(data, unicode): |
462 data = data.encode(config.charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT. | 420 data = data.encode(charset) # XXX wrong! if contenttype gives a codi ng, we MUST use THAT. |
463 | 421 |
464 if isinstance(data, str): | 422 if isinstance(data, str): |
465 data = StringIO(data) | 423 data = StringIO(data) |
466 | 424 |
467 newrev = storage_item.store_revision(meta, data, overwrite=overwrite, | 425 newrev = storage_item.store_revision(meta, data, overwrite=overwrite, |
468 action=unicode(action), | 426 action=unicode(action), |
469 contenttype_current=contenttype_cur rent, | 427 contenttype_current=contenttype_cur rent, |
470 contenttype_guessed=contenttype_gue ssed, | 428 contenttype_guessed=contenttype_gue ssed, |
471 ) | 429 ) |
472 item_modified.send(app._get_current_object(), item_name=name) | 430 item_modified.send(app._get_current_object(), item_name=name) |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
592 initials = [(name[1][0]) | 550 initials = [(name[1][0]) |
593 for name in names] | 551 for name in names] |
594 return initials | 552 return initials |
595 | 553 |
596 delete_template = 'delete.html' | 554 delete_template = 'delete.html' |
597 destroy_template = 'destroy.html' | 555 destroy_template = 'destroy.html' |
598 diff_template = 'diff.html' | 556 diff_template = 'diff.html' |
599 rename_template = 'rename.html' | 557 rename_template = 'rename.html' |
600 revert_template = 'revert.html' | 558 revert_template = 'revert.html' |
601 | 559 |
560 | |
561 class Contentful(Item): | |
562 """ | |
563 Base class for Item subclasses that have content. | |
564 """ | |
565 @property | |
566 def ModifyForm(self): | |
567 class C(Item._ModifyForm): | |
568 content_form = self.content.ModifyForm | |
569 C.__name__ = 'ModifyForm' | |
570 return C | |
571 | |
572 | |
573 # TODO better name and clearer definition | |
574 class Default(Contentful): | |
575 """ | |
576 A "conventional" wiki item. | |
577 """ | |
578 def _do_modify_show_templates(self): | |
579 # call this if the item is still empty | |
580 rev_ids = [] | |
581 item_templates = self.content.get_templates(self.contenttype) | |
582 return render_template('modify_show_template_selection.html', | |
583 item_name=self.name, | |
584 # XXX avoid the magic string | |
585 itemtype=u'default', | |
586 rev=self.rev, | |
587 contenttype=self.contenttype, | |
588 templates=item_templates, | |
589 first_rev_id=rev_ids and rev_ids[0], | |
590 last_rev_id=rev_ids and rev_ids[-1], | |
591 meta_rendered='', | |
592 data_rendered='', | |
593 ) | |
594 | |
595 def do_modify(self, template_name): | |
596 method = request.method | |
597 if method == 'GET': | |
598 if isinstance(self.content, NonExistentContent): | |
599 return render_template('modify_show_contenttype_selection.html', | |
600 item_name=self.name, | |
601 # XXX avoid the magic string | |
602 itemtype=u'default', | |
603 contenttype_groups=CONTENTTYPE_GROUPS, | |
604 ) | |
605 item = self | |
606 if isinstance(self.rev, DummyRev): | |
607 if template_name is None: | |
608 return self._do_modify_show_templates() | |
609 elif template_name: | |
610 item = Item.create(template_name) | |
611 form = self.ModifyForm.from_item(item) | |
612 elif method == 'POST': | |
613 # XXX workaround for *Draw items | |
614 if isinstance(self.content, Draw): | |
615 try: | |
616 self.content.handle_post() | |
617 except AccessDenied: | |
618 abort(403) | |
619 else: | |
620 # *Draw Applets POSTs more than once, redirecting would | |
621 # break them | |
622 return "OK" | |
623 form = self.ModifyForm.from_request(request) | |
624 if form.validate(): | |
625 meta, data, contenttype_guessed, comment = form._dump(self) | |
626 contenttype_qs = request.values.get('contenttype') | |
627 try: | |
628 self.modify(meta, data, comment, contenttype_guessed, conten ttype_qs) | |
629 except AccessDenied: | |
630 abort(403) | |
631 else: | |
632 return redirect(url_for_item(self.name)) | |
633 return render_template(self.modify_template, | |
634 item_name=self.name, | |
635 rows_meta=str(ROWS_META), cols=str(COLS), | |
636 form=form, | |
637 search_form=None, | |
638 ) | |
639 | |
640 modify_template = 'modify.html' | |
641 | |
642 item_registry.register(Default._factory, u'default') | |
643 | |
644 | |
645 class Ticket(Contentful): | |
646 """ | |
647 Stub for ticket item class. | |
648 """ | |
649 | |
650 item_registry.register(Ticket._factory, u'ticket') | |
651 | |
652 | |
653 class Userprofile(Item): | |
654 """ | |
655 Currently userprofile is implemented as a contenttype. This is a stub of an | |
656 itemtype implementation of userprofile. | |
657 """ | |
658 | |
659 item_registry.register(Userprofile._factory, u'userprofile') | |
660 | |
661 | |
602 class NonExistent(Item): | 662 class NonExistent(Item): |
603 def do_get(self, force_attachment=False, mimetype=None): | 663 def do_get(self, force_attachment=False, mimetype=None): |
604 abort(404) | 664 abort(404) |
605 | 665 |
606 def _convert(self, doc): | 666 def _convert(self, doc): |
607 abort(404) | 667 abort(404) |
608 | 668 |
609 def do_modify(self, contenttype, template_name): | 669 def do_modify(self, template_name): |
610 # First, check if the current user has the required privileges | 670 # First, check if the current user has the required privileges |
611 if not flaskg.user.may.create(self.name): | 671 if not flaskg.user.may.create(self.name): |
612 abort(403) | 672 abort(403) |
613 | 673 |
614 return render_template('modify_show_type_selection.html', | 674 # TODO Construct this list from the item_registry. Two more fields (ie. |
675 # display name and description) are needed in the registry then to | |
676 # support the automatic construction. | |
677 ITEMTYPES = [ | |
678 (u'default', u'Default', 'Wiki item'), | |
679 (u'ticket', u'Ticket', 'Ticket item'), | |
680 ] | |
681 | |
682 return render_template('modify_show_itemtype_selection.html', | |
615 item_name=self.name, | 683 item_name=self.name, |
616 contenttype_groups=CONTENTTYPE_GROUPS, | 684 itemtypes=ITEMTYPES, |
617 ) | 685 ) |
618 | 686 |
619 item_registry.register(NonExistent._factory, Type('application/x-nonexistent')) | 687 item_registry.register(NonExistent._factory, u'nonexistent') |
620 | 688 |
621 class ValidJSON(Validator): | 689 |
622 """Validator for JSON | 690 # This should be a separate module items/content.py. Included here to |
691 # faciliate codereview. | |
692 """ | |
693 MoinMoin - item contents | |
694 | |
695 Classes handling the content part of items (ie. minus metadata). The | |
696 content part is sometimes called the "data" part in other places, but is | |
697 always called content in this module to avoid confusion. | |
698 | |
699 Each class in this module corresponds to a contenttype value. | |
700 """ | |
701 | |
702 import os, re, base64 | |
703 import tarfile | |
704 import zipfile | |
705 import tempfile | |
706 from StringIO import StringIO | |
707 from array import array | |
708 | |
709 from flatland import Form, String | |
710 | |
711 from whoosh.query import Term, And | |
712 | |
713 from MoinMoin.forms import File | |
714 | |
715 from MoinMoin.util.mimetype import MimeType | |
716 from MoinMoin.util.mime import Type, type_moin_document | |
717 from MoinMoin.util.tree import moin_page, html, xlink, docbook | |
718 from MoinMoin.util.iri import Iri | |
719 from MoinMoin.util.crypto import cache_key | |
720 from MoinMoin.storage.middleware.protecting import AccessDenied | |
721 | |
722 try: | |
723 import PIL | |
724 from PIL import Image as PILImage | |
725 from PIL.ImageChops import difference as PILdiff | |
726 except ImportError: | |
727 PIL = None | |
728 | |
729 from MoinMoin import log | |
730 logging = log.getLogger(__name__) | |
731 | |
732 from flask import current_app as app | |
733 from flask import g as flaskg | |
734 | |
735 from flask import request, url_for, Response, abort, escape | |
736 | |
737 from jinja2 import Markup | |
738 | |
739 from MoinMoin.i18n import _, L_ | |
740 from MoinMoin.themes import render_template | |
741 from MoinMoin import wikiutil, config | |
742 from MoinMoin.util.send_file import send_file | |
743 from MoinMoin.util.interwiki import url_for_item | |
744 from MoinMoin.storage.error import StorageError | |
745 from MoinMoin.util.registry import RegistryBase | |
746 from MoinMoin.constants.keys import ( | |
747 NAME, NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM | |
748 ) | |
749 | |
750 | |
751 COLS = 80 | |
752 ROWS_DATA = 20 | |
Reimar Bauer
2012/07/23 21:40:57
repeating constant
xiaq
2012/07/24 02:50:06
fixing
| |
753 | |
Reimar Bauer
2012/07/23 21:40:57
both lines sound like regular constants and also m
xiaq
2012/07/24 02:50:06
yes but let's do that in another changeset.
| |
754 | |
755 class RegistryContent(RegistryBase): | |
756 # XXX Too much boilerplate in Entry implementation. Maybe use namedtuple | |
757 # as a starting point? | |
758 class Entry(object): | |
759 def __init__(self, factory, content_type, priority): | |
760 self.factory = factory | |
761 self.content_type = content_type | |
762 self.priority = priority | |
763 | |
764 def __call__(self, name, content_type, kw): | |
765 if self.content_type.issupertype(content_type): | |
766 return self.factory(name, content_type, **kw) | |
767 | |
768 def __eq__(self, other): | |
769 if isinstance(other, self.__class__): | |
770 return (self.factory == other.factory and | |
771 self.content_type == other.content_type and | |
772 self.priority == other.priority) | |
773 return NotImplemented | |
774 | |
775 def __lt__(self, other): | |
776 if isinstance(other, self.__class__): | |
777 if self.priority < other.priority: | |
778 return True | |
779 if self.content_type != other.content_type: | |
780 return other.content_type.issupertype(self.content_type) | |
781 return False | |
782 return NotImplemented | |
783 | |
784 def __repr__(self): | |
785 return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__ , | |
786 self.content_type, | |
787 self.priority, | |
788 self.factory) | |
789 | |
790 def get(self, name, content_type, **kw): | |
791 for entry in self._entries: | |
792 item = entry(name, content_type, kw) | |
793 if item is not None: | |
794 return item | |
795 | |
796 def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MID DLE): | |
797 """ | |
798 Register a factory | |
799 | |
800 :param factory: Factory to register. Callable, must return an object. | |
801 """ | |
802 return self._register(self.Entry(factory, content_type, priority)) | |
803 | |
804 | |
805 content_registry = RegistryContent() | |
806 | |
807 | |
808 def conv_serialize(doc, namespaces, method='polyglot'): | |
809 out = array('u') | |
810 flaskg.clock.start('conv_serialize') | |
811 doc.write(out.fromunicode, namespaces=namespaces, method=method) | |
812 out = out.tounicode() | |
813 flaskg.clock.stop('conv_serialize') | |
814 return out | |
815 | |
816 | |
817 class Content(object): | |
623 """ | 818 """ |
624 invalid_json_msg = L_('Invalid JSON.') | 819 Base for content classes defining some helpers, agnostic about content |
625 | 820 data. |
626 def validate(self, element, state): | 821 """ |
627 try: | 822 @classmethod |
628 json.loads(element.value) | 823 def _factory(cls, name=u'', contenttype=None, **kw): |
629 except: | 824 return cls(name, contenttype=unicode(contenttype), **kw) |
630 return self.note_error(element, state, 'invalid_json_msg') | 825 |
631 return True | 826 def __init__(self, name, rev=None, contenttype=None, hash_hexdigest=None): |
632 | 827 self.name = name |
633 | 828 self.rev = rev |
634 class BaseChangeForm(TextChaizedForm): | 829 self.contenttype = contenttype |
635 comment = OptionalText.using(label=L_('Comment')).with_properties(placeholde r=L_("Comment about your change")) | 830 self.hash_hexdigest = hash_hexdigest |
636 submit = Submit | 831 |
637 | |
638 | |
639 class Binary(Item): | |
640 """ An arbitrary binary item, fallback class for every item mimetype. """ | |
641 modify_help = """\ | |
642 There is no help, you're doomed! | |
643 """ | |
644 | |
645 template = "modify_binary.html" | |
646 | |
647 # XXX reads item rev data into memory! | |
648 def get_data(self): | 832 def get_data(self): |
649 if self.rev is not None: | 833 return '' # TODO create a better method for binary stuff |
650 return self.rev.data.read() | 834 data = property(fget=get_data) |
835 | |
836 def internal_representation(self, converters=['smiley']): | |
Reimar Bauer
2012/07/23 21:40:57
Thomas:
that code looks like limited to smileys.
| |
837 """ | |
838 Return the internal representation of a document using a DOM Tree | |
839 """ | |
840 flaskg.clock.start('conv_in_dom') | |
841 hash_name = HASH_ALGORITHM | |
842 hash_hexdigest = self.rev.meta.get(hash_name) | |
843 if hash_hexdigest: | |
844 cid = cache_key(usage="internal_representation", | |
845 hash_name=hash_name, | |
846 hash_hexdigest=hash_hexdigest) | |
847 doc = app.cache.get(cid) | |
651 else: | 848 else: |
652 return '' | 849 # likely a non-existing item |
653 data = property(fget=get_data) | 850 doc = cid = None |
654 | 851 if doc is None: |
655 def _render_meta(self): | 852 # We will see if we can perform the conversion: |
656 return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False))) | 853 # FROM_mimetype --> DOM |
854 # if so we perform the transformation, otherwise we don't | |
855 from MoinMoin.converter import default_registry as reg | |
856 input_conv = reg.get(Type(self.contenttype), type_moin_document) | |
857 if not input_conv: | |
858 raise TypeError("We cannot handle the conversion from {0} to the DOM tree".format(self.contenttype)) | |
859 smiley_conv = reg.get(type_moin_document, type_moin_document, | |
860 icon='smiley') | |
861 | |
862 # We can process the conversion | |
863 links = Iri(scheme='wiki', authority='', path='/' + self.name) | |
864 doc = input_conv(self.rev, self.contenttype) | |
865 # XXX is the following assuming that the top element of the doc tree | |
866 # is a moin_page.page element? if yes, this is the wrong place to do that | |
867 # as not every doc will have that element (e.g. for images, we just get | |
868 # moin_page.object, for a tar item, we get a moin_page.table): | |
869 doc.set(moin_page.page_href, unicode(links)) | |
870 for conv in converters: | |
871 if conv == 'smiley': | |
872 doc = smiley_conv(doc) | |
873 if cid: | |
874 app.cache.set(cid, doc) | |
875 flaskg.clock.stop('conv_in_dom') | |
876 return doc | |
877 | |
878 def _expand_document(self, doc): | |
879 from MoinMoin.converter import default_registry as reg | |
880 include_conv = reg.get(type_moin_document, type_moin_document, includes= 'expandall') | |
881 macro_conv = reg.get(type_moin_document, type_moin_document, macros='exp andall') | |
882 link_conv = reg.get(type_moin_document, type_moin_document, links='exter n') | |
883 flaskg.clock.start('conv_include') | |
884 doc = include_conv(doc) | |
885 flaskg.clock.stop('conv_include') | |
886 flaskg.clock.start('conv_macro') | |
887 doc = macro_conv(doc) | |
888 flaskg.clock.stop('conv_macro') | |
889 flaskg.clock.start('conv_link') | |
890 doc = link_conv(doc) | |
891 flaskg.clock.stop('conv_link') | |
892 return doc | |
893 | |
894 def _render_data(self): | |
895 from MoinMoin.converter import default_registry as reg | |
896 # TODO: Real output format | |
897 doc = self.internal_representation() | |
898 doc = self._expand_document(doc) | |
899 flaskg.clock.start('conv_dom_html') | |
900 html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-p age')) | |
901 doc = html_conv(doc) | |
902 flaskg.clock.stop('conv_dom_html') | |
903 rendered_data = conv_serialize(doc, {html.namespace: ''}) | |
904 return rendered_data | |
905 | |
906 def _render_data_xml(self): | |
907 doc = self.internal_representation() | |
908 return conv_serialize(doc, | |
909 {moin_page.namespace: '', | |
910 xlink.namespace: 'xlink', | |
911 html.namespace: 'html', | |
912 }, | |
913 'xml') | |
914 | |
915 def _render_data_highlight(self): | |
916 # override this in child classes | |
917 return '' | |
657 | 918 |
658 def get_templates(self, contenttype=None): | 919 def get_templates(self, contenttype=None): |
659 """ create a list of templates (for some specific contenttype) """ | 920 """ create a list of templates (for some specific contenttype) """ |
660 terms = [Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, u'template')] | 921 terms = [Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, u'template')] |
661 if contenttype is not None: | 922 if contenttype is not None: |
662 terms.append(Term(CONTENTTYPE, contenttype)) | 923 terms.append(Term(CONTENTTYPE, contenttype)) |
663 query = And(terms) | 924 query = And(terms) |
664 revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None) | 925 revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None) |
665 return [rev.meta[NAME] for rev in revs] | 926 return [rev.meta[NAME] for rev in revs] |
666 | 927 |
667 class ModifyForm(BaseChangeForm): | 928 def _write_stream(self, content, new_rev, bufsize=8192): |
668 """Base class for ModifyForm of Binary's subclasses.""" | 929 written = 0 |
669 meta_text = RequiredText.with_properties(placeholder=L_("MetaData (JSON) ")).validated_by(ValidJSON()) | 930 if hasattr(content, "read"): |
931 while True: | |
932 buf = content.read(bufsize) | |
933 if not buf: | |
934 break | |
935 new_rev.data.write(buf) | |
936 written += len(buf) | |
937 elif isinstance(content, str): | |
938 new_rev.data.write(content) | |
939 written += len(content) | |
940 else: | |
941 raise StorageError("unsupported content object: {0!r}".format(conten t)) | |
942 return written | |
943 | |
944 | |
945 class NonExistentContent(Content): | |
946 """Dummy Content to use when with NonExistent.""" | |
947 | |
948 #content_registry.register(NonExistentContent._factory, Type('application/x-none xistent')) | |
949 | |
950 | |
951 class Binary(Content): | |
952 """ An arbitrary binary item, fallback class for every item mimetype. """ | |
953 | |
954 # XXX reads item rev data into memory! | |
955 def get_data(self): | |
956 if self.rev is not None: | |
957 return self.rev.data.read() | |
958 else: | |
959 return '' | |
960 data = property(fget=get_data) | |
961 | |
962 | |
963 class ModifyForm(Form): | |
964 template = 'modify_binary.html' | |
965 help = """\ | |
966 There is no help, you're doomed! | |
967 """ | |
670 data_file = File.using(optional=True, label=L_('Upload file:')) | 968 data_file = File.using(optional=True, label=L_('Upload file:')) |
671 | 969 |
672 def _load(self, item): | 970 def _load(self, item): |
673 self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_mod ify(item.meta)) | 971 pass |
674 | 972 |
675 def _dump(self, item): | 973 def _dump(self, item): |
676 data = meta = contenttype_guessed = None | |
677 data_file = self['data_file'].value | 974 data_file = self['data_file'].value |
678 if data_file: | 975 if data_file: |
679 data = data_file.stream | 976 data = data_file.stream |
680 # this is likely a guess by the browser, based on the filename | 977 # this is likely a guess by the browser, based on the filename |
681 contenttype_guessed = data_file.content_type # comes from form m ultipart data | 978 contenttype_guessed = data_file.content_type # comes from form m ultipart data |
682 meta = item.meta_text_to_dict(self['meta_text'].value) | 979 return data, contenttype_guessed |
683 comment = self['comment'].value | 980 else: |
684 return meta, data, contenttype_guessed, comment | 981 return None, None |
685 | |
686 extra_template_args = {} | |
687 | |
688 @classmethod | |
689 def from_item(cls, item): | |
690 form = cls.from_defaults() | |
691 TextCha(form).amend_form() | |
692 form._load(item) | |
693 return form | |
694 | |
695 @classmethod | |
696 def from_request(cls, request): | |
697 form = cls.from_flat(request.form.items() + request.files.items()) | |
698 TextCha(form).amend_form() | |
699 return form | |
700 | |
701 def do_modify(self, contenttype, template_name): | |
702 """ | |
703 Handle +modify requests, both GET and POST. | |
704 | |
705 This method can be overridden in subclasses, providing polymorphic | |
706 behavior for the +modify view. | |
707 """ | |
708 method = request.method | |
709 if method == 'GET': | |
710 item = self | |
711 if isinstance(self.rev, DummyRev): | |
712 if template_name is None: | |
713 return self._do_modify_show_templates() | |
714 elif template_name: | |
715 item = Item.create(template_name) | |
716 form = self.ModifyForm.from_item(item) | |
717 elif method == 'POST': | |
718 form = self.ModifyForm.from_request(request) | |
719 if form.validate(): | |
720 meta, data, contenttype_guessed, comment = form._dump(self) | |
721 contenttype_qs = request.values.get('contenttype') | |
722 try: | |
723 self.modify(meta, data, comment, contenttype_guessed, conten ttype_qs) | |
724 except AccessDenied: | |
725 abort(403) | |
726 else: | |
727 return redirect(url_for_item(self.name)) | |
728 return render_template(self.template, | |
729 item_name=self.name, | |
730 rows_meta=str(ROWS_META), cols=str(COLS), | |
731 help=self.modify_help, | |
732 form=form, | |
733 search_form=None, | |
734 **form.extra_template_args | |
735 ) | |
736 | 982 |
737 def _render_data_diff(self, oldrev, newrev): | 983 def _render_data_diff(self, oldrev, newrev): |
738 hash_name = HASH_ALGORITHM | 984 hash_name = HASH_ALGORITHM |
739 if oldrev.meta[hash_name] == newrev.meta[hash_name]: | 985 if oldrev.meta[hash_name] == newrev.meta[hash_name]: |
740 return _("The items have the same data hash code (that means they ve ry likely have the same data).") | 986 return _("The items have the same data hash code (that means they ve ry likely have the same data).") |
741 else: | 987 else: |
742 return _("The items have different data.") | 988 return _("The items have different data.") |
743 | 989 |
744 _render_data_diff_text = _render_data_diff | 990 _render_data_diff_text = _render_data_diff |
745 _render_data_diff_raw = _render_data_diff | 991 _render_data_diff_raw = _render_data_diff |
746 | 992 |
747 def _render_data_diff_atom(self, oldrev, newrev): | 993 def _render_data_diff_atom(self, oldrev, newrev): |
748 return render_template('atom.html', | 994 return render_template('atom.html', |
749 oldrev=oldrev, newrev=newrev, get='binary', | 995 oldrev=oldrev, newrev=newrev, get='binary', |
750 content=Markup(self._render_data())) | 996 content=Markup(self._render_data())) |
751 | 997 |
752 def _convert(self, doc): | 998 def _convert(self, doc): |
753 return _("Impossible to convert the data to the contenttype: %(contentty pe)s", | 999 return _("Impossible to convert the data to the contenttype: %(contentty pe)s", |
754 contenttype=request.values.get('contenttype')) | 1000 contenttype=request.values.get('contenttype')) |
755 | 1001 |
756 def do_get(self, force_attachment=False, mimetype=None): | |
757 hash = self.rev.meta.get(HASH_ALGORITHM) | |
758 if is_resource_modified(request.environ, hash): # use hash as etag | |
759 return self._do_get_modified(hash, force_attachment=force_attachment , mimetype=mimetype) | |
760 else: | |
761 return Response(status=304) | |
762 | |
763 def _do_get_modified(self, hash, force_attachment=False, mimetype=None): | 1002 def _do_get_modified(self, hash, force_attachment=False, mimetype=None): |
764 member = request.values.get('member') | 1003 member = request.values.get('member') |
765 return self._do_get(hash, member, force_attachment=force_attachment, mim etype=mimetype) | 1004 return self._do_get(hash, member, force_attachment=force_attachment, mim etype=mimetype) |
766 | 1005 |
767 def _do_get(self, hash, member=None, force_attachment=False, mimetype=None): | 1006 def _do_get(self, hash, member=None, force_attachment=False, mimetype=None): |
768 if member: # content = file contained within a archive item revision | 1007 if member: # content = file contained within a archive item revision |
769 path, filename = os.path.split(member) | 1008 path, filename = os.path.split(member) |
770 mt = MimeType(filename=filename) | 1009 mt = MimeType(filename=filename) |
771 content_length = None | 1010 content_length = None |
772 file_to_send = self.get_member(member) | 1011 file_to_send = self.get_member(member) |
(...skipping 12 matching lines...) Expand all Loading... | |
785 content_type = mimetype | 1024 content_type = mimetype |
786 else: | 1025 else: |
787 content_type = mt.content_type() | 1026 content_type = mt.content_type() |
788 as_attachment = force_attachment or mt.as_attachment(app.cfg) | 1027 as_attachment = force_attachment or mt.as_attachment(app.cfg) |
789 return send_file(file=file_to_send, | 1028 return send_file(file=file_to_send, |
790 mimetype=content_type, | 1029 mimetype=content_type, |
791 as_attachment=as_attachment, attachment_filename=filena me, | 1030 as_attachment=as_attachment, attachment_filename=filena me, |
792 cache_timeout=10, # wiki data can change rapidly | 1031 cache_timeout=10, # wiki data can change rapidly |
793 add_etags=True, etag=hash, conditional=True) | 1032 add_etags=True, etag=hash, conditional=True) |
794 | 1033 |
795 item_registry.register(Binary._factory, Type('*/*')) | 1034 content_registry.register(Binary._factory, Type('*/*')) |
796 | 1035 |
797 | 1036 |
798 class RenderableBinary(Binary): | 1037 class RenderableBinary(Binary): |
799 """ Base class for some binary stuff that renders with a object tag. """ | 1038 """ Base class for some binary stuff that renders with a object tag. """ |
800 | 1039 |
801 | 1040 |
802 class Application(Binary): | 1041 class Application(Binary): |
803 """ Base class for application/* """ | 1042 """ Base class for application/* """ |
804 | 1043 |
805 | 1044 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
869 self._save(meta, data, name=self.name, action=u'SAVE', comment='') | 1108 self._save(meta, data, name=self.name, action=u'SAVE', comment='') |
870 data.close() | 1109 data.close() |
871 os.remove(temp_fname) | 1110 os.remove(temp_fname) |
872 | 1111 |
873 | 1112 |
874 class ApplicationXTar(TarMixin, Application): | 1113 class ApplicationXTar(TarMixin, Application): |
875 """ | 1114 """ |
876 Tar items | 1115 Tar items |
877 """ | 1116 """ |
878 | 1117 |
879 item_registry.register(ApplicationXTar._factory, Type('application/x-tar')) | 1118 content_registry.register(ApplicationXTar._factory, Type('application/x-tar')) |
880 item_registry.register(ApplicationXTar._factory, Type('application/x-gtar')) | 1119 content_registry.register(ApplicationXTar._factory, Type('application/x-gtar')) |
881 | 1120 |
882 | 1121 |
883 class ZipMixin(object): | 1122 class ZipMixin(object): |
884 """ | 1123 """ |
885 ZipMixin offers additional functionality for zip-like items to list and | 1124 ZipMixin offers additional functionality for zip-like items to list and |
886 access member files. | 1125 access member files. |
887 """ | 1126 """ |
888 def list_members(self): | 1127 def list_members(self): |
889 """ | 1128 """ |
890 list zip file contents (member file names) | 1129 list zip file contents (member file names) |
(...skipping 14 matching lines...) Expand all Loading... | |
905 | 1144 |
906 def put_member(self, name, content, content_length, expected_members): | 1145 def put_member(self, name, content, content_length, expected_members): |
907 raise NotImplementedError | 1146 raise NotImplementedError |
908 | 1147 |
909 | 1148 |
910 class ApplicationZip(ZipMixin, Application): | 1149 class ApplicationZip(ZipMixin, Application): |
911 """ | 1150 """ |
912 Zip items | 1151 Zip items |
913 """ | 1152 """ |
914 | 1153 |
915 item_registry.register(ApplicationZip._factory, Type('application/zip')) | 1154 content_registry.register(ApplicationZip._factory, Type('application/zip')) |
916 | 1155 |
917 | 1156 |
918 class PDF(Application): | 1157 class PDF(Application): |
919 """ PDF """ | 1158 """ PDF """ |
920 | 1159 |
921 item_registry.register(PDF._factory, Type('application/pdf')) | 1160 content_registry.register(PDF._factory, Type('application/pdf')) |
922 | 1161 |
923 | 1162 |
924 class Video(Binary): | 1163 class Video(Binary): |
925 """ Base class for video/* """ | 1164 """ Base class for video/* """ |
926 | 1165 |
927 item_registry.register(Video._factory, Type('video/*')) | 1166 content_registry.register(Video._factory, Type('video/*')) |
928 | 1167 |
929 | 1168 |
930 class Audio(Binary): | 1169 class Audio(Binary): |
931 """ Base class for audio/* """ | 1170 """ Base class for audio/* """ |
932 | 1171 |
933 item_registry.register(Audio._factory, Type('audio/*')) | 1172 content_registry.register(Audio._factory, Type('audio/*')) |
934 | 1173 |
935 | 1174 |
936 class Image(Binary): | 1175 class Image(Binary): |
937 """ Base class for image/* """ | 1176 """ Base class for image/* """ |
938 | 1177 |
939 item_registry.register(Image._factory, Type('image/*')) | 1178 content_registry.register(Image._factory, Type('image/*')) |
940 | 1179 |
941 | 1180 |
942 class RenderableImage(RenderableBinary): | 1181 class RenderableImage(RenderableBinary): |
943 """ Base class for renderable Image mimetypes """ | 1182 """ Base class for renderable Image mimetypes """ |
944 | 1183 |
945 | 1184 |
946 class SvgImage(RenderableImage): | 1185 class SvgImage(RenderableImage): |
947 """ SVG images use <object> tag mechanism from RenderableBinary base class " "" | 1186 """ SVG images use <object> tag mechanism from RenderableBinary base class " "" |
948 | 1187 |
949 item_registry.register(SvgImage._factory, Type('image/svg+xml')) | 1188 content_registry.register(SvgImage._factory, Type('image/svg+xml')) |
950 | 1189 |
951 | 1190 |
952 class RenderableBitmapImage(RenderableImage): | 1191 class RenderableBitmapImage(RenderableImage): |
953 """ PNG/JPEG/GIF images use <img> tag (better browser support than <object>) """ | 1192 """ PNG/JPEG/GIF images use <img> tag (better browser support than <object>) """ |
954 # if mimetype is also transformable, please register in TransformableImage O NLY! | 1193 # if mimetype is also transformable, please register in TransformableImage O NLY! |
955 | 1194 |
956 | 1195 |
957 class TransformableBitmapImage(RenderableBitmapImage): | 1196 class TransformableBitmapImage(RenderableBitmapImage): |
958 """ We can transform (resize, rotate, mirror) some image types """ | 1197 """ We can transform (resize, rotate, mirror) some image types """ |
959 def _transform(self, content_type, size=None, transpose_op=None): | 1198 def _transform(self, content_type, size=None, transpose_op=None): |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1100 logging.exception("error during PILdiff: {0}".format(err.message )) | 1339 logging.exception("error during PILdiff: {0}".format(err.message )) |
1101 abort(404) # TODO render user friendly error image | 1340 abort(404) # TODO render user friendly error image |
1102 else: | 1341 else: |
1103 # XXX TODO check ACL behaviour | 1342 # XXX TODO check ACL behaviour |
1104 headers, data = c | 1343 headers, data = c |
1105 return Response(data, headers=headers) | 1344 return Response(data, headers=headers) |
1106 | 1345 |
1107 def _render_data_diff_text(self, oldrev, newrev): | 1346 def _render_data_diff_text(self, oldrev, newrev): |
1108 return super(TransformableBitmapImage, self)._render_data_diff_text(oldr ev, newrev) | 1347 return super(TransformableBitmapImage, self)._render_data_diff_text(oldr ev, newrev) |
1109 | 1348 |
1110 item_registry.register(TransformableBitmapImage._factory, Type('image/png')) | 1349 content_registry.register(TransformableBitmapImage._factory, Type('image/png')) |
1111 item_registry.register(TransformableBitmapImage._factory, Type('image/jpeg')) | 1350 content_registry.register(TransformableBitmapImage._factory, Type('image/jpeg')) |
1112 item_registry.register(TransformableBitmapImage._factory, Type('image/gif')) | 1351 content_registry.register(TransformableBitmapImage._factory, Type('image/gif')) |
1113 | 1352 |
1114 | 1353 |
1115 class Text(Binary): | 1354 class Text(Binary): |
1116 """ Base class for text/* """ | 1355 """ Base class for text/* """ |
1117 template = "modify_text.html" | |
1118 | 1356 |
1119 class ModifyForm(Binary.ModifyForm): | 1357 class ModifyForm(Binary.ModifyForm): |
1358 template = 'modify_text.html' | |
1120 data_text = String.using(strip=False, optional=True).with_properties(pla ceholder=L_("Type your text here")) | 1359 data_text = String.using(strip=False, optional=True).with_properties(pla ceholder=L_("Type your text here")) |
1360 rows = ROWS_DATA | |
1361 cols = COLS | |
1121 | 1362 |
1122 def _load(self, item): | 1363 def _load(self, item): |
1123 super(Text.ModifyForm, self)._load(item) | 1364 super(Text.ModifyForm, self)._load(item) |
1124 data = item.data | 1365 data = item.data |
1125 data = item.data_storage_to_internal(data) | 1366 data = item.data_storage_to_internal(data) |
1126 data = item.data_internal_to_form(data) | 1367 data = item.data_internal_to_form(data) |
1127 self['data_text'] = data | 1368 self['data_text'] = data |
1128 | 1369 |
1129 def _dump(self, item): | 1370 def _dump(self, item): |
1130 meta, data, contenttype_guessed, comment = super(Text.ModifyForm, se lf)._dump(item) | 1371 data, contenttype_guessed = super(Text.ModifyForm, self)._dump(item) |
1131 if data is None: | 1372 if data is None: |
1132 data = self['data_text'].value | 1373 data = self['data_text'].value |
1133 data = item.data_form_to_internal(data) | 1374 data = item.data_form_to_internal(data) |
1134 data = item.data_internal_to_storage(data) | 1375 data = item.data_internal_to_storage(data) |
1135 # we know it is text and utf-8 - XXX is there a way to get the c harset of the form? | 1376 # we know it is text and utf-8 - XXX is there a way to get the c harset of the form? |
1136 contenttype_guessed = u'text/plain;charset=utf-8' | 1377 contenttype_guessed = u'text/plain;charset=utf-8' |
1137 return meta, data, contenttype_guessed, comment | 1378 return data, contenttype_guessed |
1138 | |
1139 extra_template_args = {'rows_data': str(ROWS_DATA)} | |
1140 | 1379 |
1141 # text/plain mandates crlf - but in memory, we want lf only | 1380 # text/plain mandates crlf - but in memory, we want lf only |
1142 def data_internal_to_form(self, text): | 1381 def data_internal_to_form(self, text): |
1143 """ convert data from memory format to form format """ | 1382 """ convert data from memory format to form format """ |
1144 return text.replace(u'\n', u'\r\n') | 1383 return text.replace(u'\n', u'\r\n') |
1145 | 1384 |
1146 def data_form_to_internal(self, data): | 1385 def data_form_to_internal(self, data): |
1147 """ convert data from form format to memory format """ | 1386 """ convert data from form format to memory format """ |
1148 return data.replace(u'\r\n', u'\n') | 1387 return data.replace(u'\r\n', u'\n') |
1149 | 1388 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1189 data_text = self.data_storage_to_internal(self.data) | 1428 data_text = self.data_storage_to_internal(self.data) |
1190 # TODO: use registry as soon as it is in there | 1429 # TODO: use registry as soon as it is in there |
1191 from MoinMoin.converter.pygments_in import Converter as PygmentsConverte r | 1430 from MoinMoin.converter.pygments_in import Converter as PygmentsConverte r |
1192 pygments_conv = PygmentsConverter(contenttype=self.contenttype) | 1431 pygments_conv = PygmentsConverter(contenttype=self.contenttype) |
1193 doc = pygments_conv(data_text) | 1432 doc = pygments_conv(data_text) |
1194 # TODO: Real output format | 1433 # TODO: Real output format |
1195 html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-p age')) | 1434 html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-p age')) |
1196 doc = html_conv(doc) | 1435 doc = html_conv(doc) |
1197 return conv_serialize(doc, {html.namespace: ''}) | 1436 return conv_serialize(doc, {html.namespace: ''}) |
1198 | 1437 |
1199 item_registry.register(Text._factory, Type('text/*')) | 1438 content_registry.register(Text._factory, Type('text/*')) |
1200 | 1439 |
1201 | 1440 |
1202 class MarkupItem(Text): | 1441 class MarkupItem(Text): |
1203 """ | 1442 """ |
1204 some kind of item with markup | 1443 some kind of item with markup |
1205 (internal links and transcluded items) | 1444 (internal links and transcluded items) |
1206 """ | 1445 """ |
1207 | 1446 |
1208 | 1447 |
1209 class MoinWiki(MarkupItem): | 1448 class MoinWiki(MarkupItem): |
1210 """ MoinMoin wiki markup """ | 1449 """ MoinMoin wiki markup """ |
1211 | 1450 |
1212 item_registry.register(MoinWiki._factory, Type('text/x.moin.wiki')) | 1451 content_registry.register(MoinWiki._factory, Type('text/x.moin.wiki')) |
1213 | 1452 |
1214 | 1453 |
1215 class CreoleWiki(MarkupItem): | 1454 class CreoleWiki(MarkupItem): |
1216 """ Creole wiki markup """ | 1455 """ Creole wiki markup """ |
1217 | 1456 |
1218 item_registry.register(CreoleWiki._factory, Type('text/x.moin.creole')) | 1457 content_registry.register(CreoleWiki._factory, Type('text/x.moin.creole')) |
1219 | 1458 |
1220 | 1459 |
1221 class MediaWiki(MarkupItem): | 1460 class MediaWiki(MarkupItem): |
1222 """ MediaWiki markup """ | 1461 """ MediaWiki markup """ |
1223 | 1462 |
1224 item_registry.register(MediaWiki._factory, Type('text/x-mediawiki')) | 1463 content_registry.register(MediaWiki._factory, Type('text/x-mediawiki')) |
1225 | 1464 |
1226 | 1465 |
1227 class ReST(MarkupItem): | 1466 class ReST(MarkupItem): |
1228 """ ReStructured Text markup """ | 1467 """ ReStructured Text markup """ |
1229 | 1468 |
1230 item_registry.register(ReST._factory, Type('text/x-rst')) | 1469 content_registry.register(ReST._factory, Type('text/x-rst')) |
1231 | 1470 |
1232 | 1471 |
1233 class HTML(Text): | 1472 class HTML(Text): |
1234 """ | 1473 """ |
1235 HTML markup | 1474 HTML markup |
1236 | 1475 |
1237 Note: As we use html_in converter to convert this to DOM and later some | 1476 Note: As we use html_in converter to convert this to DOM and later some |
1238 output converterter to produce output format (e.g. html_out for html | 1477 output converterter to produce output format (e.g. html_out for html |
1239 output), all(?) unsafe stuff will get lost. | 1478 output), all(?) unsafe stuff will get lost. |
1240 | 1479 |
1241 Note: If raw revision data is accessed, unsafe stuff might be present! | 1480 Note: If raw revision data is accessed, unsafe stuff might be present! |
1242 """ | 1481 """ |
1243 template = "modify_text_html.html" | 1482 class ModifyForm(Text.ModifyForm): |
1483 template = "modify_text_html.html" | |
1244 | 1484 |
1245 item_registry.register(HTML._factory, Type('text/html')) | 1485 content_registry.register(HTML._factory, Type('text/html')) |
1246 | 1486 |
1247 | 1487 |
1248 class DocBook(MarkupItem): | 1488 class DocBook(MarkupItem): |
1249 """ DocBook Document """ | 1489 """ DocBook Document """ |
1250 def _convert(self, doc): | 1490 def _convert(self, doc): |
1251 from emeraldtree import ElementTree as ET | 1491 from emeraldtree import ElementTree as ET |
1252 from MoinMoin.converter import default_registry as reg | 1492 from MoinMoin.converter import default_registry as reg |
1253 | 1493 |
1254 doc = self._expand_document(doc) | 1494 doc = self._expand_document(doc) |
1255 | 1495 |
(...skipping 27 matching lines...) Expand all Loading... | |
1283 content_length = file_to_send.tell() | 1523 content_length = file_to_send.tell() |
1284 file_to_send.seek(0) | 1524 file_to_send.seek(0) |
1285 # Important: empty filename keeps flask from trying to autodetect filena me, | 1525 # Important: empty filename keeps flask from trying to autodetect filena me, |
1286 # as this would not work for us, because our file's are not necessarily fs files. | 1526 # as this would not work for us, because our file's are not necessarily fs files. |
1287 return send_file(file=file_to_send, | 1527 return send_file(file=file_to_send, |
1288 mimetype=content_type, | 1528 mimetype=content_type, |
1289 as_attachment=as_attachment, attachment_filename=None, | 1529 as_attachment=as_attachment, attachment_filename=None, |
1290 cache_timeout=10, # wiki data can change rapidly | 1530 cache_timeout=10, # wiki data can change rapidly |
1291 add_etags=False, etag=None, conditional=True) | 1531 add_etags=False, etag=None, conditional=True) |
1292 | 1532 |
1293 item_registry.register(DocBook._factory, Type('application/docbook+xml')) | 1533 content_registry.register(DocBook._factory, Type('application/docbook+xml')) |
1294 | 1534 |
1295 | 1535 |
1296 class Draw(TarMixin, Image): | 1536 class Draw(TarMixin, Image): |
1297 """ | 1537 """ |
1298 Base class for *Draw that use special Java/Javascript applets to modify and store data in a tar file. | 1538 Base class for *Draw that use special Java/Javascript applets to modify and store data in a tar file. |
1299 """ | 1539 """ |
1300 class ModifyForm(Binary.ModifyForm): | 1540 class ModifyForm(Binary.ModifyForm): |
1301 pass | 1541 # Set the workaround flag respected in modify.html |
1542 is_draw = True | |
1302 | 1543 |
1303 def handle_post(): | 1544 def handle_post(): |
1304 raise NotImplementedError | 1545 raise NotImplementedError |
1305 | 1546 |
1306 def do_modify(self, contenttype, template_name): | |
1307 # XXX as the "saving" POSTs come from *Draw applets (not the form), | |
1308 # they need to be handled specially for each applet. Besides, editing | |
1309 # meta_text doesn't work | |
1310 if request.method == 'POST': | |
1311 try: | |
1312 self.handle_post() | |
1313 except AccessDenied: | |
1314 abort(403) | |
1315 else: | |
1316 # *Draw Applets POSTs more than once, redirecting would break th em | |
1317 return "OK" | |
1318 else: | |
1319 return super(Draw, self).do_modify(contenttype, template_name) | |
1320 | |
1321 | 1547 |
1322 class TWikiDraw(Draw): | 1548 class TWikiDraw(Draw): |
1323 """ | 1549 """ |
1324 drawings by TWikiDraw applet. It creates three files which are stored as tar file. | 1550 drawings by TWikiDraw applet. It creates three files which are stored as tar file. |
1325 """ | 1551 """ |
1326 modify_help = "" | 1552 |
1327 template = "modify_twikidraw.html" | 1553 class ModifyForm(Draw.ModifyForm): |
1554 template = "modify_twikidraw.html" | |
1555 help = "" | |
1328 | 1556 |
1329 def handle_post(self): | 1557 def handle_post(self): |
1330 # called from modify UI/POST | 1558 # called from modify UI/POST |
1331 file_upload = request.files.get('filepath') | 1559 file_upload = request.files.get('filepath') |
1332 filename = request.form['filename'] | 1560 filename = request.form['filename'] |
1333 basepath, basename = os.path.split(filename) | 1561 basepath, basename = os.path.split(filename) |
1334 basename, ext = os.path.splitext(basename) | 1562 basename, ext = os.path.splitext(basename) |
1335 | 1563 |
1336 filecontent = file_upload.stream | 1564 filecontent = file_upload.stream |
1337 content_length = None | 1565 content_length = None |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1370 image_map = image_map.replace('%MAPNAME%', mapid) | 1598 image_map = image_map.replace('%MAPNAME%', mapid) |
1371 # add alt and title tags to areas | 1599 # add alt and title tags to areas |
1372 image_map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', image_map) | 1600 image_map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', image_map) |
1373 image_map = image_map.replace('%TWIKIDRAW%"', '{0}" alt="{1}" title= "{2}"'.format((drawing_url, title, title))) | 1601 image_map = image_map.replace('%TWIKIDRAW%"', '{0}" alt="{1}" title= "{2}"'.format((drawing_url, title, title))) |
1374 title = _('Clickable drawing: %(filename)s', filename=item_name) | 1602 title = _('Clickable drawing: %(filename)s', filename=item_name) |
1375 | 1603 |
1376 return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" / >'.format(png_url, title, mapid)) | 1604 return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" / >'.format(png_url, title, mapid)) |
1377 else: | 1605 else: |
1378 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title)) | 1606 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title)) |
1379 | 1607 |
1380 item_registry.register(TWikiDraw._factory, Type('application/x-twikidraw')) | 1608 content_registry.register(TWikiDraw._factory, Type('application/x-twikidraw')) |
1381 | 1609 |
1382 | 1610 |
1383 class AnyWikiDraw(Draw): | 1611 class AnyWikiDraw(Draw): |
1384 """ | 1612 """ |
1385 drawings by AnyWikiDraw applet. It creates three files which are stored as t ar file. | 1613 drawings by AnyWikiDraw applet. It creates three files which are stored as t ar file. |
1386 """ | 1614 """ |
1387 modify_help = "" | |
1388 template = "modify_anywikidraw.html" | |
1389 | 1615 |
1390 class ModifyForm(Draw.ModifyForm): | 1616 class ModifyForm(Draw.ModifyForm): |
1617 template = "modify_anywikidraw.html" | |
1618 help = "" | |
1391 def _load(self, item): | 1619 def _load(self, item): |
1392 super(AnyWikiDraw.ModifyForm, self)._load(item) | 1620 super(AnyWikiDraw.ModifyForm, self)._load(item) |
1393 try: | 1621 try: |
1394 drawing_exists = 'drawing.svg' in item.list_members() | 1622 drawing_exists = 'drawing.svg' in item.list_members() |
1395 except tarfile.TarError: # item doesn't exist yet | 1623 except tarfile.TarError: # item doesn't exist yet |
1396 drawing_exists = False | 1624 drawing_exists = False |
1397 self.extra_template_args = {'drawing_exists': drawing_exists} | 1625 self.drawing_exists = drawing_exists |
1398 | 1626 |
1399 def handle_post(self): | 1627 def handle_post(self): |
1400 # called from modify UI/POST | 1628 # called from modify UI/POST |
1401 file_upload = request.files.get('filepath') | 1629 file_upload = request.files.get('filepath') |
1402 filename = request.form['filename'] | 1630 filename = request.form['filename'] |
1403 basepath, basename = os.path.split(filename) | 1631 basepath, basename = os.path.split(filename) |
1404 basename, ext = os.path.splitext(basename) | 1632 basename, ext = os.path.splitext(basename) |
1405 filecontent = file_upload.stream | 1633 filecontent = file_upload.stream |
1406 content_length = None | 1634 content_length = None |
1407 if ext == '.svg': | 1635 if ext == '.svg': |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1439 mapid = 'ImageMapOf' + self.name | 1667 mapid = 'ImageMapOf' + self.name |
1440 image_map = image_map.replace(u'id="drawing.svg"', '') | 1668 image_map = image_map.replace(u'id="drawing.svg"', '') |
1441 image_map = image_map.replace(u'name="drawing.svg"', u'name="{0}"'.f ormat(mapid)) | 1669 image_map = image_map.replace(u'name="drawing.svg"', u'name="{0}"'.f ormat(mapid)) |
1442 # unxml, because 4.01 concrete will not validate /> | 1670 # unxml, because 4.01 concrete will not validate /> |
1443 image_map = image_map.replace(u'/>', u'>') | 1671 image_map = image_map.replace(u'/>', u'>') |
1444 title = _('Clickable drawing: %(filename)s', filename=self.name) | 1672 title = _('Clickable drawing: %(filename)s', filename=self.name) |
1445 return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" / >'.format(png_url, title, mapid)) | 1673 return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" / >'.format(png_url, title, mapid)) |
1446 else: | 1674 else: |
1447 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title)) | 1675 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title)) |
1448 | 1676 |
1449 item_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw')) | 1677 content_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw' )) |
1450 | 1678 |
1451 | 1679 |
1452 class SvgDraw(Draw): | 1680 class SvgDraw(Draw): |
1453 """ drawings by svg-edit. It creates two files (svg, png) which are stored a s tar file. """ | 1681 """ drawings by svg-edit. It creates two files (svg, png) which are stored a s tar file. """ |
1454 modify_help = "" | 1682 |
1455 template = "modify_svg-edit.html" | 1683 class ModifyForm(Draw.ModifyForm): |
1684 template = "modify_svg-edit.html" | |
1685 help = "" | |
1456 | 1686 |
1457 def handle_post(self): | 1687 def handle_post(self): |
1458 # called from modify UI/POST | 1688 # called from modify UI/POST |
1459 png_upload = request.values.get('png_data') | 1689 png_upload = request.values.get('png_data') |
1460 svg_upload = request.values.get('filepath') | 1690 svg_upload = request.values.get('filepath') |
1461 filename = request.form['filename'] | 1691 filename = request.form['filename'] |
1462 png_content = png_upload.decode('base_64') | 1692 png_content = png_upload.decode('base_64') |
1463 png_content = base64.urlsafe_b64decode(png_content.split(',')[1]) | 1693 png_content = base64.urlsafe_b64decode(png_content.split(',')[1]) |
1464 svg_content = svg_upload.decode('base_64') | 1694 svg_content = svg_upload.decode('base_64') |
1465 content_length = None | 1695 content_length = None |
1466 self.put_member("drawing.svg", svg_content, content_length, | 1696 self.put_member("drawing.svg", svg_content, content_length, |
1467 expected_members=set(['drawing.svg', 'drawing.png'])) | 1697 expected_members=set(['drawing.svg', 'drawing.png'])) |
1468 self.put_member("drawing.png", png_content, content_length, | 1698 self.put_member("drawing.png", png_content, content_length, |
1469 expected_members=set(['drawing.svg', 'drawing.png'])) | 1699 expected_members=set(['drawing.svg', 'drawing.png'])) |
1470 | 1700 |
1471 def _render_data(self): | 1701 def _render_data(self): |
1472 # TODO: this could be a converter -> dom, then transcluding this kind | 1702 # TODO: this could be a converter -> dom, then transcluding this kind |
1473 # of items and also rendering them with the code in base class could wor k | 1703 # of items and also rendering them with the code in base class could wor k |
1474 item_name = self.name | 1704 item_name = self.name |
1475 drawing_url = url_for('frontend.get_item', item_name=item_name, member=' drawing.svg', rev=self.rev.revid) | 1705 drawing_url = url_for('frontend.get_item', item_name=item_name, member=' drawing.svg', rev=self.rev.revid) |
1476 png_url = url_for('frontend.get_item', item_name=item_name, member='draw ing.png', rev=self.rev.revid) | 1706 png_url = url_for('frontend.get_item', item_name=item_name, member='draw ing.png', rev=self.rev.revid) |
1477 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, drawing_url )) | 1707 return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, drawing_url )) |
1478 | 1708 |
1479 item_registry.register(SvgDraw._factory, Type('application/x-svgdraw')) | 1709 content_registry.register(SvgDraw._factory, Type('application/x-svgdraw')) |
OLD | NEW |