web/lib/django/contrib/admindocs/views.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     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
       
    16 
       
    17 # Exclude methods starting with these strings from documentation
       
    18 MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
       
    19 
       
    20 class GenericSite(object):
       
    21     domain = 'example.com'
       
    22     name = 'my site'
       
    23 
       
    24 def get_root_path():
       
    25     try:
       
    26         return urlresolvers.reverse('admin:index')
       
    27     except urlresolvers.NoReverseMatch:
       
    28         from django.contrib import admin
       
    29         try:
       
    30             return urlresolvers.reverse(admin.site.root, args=[''])
       
    31         except urlresolvers.NoReverseMatch:
       
    32             return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
       
    33 
       
    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)
       
    41 
       
    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)
       
    49 
       
    50 def template_tag_index(request):
       
    51     if not utils.docutils_is_available:
       
    52         return missing_docutils_page(request)
       
    53 
       
    54     load_all_installed_template_libraries()
       
    55 
       
    56     tags = []
       
    57     for module_name, library in template.libraries.items():
       
    58         for tag_name, tag_func in library.tags.items():
       
    59             title, body, metadata = utils.parse_docstring(tag_func.__doc__)
       
    60             if title:
       
    61                 title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
       
    62             if body:
       
    63                 body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
       
    64             for key in metadata:
       
    65                 metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
       
    66             if library in template.builtins:
       
    67                 tag_library = None
       
    68             else:
       
    69                 tag_library = module_name.split('.')[-1]
       
    70             tags.append({
       
    71                 'name': tag_name,
       
    72                 'title': title,
       
    73                 'body': body,
       
    74                 'meta': metadata,
       
    75                 'library': tag_library,
       
    76             })
       
    77     return render_to_response('admin_doc/template_tag_index.html', {
       
    78         'root_path': get_root_path(),
       
    79         'tags': tags
       
    80     }, context_instance=RequestContext(request))
       
    81 template_tag_index = staff_member_required(template_tag_index)
       
    82 
       
    83 def template_filter_index(request):
       
    84     if not utils.docutils_is_available:
       
    85         return missing_docutils_page(request)
       
    86 
       
    87     load_all_installed_template_libraries()
       
    88 
       
    89     filters = []
       
    90     for module_name, library in template.libraries.items():
       
    91         for filter_name, filter_func in library.filters.items():
       
    92             title, body, metadata = utils.parse_docstring(filter_func.__doc__)
       
    93             if title:
       
    94                 title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
       
    95             if body:
       
    96                 body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
       
    97             for key in metadata:
       
    98                 metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
       
    99             if library in template.builtins:
       
   100                 tag_library = None
       
   101             else:
       
   102                 tag_library = module_name.split('.')[-1]
       
   103             filters.append({
       
   104                 'name': filter_name,
       
   105                 'title': title,
       
   106                 'body': body,
       
   107                 'meta': metadata,
       
   108                 'library': tag_library,
       
   109             })
       
   110     return render_to_response('admin_doc/template_filter_index.html', {
       
   111         'root_path': get_root_path(),
       
   112         'filters': filters
       
   113     }, context_instance=RequestContext(request))
       
   114 template_filter_index = staff_member_required(template_filter_index)
       
   115 
       
   116 def view_index(request):
       
   117     if not utils.docutils_is_available:
       
   118         return missing_docutils_page(request)
       
   119 
       
   120     if settings.ADMIN_FOR:
       
   121         settings_modules = [import_module(m) for m in settings.ADMIN_FOR]
       
   122     else:
       
   123         settings_modules = [settings]
       
   124 
       
   125     views = []
       
   126     for settings_mod in settings_modules:
       
   127         urlconf = import_module(settings_mod.ROOT_URLCONF)
       
   128         view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
       
   129         if Site._meta.installed:
       
   130             site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
       
   131         else:
       
   132             site_obj = GenericSite()
       
   133         for (func, regex) in view_functions:
       
   134             views.append({
       
   135                 'name': getattr(func, '__name__', func.__class__.__name__),
       
   136                 'module': func.__module__,
       
   137                 'site_id': settings_mod.SITE_ID,
       
   138                 'site': site_obj,
       
   139                 'url': simplify_regex(regex),
       
   140             })
       
   141     return render_to_response('admin_doc/view_index.html', {
       
   142         'root_path': get_root_path(),
       
   143         'views': views
       
   144     }, context_instance=RequestContext(request))
       
   145 view_index = staff_member_required(view_index)
       
   146 
       
   147 def view_detail(request, view):
       
   148     if not utils.docutils_is_available:
       
   149         return missing_docutils_page(request)
       
   150 
       
   151     mod, func = urlresolvers.get_mod_func(view)
       
   152     try:
       
   153         view_func = getattr(import_module(mod), func)
       
   154     except (ImportError, AttributeError):
       
   155         raise Http404
       
   156     title, body, metadata = utils.parse_docstring(view_func.__doc__)
       
   157     if title:
       
   158         title = utils.parse_rst(title, 'view', _('view:') + view)
       
   159     if body:
       
   160         body = utils.parse_rst(body, 'view', _('view:') + view)
       
   161     for key in metadata:
       
   162         metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view)
       
   163     return render_to_response('admin_doc/view_detail.html', {
       
   164         'root_path': get_root_path(),
       
   165         'name': view,
       
   166         'summary': title,
       
   167         'body': body,
       
   168         'meta': metadata,
       
   169     }, context_instance=RequestContext(request))
       
   170 view_detail = staff_member_required(view_detail)
       
   171 
       
   172 def model_index(request):
       
   173     if not utils.docutils_is_available:
       
   174         return missing_docutils_page(request)
       
   175     m_list = [m._meta for m in models.get_models()]
       
   176     return render_to_response('admin_doc/model_index.html', {
       
   177         'root_path': get_root_path(),
       
   178         'models': m_list
       
   179     }, context_instance=RequestContext(request))
       
   180 model_index = staff_member_required(model_index)
       
   181 
       
   182 def model_detail(request, app_label, model_name):
       
   183     if not utils.docutils_is_available:
       
   184         return missing_docutils_page(request)
       
   185 
       
   186     # Get the model class.
       
   187     try:
       
   188         app_mod = models.get_app(app_label)
       
   189     except ImproperlyConfigured:
       
   190         raise Http404, _("App %r not found") % app_label
       
   191     model = None
       
   192     for m in models.get_models(app_mod):
       
   193         if m._meta.object_name.lower() == model_name:
       
   194             model = m
       
   195             break
       
   196     if model is None:
       
   197         raise Http404, _("Model %(model_name)r not found in app %(app_label)r") % {'model_name': model_name, 'app_label': app_label}
       
   198 
       
   199     opts = model._meta
       
   200 
       
   201     # Gather fields/field descriptions.
       
   202     fields = []
       
   203     for field in opts.fields:
       
   204         # ForeignKey is a special case since the field will actually be a
       
   205         # descriptor that returns the other object
       
   206         if isinstance(field, models.ForeignKey):
       
   207             data_type = related_object_name = field.rel.to.__name__
       
   208             app_label = field.rel.to._meta.app_label
       
   209             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)
       
   210         else:
       
   211             data_type = get_readable_field_data_type(field)
       
   212             verbose = field.verbose_name
       
   213         fields.append({
       
   214             'name': field.name,
       
   215             'data_type': data_type,
       
   216             'verbose': verbose,
       
   217             'help_text': field.help_text,
       
   218         })
       
   219 
       
   220     # Gather many-to-many fields.
       
   221     for field in opts.many_to_many:
       
   222         data_type = related_object_name = field.rel.to.__name__
       
   223         app_label = field.rel.to._meta.app_label
       
   224         verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': app_label, 'object_name': data_type}
       
   225         fields.append({
       
   226             'name': "%s.all" % field.name,
       
   227             "data_type": 'List',
       
   228             'verbose': utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name),
       
   229         })
       
   230         fields.append({
       
   231             'name'      : "%s.count" % field.name,
       
   232             'data_type' : 'Integer',
       
   233             'verbose'   : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name),
       
   234         })
       
   235 
       
   236     # Gather model methods.
       
   237     for func_name, func in model.__dict__.items():
       
   238         if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1):
       
   239             try:
       
   240                 for exclude in MODEL_METHODS_EXCLUDE:
       
   241                     if func_name.startswith(exclude):
       
   242                         raise StopIteration
       
   243             except StopIteration:
       
   244                 continue
       
   245             verbose = func.__doc__
       
   246             if verbose:
       
   247                 verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.module_name)
       
   248             fields.append({
       
   249                 'name': func_name,
       
   250                 'data_type': get_return_data_type(func_name),
       
   251                 'verbose': verbose,
       
   252             })
       
   253 
       
   254     # Gather related objects
       
   255     for rel in opts.get_all_related_objects() + opts.get_all_related_many_to_many_objects():
       
   256         verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel.opts.app_label, 'object_name': rel.opts.object_name}
       
   257         accessor = rel.get_accessor_name()
       
   258         fields.append({
       
   259             'name'      : "%s.all" % accessor,
       
   260             'data_type' : 'List',
       
   261             'verbose'   : utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name),
       
   262         })
       
   263         fields.append({
       
   264             'name'      : "%s.count" % accessor,
       
   265             'data_type' : 'Integer',
       
   266             'verbose'   : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name),
       
   267         })
       
   268     return render_to_response('admin_doc/model_detail.html', {
       
   269         'root_path': get_root_path(),
       
   270         'name': '%s.%s' % (opts.app_label, opts.object_name),
       
   271         'summary': _("Fields on %s objects") % opts.object_name,
       
   272         'description': model.__doc__,
       
   273         'fields': fields,
       
   274     }, context_instance=RequestContext(request))
       
   275 model_detail = staff_member_required(model_detail)
       
   276 
       
   277 def template_detail(request, template):
       
   278     templates = []
       
   279     for site_settings_module in settings.ADMIN_FOR:
       
   280         settings_mod = import_module(site_settings_module)
       
   281         if Site._meta.installed:
       
   282             site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
       
   283         else:
       
   284             site_obj = GenericSite()
       
   285         for dir in settings_mod.TEMPLATE_DIRS:
       
   286             template_file = os.path.join(dir, template)
       
   287             templates.append({
       
   288                 'file': template_file,
       
   289                 'exists': os.path.exists(template_file),
       
   290                 'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '',
       
   291                 'site_id': settings_mod.SITE_ID,
       
   292                 'site': site_obj,
       
   293                 'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
       
   294             })
       
   295     return render_to_response('admin_doc/template_detail.html', {
       
   296         'root_path': get_root_path(),
       
   297         'name': template,
       
   298         'templates': templates,
       
   299     }, context_instance=RequestContext(request))
       
   300 template_detail = staff_member_required(template_detail)
       
   301 
       
   302 ####################
       
   303 # Helper functions #
       
   304 ####################
       
   305 
       
   306 def missing_docutils_page(request):
       
   307     """Display an error message for people without docutils"""
       
   308     return render_to_response('admin_doc/missing_docutils.html')
       
   309 
       
   310 def load_all_installed_template_libraries():
       
   311     # Load/register all template tag libraries from installed apps.
       
   312     for e in templatetags.__path__:
       
   313         libraries = [os.path.splitext(p)[0] for p in os.listdir(e) if p.endswith('.py') and p[0].isalpha()]
       
   314         for library_name in libraries:
       
   315             try:
       
   316                 lib = template.get_library("django.templatetags.%s" % library_name.split('.')[-1])
       
   317             except template.InvalidTemplateLibrary:
       
   318                 pass
       
   319 
       
   320 def get_return_data_type(func_name):
       
   321     """Return a somewhat-helpful data type given a function name"""
       
   322     if func_name.startswith('get_'):
       
   323         if func_name.endswith('_list'):
       
   324             return 'List'
       
   325         elif func_name.endswith('_count'):
       
   326             return 'Integer'
       
   327     return ''
       
   328 
       
   329 # Maps Field objects to their human-readable data types, as strings.
       
   330 # Column-type strings can contain format strings; they'll be interpolated
       
   331 # against the values of Field.__dict__ before being output.
       
   332 # If a column type is set to None, it won't be included in the output.
       
   333 DATA_TYPE_MAPPING = {
       
   334     'AutoField'                 : _('Integer'),
       
   335     'BooleanField'              : _('Boolean (Either True or False)'),
       
   336     'CharField'                 : _('String (up to %(max_length)s)'),
       
   337     'CommaSeparatedIntegerField': _('Comma-separated integers'),
       
   338     'DateField'                 : _('Date (without time)'),
       
   339     'DateTimeField'             : _('Date (with time)'),
       
   340     'DecimalField'              : _('Decimal number'),
       
   341     'EmailField'                : _('E-mail address'),
       
   342     'FileField'                 : _('File path'),
       
   343     'FilePathField'             : _('File path'),
       
   344     'FloatField'                : _('Floating point number'),
       
   345     'ForeignKey'                : _('Integer'),
       
   346     'ImageField'                : _('File path'),
       
   347     'IntegerField'              : _('Integer'),
       
   348     'IPAddressField'            : _('IP address'),
       
   349     'ManyToManyField'           : '',
       
   350     'NullBooleanField'          : _('Boolean (Either True, False or None)'),
       
   351     'OneToOneField'             : _('Relation to parent model'),
       
   352     'PhoneNumberField'          : _('Phone number'),
       
   353     'PositiveIntegerField'      : _('Integer'),
       
   354     'PositiveSmallIntegerField' : _('Integer'),
       
   355     'SlugField'                 : _('String (up to %(max_length)s)'),
       
   356     'SmallIntegerField'         : _('Integer'),
       
   357     'TextField'                 : _('Text'),
       
   358     'TimeField'                 : _('Time'),
       
   359     'URLField'                  : _('URL'),
       
   360     'USStateField'              : _('U.S. state (two uppercase letters)'),
       
   361     'XMLField'                  : _('XML text'),
       
   362 }
       
   363 
       
   364 def get_readable_field_data_type(field):
       
   365     return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
       
   366 
       
   367 def extract_views_from_urlpatterns(urlpatterns, base=''):
       
   368     """
       
   369     Return a list of views from a list of urlpatterns.
       
   370 
       
   371     Each object in the returned list is a two-tuple: (view_func, regex)
       
   372     """
       
   373     views = []
       
   374     for p in urlpatterns:
       
   375         if hasattr(p, '_get_callback'):
       
   376             try:
       
   377                 views.append((p._get_callback(), base + p.regex.pattern))
       
   378             except ViewDoesNotExist:
       
   379                 continue
       
   380         elif hasattr(p, '_get_url_patterns'):
       
   381             try:
       
   382                 patterns = p.url_patterns
       
   383             except ImportError:
       
   384                 continue
       
   385             views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern))
       
   386         else:
       
   387             raise TypeError, _("%s does not appear to be a urlpattern object") % p
       
   388     return views
       
   389 
       
   390 named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)')
       
   391 non_named_group_matcher = re.compile(r'\(.*?\)')
       
   392 
       
   393 def simplify_regex(pattern):
       
   394     """
       
   395     Clean up urlpattern regexes into something somewhat readable by Mere Humans:
       
   396     turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
       
   397     into "<sport_slug>/athletes/<athlete_slug>/"
       
   398     """
       
   399     # handle named groups first
       
   400     pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)
       
   401 
       
   402     # handle non-named groups
       
   403     pattern = non_named_group_matcher.sub("<var>", pattern)
       
   404 
       
   405     # clean up any outstanding regex-y characters.
       
   406     pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
       
   407     if not pattern.startswith('/'):
       
   408         pattern = '/' + pattern
       
   409     return pattern