| 1 | import sys, os
|
|---|
| 2 | from django.conf.urls import defaults
|
|---|
| 3 |
|
|---|
| 4 | """
|
|---|
| 5 | Automatic site configuration.
|
|---|
| 6 |
|
|---|
| 7 | In your settings module::
|
|---|
| 8 |
|
|---|
| 9 | import autosite
|
|---|
| 10 | ROOT_URLCONF = autosite.root_urlconf('sitename')
|
|---|
| 11 | TEMPLATE_DIRS = autosite.template_dirs('sitename')
|
|---|
| 12 | INSTALLED_APPS = autosite.installed_apps('sitename')
|
|---|
| 13 |
|
|---|
| 14 | In your site-level url configuration module, which thanks to `root_urlconf`
|
|---|
| 15 | can be in ``sitename/urls.py``
|
|---|
| 16 | rather than ``sitename/settings/urls/main.py``, you can use `sitepatterns`
|
|---|
| 17 | rather than `patterns` to do most of the work for you::
|
|---|
| 18 |
|
|---|
| 19 | from autosite import sitepatterns
|
|---|
| 20 | urlpatterns = patterns('', 'sitename',
|
|---|
| 21 | (r'^/?$', 'sitename.views.root'),
|
|---|
| 22 | # ... any more explicit site-level patterns and destinations...
|
|---|
| 23 | )
|
|---|
| 24 |
|
|---|
| 25 | `sitepatterns` will iterate through the application packages looking
|
|---|
| 26 | for app-level url configuration modules (either ``appname/urls.py`` or
|
|---|
| 27 | ``appname/urls/app.py``) and automatically add them to the site's pattern
|
|---|
| 28 | list with the pattern ``r'^appname/'``.
|
|---|
| 29 |
|
|---|
| 30 | If a app-level url configuration module can't be found, `sitepatterns`
|
|---|
| 31 | will iterate through ``sitename.apps.appname.views`` (whether it's a module
|
|---|
| 32 | or a package) looking for callables with a ``.urlpattern`` attribute
|
|---|
| 33 | (for which the value should be a regular expression) or a ``.urlpatterns``
|
|---|
| 34 | attribute (for which the value should be a list of regular expressions).
|
|---|
| 35 |
|
|---|
| 36 | For Python 2.3, you should set ``.urlpattern`` manually. For Python 2.4,
|
|---|
| 37 | the ``urlpattern`` decorator will do it for you::
|
|---|
| 38 |
|
|---|
| 39 | @urlpattern(r'^/?$')
|
|---|
| 40 | def index(request):
|
|---|
| 41 | # ...
|
|---|
| 42 |
|
|---|
| 43 | Note that the view modules' expressions will be added in the alphabetic
|
|---|
| 44 | order of their function names. If you were depending on careful ordering
|
|---|
| 45 | of your pattern list, either keep using your manual url configuration
|
|---|
| 46 | module or use the ``(?<!...)`` negative lookbehind assertion.
|
|---|
| 47 |
|
|---|
| 48 | The practical upshot of using autosite is that you type less and don't
|
|---|
| 49 | have to maintain quite so many files. Rather than::
|
|---|
| 50 |
|
|---|
| 51 | sitename/settings/__init__.py
|
|---|
| 52 | sitename/settings/main.py
|
|---|
| 53 | sitename/settings/admin.py
|
|---|
| 54 | sitename/settings/urls/main.py
|
|---|
| 55 | sitename/apps/appname/urls/__init__.py
|
|---|
| 56 | sitename/apps/appname/urls/appname.py
|
|---|
| 57 | sitename/apps/appname/views/__init__.py
|
|---|
| 58 | sitename/apps/appname/views/appname.py
|
|---|
| 59 |
|
|---|
| 60 | ... you can consolidate back to:
|
|---|
| 61 |
|
|---|
| 62 | sitename/settings.py
|
|---|
| 63 | sitename/urls.py
|
|---|
| 64 | sitename/apps/appname/views.py
|
|---|
| 65 |
|
|---|
| 66 | This module has been brought to you by one programmer's bizarre tendency to
|
|---|
| 67 | to spend two hours writing 300+ lines of code to replace around 30 lines of
|
|---|
| 68 | code that were taking him less than two minutes per day to maintain. His
|
|---|
| 69 | insanity is your gain. If only 100 Django programmers benefit from this
|
|---|
| 70 | module, all his hard work will have been worthwhile.
|
|---|
| 71 | """
|
|---|
| 72 |
|
|---|
| 73 | __all__ = [
|
|---|
| 74 | 'urlconf',
|
|---|
| 75 | 'patterns',
|
|---|
| 76 | 'template_dirs',
|
|---|
| 77 | 'installed_apps',
|
|---|
| 78 | 'sitepatterns',
|
|---|
| 79 | 'apppatterns',
|
|---|
| 80 | 'urlpattern',
|
|---|
| 81 | ]
|
|---|
| 82 |
|
|---|
| 83 | def splitall(path):
|
|---|
| 84 | """Split `path` into all of its components."""
|
|---|
| 85 | segments = []
|
|---|
| 86 | base = path
|
|---|
| 87 | while True:
|
|---|
| 88 | base, name = os.path.split(base)
|
|---|
| 89 | if name:
|
|---|
| 90 | segments.append(name)
|
|---|
| 91 | else:
|
|---|
| 92 | if base:
|
|---|
| 93 | segments.append(base)
|
|---|
| 94 | segments.reverse()
|
|---|
| 95 | return segments
|
|---|
| 96 |
|
|---|
| 97 | def child_packages_and_modules(
|
|---|
| 98 | packagename,
|
|---|
| 99 | relative=True,
|
|---|
| 100 | onlypackages=False,
|
|---|
| 101 | returndirs=False,
|
|---|
| 102 | depth=0):
|
|---|
| 103 | """Return a list of packages and modules under `packagename`.
|
|---|
| 104 |
|
|---|
| 105 | relative -- exclude the package name itself from the results
|
|---|
| 106 | onlypackages -- exclude modules (*.py) from the results
|
|---|
| 107 | returndirs -- return the path, not the module/package name
|
|---|
| 108 | depth -- if not 0, limit the result depth
|
|---|
| 109 | """
|
|---|
| 110 |
|
|---|
| 111 | # Find the package
|
|---|
| 112 | modstack = packagename.split('.')
|
|---|
| 113 | package = __import__(packagename, {}, {}, modstack[-1])
|
|---|
| 114 | packagedir, initfile = os.path.split(package.__file__)
|
|---|
| 115 | packagedir = os.path.abspath(packagedir)
|
|---|
| 116 | assert initfile in [ '__init__.py', '__init__.pyc' ]
|
|---|
| 117 | assert os.path.isdir(packagedir)
|
|---|
| 118 | packagedirstack = splitall(packagedir)
|
|---|
| 119 | lpds = len(packagedirstack)
|
|---|
| 120 | results = []
|
|---|
| 121 |
|
|---|
| 122 | # Define a helpful 'append' method so we don't have to duplicate
|
|---|
| 123 | # this logic.
|
|---|
| 124 | def append(segments):
|
|---|
| 125 | if depth and len(segments) > depth:
|
|---|
| 126 | return
|
|---|
| 127 | if returndirs:
|
|---|
| 128 | joiner = lambda l: os.path.join(*l)
|
|---|
| 129 | basesegments = packagedirstack
|
|---|
| 130 | else:
|
|---|
| 131 | joiner = '.'.join
|
|---|
| 132 | basesegments = modstack
|
|---|
| 133 | if relative and not returndirs:
|
|---|
| 134 | if segments:
|
|---|
| 135 | results.append(joiner(segments))
|
|---|
| 136 | else:
|
|---|
| 137 | results.append(joiner(basesegments + segments))
|
|---|
| 138 |
|
|---|
| 139 | # Walk the package directory, gathering results.
|
|---|
| 140 | for dirpath, dirnames, filenames in os.walk(packagedir):
|
|---|
| 141 | dirstack = splitall(dirpath)
|
|---|
| 142 | assert dirstack[:lpds] == packagedirstack
|
|---|
| 143 | relsegments = dirstack[lpds:]
|
|---|
| 144 | heremodstack = modstack + relsegments
|
|---|
| 145 | heredirstack = packagedirstack + relsegments
|
|---|
| 146 | if not ('__init__.py' in filenames or '__init__.pyc' in filenames):
|
|---|
| 147 | # wherever we are, it isn't a package and we shouldn't
|
|---|
| 148 | # recurse any deeper
|
|---|
| 149 | del dirnames[:]
|
|---|
| 150 | else:
|
|---|
| 151 | append(relsegments)
|
|---|
| 152 | if not onlypackages:
|
|---|
| 153 | for filename in filenames:
|
|---|
| 154 | name, ext = os.path.splitext(filename)
|
|---|
| 155 | if ext == '.py' and name != '__init__':
|
|---|
| 156 | append(relsegments + [name])
|
|---|
| 157 | return results
|
|---|
| 158 |
|
|---|
| 159 | def urlconf(modulename, lookfor=None, verify=False):
|
|---|
| 160 | """Find the urlconf for `modulename`, which should be the name of the
|
|---|
| 161 | site or one of its applications (``"sitename.appname"``).
|
|---|
| 162 |
|
|---|
| 163 | lookfor -- list of submodules to look for
|
|---|
| 164 | """
|
|---|
| 165 |
|
|---|
| 166 | if lookfor is None:
|
|---|
| 167 | segments = modulename.split('.')
|
|---|
| 168 | if 'apps' in segments:
|
|---|
| 169 | # sitename.apps.appname
|
|---|
| 170 | lookfor = ['urls.%s' % segments[-1], 'urls']
|
|---|
| 171 | else:
|
|---|
| 172 | # sitename
|
|---|
| 173 | lookfor = ['settings.urls.main', 'urls']
|
|---|
| 174 | submodules = child_packages_and_modules(modulename)
|
|---|
| 175 | fails = []
|
|---|
| 176 | for submodule in lookfor:
|
|---|
| 177 | modname = '%s.%s' % (modulename, submodule)
|
|---|
| 178 | if submodule in submodules:
|
|---|
| 179 | if verify:
|
|---|
| 180 | module = __import__(modname, {}, {}, 'urlconf_module')
|
|---|
| 181 | if hasattr(module, 'urlpatterns'):
|
|---|
| 182 | return modname
|
|---|
| 183 | else:
|
|---|
| 184 | raise AssertionError, "We thought %s was a urlconf " \
|
|---|
| 185 | "module, but couldn't find urlpatterns in it." % (
|
|---|
| 186 | modname)
|
|---|
| 187 | else:
|
|---|
| 188 | return modname
|
|---|
| 189 | else:
|
|---|
| 190 | fails.append(modname)
|
|---|
| 191 | raise AssertionError, "Couldn't find a url module amongst %s." % (
|
|---|
| 192 | ', '.join(fails))
|
|---|
| 193 | root_urlconf = urlconf
|
|---|
| 194 |
|
|---|
| 195 | def template_dirs(sitemodulename, lookfor=['templates'], incadmin=True):
|
|---|
| 196 | """Find all the template/ directories in `sitemodulename`.
|
|---|
| 197 |
|
|---|
| 198 | lookfor -- adjust what subdirectory names to look for
|
|---|
| 199 | incadmin -- if True (default), include the administration templates."""
|
|---|
| 200 |
|
|---|
| 201 | results = []
|
|---|
| 202 | for packagedir in child_packages_and_modules(sitemodulename,
|
|---|
| 203 | onlypackages=True, returndirs=True):
|
|---|
| 204 | for subdir in lookfor:
|
|---|
| 205 | templatedir = os.path.join(packagedir, subdir)
|
|---|
| 206 | if os.path.isdir(templatedir):
|
|---|
| 207 | results.append(templatedir)
|
|---|
| 208 | if incadmin:
|
|---|
| 209 | return results + template_dirs('django.conf',
|
|---|
| 210 | lookfor=['admin_templates'],
|
|---|
| 211 | incadmin=False)
|
|---|
| 212 | return results
|
|---|
| 213 |
|
|---|
| 214 | def installed_apps(sitemodulename):
|
|---|
| 215 | """Find all the apps in `sitemodulename`."""
|
|---|
| 216 | return child_packages_and_modules('%s.apps' % sitemodulename,
|
|---|
| 217 | onlypackages=True, depth=1, relative=False)[1:]
|
|---|
| 218 |
|
|---|
| 219 | def sitepatterns(basename, sitename, incadmin=True, *patlist):
|
|---|
| 220 | """Like django.conf.urls.defaults.patterns, but automatically includes
|
|---|
| 221 | the urlconfs for any applications it can find.
|
|---|
| 222 |
|
|---|
| 223 | basename -- hopefully, used only for patlist
|
|---|
| 224 | sitename -- the name of the site module to scan for apps and urlconfs
|
|---|
| 225 | incadmin -- include the administration site.
|
|---|
| 226 | """
|
|---|
| 227 |
|
|---|
| 228 | patlist = list(patlist)
|
|---|
| 229 | for appmodname in installed_apps(sitename):
|
|---|
| 230 | appname = appmodname.split('.')[-1]
|
|---|
| 231 | try:
|
|---|
| 232 | uc = urlconf(appmodname)
|
|---|
| 233 | pattern = (
|
|---|
| 234 | r'^%s/' % appname,
|
|---|
| 235 | defaults.include(urlconf(appmodname))
|
|---|
| 236 | )
|
|---|
| 237 | patlist.append(pattern)
|
|---|
| 238 | except AssertionError: # Couldn't find it!
|
|---|
| 239 | for urlpattern, target in _apppatterns(appmodname):
|
|---|
| 240 | if urlpattern[:1] == '^':
|
|---|
| 241 | urlpattern = urlpattern[1:]
|
|---|
| 242 | pattern = (
|
|---|
| 243 | r'^%s/%s' % (appname, urlpattern),
|
|---|
| 244 | target
|
|---|
| 245 | )
|
|---|
| 246 | patlist.append(pattern)
|
|---|
| 247 | if incadmin:
|
|---|
| 248 | pattern = (r'^admin/', defaults.include('django.conf.urls.admin'))
|
|---|
| 249 | patlist.append(pattern)
|
|---|
| 250 | import pprint
|
|---|
| 251 | pprint.pprint(patlist)
|
|---|
| 252 | return defaults.patterns(basename, *patlist)
|
|---|
| 253 |
|
|---|
| 254 | def _apppatterns(appmodname):
|
|---|
| 255 | """Does the heavy lifting for `apppatterns`."""
|
|---|
| 256 | patlist = []
|
|---|
| 257 | for modname in child_packages_and_modules(appmodname):
|
|---|
| 258 | if modname == 'views' or modname.startswith('views.'):
|
|---|
| 259 | fullmodname = '%s.%s' % (appmodname, modname)
|
|---|
| 260 | module = __import__(fullmodname, {}, {}, fullmodname)
|
|---|
| 261 | for attname in dir(module):
|
|---|
| 262 | att = getattr(module, attname)
|
|---|
| 263 | if callable(att):
|
|---|
| 264 | if hasattr(att, 'urlpattern'):
|
|---|
| 265 | pattern = (
|
|---|
| 266 | getattr(att, 'urlpattern'),
|
|---|
| 267 | '%s.%s' % (fullmodname, attname)
|
|---|
| 268 | )
|
|---|
| 269 | patlist.append(pattern)
|
|---|
| 270 | elif hasattr(att, 'urlpatterns'):
|
|---|
| 271 | for urlpattern in getattr(att, 'urlpatterns'):
|
|---|
| 272 | pattern = (
|
|---|
| 273 | urlpattern,
|
|---|
| 274 | '%s.%s' % (fullmodname, attname)
|
|---|
| 275 | )
|
|---|
| 276 | patlist.append(pattern)
|
|---|
| 277 | return patlist
|
|---|
| 278 |
|
|---|
| 279 | def apppatterns(basename, appmodname, *patlist):
|
|---|
| 280 | """Like django.conf.urls.defaults.patterns, but looks for views modules
|
|---|
| 281 | and scans them for view modules with `urlpattern` attributes.
|
|---|
| 282 |
|
|---|
| 283 | basename -- only used for patlist
|
|---|
| 284 | appmodname -- the name of the app in ``sitename.apps.appname`` format.
|
|---|
| 285 | """
|
|---|
| 286 |
|
|---|
| 287 | if basename:
|
|---|
| 288 | # Incorporate basename so we don't have to pass it down.
|
|---|
| 289 | patlist = [(urlpattern, '%s.%s' % (basename, target))
|
|---|
| 290 | for urlpattern, target in patlist[:]]
|
|---|
| 291 | else:
|
|---|
| 292 | # Just make a list of it.
|
|---|
| 293 | patlist = list(patlist)
|
|---|
| 294 | patlist.extend(_apppatterns(appmodname))
|
|---|
| 295 | return defaults.patterns('', *patlist)
|
|---|
| 296 |
|
|---|
| 297 | def urlpattern(urlp):
|
|---|
| 298 | """Decorate a view function with a URL pattern to be picked up by
|
|---|
| 299 | the automatic stuff above."""
|
|---|
| 300 | def decorator(func):
|
|---|
| 301 | if hasattr(func, 'urlpatterns'):
|
|---|
| 302 | func.urlpatterns.append(urlp)
|
|---|
| 303 | else:
|
|---|
| 304 | func.urlpatterns = [urlp]
|
|---|
| 305 | return func
|
|---|
| 306 | return decorator
|
|---|