Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/routes/routes/util.py
0001"""Utility functions for use in templates / controllers
0002
0003*PLEASE NOTE*: Many of these functions expect an initialized RequestConfig
0004object. This is expected to have been initialized for EACH REQUEST by the web
0005framework.
0006
0007"""
0008import os
0009import re
0010import urllib
0011from routes import request_config
0012
0013def _screenargs(kargs):
0014    """
0015    Private function that takes a dict, and screens it against the current 
0016    request dict to determine what the dict should look like that is used. 
0017    This is responsible for the requests "memory" of the current.
0018    """
0019    config = request_config()
0020    if config.mapper.explicit and config.mapper.sub_domains:
0021        return _subdomain_check(config, kargs)
0022    elif config.mapper.explicit:
0023        return kargs
0024
0025    controller_name = kargs.get('controller')
0026
0027    if controller_name and controller_name.startswith('/'):
0028        # If the controller name starts with '/', ignore route memory
0029        kargs['controller'] = kargs['controller'][1:]
0030        return kargs
0031    elif controller_name and not kargs.has_key('action'):
0032        # Fill in an action if we don't have one, but have a controller
0033        kargs['action'] = 'index'
0034
0035    memory_kargs = getattr(config, 'mapper_dict', {}).copy()
0036
0037    # Remove keys from memory and kargs if kargs has them as None
0038    for key in [key for key in kargs.keys() if kargs[key] is None]:
0039        del kargs[key]
0040        if memory_kargs.has_key(key):
0041            del memory_kargs[key]
0042
0043    # Merge the new args on top of the memory args
0044    memory_kargs.update(kargs)
0045
0046    # Setup a sub-domain if applicable
0047    if config.mapper.sub_domains:
0048        memory_kargs = _subdomain_check(config, memory_kargs)
0049
0050    return memory_kargs
0051
0052def _subdomain_check(config, kargs):
0053    """Screen the kargs for a subdomain and alter it appropriately depending
0054    on the current subdomain or lack therof."""
0055    if config.mapper.sub_domains:
0056        subdomain = kargs.pop('sub_domain', None)
0057        fullhost = config.environ.get('HTTP_HOST') or               config.environ.get('SERVER_NAME')
0059        hostmatch = fullhost.split(':')
0060        host = hostmatch[0]
0061        port = ''
0062        if len(hostmatch) > 1:
0063            port += ':' + hostmatch[1]
0064        sub_match = re.compile('^.+?\.(%s)$' % config.mapper.domain_match)
0065        domain = re.sub(sub_match, r'\1', host)
0066        if subdomain and not host.startswith(subdomain) and               subdomain not in config.mapper.sub_domains_ignore:
0068            kargs['_host'] = subdomain + '.' + domain + port
0069        elif (subdomain in config.mapper.sub_domains_ignore or               subdomain is None) and domain != host:
0071            kargs['_host'] = domain + port
0072        return kargs
0073    else:
0074        return kargs
0075
0076
0077def _url_quote(string, encoding):
0078    """A Unicode handling version of urllib.quote_plus."""
0079    if encoding:
0080        return urllib.quote_plus(unicode(string).encode(encoding), '/')
0081    else:
0082        return urllib.quote_plus(str(string), '/')
0083
0084def url_for(*args, **kargs):
0085    """Generates a URL 
0086    
0087    All keys given to url_for are sent to the Routes Mapper instance for 
0088    generation except for::
0089        
0090        anchor          specified the anchor name to be appened to the path
0091        host            overrides the default (current) host if provided
0092        protocol        overrides the default (current) protocol if provided
0093        qualified       creates the URL with the host/port information as 
0094                        needed
0095        
0096    The URL is generated based on the rest of the keys. When generating a new 
0097    URL, values will be used from the current request's parameters (if 
0098    present). The following rules are used to determine when and how to keep 
0099    the current requests parameters:
0100    
0101    * If the controller is present and begins with '/', no defaults are used
0102    * If the controller is changed, action is set to 'index' unless otherwise 
0103      specified
0104    
0105    For example, if the current request yielded a dict of
0106    {'controller': 'blog', 'action': 'view', 'id': 2}, with the standard 
0107    ':controller/:action/:id' route, you'd get the following results::
0108    
0109        url_for(id=4)                    =>  '/blog/view/4',
0110        url_for(controller='/admin')     =>  '/admin',
0111        url_for(controller='admin')      =>  '/admin/view/2'
0112        url_for(action='edit')           =>  '/blog/edit/2',
0113        url_for(action='list', id=None)  =>  '/blog/list'
0114    
0115    **Static and Named Routes**
0116    
0117    If there is a string present as the first argument, a lookup is done 
0118    against the named routes table to see if there's any matching routes. The
0119    keyword defaults used with static routes will be sent in as GET query 
0120    arg's if a route matches.
0121    
0122    If no route by that name is found, the string is assumed to be a raw URL. 
0123    Should the raw URL begin with ``/`` then appropriate SCRIPT_NAME data will
0124    be added if present, otherwise the string will be used as the url with 
0125    keyword args becoming GET query args.
0126    """
0127    anchor = kargs.get('anchor')
0128    host = kargs.get('host')
0129    protocol = kargs.get('protocol')
0130    qualified = kargs.pop('qualified', None)
0131
0132    # Remove special words from kargs, convert placeholders
0133    for key in ['anchor', 'host', 'protocol']:
0134        if kargs.get(key):
0135            del kargs[key]
0136        if kargs.has_key(key+'_'):
0137            kargs[key] = kargs.pop(key+'_')
0138    config = request_config()
0139    route = None
0140    static = False
0141    encoding = config.mapper.encoding
0142    url = ''
0143    if len(args) > 0:
0144        route = config.mapper._routenames.get(args[0])
0145
0146        if route and route.defaults.has_key('_static'):
0147            static = True
0148            url = route.routepath
0149
0150        # No named route found, assume the argument is a relative path
0151        if not route:
0152            static = True
0153            url = args[0]
0154
0155        if url.startswith('/') and hasattr(config, 'environ')                   and config.environ.get('SCRIPT_NAME'):
0157            url = config.environ.get('SCRIPT_NAME') + url
0158
0159        if static:
0160            if kargs:
0161                url += '?'
0162                query_args = []
0163                for key, val in kargs.iteritems():
0164                    query_args.append("%s=%s" % (
0165                        urllib.quote_plus(unicode(key).encode(encoding)),
0166                        urllib.quote_plus(unicode(val).encode(encoding))))
0167                url += '&'.join(query_args)
0168    if not static:
0169        route_args = []
0170        if route:
0171            if config.mapper.hardcode_names:
0172                route_args.append(route)
0173            newargs = route.defaults.copy()
0174            newargs.update(kargs)
0175
0176            # If this route has a filter, apply it
0177            if route.filter:
0178                newargs = route.filter(newargs)
0179
0180            # Handle sub-domains
0181            newargs = _subdomain_check(config, newargs)
0182        else:
0183            newargs = _screenargs(kargs)
0184        anchor = newargs.pop('_anchor', None) or anchor
0185        host = newargs.pop('_host', None) or host
0186        protocol = newargs.pop('_protocol', None) or protocol
0187        url = config.mapper.generate(*route_args, **newargs)
0188    if anchor:
0189        url += '#' + _url_quote(anchor, encoding)
0190    if host or protocol or qualified:
0191        if not host and not qualified:
0192            # Ensure we don't use a specific port, as changing the protocol
0193            # means that we most likely need a new port
0194            host = config.host.split(':')[0]
0195        elif not host:
0196            host = config.host
0197        if not protocol:
0198            protocol = config.protocol
0199        if url is not None:
0200            url = protocol + '://' + host + url
0201
0202    if not isinstance(url, str) and url is not None:
0203        raise Exception("url_for can only return a string or None, got "
0204                        "unicode instead: %s" % url)
0205
0206    return url
0207
0208def redirect_to(*args, **kargs):
0209    """Issues a redirect based on the arguments. 
0210    
0211    Redirect's *should* occur as a "302 Moved" header, however the web 
0212    framework may utilize a different method.
0213    
0214    All arguments are passed to url_for to retrieve the appropriate URL, then
0215    the resulting URL it sent to the redirect function as the URL.
0216    """
0217    target = url_for(*args, **kargs)
0218    config = request_config()
0219    return config.redirect(target)
0220
0221def controller_scan(directory=None):
0222    """Scan a directory for python files and use them as controllers"""
0223    if directory is None:
0224        return []
0225
0226    def find_controllers(dirname, prefix=''):
0227        """Locate controllers in a directory"""
0228        controllers = []
0229        for fname in os.listdir(dirname):
0230            filename = os.path.join(dirname, fname)
0231            if os.path.isfile(filename) and                   re.match('^[^_]{1,1}.*\.py$', fname):
0233                controllers.append(prefix + fname[:-3])
0234            elif os.path.isdir(filename):
0235                controllers.extend(find_controllers(filename,
0236                                                    prefix=prefix+fname+'/'))
0237        return controllers
0238    def longest_first(fst, lst):
0239        """Compare the length of one string to another, shortest goes first"""
0240        return cmp(len(lst), len(fst))
0241    controllers = find_controllers(directory)
0242    controllers.sort(longest_first)
0243    return controllers
0244
0245class RouteException(Exception):
0246    """Tossed during Route exceptions"""
0247    pass

Top