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