Ticket #336: resource.patch

File resource.patch, 11.7 KB (added by donovanhide, 4 years ago)
  • ckan/controllers/rest.py

    diff -r 170cac0b50ac ckan/controllers/rest.py
    a b  
    22 
    33from ckan.lib.base import * 
    44from ckan.lib.helpers import json 
     5from ckan.lib.urlnorm import norms 
    56import ckan.model as model 
    67import ckan.forms 
    78from ckan.lib.search import make_search, SearchOptions 
    89import ckan.authz 
    910import ckan.rating 
     11from collections import defaultdict 
    1012 
    1113class RestController(BaseController): 
    1214 
     
    1719        if register == 'revision': 
    1820            revs = model.Session.query(model.Revision).all() 
    1921            return self._finish_ok([rev.id for rev in revs]) 
     22        elif register == u'resource': 
     23            resources = model.Session.query(model.PackageResource).all() 
     24            urls = [[norms(resource.url),resource.package.ckan_url] for resource in resources] 
     25            results = defaultdict(list) 
     26            for resource, package in urls: 
     27                results[resource].append(package) 
     28            return self._finish_ok(results) 
    2029        elif register == u'package' and not subregister: 
    2130            query = ckan.authz.Authorizer().authorized_query(self._get_username(), model.Package) 
    2231            packages = query.all()  
     
    501510 
    502511    def _finish_ok(self, response_data=None): 
    503512        response.status_int = 200 
    504         response.headers['Content-Type'] = 'application/json' 
     513        response.headers['Content-Type'] = 'application/json;charset=utf-8' 
    505514        if response_data is not None: 
     515            if request.params.has_key('callback'): 
     516                return '%s(%s);' % (request.params['callback'],json.dumps(response_data)) 
    506517            return json.dumps(response_data) 
    507518        else: 
    508519            return '' 
  • new file ckan/lib/urlnorm.py

    diff -r 170cac0b50ac ckan/lib/urlnorm.py
    - +  
     1#!/usr/bin/env python 
     2 
     3""" 
     4urlnorm.py - URL normalisation routines 
     5 
     6urlnorm normalises a URL by; 
     7  * lowercasing the scheme and hostname 
     8  * taking out default port if present (e.g., http://www.foo.com:80/) 
     9  * collapsing the path (./, ../, etc) 
     10  * removing the last character in the hostname if it is '.' 
     11  * unquoting any %-escaped characters 
     12 
     13Available functions: 
     14  norms - given a URL (string), returns a normalised URL 
     15  norm - given a URL tuple, returns a normalised tuple 
     16  test - test suite 
     17 
     18CHANGES: 
     190.92 - unknown schemes now pass the port through silently 
     200.91 - general cleanup 
     21     - changed dictionaries to lists where appropriate 
     22     - more fine-grained authority parsing and normalisation 
     23""" 
     24 
     25__license__ = """ 
     26Copyright (c) 1999-2002 Mark Nottingham <[email protected]> 
     27 
     28Permission is hereby granted, free of charge, to any person obtaining a copy 
     29of this software and associated documentation files (the "Software"), to deal 
     30in the Software without restriction, including without limitation the rights 
     31to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
     32copies of the Software, and to permit persons to whom the Software is 
     33furnished to do so, subject to the following conditions: 
     34 
     35The above copyright notice and this permission notice shall be included in all 
     36copies or substantial portions of the Software. 
     37 
     38THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
     39IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
     40FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
     41AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
     42LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
     43OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
     44SOFTWARE. 
     45""" 
     46 
     47__version__ = "0.93" 
     48 
     49from urlparse import urlparse, urlunparse 
     50from urllib import unquote 
     51from string import lower 
     52import re 
     53 
     54_collapse = re.compile('([^/]+/\.\./?|/\./|//|/\.$|/\.\.$)') 
     55_server_authority = re.compile('^(?:([^\@]+)\@)?([^\:]+)(?:\:(.+))?$') 
     56_default_port = {   'http': '80', 
     57                    'https': '443', 
     58                    'gopher': '70', 
     59                    'news': '119', 
     60                    'snews': '563', 
     61                    'nntp': '119', 
     62                    'snntp': '563', 
     63                    'ftp': '21', 
     64                    'telnet': '23', 
     65                    'prospero': '191', 
     66                } 
     67_relative_schemes = [   'http', 
     68                        'https', 
     69                        'news', 
     70                        'snews', 
     71                        'nntp', 
     72                        'snntp', 
     73                        'ftp', 
     74                        'file', 
     75                        '' 
     76                    ] 
     77_server_authority_schemes = [   'http', 
     78                                'https', 
     79                                'news', 
     80                                'snews', 
     81                                'ftp', 
     82                            ] 
     83 
     84 
     85def norms(urlstring): 
     86    """given a string URL, return its normalised form""" 
     87    return urlunparse(norm(urlparse(urlstring))) 
     88 
     89 
     90def norm(urltuple): 
     91    """given a six-tuple URL, return its normalised form""" 
     92    (scheme, authority, path, parameters, query, fragment) = urltuple 
     93    scheme = lower(scheme) 
     94    if authority: 
     95        userinfo, host, port = _server_authority.match(authority).groups() 
     96        if host[-1] == '.': 
     97            host = host[:-1] 
     98        authority = lower(host) 
     99        if userinfo: 
     100            authority = "%s@%s" % (userinfo, authority) 
     101        if port and port != _default_port.get(scheme, None): 
     102            authority = "%s:%s" % (authority, port) 
     103    if scheme in _relative_schemes: 
     104        last_path = path 
     105        while 1: 
     106            path = _collapse.sub('/', path, 1) 
     107            if last_path == path: 
     108                break 
     109            last_path = path 
     110    path = unquote(path) 
     111    return (scheme, authority, path, parameters, query, fragment) 
     112 
     113 
     114 
     115def test(): 
     116    """ test suite; some taken from RFC1808. """ 
     117    tests = { 
     118        '/foo/bar/.':                    '/foo/bar/', 
     119        '/foo/bar/./':                   '/foo/bar/', 
     120        '/foo/bar/..':                   '/foo/', 
     121        '/foo/bar/../':                  '/foo/', 
     122        '/foo/bar/../baz':               '/foo/baz', 
     123        '/foo/bar/../..':                '/', 
     124        '/foo/bar/../../':               '/', 
     125        '/foo/bar/../../baz':            '/baz', 
     126        '/foo/bar/../../../baz':         '/../baz', 
     127        '/foo/bar/../../../../baz':      '/baz', 
     128        '/./foo':                        '/foo', 
     129        '/../foo':                       '/../foo', 
     130        '/foo.':                         '/foo.', 
     131        '/.foo':                         '/.foo', 
     132        '/foo..':                        '/foo..', 
     133        '/..foo':                        '/..foo', 
     134        '/./../foo':                     '/../foo', 
     135        '/./foo/.':                      '/foo/', 
     136        '/foo/./bar':                    '/foo/bar', 
     137        '/foo/../bar':                   '/bar', 
     138        '/foo//':                        '/foo/', 
     139        '/foo///bar//':                  '/foo/bar/', 
     140        'http://www.foo.com:80/foo':     'http://www.foo.com/foo', 
     141        'http://www.foo.com:8000/foo':   'http://www.foo.com:8000/foo', 
     142        'http://www.foo.com./foo/bar.html': 'http://www.foo.com/foo/bar.html', 
     143        'http://www.foo.com.:81/foo':    'http://www.foo.com:81/foo', 
     144        'http://www.foo.com/%7ebar':     'http://www.foo.com/~bar', 
     145        'http://www.foo.com/%7Ebar':     'http://www.foo.com/~bar', 
     146        'ftp://user:[email protected]/foo/bar': 'ftp://user:[email protected]/foo/bar', 
     147        'http://USER:[email protected]/foo/bar': 'http://USER:[email protected]/foo/bar', 
     148        'http://www.example.com./':      'http://www.example.com/', 
     149        '-':                             '-', 
     150    } 
     151 
     152    n_correct, n_fail = 0, 0 
     153    test_keys = tests.keys() 
     154    test_keys.sort() 
     155    for i in test_keys: 
     156        print 'ORIGINAL:', i 
     157        cleaned = norms(i) 
     158        answer = tests[i] 
     159        print 'CLEANED: ', cleaned 
     160        print 'CORRECT: ', answer 
     161        if cleaned != answer: 
     162            print "*** TEST FAILED" 
     163            n_fail = n_fail + 1 
     164        else: 
     165            n_correct = n_correct + 1 
     166        print 
     167    print "TOTAL CORRECT:", n_correct 
     168    print "TOTAL FAILURE:", n_fail 
     169 
     170 
     171if __name__ == '__main__': 
     172    test() 
  • ckan/model/core.py

    diff -r 170cac0b50ac ckan/model/core.py
    a b  
    203203        _dict['ratings_count'] = len(self.ratings) 
    204204        _dict['resources'] = [{'url':res.url, 'format':res.format, 'description':res.description} for res in self.resources] 
    205205        _dict['download_url'] = self.resources[0].url if self.resources else '' 
     206        _dict['ckan_url'] = self.ckan_url 
     207        _dict['relationships'] = [rel.as_dict(self) for rel in self.get_relationships()] 
     208        return _dict 
     209 
     210    @property 
     211    def ckan_url(self): 
    206212        ckan_host = config.get('ckan_host', None) 
    207213        if ckan_host: 
    208             _dict['ckan_url'] = 'http://%s/package/%s' % (ckan_host, self.name) 
    209         _dict['relationships'] = [rel.as_dict(self) for rel in self.get_relationships()] 
    210         return _dict 
     214            return 'http://%s/package/%s' % (ckan_host, self.name) 
     215        return None 
    211216 
    212217    def add_relationship(self, type_, related_package, comment=u''): 
    213218        '''Creates a new relationship between this package and a 
  • doc/api.rst

    diff -r 170cac0b50ac doc/api.rst
    a b  
    9292+--------------------------------+-------------------------------------------------------------------+ 
    9393| Rating Entity                  | ``/api/rest/rating/PACKAGE-NAME``                                 | 
    9494+--------------------------------+-------------------------------------------------------------------+ 
     95| Resource Package               | /api/rest/resource                                                | 
     96+--------------------------------+-------------------------------------------------------------------+ 
    9597| Package Relationships Register | ``/api/rest/package/PACKAGE-NAME/relationships``                  | 
    9698+--------------------------------+-------------------------------------------------------------------+ 
    9799| Package Relationships Register | ``/api/rest/package/PACKAGE-NAME/relationships/PACKAGE-NAME``     | 
     
    156158+-------------------------------+--------+------------------+-------------------+ 
    157159| Rating Entity                 | GET    |                  | Rating            |  
    158160+-------------------------------+--------+------------------+-------------------+ 
     161| Resource Package              | GET    |                  | Resource-Package  | 
     162+-------------------------------+--------+------------------+-------------------+ 
    159163| Package Relationships Register| GET    |                  | Pkg-Relationships |  
    160164+-------------------------------+--------+------------------+-------------------+ 
    161165| Package Relationship Entity   | GET    |                  | Pkg-Relationship  | 
     
    214218| Resource        | { url: String, format: String, description: String,        | 
    215219|                 | hash: String }                                             | 
    216220+-----------------+------------------------------------------------------------+ 
     221|Resource-Package | { resource_url: String, [package_url, package_url, ...]    | 
     222+-----------------+------------------------------------------------------------+ 
    217223| Rating          | { package: Name-String, rating: int }                      | 
    218224+-----------------+------------------------------------------------------------+ 
    219225|Pkg-Relationships| [ Pkg-Relationship, Pkg-Relationship, ... ]                |