Changeset 75
- Timestamp:
- 01/02/07 17:49:59 (2 years ago)
- Files:
-
- sandbox/annotater/annotater.py (modified) (1 diff)
- sandbox/annotater/annotater_test.py (modified) (1 diff)
- sandbox/annotater/marginalia/annotation.js (modified) (1 diff)
- sandbox/annotater/marginalia/index.html (modified) (1 diff)
- sandbox/annotater/marginalia/rest-annotate.js (modified) (1 diff)
- sandbox/annotater/model.py (modified) (1 diff)
- sandbox/annotater/model_test.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
sandbox/annotater/annotater.py
Revision 71 Revision 75 1 """ 1 """ 2 Annotation of a web resource. 2 Annotation of a web resource. 3 3 4 @copyright: (c) 2006 Open Knowledge Foundation 4 @copyright: (c) 2006 Open Knowledge Foundation 5 @author: Rufus Pollock (Open Knowledge Foundation) 5 @author: Rufus Pollock (Open Knowledge Foundation) 6 @license: MIT License <http://www.opensource.org/licenses/mit-license.php> 6 @license: MIT License <http://www.opensource.org/licenses/mit-license.php> 7 """ 7 """ 8 import os 8 import os 9 9 10 import wsgiref.simple_server 10 import wsgiref.simple_server 11 import paste.request 11 import paste.request 12 # import genshi.template 12 # import genshi.template 13 # import genshi.output 13 # import genshi.output 14 14 15 from routes import * 15 from routes import * 16 16 17 # annotater stuff 17 # annotater stuff 18 import model 18 import model 19 19 20 # absolute url to annotation service 20 # absolute url to annotation service 21 # this should go 21 # this should go 22 service_path = '/annotation' 22 service_path = '/annotation' 23 23 24 map = Mapper() 24 map = Mapper() 25 map.connect('annotation/delete/:id', controller='annotation', action='delete', 25 map.connect('annotation/delete/:id', controller='annotation', action='delete', 26 conditions=dict(method=['GET'])) 26 conditions=dict(method=['GET'])) 27 map.connect('annotation/edit/:id', controller='annotation', action='edit', 27 map.connect('annotation/edit/:id', controller='annotation', action='edit', 28 conditions=dict(method=['GET'])) 28 conditions=dict(method=['GET'])) 29 29 30 map.resource('annotation') 30 map.resource('annotation') 31 31 32 # map.resource assumes PUT for update but marginalias uses POST 32 # map.resource assumes PUT for update but marginalias uses POST 33 # the exacting mappings for REST seems a hotly contested matter see e.g. 33 # the exacting mappings for REST seems a hotly contested matter see e.g. 34 # http://www.megginson.com/blogs/quoderat/archives/2005/04/03/post-in-rest-create-update-or-action/ 34 # http://www.megginson.com/blogs/quoderat/archives/2005/04/03/post-in-rest-create-update-or-action/ 35 # must have this *after* map.resource as otherwise overrides the create action 35 # must have this *after* map.resource as otherwise overrides the create action 36 map.connect('annotation/:id', controller='annotation', action='update', 36 map.connect('annotation/:id', controller='annotation', action='update', 37 conditions=dict(method=['POST'])) 37 conditions=dict(method=['POST'])) 38 38 39 # misc config 39 # misc config 40 marginalia_path = os.path.abspath('./marginalia') 40 marginalia_path = os.path.abspath('./marginalia') 41 html_doc_path = os.path.join(marginalia_path, 'index.html') 41 html_doc_path = os.path.join(marginalia_path, 'index.html') 42 42 43 43 44 import logging 44 import logging 45 def setup_logging(): 45 def setup_logging(): 46 level = logging.DEBUG 46 level = logging.DEBUG 47 logger = logging.getLogger('annotater') 47 logger = logging.getLogger('annotater') 48 logger.setLevel(level) 48 logger.setLevel(level) 49 log_file_path = 'debug.log' 49 log_file_path = 'debug.log' 50 fh = logging.FileHandler(log_file_path, 'w') 50 fh = logging.FileHandler(log_file_path, 'w') 51 fh.setLevel(level) 51 fh.setLevel(level) 52 logger.addHandler(fh) 52 logger.addHandler(fh) 53 logger.info('START LOGGING') 53 logger.info('START LOGGING') 54 return logger 54 return logger 55 55 56 logger = setup_logging() 56 logger = setup_logging() 57 57 58 class AnnotaterApp(object): 58 class AnnotaterApp(object): 59 59 60 def __init__(self): 60 def __init__(self): 61 pass 61 pass 62 62 63 def __call__(self, environ, start_response): 63 def __call__(self, environ, start_response): 64 self.environ = environ 64 self.environ = environ 65 self.map = map 65 self.map = map 66 self.map.environ = environ 66 self.map.environ = environ 67 self.start_response = start_response 67 self.start_response = start_response 68 self.path = environ['PATH_INFO'] 68 self.path = environ['PATH_INFO'] 69 logger.debug(self.path) 69 logger.debug(self.path) 70 # special test cases 70 # special test cases 71 if self.path.startswith('/debug'): 71 if self.path.startswith('/debug'): 72 return wsgiref.simple_server.demo_app(environ, start_response) 72 return wsgiref.simple_server.demo_app(environ, start_response) 73 elif self.path.startswith('/_js/'): 73 elif self.path.startswith('/_js/'): 74 status = '200 OK' 74 status = '200 OK' 75 response_headers = [('Content-type','text/plain')] 75 response_headers = [('Content-type','text/plain')] 76 start_response(status, response_headers) 76 start_response(status, response_headers) 77 jspath = os.path.join(marginalia_path, self.path[5:]) 77 jspath = os.path.join(marginalia_path, self.path[5:]) 78 jsfile = file(jspath).read() 78 jsfile = file(jspath).read() 79 return [jsfile] 79 return [jsfile] 80 elif self.path.endswith('.js') or self.path.endswith('.css'): 80 elif self.path.endswith('.js') or self.path.endswith('.css'): 81 status = '200 OK' 81 status = '200 OK' 82 if self.path.endswith('.js'): filetype = 'text/javascript' 82 if self.path.endswith('.js'): filetype = 'text/javascript' 83 else: filetype = 'text/css' 83 else: filetype = 'text/css' 84 response_headers = [('Content-type', filetype)] 84 response_headers = [('Content-type', filetype)] 85 start_response(status, response_headers) 85 start_response(status, response_headers) 86 jspath = os.path.join(marginalia_path, self.path[1:]) 86 jspath = os.path.join(marginalia_path, self.path[1:]) 87 jsfile = file(jspath).read() 87 jsfile = file(jspath).read() 88 return [jsfile] 88 return [jsfile] 89 elif self.path.startswith('/example-annotations.xml'): 89 elif self.path.startswith('/example-annotations.xml'): 90 status = '200 OK' 90 status = '200 OK' 91 filetype = 'text/xml' 91 filetype = 'text/xml' 92 response_headers = [('Content-type', filetype)] 92 response_headers = [('Content-type', filetype)] 93 start_response(status, response_headers) 93 start_response(status, response_headers) 94 jspath = os.path.join(marginalia_path, self.path[1:]) 94 jspath = os.path.join(marginalia_path, self.path[1:]) 95 jsfile = file(jspath).read() 95 jsfile = file(jspath).read() 96 return [jsfile] 96 return [jsfile] 97 elif self.path.startswith(service_path): 97 elif self.path.startswith(service_path): 98 return self.annotate() 98 return self.annotate() 99 else: 99 else: 100 logger.info('Call to base url /') 100 logger.info('Call to base url /') 101 status = '200 OK' 101 status = '200 OK' 102 response_headers = [('Content-type','text/html')] 102 response_headers = [('Content-type','text/html')] 103 start_response(status, response_headers) 103 start_response(status, response_headers) 104 out = file(html_doc_path).read() 104 out = file(html_doc_path).read() 105 return [out] 105 return [out] 106 106 107 def _make_annotate_form(self, form_name, action_url, form_defaults): 107 def _make_annotate_form(self, form_name, action_url, form_defaults): 108 from formencode import htmlfill 108 from formencode import htmlfill 109 keys = [ 'url' , 'range', 'note' ] 109 keys = [ 'url' , 'range', 'note' ] 110 vals = {} 110 vals = {} 111 for key in keys: 111 for key in keys: 112 vals[key] = form_defaults.get(key, '') 112 vals[key] = form_defaults.get(key, '') 113 formfields = '' 113 formfields = '' 114 for key in keys: 114 for key in keys: 115 formfields += \ 115 formfields += \ 116 ''' <label for="%s">%s:</label><input name="%s" id="%s" /><br /> 116 ''' <label for="%s">%s:</label><input name="%s" id="%s" /><br /> 117 ''' % (key, key, key, key) 117 ''' % (key, key, key, key) 118 118 119 119 120 form = \ 120 form = \ 121 '''<html> 121 '''<html> 122 <head></head> 122 <head></head> 123 <body> 123 <body> 124 <form name="%s" action="%s" method="POST"> 124 <form name="%s" action="%s" method="POST"> 125 %s 125 %s 126 <input type="submit" name="submission" value="send the form" /> 126 <input type="submit" name="submission" value="send the form" /> 127 </form> 127 </form> 128 </body> 128 </body> 129 </html>''' % (form_name, action_url, formfields) 129 </html>''' % (form_name, action_url, formfields) 130 130 131 form = htmlfill.render(form, vals) 131 form = htmlfill.render(form, vals) 132 return form 132 return form 133 133 134 134 135 def annotate(self): 135 def annotate(self): 136 query_vals = paste.request.parse_formvars(self.environ) 136 query_vals = paste.request.parse_formvars(self.environ) 137 request_method = self.environ['REQUEST_METHOD'] 137 request_method = self.environ['REQUEST_METHOD'] 138 mapdict = self.map.match(self.path) 139 format = query_vals.get('format', 'html') 138 logger.debug('CALL TO ANNOTATE') 140 logger.debug('CALL TO ANNOTATE') 139 logger.debug(self.path) 141 logger.debug(self.path) 140 logger.debug(query_vals) 142 logger.debug(query_vals) 141 logger.debug(self.environ['QUERY_STRING']) 143 logger.debug(self.environ['QUERY_STRING']) 142 logger.debug(request_method) 144 logger.debug(request_method) 143 mapdict = self.map.match(self.path)144 logger.debug('mapdict: %s' % mapdict) 145 logger.debug('mapdict: %s' % mapdict) 145 action = mapdict['action'] 146 action = mapdict['action'] 146 anno_schema = model.AnnotationSchema() 147 anno_schema = model.AnnotationSchema() 147 148 148 if action == 'index': 149 if action == 'index': 149 status = '200 OK' 150 status = '200 OK' 150 response_headers = [ 151 response_headers = [ ('Content-type', 'text/html') ] 151 ('Content-type', 'text/plain'), 152 result = '' 152 ] 153 if format == 'html': 153 items = list(model.Annotation.select()) 154 result = model.Annotation.list_annotations_html() 154 out = '' 155 result = \ 155 for item in items: 156 '''<html> 156 out += str(item) + '\n\n' 157 <head> 157 self.start_response(status, response_headers) 158 <title>Annotations</title> 158 return [out] 159 </head> 160 <body> 161 %s 162 </body> 163 </html>''' % (result) 164 elif format == 'atom': 165 response_headers = [ ('Content-type', 'application/xml') ] 166 result = model.Annotation.list_annotations_atom() 167 else: 168 status = '500 Internal server error' 169 result = 'Unknown format: %s' % format 170 self.start_response(status, response_headers) 171 return [result] 159 elif action == 'new': 172 elif action == 'new': 160 status = '200 OK' 173 status = '200 OK' 161 response_headers = [ 174 response_headers = [ 162 ('Content-type', 'text/html'), 175 ('Content-type', 'text/html'), 163 ] 176 ] 164 posturl = self.map.generate(controller='annotation', action='create') 177 posturl = self.map.generate(controller='annotation', action='create') 165 form = \ 178 form = \ 166 '''<html> 179 '''<html> 167 <head></head> 180 <head></head> 168 <body> 181 <body> 169 <form name='annotation_create' action="%s" method="POST"> 182 <form name='annotation_create' action="%s" method="POST"> 170 <label>url:</label> <input name="url" id="url" /><br /> 183 <label>url:</label> <input name="url" id="url" /><br /> 171 <label>range:</label><input name="range" id="range" /><br /> 184 <label>range:</label><input name="range" id="range" /><br /> 172 <label>note:</label><input name="note" id="note" /><br /> 185 <label>note:</label><input name="note" id="note" /><br /> 173 <input type="submit" name="submission" value="send the form" /> 186 <input type="submit" name="submission" value="send the form" /> 174 </form> 187 </form> 175 </body> 188 </body> 176 </html>''' % posturl 189 </html>''' % posturl 177 self.start_response(status, response_headers) 190 self.start_response(status, response_headers) 178 return [ form ] 191 return [ form ] 179 elif action == 'create': 192 elif action == 'create': 180 url = query_vals.get('url') 193 url = query_vals.get('url') 181 range = query_vals.get('range', 'NO RANGE') 194 range = query_vals.get('range', 'NO RANGE') 182 note = query_vals.get('note', 'NO NOTE') 195 note = query_vals.get('note', 'NO NOTE') 183 anno = model.Annotation( 196 anno = model.Annotation( 184 url=url, 197 url=url, 185 range=range, 198 range=range, 186 note=note) 199 note=note) 187 status = '201 Created' 200 status = '201 Created' 188 location = '/annotation/%s' % anno.id 201 location = '/annotation/%s' % anno.id 189 response_headers = [ 202 response_headers = [ 190 ('Content-type', 'text/html'), 203 ('Content-type', 'text/html'), 191 ('Location', location) 204 ('Location', location) 192 ] 205 ] 193 self.start_response(status, response_headers) 206 self.start_response(status, response_headers) 194 return [''] 207 return [''] 195 elif action == 'edit': 208 elif action == 'edit': 196 id = mapdict['id'] 209 id = mapdict['id'] 197 try: 210 try: 198 id = int(id) 211 id = int(id) 199 except: 212 except: 200 status = '400 Bad Request' 213 status = '400 Bad Request' 201 response_headers = [ 214 response_headers = [ 202 ('Content-type', 'text/html'), 215 ('Content-type', 'text/html'), 203 ] 216 ] 204 self.start_response(status, response_headers) 217 self.start_response(status, response_headers) 205 msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 218 msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 206 return [msg] 219 return [msg] 207 anno = model.Annotation.get(id) 220 anno = model.Annotation.get(id) 208 posturl = self.map.generate(controller='annotation', 221 posturl = self.map.generate(controller='annotation', 209 action='update', id=anno.id, method='POST') 222 action='update', id=anno.id, method='POST') 210 print 'Post url:', posturl 223 print 'Post url:', posturl 211 form_defaults = anno_schema.from_python(anno) 224 form_defaults = anno_schema.from_python(anno) 212 form = self._make_annotate_form('annotate_edit', posturl, 225 form = self._make_annotate_form('annotate_edit', posturl, 213 form_defaults) 226 form_defaults) 214 status = '200 OK' 227 status = '200 OK' 215 response_headers = [ 228 response_headers = [ 216 ('Content-type', 'text/html'), 229 ('Content-type', 'text/html'), 217 ] 230 ] 218 self.start_response(status, response_headers) 231 self.start_response(status, response_headers) 219 return [ form ] 232 return [ form ] 220 233 221 elif action == 'update': 234 elif action == 'update': 222 id = mapdict['id'] 235 id = mapdict['id'] 223 try: 236 try: 224 id = int(id) 237 id = int(id) 225 except: 238 except: 226 status = '400 Bad Request' 239 status = '400 Bad Request' 227 response_headers = [ 240 response_headers = [ 228 ('Content-type', 'text/html'), 241 ('Content-type', 'text/html'), 229 ] 242 ] 230 self.start_response(status, response_headers) 243 self.start_response(status, response_headers) 231 msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 244 msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 232 return [msg] 245 return [msg] 233 new_values = dict(query_vals) 246 new_values = dict(query_vals) 234 new_values['id'] = id 247 new_values['id'] = id 235 del new_values['submission'] 248 # if this comes from a form POST have to remove submission field 249 if new_values.has_key('submission'): 250 del new_values['submission'] 236 anno_edited = anno_schema.to_python(new_values) 251 anno_edited = anno_schema.to_python(new_values) 237 status = '204 Updated' 252 status = '204 Updated' 238 response_headers = [] 253 response_headers = [] 239 self.start_response(status, response_headers) 254 self.start_response(status, response_headers) 240 return [''] 255 return [''] 241 256 242 elif action == 'delete': 257 elif action == 'delete': 243 id = mapdict['id'] 258 id = mapdict['id'] 244 if id is None: 259 if id is None: 245 status = '400 Bad Request' 260 status = '400 Bad Request' 246 response_headers = [ 261 response_headers = [ 247 ('Content-type', 'text/html'), 262 ('Content-type', 'text/html'), 248 ] 263 ] 249 self.start_response(status, response_headers) 264 self.start_response(status, response_headers) 250 return ['<h1>400 Bad Request</h1><p>Bad ID</p>'] 265 return ['<h1>400 Bad Request</h1><p>Bad ID</p>'] 251 else: 266 else: 252 status = '204 Deleted' 267 status = '204 Deleted' 253 response_headers = [] 268 response_headers = [] 254 try: 269 try: 255 id = int(id) 270 id = int(id) 256 model.Annotation.delete(id) 271 model.Annotation.delete(id) 257 self.start_response(status, response_headers) 272 self.start_response(status, response_headers) 258 return [''] 273 return [''] 259 except: 274 except: 260 status = '500 Internal server error' 275 status = '500 Internal server error' 261 self.start_response(status, response_headers) 276 self.start_response(status, response_headers) 262 return ['<h1>500 Internal Server Error</h1>Delete failed'] 277 return ['<h1>500 Internal Server Error</h1>Delete failed'] 263 else: 278 else: 264 status = '404 Not Found' 279 status = '404 Not Found' 265 response_headers = [ 280 response_headers = [ 266 ('Content-type', 'text/plain'), 281 ('Content-type', 'text/plain'), 267 ] 282 ] 268 self.start_response(status, response_headers) 283 self.start_response(status, response_headers) 269 return ['Not found or method not allowed'] 284 return ['Not found or method not allowed'] 270 285 271 286 272 if __name__ == '__main__': 287 if __name__ == '__main__': 273 app = AnnotaterApp() 288 app = AnnotaterApp() 274 import paste.httpserver 289 import paste.httpserver 275 paste.httpserver.serve(app) 290 paste.httpserver.serve(app) sandbox/annotater/annotater_test.py
Revision 71 Revision 75 1 import os 1 import os 2 import shutil 2 import shutil 3 import tempfile 3 import tempfile 4 import commands 4 import commands 5 from StringIO import StringIO 5 from StringIO import StringIO 6 6 7 import twill 7 import twill 8 from twill import commands as web 8 from twill import commands as web 9 9 10 import annotater 10 import annotater 11 import model 11 import model 12 12 13 class TestMapper: 13 class TestMapper: 14 14 15 def test_match_new(self): 15 def test_match_new(self): 16 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 16 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 17 out = annotater.map.match('/annotation/new') 17 out = annotater.map.match('/annotation/new') 18 assert out['action'] == 'new' 18 assert out['action'] == 'new' 19 19 20 def test_match_index(self): 20 def test_match_index(self): 21 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 21 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 22 out = annotater.map.match('/annotation') 22 out = annotater.map.match('/annotation') 23 assert out['action'] == 'index' 23 assert out['action'] == 'index' 24 24 25 def test_match_create(self): 25 def test_match_create(self): 26 annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 26 annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 27 out = annotater.map.match('/annotation') 27 out = annotater.map.match('/annotation') 28 assert out['action'] == 'create' 28 assert out['action'] == 'create' 29 29 30 def test_match_delete(self): 30 def test_match_delete(self): 31 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 31 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 32 out = annotater.map.match('/annotation/delete/1') 32 out = annotater.map.match('/annotation/delete/1') 33 assert out['action'] == 'delete' 33 assert out['action'] == 'delete' 34 assert out['id'] == '1' 34 assert out['id'] == '1' 35 annotater.map.environ = { 'REQUEST_METHOD' : 'DELETE' } 35 annotater.map.environ = { 'REQUEST_METHOD' : 'DELETE' } 36 out = annotater.map.match('/annotation/1') 36 out = annotater.map.match('/annotation/1') 37 assert out['action'] == 'delete' 37 assert out['action'] == 'delete' 38 assert out['id'] == '1' 38 assert out['id'] == '1' 39 out = annotater.map.match('/annotation/') 39 out = annotater.map.match('/annotation/') 40 assert out['id'] == None 40 assert out['id'] == None 41 41 42 def test_match_delete(self): 42 def test_match_delete(self): 43 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 43 annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 44 out = annotater.map.match('/annotation/edit/1') 44 out = annotater.map.match('/annotation/edit/1') 45 assert out['action'] == 'edit' 45 assert out['action'] == 'edit' 46 assert out['id'] == '1' 46 assert out['id'] == '1' 47 annotater.map.environ = { 'REQUEST_METHOD' : 'PUT' } 47 annotater.map.environ = { 'REQUEST_METHOD' : 'PUT' } 48 out = annotater.map.match('/annotation/1') 48 out = annotater.map.match('/annotation/1') 49 assert out['action'] == 'update' 49 assert out['action'] == 'update' 50 assert out['id'] == '1' 50 assert out['id'] == '1' 51 annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 51 annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 52 out = annotater.map.match('/annotation/1') 52 out = annotater.map.match('/annotation/1') 53 assert out['action'] == 'update' 53 assert out['action'] == 'update' 54 54 55 def test_url_for_new(self): 55 def test_url_for_new(self): 56 offset = annotater.map.generate(controller='annotation', action='new') 56 offset = annotater.map.generate(controller='annotation', action='new') 57 exp = '/annotation/new' 57 exp = '/annotation/new' 58 assert offset == exp 58 assert offset == exp 59 59 60 def test_url_for_create(self): 60 def test_url_for_create(self): 61 offset = annotater.map.generate(controller='annotation', action='create', 61 offset = annotater.map.generate(controller='annotation', action='create', 62 method='POST' ) 62 method='POST' ) 63 exp = '/annotation' 63 exp = '/annotation' 64 assert offset == exp 64 assert offset == exp 65 65 66 def test_url_for_delete(self): 66 def test_url_for_delete(self): 67 offset = annotater.map.generate(controller='annotation', 67 offset = annotater.map.generate(controller='annotation', 68 action='delete', id=1, method='GET' ) 68 action='delete', id=1, method='GET' ) 69 exp = '/annotation/delete/1' 69 exp = '/annotation/delete/1' 70 assert offset == exp 70 assert offset == exp 71 offset = annotater.map.generate(controller='annotation', 71 offset = annotater.map.generate(controller='annotation', 72 action='delete', id=1, method='DELETE' ) 72 action='delete', id=1, method='DELETE' ) 73 exp = '/annotation/1' 73 exp = '/annotation/1' 74 assert offset == exp 74 assert offset == exp 75 75 76 def test_url_for_edit(self): 76 def test_url_for_edit(self): 77 offset = annotater.map.generate(controller='annotation', 77 offset = annotater.map.generate(controller='annotation', 78 action='edit', id=1, method='GET') 78 action='edit', id=1, method='GET') 79 exp = '/annotation/edit/1' 79 exp = '/annotation/edit/1' 80 assert offset == exp 80 assert offset == exp 81 81 82 class TestStatic: 82 class TestStatic: 83 83 84 def test__make_annotate_form(self): 84 def test__make_annotate_form(self): 85 app = annotater.AnnotaterApp() 85 app = annotater.AnnotaterApp() 86 defaults = { 'url' : 'http://www.blackandwhite.com' } 86 defaults = { 'url' : 'http://www.blackandwhite.com' } 87 out = app._make_annotate_form(form_name='formname', action_url='.', 87 out = app._make_annotate_form(form_name='formname', action_url='.', 88 form_defaults=defaults) 88 form_defaults=defaults) 89 exp1 = '<label for="url">url:</label><input name="url" id="url" \ 89 exp1 = '<label for="url">url:</label><input name="url" id="url" \ 90 value="%s" /><br />' % defaults['url'] 90 value="%s" /><br />' % defaults['url'] 91 assert exp1 in out 91 assert exp1 in out 92 92 93 93 94 class TestWsgi: 94 class TestWsgi: 95 95 96 # disabled = True 96 # disabled = True 97 97 98 def setup_method(self, name=''): 98 def setup_method(self, name=''): 99 wsgi_app = annotater.AnnotaterApp() 99 wsgi_app = annotater.AnnotaterApp() 100 twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi_app) 100 twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi_app) 101 self.outp = StringIO() 101 self.outp = StringIO() 102 twill.set_output(self.outp) 102 twill.set_output(self.outp) 103 self.siteurl = 'http://localhost:8080/' 103 self.siteurl = 'http://localhost:8080/' 104 104 105 def teardown_method(self, name=''): 105 def teardown_method(self, name=''): 106 # remove intercept. 106 # remove intercept. 107 twill.remove_wsgi_intercept('localhost', 8080) 107 twill.remove_wsgi_intercept('localhost', 8080) 108 108 109 def test_js(self): 109 def test_js(self): 110 filename = 'domutil.js' 110 filename = 'domutil.js' 111 url = self.siteurl + '_js/' + filename 111 url = self.siteurl + '_js/' + filename 112 web.go(url) 112 web.go(url) 113 web.code(200) 113 web.code(200) 114 web.find('ELEMENT_NODE = 1;') 114 web.find('ELEMENT_NODE = 1;') 115 115 116 def test_js_2(self): 116 def test_js_2(self): 117 filename = 'domutil.js' 117 filename = 'domutil.js' 118 url = self.siteurl + filename 118 url = self.siteurl + filename 119 web.go(url) 119 web.go(url) 120 web.code(200) 120 web.code(200) 121 web.find('ELEMENT_NODE = 1;') 121 web.find('ELEMENT_NODE = 1;') 122 122 123 def test_show_root(self): 123 def test_show_root(self): 124 web.go(self.siteurl) 124 web.go(self.siteurl) 125 web.code(200) 125 web.code(200) 126 web.find('This is a demonstration of') 126 web.find('This is a demonstration of') 127 127 128 def test_annotate_get(self): 128 def test_annotate_get(self): 129 anno = model.Annotation( 129 anno = self._create_annotation() 130 url='http://xyz.com', 131 range='blah range', 132 note='blah note', 133 ) 134 offset = annotater.map.generate(controller='annotation', action='index') 130 offset = annotater.map.generate(controller='annotation', action='index') 135 url = self.siteurl + offset[1:] 131 url = self.siteurl + offset[1:] 136 web.go(url) 132 web.go(url) 137 web.code(200) 133 web.code(200) 138 web.find(anno.url) 134 web.find(anno.url) 139 web.find(anno.range) 135 web.find(anno.range) 136 137 def test_annotate_get_atom(self): 138 anno = self._create_annotation() 139 offset = annotater.map.generate(controller='annotation', action='index') 140 url = self.siteurl + offset[1:] + '?format=atom' 141 web.go(url) 142 web.code(200) 143 web.find(anno.note) 144 web.find(anno.range) 145 out = web.show() 146 exp1 = '<feed xmlns:ptr="http://www.geof.net/code/annotation/"' 147 assert exp1 in out 140 148 141 def test_annotate_new(self): 149 def test_annotate_new(self): 142 # exercises both create and new 150 # exercises both create and new 143 import model 151 import model 144 model.rebuilddb() 152 model.rebuilddb() 145 offset = annotater.map.generate(controller='annotation', action='new', 153 offset = annotater.map.generate(controller='annotation', action='new', 146 method='GET') 154 method='GET') 147 url = self.siteurl + offset[1:] 155 url = self.siteurl + offset[1:] 148 web.go(url) 156 web.go(url) 149 web.code(200) 157 web.code(200) 150 note = 'any old thing' 158 note = 'any old thing' 151 web.fv('', 'url', 'http://localhost/') 159 web.fv('', 'url', 'http://localhost/') 152 web.fv('', 'note', note) 160 web.fv('', 'note', note) 153 web.submit() 161 web.submit() 154 web.code(201) 162 web.code(201) 155 # TODO make this test more selective 163 # TODO make this test more selective 156 items = model.Annotation.select() 164 items = model.Annotation.select() 157 items = list(items) 165 items = list(items) 158 assert len(items) == 1 166 assert len(items) == 1 159 assert items[0].note == note 167 assert items[0].note == note 160 168 161 def test_annotate_delete(self): 169 def test_annotate_delete(self): 162 anno = model.Annotation( 170 anno = self._create_annotation() 163 url='http://xyz.com', 164 range='blah range', 165 note='blah note', 166 ) 167 offset = annotater.map.generate(controller='annotation', action='delete', 171 offset = annotater.map.generate(controller='annotation', action='delete', 168 id=anno.id) 172 id=anno.id) 169 url = self.siteurl + offset[1:] 173 url = self.siteurl + offset[1:] 170 web.go(url) 174 web.go(url) 171 web.code(204) 175 web.code(204) 172 tmp = model.Annotation.select(model.Annotation.q.id == anno.id) 176 tmp = model.Annotation.select(model.Annotation.q.id == anno.id) 173 num = len(list(tmp)) 177 num = len(list(tmp)) 174 assert num == 0 178 assert num == 0 175 179 176 def _create_annotation(self): 180 def _create_annotation(self): 177 anno = model.Annotation( 181 anno = model.Annotation( 178 url='http://xyz.com', 182 url='http://xyz.com', 179 range=' blah range',183 range='1.0 2.0', 180 note='blah note', 184 note='blah note', 181 ) 185 ) 182 return anno 186 return anno 183 187 184 def test_annotate_edit(self): 188 def test_annotate_edit(self): 185 anno = self._create_annotation() 189 anno = self._create_annotation() 186 offset = annotater.map.generate(controller='annotation', action='edit', 190 offset = annotater.map.generate(controller='annotation', action='edit', 187 id=anno.id, method='GET') 191 id=anno.id, method='GET') 188 url = self.siteurl + offset[1:] 192 url = self.siteurl + offset[1:] 189 web.go(url) 193 web.go(url) 190 web.code(200) 194 web.code(200) 191 newnote = u'This is a NEW note, a NEW note I say.' 195 newnote = u'This is a NEW note, a NEW note I say.' 192 web.fv('', 'note', newnote) 196 web.fv('', 'note', newnote) 193 web.submit() 197 web.submit() 194 web.code(204) 198 web.code(204) 195 assert anno.note == newnote 199 assert anno.note == newnote 196 200 197 def test_not_found(self): 201 def test_not_found(self): 198 offset = annotater.map.generate(controller='annotation') 202 offset = annotater.map.generate(controller='annotation') 199 url = self.siteurl + offset[1:] + '/blah' 203 url = self.siteurl + offset[1:] + '/blah' 200 web.go(url) 204 web.go(url) 201 web.code(404) 205 web.code(404) 202 206 203 def test_bad_request(self): 207 def test_bad_request(self): 204 offset = annotater.map.generate(controller='annotation', action='edit', 208 offset = annotater.map.generate(controller='annotation', action='edit', 205 method='GET') 209 method='GET') 206 url = self.siteurl + offset[1:] 210 url = self.siteurl + offset[1:] 207 web.go(url) 211 web.go(url) 208 web.code(400) 212 web.code(400) 209 213 210 214 sandbox/annotater/marginalia/annotation.js
Revision 70 Revision 75 1 /* 1 /* 2 * annotation.js 2 * annotation.js 3 * 3 * 4 * Web Annotation is being developed for Moodle with funding from BC Campus 4 * Web Annotation is being developed for Moodle with funding from BC Campus 5 * and support from Simon Fraser University and SFU's Applied Communication 5 * and support from Simon Fraser University and SFU's Applied Communication 6 * Technologies Group and the e-Learning Innovation Centre of the 6 * Technologies Group and the e-Learning Innovation Centre of the 7 * Learning Instructional Development Centre at SFU 7 * Learning Instructional Development Centre at SFU 8 * Copyright (C) 2005 Geoffrey Glass www.geof.net 8 * Copyright (C) 2005 Geoffrey Glass www.geof.net 9 * 9 * 10 * This program is free software; you can redistribute it and/or 10 * This program is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU General Public License 11 * modify it under the terms of the GNU General Public License 12 * as published by the Free Software Foundation; either version 2 12 * as published by the Free Software Foundation; either version 2 13 * of the License, or (at your option) any later version. 13 * of the License, or (at your option) any later version. 14 * 14 * 15 * This program is distributed in the hope that it will be useful, 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 18 * GNU General Public License for more details. 19 * 19 * 20 * You should have received a copy of the GNU General Public License 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 */ 23 */ 24 24 25 // namespaces 25 // namespaces 26 NS_PTR = 'http://www.geof.net/code/annotation/'; 26 NS_PTR = 'http://www.geof.net/code/annotation/'; 27 NS_ATOM = 'http://www.w3.org/2005/Atom'; 27 NS_ATOM = 'http://www.w3.org/2005/Atom'; 28 28 29 // The names of HTML/CSS classes used by the annotation code. 29 // The names of HTML/CSS classes used by the annotation code. 30 AN_NOTES_CLASS = 'notes'; // the notes portion of a fragment 30 AN_NOTES_CLASS = 'notes'; // the notes portion of a fragment 31 AN_HIGHLIGHT_CLASS = 'annotation';// class given to em nodes for highlighting 31 AN_HIGHLIGHT_CLASS = 'annotation';// class given to em nodes for highlighting 32 AN_HOVER_CLASS = 'hover'; // assigned to highlights and notes when the mouse is over the other 32 AN_HOVER_CLASS = 'hover'; // assigned to highlights and notes when the mouse is over the other 33 AN_ANNOTATED_CLASS = 'annotated'; // class added to fragment when annotation is on 33 AN_ANNOTATED_CLASS = 'annotated'; // class added to fragment when annotation is on 34 AN_SELFANNOTATED_CLASS = 'self-annotated'; // annotations are by the current user (and therefore editable) 34 AN_SELFANNOTATED_CLASS = 'self-annotated'; // annotations are by the current user (and therefore editable) 35 AN_DUMMY_CLASS = 'dummy'; // used for dummy item in note list 35 AN_DUMMY_CLASS = 'dummy'; // used for dummy item in note list 36 AN_RANGEMISMATCH_ERROR_CLASS = 'annotation-range-mismatch'; // one or more annotations don't match the current state of the document 36 AN_RANGEMISMATCH_ERROR_CLASS = 'annotation-range-mismatch'; // one or more annotations don't match the current state of the document 37 AN_ID_PREFIX = 'a'; // prefix for annotation IDs in element classes and IDs 37 AN_ID_PREFIX = 'a'; // prefix for annotation IDs in element classes and IDs 38 AN_SUN_SYMBOL = '\u25cb'; //'\u263c'; 38 AN_SUN_SYMBOL = '\u25cb'; //'\u263c'; 39 AN_MOON_SYMBOL = '\u25c6'; //'\u2641'; 39 AN_MOON_SYMBOL = '\u25c6'; //'\u2641'; 40 40 41 // Length limits 41 // Length limits 42 MAX_QUOTE_LENGTH = 1000; 42 MAX_QUOTE_LENGTH = 1000; 43 MAX_NOTE_LENGTH = 250; 43 MAX_NOTE_LENGTH = 250; 44 <
