1 from django
import template
, templatetags
2 from django
.template
import RequestContext
3 from django
.conf
import settings
4 from django
.contrib
.admin
.views
.decorators
import staff_member_required
5 from django
.db
import models
6 from django
.shortcuts
import render_to_response
7 from django
.core
.exceptions
import ImproperlyConfigured
, ViewDoesNotExist
8 from django
.http
import Http404
9 from django
.core
import urlresolvers
10 from django
.contrib
.admindocs
import utils
11 from django
.contrib
.sites
.models
import Site
12 from django
.utils
.importlib
import import_module
13 from django
.utils
.translation
import ugettext
as _
14 from django
.utils
.safestring
import mark_safe
15 import inspect
, os
, re
17 # Exclude methods starting with these strings from documentation
18 MODEL_METHODS_EXCLUDE
= ('_', 'add_', 'delete', 'save', 'set_')
20 class GenericSite(object):
21 domain
= 'example.com'
26 return urlresolvers
.reverse('admin:index')
27 except urlresolvers
.NoReverseMatch
:
28 from django
.contrib
import admin
30 return urlresolvers
.reverse(admin
.site
.root
, args
=[''])
31 except urlresolvers
.NoReverseMatch
:
32 return getattr(settings
, "ADMIN_SITE_ROOT_URL", "/admin/")
34 def doc_index(request
):
35 if not utils
.docutils_is_available
:
36 return missing_docutils_page(request
)
37 return render_to_response('admin_doc/index.html', {
38 'root_path': get_root_path(),
39 }, context_instance
=RequestContext(request
))
40 doc_index
= staff_member_required(doc_index
)
42 def bookmarklets(request
):
43 admin_root
= get_root_path()
44 return render_to_response('admin_doc/bookmarklets.html', {
45 'root_path': admin_root
,
46 'admin_url': mark_safe("%s://%s%s" % (request
.is_secure() and 'https' or 'http', request
.get_host(), admin_root
)),
47 }, context_instance
=RequestContext(request
))
48 bookmarklets
= staff_member_required(bookmarklets
)
50 def template_tag_index(request
):
51 if not utils
.docutils_is_available
:
52 return missing_docutils_page(request
)
54 load_all_installed_template_libraries()
57 app_libs
= template
.libraries
.items()
58 builtin_libs
= [(None, lib
) for lib
in template
.builtins
]
59 for module_name
, library
in builtin_libs
+ app_libs
:
60 for tag_name
, tag_func
in library
.tags
.items():
61 title
, body
, metadata
= utils
.parse_docstring(tag_func
.__doc
__)
63 title
= utils
.parse_rst(title
, 'tag', _('tag:') + tag_name
)
65 body
= utils
.parse_rst(body
, 'tag', _('tag:') + tag_name
)
67 metadata
[key
] = utils
.parse_rst(metadata
[key
], 'tag', _('tag:') + tag_name
)
68 if library
in template
.builtins
:
71 tag_library
= module_name
.split('.')[-1]
77 'library': tag_library
,
79 return render_to_response('admin_doc/template_tag_index.html', {
80 'root_path': get_root_path(),
82 }, context_instance
=RequestContext(request
))
83 template_tag_index
= staff_member_required(template_tag_index
)
85 def template_filter_index(request
):
86 if not utils
.docutils_is_available
:
87 return missing_docutils_page(request
)
89 load_all_installed_template_libraries()
92 app_libs
= template
.libraries
.items()
93 builtin_libs
= [(None, lib
) for lib
in template
.builtins
]
94 for module_name
, library
in builtin_libs
+ app_libs
:
95 for filter_name
, filter_func
in library
.filters
.items():
96 title
, body
, metadata
= utils
.parse_docstring(filter_func
.__doc
__)
98 title
= utils
.parse_rst(title
, 'filter', _('filter:') + filter_name
)
100 body
= utils
.parse_rst(body
, 'filter', _('filter:') + filter_name
)
102 metadata
[key
] = utils
.parse_rst(metadata
[key
], 'filter', _('filter:') + filter_name
)
103 if library
in template
.builtins
:
106 tag_library
= module_name
.split('.')[-1]
112 'library': tag_library
,
114 return render_to_response('admin_doc/template_filter_index.html', {
115 'root_path': get_root_path(),
117 }, context_instance
=RequestContext(request
))
118 template_filter_index
= staff_member_required(template_filter_index
)
120 def view_index(request
):
121 if not utils
.docutils_is_available
:
122 return missing_docutils_page(request
)
124 if settings
.ADMIN_FOR
:
125 settings_modules
= [import_module(m
) for m
in settings
.ADMIN_FOR
]
127 settings_modules
= [settings
]
130 for settings_mod
in settings_modules
:
131 urlconf
= import_module(settings_mod
.ROOT_URLCONF
)
132 view_functions
= extract_views_from_urlpatterns(urlconf
.urlpatterns
)
133 if Site
._meta
.installed
:
134 site_obj
= Site
.objects
.get(pk
=settings_mod
.SITE_ID
)
136 site_obj
= GenericSite()
137 for (func
, regex
) in view_functions
:
139 'name': getattr(func
, '__name__', func
.__class
__.__name
__),
140 'module': func
.__module
__,
141 'site_id': settings_mod
.SITE_ID
,
143 'url': simplify_regex(regex
),
145 return render_to_response('admin_doc/view_index.html', {
146 'root_path': get_root_path(),
148 }, context_instance
=RequestContext(request
))
149 view_index
= staff_member_required(view_index
)
151 def view_detail(request
, view
):
152 if not utils
.docutils_is_available
:
153 return missing_docutils_page(request
)
155 mod
, func
= urlresolvers
.get_mod_func(view
)
157 view_func
= getattr(import_module(mod
), func
)
158 except (ImportError, AttributeError):
160 title
, body
, metadata
= utils
.parse_docstring(view_func
.__doc
__)
162 title
= utils
.parse_rst(title
, 'view', _('view:') + view
)
164 body
= utils
.parse_rst(body
, 'view', _('view:') + view
)
166 metadata
[key
] = utils
.parse_rst(metadata
[key
], 'model', _('view:') + view
)
167 return render_to_response('admin_doc/view_detail.html', {
168 'root_path': get_root_path(),
173 }, context_instance
=RequestContext(request
))
174 view_detail
= staff_member_required(view_detail
)
176 def model_index(request
):
177 if not utils
.docutils_is_available
:
178 return missing_docutils_page(request
)
179 m_list
= [m
._meta
for m
in models
.get_models()]
180 return render_to_response('admin_doc/model_index.html', {
181 'root_path': get_root_path(),
183 }, context_instance
=RequestContext(request
))
184 model_index
= staff_member_required(model_index
)
186 def model_detail(request
, app_label
, model_name
):
187 if not utils
.docutils_is_available
:
188 return missing_docutils_page(request
)
190 # Get the model class.
192 app_mod
= models
.get_app(app_label
)
193 except ImproperlyConfigured
:
194 raise Http404(_("App %r not found") % app_label
)
196 for m
in models
.get_models(app_mod
):
197 if m
._meta
.object_name
.lower() == model_name
:
201 raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % {'model_name': model_name
, 'app_label': app_label
})
205 # Gather fields/field descriptions.
207 for field
in opts
.fields
:
208 # ForeignKey is a special case since the field will actually be a
209 # descriptor that returns the other object
210 if isinstance(field
, models
.ForeignKey
):
211 data_type
= related_object_name
= field
.rel
.to
.__name
__
212 app_label
= field
.rel
.to
._meta
.app_label
213 verbose
= utils
.parse_rst((_("the related `%(app_label)s.%(data_type)s` object") % {'app_label': app_label
, 'data_type': data_type
}), 'model', _('model:') + data_type
)
215 data_type
= get_readable_field_data_type(field
)
216 verbose
= field
.verbose_name
219 'data_type': data_type
,
221 'help_text': field
.help_text
,
224 # Gather many-to-many fields.
225 for field
in opts
.many_to_many
:
226 data_type
= related_object_name
= field
.rel
.to
.__name
__
227 app_label
= field
.rel
.to
._meta
.app_label
228 verbose
= _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': app_label
, 'object_name': data_type
}
230 'name': "%s.all" % field
.name
,
232 'verbose': utils
.parse_rst(_("all %s") % verbose
, 'model', _('model:') + opts
.module_name
),
235 'name' : "%s.count" % field
.name
,
236 'data_type' : 'Integer',
237 'verbose' : utils
.parse_rst(_("number of %s") % verbose
, 'model', _('model:') + opts
.module_name
),
240 # Gather model methods.
241 for func_name
, func
in model
.__dict
__.items():
242 if (inspect
.isfunction(func
) and len(inspect
.getargspec(func
)[0]) == 1):
244 for exclude
in MODEL_METHODS_EXCLUDE
:
245 if func_name
.startswith(exclude
):
247 except StopIteration:
249 verbose
= func
.__doc
__
251 verbose
= utils
.parse_rst(utils
.trim_docstring(verbose
), 'model', _('model:') + opts
.module_name
)
254 'data_type': get_return_data_type(func_name
),
258 # Gather related objects
259 for rel
in opts
.get_all_related_objects() + opts
.get_all_related_many_to_many_objects():
260 verbose
= _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel
.opts
.app_label
, 'object_name': rel
.opts
.object_name
}
261 accessor
= rel
.get_accessor_name()
263 'name' : "%s.all" % accessor
,
264 'data_type' : 'List',
265 'verbose' : utils
.parse_rst(_("all %s") % verbose
, 'model', _('model:') + opts
.module_name
),
268 'name' : "%s.count" % accessor
,
269 'data_type' : 'Integer',
270 'verbose' : utils
.parse_rst(_("number of %s") % verbose
, 'model', _('model:') + opts
.module_name
),
272 return render_to_response('admin_doc/model_detail.html', {
273 'root_path': get_root_path(),
274 'name': '%s.%s' % (opts
.app_label
, opts
.object_name
),
275 'summary': _("Fields on %s objects") % opts
.object_name
,
276 'description': model
.__doc
__,
278 }, context_instance
=RequestContext(request
))
279 model_detail
= staff_member_required(model_detail
)
281 def template_detail(request
, template
):
283 for site_settings_module
in settings
.ADMIN_FOR
:
284 settings_mod
= import_module(site_settings_module
)
285 if Site
._meta
.installed
:
286 site_obj
= Site
.objects
.get(pk
=settings_mod
.SITE_ID
)
288 site_obj
= GenericSite()
289 for dir in settings_mod
.TEMPLATE_DIRS
:
290 template_file
= os
.path
.join(dir, template
)
292 'file': template_file
,
293 'exists': os
.path
.exists(template_file
),
294 'contents': lambda: os
.path
.exists(template_file
) and open(template_file
).read() or '',
295 'site_id': settings_mod
.SITE_ID
,
297 'order': list(settings_mod
.TEMPLATE_DIRS
).index(dir),
299 return render_to_response('admin_doc/template_detail.html', {
300 'root_path': get_root_path(),
302 'templates': templates
,
303 }, context_instance
=RequestContext(request
))
304 template_detail
= staff_member_required(template_detail
)
310 def missing_docutils_page(request
):
311 """Display an error message for people without docutils"""
312 return render_to_response('admin_doc/missing_docutils.html')
314 def load_all_installed_template_libraries():
315 # Load/register all template tag libraries from installed apps.
316 for module_name
in template
.get_templatetags_modules():
317 mod
= import_module(module_name
)
319 os
.path
.splitext(p
)[0]
320 for p
in os
.listdir(os
.path
.dirname(mod
.__file
__))
321 if p
.endswith('.py') and p
[0].isalpha()
323 for library_name
in libraries
:
325 lib
= template
.get_library(library_name
)
326 except template
.InvalidTemplateLibrary
, e
:
329 def get_return_data_type(func_name
):
330 """Return a somewhat-helpful data type given a function name"""
331 if func_name
.startswith('get_'):
332 if func_name
.endswith('_list'):
334 elif func_name
.endswith('_count'):
338 def get_readable_field_data_type(field
):
339 """Returns the description for a given field type, if it exists,
340 Fields' descriptions can contain format strings, which will be interpolated
341 against the values of field.__dict__ before being output."""
343 return field
.description
% field
.__dict
__
345 def extract_views_from_urlpatterns(urlpatterns
, base
=''):
347 Return a list of views from a list of urlpatterns.
349 Each object in the returned list is a two-tuple: (view_func, regex)
352 for p
in urlpatterns
:
353 if hasattr(p
, '_get_callback'):
355 views
.append((p
._get
_callback
(), base
+ p
.regex
.pattern
))
356 except ViewDoesNotExist
:
358 elif hasattr(p
, '_get_url_patterns'):
360 patterns
= p
.url_patterns
363 views
.extend(extract_views_from_urlpatterns(patterns
, base
+ p
.regex
.pattern
))
365 raise TypeError(_("%s does not appear to be a urlpattern object") % p
)
368 named_group_matcher
= re
.compile(r
'\(\?P(<\w+>).+?\)')
369 non_named_group_matcher
= re
.compile(r
'\(.*?\)')
371 def simplify_regex(pattern
):
373 Clean up urlpattern regexes into something somewhat readable by Mere Humans:
374 turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
375 into "<sport_slug>/athletes/<athlete_slug>/"
377 # handle named groups first
378 pattern
= named_group_matcher
.sub(lambda m
: m
.group(1), pattern
)
380 # handle non-named groups
381 pattern
= non_named_group_matcher
.sub("<var>", pattern
)
383 # clean up any outstanding regex-y characters.
384 pattern
= pattern
.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
385 if not pattern
.startswith('/'):
386 pattern
= '/' + pattern