Changeset 71

Show
Ignore:
Timestamp:
12/22/06 22:03:41 (2 years ago)
Author:
rgrp
Message:

Complete (or nearly complete) skeletal REST and non-REST interface to annotation store but a lot of fleshing out and integration needs to be done.

  • annotater/annotater.py:
    • use routes Mapper.resource method and add other methods needed for non-REST interface
    • implement delete, update, edit
    • refactor form generate code to formencode's sqlschema and htmlfill
  • annotater/annotater_test.py: extensive tests for the above changes (now 18 passing tests)
  • annotater/model.py
    • change to generic sqlite path
    • add formencode sqlschema AnnotationSchema? associated to Annotation
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • sandbox/annotater/annotater.py

    Revision 68 Revision 71
    1""" 1""" 
    2Annotation of a web resource. 2Annotation 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""" 
    8import os 8import os 
    9 9 
    10import wsgiref.simple_server 10import wsgiref.simple_server 
    11import paste.request 11import paste.request 
    12# import genshi.template 12# import genshi.template 
    13# import genshi.output 13# import genshi.output 
    14 14 
    15from routes import * 15from routes import * 
    16 16 
      17# annotater stuff 
    17import model 18import model 
    18 19 
    19# absolute url to annotation service 20# absolute url to annotation service 
    20service_path = '/annotate' 21# this should go 
    21 22service_path = '/annotation' 
    22 23 
    23map = Mapper() 24map = Mapper() 
    24map.connect('annotate', controller='annotate', action='index',  25 map.connect('annotation/delete/:id', controller='annotation', action='delete', 
    25    conditions={'method': ['GET']})  26         conditions=dict(method=['GET'])) 
    26map.connect('annotate/create', controller='annotate', action='create',    27 map.connect('annotation/edit/:id', controller='annotation', action='edit', 
    27        conditions={'method': ['GET']})    28         conditions=dict(method=['GET'])) 
    28map.connect('annotate', controller='annotate', action='new', id=None,  29  
    29    conditions={'method': ['POST']})  30 map.resource('annotation') 
    30map.connect('annotate/:id', controller='annotate', action='edit',  31  
    31    conditions={'method': ['GET']})  32 # map.resource assumes PUT for update but marginalias uses POST 
    32map.connect('annotate/:id', controller='annotate', action='delete',  33 # the exacting mappings for REST seems a hotly contested matter see e.g. 
    33    conditions={'method': ['DELETE']})  34 # http://www.megginson.com/blogs/quoderat/archives/2005/04/03/post-in-rest-create-update-or-action/ 
    34  35 # must have this *after* map.resource as otherwise overrides the create action 
       36 map.connect('annotation/:id', controller='annotation', action='update', 
       37     conditions=dict(method=['POST'])) 
       38  
       39 # misc config 
    35marginalia_path = os.path.abspath('./marginalia') 40marginalia_path = os.path.abspath('./marginalia') 
    36html_doc_path = os.path.join(marginalia_path, 'index.html') 41html_doc_path = os.path.join(marginalia_path, 'index.html') 
    37 42 
    38 43 
    39import logging 44import logging 
    40def setup_logging(): 45def setup_logging(): 
    41    level = logging.DEBUG 46    level = logging.DEBUG 
    42    logger = logging.getLogger('annotater') 47    logger = logging.getLogger('annotater') 
    43    logger.setLevel(level) 48    logger.setLevel(level) 
    44    log_file_path = 'debug.log' 49    log_file_path = 'debug.log' 
    45    fh = logging.FileHandler(log_file_path, 'w') 50    fh = logging.FileHandler(log_file_path, 'w') 
    46    fh.setLevel(level) 51    fh.setLevel(level) 
    47    logger.addHandler(fh) 52    logger.addHandler(fh) 
    48    logger.info('START LOGGING') 53    logger.info('START LOGGING') 
    49    return logger 54    return logger 
    50 55 
    51logger = setup_logging() 56logger = setup_logging() 
    52 57 
    53class AnnotaterApp(object): 58class AnnotaterApp(object): 
    54 59 
    55    def __init__(self): 60    def __init__(self): 
    56        pass 61        pass 
    57     62     
    58    def __call__(self, environ, start_response): 63    def __call__(self, environ, start_response): 
    59        self.environ = environ 64        self.environ = environ 
    60        self.map = map 65        self.map = map 
    61        self.map.environ = environ 66        self.map.environ = environ 
    62        self.start_response = start_response 67        self.start_response = start_response 
    63        self.path = environ['PATH_INFO'] 68        self.path = environ['PATH_INFO'] 
    64        logger.debug(self.path) 69        logger.debug(self.path) 
    65        # special test cases 70        # special test cases 
    66        if self.path.startswith('/debug'): 71        if self.path.startswith('/debug'): 
    67            return wsgiref.simple_server.demo_app(environ, start_response) 72            return wsgiref.simple_server.demo_app(environ, start_response) 
    68        elif self.path.startswith('/_js/'): 73        elif self.path.startswith('/_js/'): 
    69            status = '200 OK' 74            status = '200 OK' 
    70            response_headers = [('Content-type','text/plain')] 75            response_headers = [('Content-type','text/plain')] 
    71            start_response(status, response_headers) 76            start_response(status, response_headers) 
    72            jspath = os.path.join(marginalia_path, self.path[5:]) 77            jspath = os.path.join(marginalia_path, self.path[5:]) 
    73            jsfile = file(jspath).read() 78            jsfile = file(jspath).read() 
    74            return [jsfile] 79            return [jsfile] 
    75        elif self.path.endswith('.js') or self.path.endswith('.css'): 80        elif self.path.endswith('.js') or self.path.endswith('.css'): 
    76            status = '200 OK' 81            status = '200 OK' 
    77            if self.path.endswith('.js'): filetype = 'text/javascript' 82            if self.path.endswith('.js'): filetype = 'text/javascript' 
    78            else: filetype = 'text/css' 83            else: filetype = 'text/css' 
    79            response_headers = [('Content-type', filetype)] 84            response_headers = [('Content-type', filetype)] 
    80            start_response(status, response_headers) 85            start_response(status, response_headers) 
    81            jspath = os.path.join(marginalia_path, self.path[1:]) 86            jspath = os.path.join(marginalia_path, self.path[1:]) 
    82            jsfile = file(jspath).read() 87            jsfile = file(jspath).read() 
    83            return [jsfile] 88            return [jsfile] 
    84        elif self.path.startswith('/example-annotations.xml'): 89        elif self.path.startswith('/example-annotations.xml'): 
    85            status = '200 OK' 90            status = '200 OK' 
    86            filetype = 'text/xml' 91            filetype = 'text/xml' 
    87            response_headers = [('Content-type', filetype)] 92            response_headers = [('Content-type', filetype)] 
    88            start_response(status, response_headers) 93            start_response(status, response_headers) 
    89            jspath = os.path.join(marginalia_path, self.path[1:]) 94            jspath = os.path.join(marginalia_path, self.path[1:]) 
    90            jsfile = file(jspath).read() 95            jsfile = file(jspath).read() 
    91            return [jsfile] 96            return [jsfile] 
    92        elif self.path.startswith(service_path): 97        elif self.path.startswith(service_path): 
    93            return self.annotate() 98            return self.annotate() 
    94        else: 99        else: 
    95            logger.info('Call to base url /') 100            logger.info('Call to base url /') 
    96            status = '200 OK' 101            status = '200 OK' 
    97            response_headers = [('Content-type','text/html')] 102            response_headers = [('Content-type','text/html')] 
    98            start_response(status, response_headers) 103            start_response(status, response_headers) 
    99            out = file(html_doc_path).read() 104            out = file(html_doc_path).read() 
    100            return [out] 105            return [out] 
    101 106 
      107    def _make_annotate_form(self, form_name, action_url, form_defaults): 
      108        from formencode import htmlfill 
      109        keys = [ 'url' , 'range', 'note' ] 
      110        vals = {} 
      111        for key in keys: 
      112            vals[key] = form_defaults.get(key, '') 
      113        formfields = '' 
      114        for key in keys: 
      115            formfields += \ 
      116'''            <label for="%s">%s:</label><input name="%s" id="%s" /><br /> 
      117''' % (key, key, key, key) 
      118             
      119 
      120        form = \ 
      121'''<html> 
      122    <head></head> 
      123    <body> 
      124        <form name="%s" action="%s" method="POST"> 
      125           %s 
      126           <input type="submit" name="submission" value="send the form" /> 
      127       </form> 
      128    </body> 
      129</html>''' % (form_name, action_url, formfields) 
      130         
      131        form = htmlfill.render(form, vals) 
      132        return form 
      133          
      134 
    102    def annotate(self): 135    def annotate(self): 
    103        query_vals = paste.request.parse_formvars(self.environ) 136        query_vals = paste.request.parse_formvars(self.environ) 
    104        request_method = self.environ['REQUEST_METHOD'] 137        request_method = self.environ['REQUEST_METHOD'] 
    105        logger.debug('CALL TO ANNOTATE') 138        logger.debug('CALL TO ANNOTATE') 
    106        logger.debug(self.path) 139        logger.debug(self.path) 
    107        logger.debug(query_vals) 140        logger.debug(query_vals) 
    108        logger.debug(self.environ['QUERY_STRING']) 141        logger.debug(self.environ['QUERY_STRING']) 
    109        logger.debug(request_method) 142        logger.debug(request_method) 
    110        mapdict = self.map.match(self.path) 143        mapdict = self.map.match(self.path) 
    111        logger.debug('mapdict: %s' % mapdict) 144        logger.debug('mapdict: %s' % mapdict) 
    112        action = mapdict['action'] 145        action = mapdict['action'] 
      146        anno_schema = model.AnnotationSchema() 
    113 147 
    114        if action == 'index': 148        if action == 'index': 
    115            status = '200 OK' 149            status = '200 OK' 
    116            response_headers = [ 150            response_headers = [ 
    117                    ('Content-type', 'text/plain'), 151                    ('Content-type', 'text/plain'), 
    118                    ] 152                    ] 
    119            items = list(model.Annotation.select()) 153            items = list(model.Annotation.select()) 
    120            out = '' 154            out = '' 
    121            for item in items: 155            for item in items: 
    122                out += str(item) + '\n\n' 156                out += str(item) + '\n\n' 
    123            self.start_response(status, response_headers) 157            self.start_response(status, response_headers) 
    124            return [out] 158            return [out] 
    125        elif action == 'create': 159        elif action == 'new': 
    126            status = '200 OK' 160            status = '200 OK' 
    127            response_headers = [ 161            response_headers = [ 
    128                    ('Content-type', 'text/html'), 162                    ('Content-type', 'text/html'), 
    129                    ] 163                    ] 
    130            posturl = self.map.generate(controller='annotate', action='new') 164            posturl = self.map.generate(controller='annotation', action='create') 
    131            form = \ 165            form = \ 
    132'''<html> 166'''<html> 
    133    <head></head> 167    <head></head> 
    134    <body> 168    <body> 
    135        <form name='annotation_create' action="%s" method="POST"> 169        <form name='annotation_create' action="%s" method="POST"> 
    136           <label>url:</label> <input name="url" id="url" /><br /> 170           <label>url:</label> <input name="url" id="url" /><br /> 
    137           <label>range:</label><input name="range" id="range" /> 171           <label>range:</label><input name="range" id="range" /><br /> 
    138           <label>note:</label><input name="note" id="note" /> 172           <label>note:</label><input name="note" id="note" /><br /> 
    139           <input type="submit" name="submission" value="send the form" /> 173           <input type="submit" name="submission" value="send the form" /> 
    140       </form> 174       </form> 
    141    </body> 175    </body> 
    142</html>''' % posturl 176</html>''' % posturl 
    143            self.start_response(status, response_headers) 177            self.start_response(status, response_headers) 
    144            return [ form ] 178            return [ form ] 
    145        elif action == 'new': 179        elif action == 'create': 
    146            url = query_vals.get('url') 180            url = query_vals.get('url') 
    147            range = query_vals.get('range', 'NO RANGE') 181            range = query_vals.get('range', 'NO RANGE') 
    148            note = query_vals.get('note', 'NO NOTE') 182            note = query_vals.get('note', 'NO NOTE') 
    149            anno = model.Annotation( 183            anno = model.Annotation( 
    150                    url=url, 184                    url=url, 
    151                    range=range, 185                    range=range, 
    152                    note=note) 186                    note=note) 
    153            status = '201 Created' 187            status = '201 Created' 
    154            location = '/annotation/%s' % anno.id 188            location = '/annotation/%s' % anno.id 
    155            response_headers = [ 189            response_headers = [ 
    156                    ('Content-type', 'text/html'), 190                    ('Content-type', 'text/html'), 
    157                    ('Location', location) 191                    ('Location', location) 
    158                    ] 192                    ] 
    159            self.start_response(status, response_headers) 193            self.start_response(status, response_headers) 
    160            return [''] 194            return [''] 
      195        elif action == 'edit': 
      196            id = mapdict['id'] 
      197            try: 
      198                id = int(id) 
      199            except: 
      200                status = '400 Bad Request' 
      201                response_headers = [ 
      202                    ('Content-type', 'text/html'), 
      203                    ] 
      204                self.start_response(status, response_headers) 
      205                msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 
      206                return [msg] 
      207            anno = model.Annotation.get(id) 
      208            posturl = self.map.generate(controller='annotation', 
      209                    action='update', id=anno.id, method='POST') 
      210            print 'Post url:', posturl 
      211            form_defaults = anno_schema.from_python(anno) 
      212            form = self._make_annotate_form('annotate_edit', posturl, 
      213                    form_defaults) 
      214            status = '200 OK' 
      215            response_headers = [ 
      216                    ('Content-type', 'text/html'), 
      217                    ] 
      218            self.start_response(status, response_headers) 
      219            return [ form ] 
      220 
      221        elif action == 'update': 
      222            id = mapdict['id'] 
      223            try: 
      224                id = int(id) 
      225            except: 
      226                status = '400 Bad Request' 
      227                response_headers = [ 
      228                    ('Content-type', 'text/html'), 
      229                    ] 
      230                self.start_response(status, response_headers) 
      231                msg = '<h1>400 Bad Request</h1><p>No such annotation #%s</p>' % id 
      232                return [msg] 
      233            new_values = dict(query_vals) 
      234            new_values['id'] = id 
      235            del new_values['submission'] 
      236            anno_edited = anno_schema.to_python(new_values) 
      237            status = '204 Updated' 
      238            response_headers = [] 
      239            self.start_response(status, response_headers) 
      240            return [''] 
      241 
    161        elif action == 'delete': 242        elif action == 'delete': 
    162            id = query_vals['id']  243             id = mapdict['id'] 
       244             if id is None: 
       245                 status = '400 Bad Request' 
       246                 response_headers = [ 
       247                     ('Content-type', 'text/html'), 
       248                     ] 
       249                 self.start_response(status, response_headers) 
       250                 return ['<h1>400 Bad Request</h1><p>Bad ID</p>'] 
       251             else: 
       252                 status = '204 Deleted' 
       253                 response_headers = [] 
       254                 try: 
       255                     id = int(id) 
       256                     model.Annotation.delete(id) 
       257                     self.start_response(status, response_headers) 
       258                     return [''] 
       259                 except: 
       260                     status = '500 Internal server error' 
       261                     self.start_response(status, response_headers) 
       262                     return ['<h1>500 Internal Server Error</h1>Delete failed'] 
    163        else: 263        else: 
    164            status = '404 Not Found' 264            status = '404 Not Found' 
    165            response_headers = [ 265            response_headers = [ 
    166                    ('Content-type', 'text/html'), 266                    ('Content-type', 'text/plain'), 
    167                    ] 267                    ] 
    168            self.start_response(status, response_headers) 268            self.start_response(status, response_headers) 
    169            return ['Not found'] 269            return ['Not found or method not allowed'] 
    170 270     
    171 271 
    172if __name__ == '__main__':  272if __name__ == '__main__':  
    173    app = AnnotaterApp() 273    app = AnnotaterApp() 
    174    import paste.httpserver 274    import paste.httpserver 
    175    paste.httpserver.serve(app) 275    paste.httpserver.serve(app) 
  • sandbox/annotater/annotater_test.py

    Revision 68 Revision 71
    1import os 1import os 
    2import shutil 2import shutil 
    3import tempfile 3import tempfile 
    4import commands 4import commands 
    5from StringIO import StringIO 5from StringIO import StringIO 
    6 6 
    7import twill 7import twill 
    8from twill import commands as web 8from twill import commands as web 
    9 9 
    10import annotater 10import annotater 
    11import model 11import model 
    12 12 
    13class TestMapper: 13class TestMapper: 
    14 14 
    15    def test_match_1(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('/annotate/create')  17         out = annotater.map.match('/annotation/new') 
       18         assert out['action'] == 'new' 
       19  
       20     def test_match_index(self): 
       21         annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 
       22         out = annotater.map.match('/annotation') 
       23         assert out['action'] == 'index' 
       24  
       25     def test_match_create(self): 
       26         annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 
       27         out = annotater.map.match('/annotation') 
    18        assert out['action'] == 'create' 28        assert out['action'] == 'create' 
    19 29 
    20    def test_match_2(self):  30     def test_match_delete(self): 
    21        annotater.map.environ = { 'REQUEST_METHOD' : 'GET' }  31         annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 
    22        out = annotater.map.match('/annotate')  32         out = annotater.map.match('/annotation/delete/1') 
    23        assert out['action'] == 'index'  33         assert out['action'] == 'delete' 
    24  34         assert out['id'] == '1' 
    25    def test_match_2(self):  35         annotater.map.environ = { 'REQUEST_METHOD' : 'DELETE' } 
       36         out = annotater.map.match('/annotation/1') 
       37         assert out['action'] == 'delete' 
       38         assert out['id'] == '1' 
       39         out = annotater.map.match('/annotation/') 
       40         assert out['id'] == None 
       41  
       42     def test_match_delete(self): 
       43         annotater.map.environ = { 'REQUEST_METHOD' : 'GET' } 
       44         out = annotater.map.match('/annotation/edit/1') 
       45         assert out['action'] == 'edit' 
       46         assert out['id'] == '1' 
       47         annotater.map.environ = { 'REQUEST_METHOD' : 'PUT' } 
       48         out = annotater.map.match('/annotation/1') 
       49         assert out['action'] == 'update' 
       50         assert out['id'] == '1' 
    26        annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 51        annotater.map.environ = { 'REQUEST_METHOD' : 'POST' } 
    27        out = annotater.map.match('/annotate') 52        out = annotater.map.match('/annotation/1') 
    28        assert out['action'] == 'new53        assert out['action'] == 'update
    29 54 
    30    def test_url_for_1(self): 55    def test_url_for_new(self): 
    31        offset = annotater.map.generate(controller='annotate', action='create') 56        offset = annotater.map.generate(controller='annotation', action='new') 
    32        exp = '/annotate/create57        exp = '/annotation/new
    33        assert offset == exp 58        assert offset == exp 
    34 59 
    35    def test_url_for_2(self): 60    def test_url_for_create(self): 
    36        offset = annotater.map.generate(controller='annotate', action='new', 61        offset = annotater.map.generate(controller='annotation', action='create', 
    37                method='POST' ) 62                method='POST' ) 
    38        exp = '/annotate'  63         exp = '/annotation' 
    39        assert offset == exp  64         assert offset == exp 
    40  65  
    41  66     def test_url_for_delete(self): 
    42class TestStuff:  67         offset = annotater.map.generate(controller='annotation', 
       68                 action='delete', id=1, method='GET' ) 
       69         exp = '/annotation/delete/1' 
       70         assert offset == exp 
       71         offset = annotater.map.generate(controller='annotation', 
       72                 action='delete', id=1, method='DELETE' ) 
       73         exp = '/annotation/1' 
       74         assert offset == exp 
       75  
       76     def test_url_for_edit(self): 
       77         offset = annotater.map.generate(controller='annotation', 
       78                 action='edit', id=1, method='GET') 
       79         exp = '/annotation/edit/1' 
       80         assert offset == exp 
       81  
       82 class TestStatic: 
       83      
       84     def test__make_annotate_form(self): 
       85         app = annotater.AnnotaterApp() 
       86         defaults = { 'url' : 'http://www.blackandwhite.com' } 
       87         out = app._make_annotate_form(form_name='formname', action_url='.', 
       88                 form_defaults=defaults) 
       89         exp1 = '<label for="url">url:</label><input name="url" id="url" \ 
       90 value="%s" /><br />' % defaults['url'] 
       91         assert exp1 in out 
       92  
       93  
       94 class TestWsgi: 
       95  
       96     # disabled = True 
    43 97 
    44    def setup_method(self, name=''): 98    def setup_method(self, name=''): 
    45        wsgi_app = annotater.AnnotaterApp() 99        wsgi_app = annotater.AnnotaterApp() 
    46        twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi_app) 100        twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi_app) 
    47        self.outp = StringIO() 101        self.outp = StringIO() 
    48        twill.set_output(self.outp) 102        twill.set_output(self.outp) 
    49        self.siteurl = 'http://localhost:8080/' 103        self.siteurl = 'http://localhost:8080/' 
    50 104 
    51    def teardown_method(self, name=''): 105    def teardown_method(self, name=''): 
    52        # remove intercept. 106        # remove intercept. 
    53        twill.remove_wsgi_intercept('localhost', 8080) 107        twill.remove_wsgi_intercept('localhost', 8080) 
    54 108 
    55    def test_js(self): 109    def test_js(self): 
    56        filename = 'domutil.js' 110        filename = 'domutil.js' 
    57        url = self.siteurl + '_js/' + filename 111        url = self.siteurl + '_js/' + filename 
    58        web.go(url) 112        web.go(url) 
    59        web.code(200) 113        web.code(200) 
    60        web.find('ELEMENT_NODE = 1;') 114        web.find('ELEMENT_NODE = 1;') 
    61 115 
    62    def test_js_2(self): 116    def test_js_2(self): 
    63        filename = 'domutil.js' 117        filename = 'domutil.js' 
    64        url = self.siteurl + filename 118        url = self.siteurl + filename 
    65        web.go(url) 119        web.go(url) 
    66        web.code(200) 120        web.code(200) 
    67        web.find('ELEMENT_NODE = 1;') 121        web.find('ELEMENT_NODE = 1;') 
    68 122 
    69    def test_show_root(self): 123    def test_show_root(self): 
    70        web.go(self.siteurl) 124        web.go(self.siteurl) 
    71        web.code(200) 125        web.code(200) 
    72        web.find('This is a demonstration of') 126        web.find('This is a demonstration of') 
    73 127 
    74    def test_annotate_get(self): 128    def test_annotate_get(self): 
    75        anno = model.Annotation( 129        anno = model.Annotation( 
    76                url='http://xyz.com', 130                url='http://xyz.com', 
    77                range='blah range', 131                range='blah range', 
    78                note='blah note', 132                note='blah note', 
    79                ) 133                ) 
    80        offset = annotater.url_for(controller='annotate', action='index') 134        offset = annotater.map.generate(controller='annotation', action='index') 
    81        url = self.siteurl + offset[1:] 135        url = self.siteurl + offset[1:] 
    82        web.go(url) 136        web.go(url) 
    83        web.code(200) 137        web.code(200) 
    84        web.find(anno.url) 138        web.find(anno.url) 
    85        web.find(anno.range) 139        web.find(anno.range) 
    86 140 
    87    def test_annotate_create(self):  141     def test_annotate_new(self): 
       142         # exercises both create and new 
    88        import model 143        import model 
    89        model.rebuilddb() 144        model.rebuilddb() 
    90        offset = annotater.url_for(controller='annotate', action='create', 145        offset = annotater.map.generate(controller='annotation', action='new', 
    91                method='GET') 146                method='GET') 
    92        url = self.siteurl + offset[1:] 147        url = self.siteurl + offset[1:] 
    93        web.go(url) 148        web.go(url) 
    94        web.code(200) 149        web.code(200) 
    95        note = 'any old thing' 150        note = 'any old thing' 
    96        web.fv('', 'url', 'http://localhost/') 151        web.fv('', 'url', 'http://localhost/') 
    97        web.fv('', 'note', note) 152        web.fv('', 'note', note) 
    98        web.submit() 153        web.submit() 
    99        web.code(201) 154        web.code(201) 
    100        # TODO make this test more selective 155        # TODO make this test more selective 
    101        items = model.Annotation.select() 156        items = model.Annotation.select() 
    102        items = list(items) 157        items = list(items) 
    103        assert len(items) == 1 158        assert len(items) == 1 
    104        assert items[0].note == note 159        assert items[0].note == note 
    105 160 
      161    def test_annotate_delete(self): 
      162        anno = model.Annotation( 
      163                url='http://xyz.com', 
      164                range='blah range', 
      165                note='blah note', 
      166                ) 
      167        offset = annotater.map.generate(controller='annotation', action='delete', 
      168                id=anno.id) 
      169        url = self.siteurl + offset[1:] 
      170        web.go(url) 
      171        web.code(204) 
      172        tmp = model.Annotation.select(model.Annotation.q.id == anno.id) 
      173        num = len(list(tmp)) 
      174        assert num == 0 
      175     
      176    def _create_annotation(self): 
      177        anno = model.Annotation( 
      178                url='http://xyz.com', 
      179                range='blah range', 
      180                note='blah note', 
      181                ) 
      182        return anno 
      183 
      184    def test_annotate_edit(self): 
      185        anno = self._create_annotation() 
      186        offset = annotater.map.generate(controller='annotation', action='edit', 
      187                id=anno.id, method='GET') 
      188        url = self.siteurl + offset[1:] 
      189        web.go(url) 
      190        web.code(200) 
      191        newnote = u'This is a NEW note, a NEW note I say.' 
      192        web.fv('', 'note', newnote) 
      193        web.submit() 
      194        web.code(204) 
      195        assert anno.note == newnote 
      196     
      197    def test_not_found(self): 
      198        offset = annotater.map.generate(controller='annotation') 
      199        url = self.siteurl + offset[1:] + '/blah' 
      200        web.go(url) 
      201        web.code(404) 
      202 
      203    def test_bad_request(self): 
      204        offset = annotater.map.generate(controller='annotation', action='edit', 
      205                method='GET') 
      206        url = self.siteurl + offset[1:] 
      207        web.go(url) 
      208        web.code(400) 
      209         
      210 
  • sandbox/annotater/model.py

    Revision 68 Revision 71
    1import sqlobject 1import sqlobject 
    2 2 
    3uri = 'sqlite:///Users/rgrp/svnroot/rgrp/code/annotater/sqlite.db'  3 import os 
       4 cwd = os.getcwd() 
       5 uri = 'sqlite://%s/sqlite.db' % cwd 
    4# uri = 'sqlite:///:memory:' 6# uri = 'sqlite:///:memory:' 
    5__connection__ = sqlobject.connectionForURI(uri) 7__connection__ = sqlobject.connectionForURI(uri) 
    6 8 
    7# note we run this at bottom of module to auto create db tables on import 9# note we run this at bottom of module to auto create db tables on import 
    8def createdb(): 10def createdb(): 
    9    Annotation.createTable(ifNotExists=True) 11    Annotation.createTable(ifNotExists=True) 
    10 12 
    11def cleandb(): 13def cleandb(): 
    12    Annotation.dropTable(ifExists=True) 14    Annotation.dropTable(ifExists=True) 
    13 15 
    14def rebuilddb(): 16def rebuilddb(): 
    15    cleandb() 17    cleandb() 
    16    createdb() 18    createdb() 
    17 19 
    18class Annotation(sqlobject.SQLObject): 20class Annotation(sqlobject.SQLObject): 
    19 21 
    20    url = sqlobject.UnicodeCol() 22    url = sqlobject.UnicodeCol() 
    21    range = sqlobject.UnicodeCol() 23    range = sqlobject.UnicodeCol() 
    22    note = sqlobject.UnicodeCol() 24    note = sqlobject.UnicodeCol() 
    23 25 
    24 # $url, $range, $note, $access, $quote, $quote_title, $quote_author ) 26 # $url, $range, $note, $access, $quote, $quote_title, $quote_author ) 
    25 27 
      28from formencode import sqlschema 
      29class AnnotationSchema(sqlschema.SQLSchema): 
      30 
      31    wrap = Annotation 
    26 32 
    27createdb() 33createdb()