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