Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(412)

Side by Side Diff: MoinMoin/items/__init__.py

Issue 6432058: Implementing experimental itemtype (Closed)
Patch Set: fix wording and comment; fix *Draw items modification Created 12 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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
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
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
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
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'))
OLDNEW

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b