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
|
---|