Loading django/contrib/admindocs/templates/admin_doc/model_detail.html +25 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ {{ description }} <h3>{% trans 'Fields' %}</h3> <div class="module"> <table class="model"> <thead> Loading @@ -48,6 +49,30 @@ </table> </div> {% if methods %} <h3>{% trans 'Methods with arguments' %}</h3> <div class="module"> <table class="model"> <thead> <tr> <th>{% trans 'Method' %}</th> <th>{% trans 'Arguments' %}</th> <th>{% trans 'Description' %}</th> </tr> </thead> <tbody> {% for method in methods|dictsort:"name" %} <tr> <td>{{ method.name }}</td> <td>{{ method.arguments }}</td> <td>{{ method.verbose }}</td> </tr> {% endfor %} </tbody> </table> </div> {% endif %} <p class="small"><a href="{% url 'django-admindocs-models-index' %}">‹ {% trans 'Back to Model Documentation' %}</a></p> </div> {% endblock %} django/contrib/admindocs/views.py +31 −8 Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ from django.db import models from django.http import Http404 from django.template.engine import Engine from django.utils.decorators import method_decorator from django.utils.inspect import func_has_no_args from django.utils.inspect import ( func_accepts_kwargs, func_accepts_var_args, func_has_no_args, get_func_full_args, ) from django.utils.translation import ugettext as _ from django.views.generic import TemplateView Loading Loading @@ -219,7 +222,7 @@ class ModelDetailView(BaseAdminDocsView): fields.append({ 'name': field.name, 'data_type': data_type, 'verbose': verbose, 'verbose': verbose or '', 'help_text': field.help_text, }) Loading @@ -242,9 +245,10 @@ class ModelDetailView(BaseAdminDocsView): 'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name), }) methods = [] # Gather model methods. for func_name, func in model.__dict__.items(): if inspect.isfunction(func) and func_has_no_args(func): if inspect.isfunction(func): try: for exclude in MODEL_METHODS_EXCLUDE: if func_name.startswith(exclude): Loading @@ -254,10 +258,28 @@ class ModelDetailView(BaseAdminDocsView): verbose = func.__doc__ if verbose: verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.model_name) # If a method has no arguments, show it as a 'field', otherwise # as a 'method with arguments'. if func_has_no_args(func) and not func_accepts_kwargs(func) and not func_accepts_var_args(func): fields.append({ 'name': func_name, 'data_type': get_return_data_type(func_name), 'verbose': verbose, 'verbose': verbose or '', }) else: arguments = get_func_full_args(func) print_arguments = arguments # Join arguments with ', ' and in case of default value, # join it with '='. Use repr() so that strings will be # correctly displayed. print_arguments = ', '.join([ '='.join(list(arg_el[:1]) + [repr(el) for el in arg_el[1:]]) for arg_el in arguments ]) methods.append({ 'name': func_name, 'arguments': print_arguments, 'verbose': verbose or '', }) # Gather related objects Loading @@ -282,6 +304,7 @@ class ModelDetailView(BaseAdminDocsView): 'summary': title, 'description': body, 'fields': fields, 'methods': methods, }) return super(ModelDetailView, self).get_context_data(**kwargs) Loading django/utils/inspect.py +52 −1 Original line number Diff line number Diff line Loading @@ -43,6 +43,44 @@ def get_func_args(func): ] def get_func_full_args(func): """ Return a list of (argument name, default value) tuples. If the argument does not have a default value, omit it in the tuple. Arguments such as *args and **kwargs are also included. """ if six.PY2: argspec = inspect.getargspec(func) args = argspec.args[1:] # ignore 'self' defaults = argspec.defaults or [] # Split args into two lists depending on whether they have default value no_default = args[:len(args) - len(defaults)] with_default = args[len(args) - len(defaults):] # Join the two lists and combine it with default values args = [(arg,) for arg in no_default] + zip(with_default, defaults) # Add possible *args and **kwargs and prepend them with '*' or '**' varargs = [('*' + argspec.varargs,)] if argspec.varargs else [] kwargs = [('**' + argspec.keywords,)] if argspec.keywords else [] return args + varargs + kwargs sig = inspect.signature(func) args = [] for arg_name, param in sig.parameters.items(): name = arg_name # Ignore 'self' if name == 'self': continue if param.kind == inspect.Parameter.VAR_POSITIONAL: name = '*' + name elif param.kind == inspect.Parameter.VAR_KEYWORD: name = '**' + name if param.default != inspect.Parameter.empty: args.append((name, param.default)) else: args.append((name,)) return args def func_accepts_kwargs(func): if six.PY2: # Not all callables are inspectable with getargspec, so we'll Loading @@ -64,10 +102,23 @@ def func_accepts_kwargs(func): ) def func_accepts_var_args(func): """ Return True if function 'func' accepts positional arguments *args. """ if six.PY2: return inspect.getargspec(func)[1] is not None return any( p for p in inspect.signature(func).parameters.values() if p.kind == p.VAR_POSITIONAL ) def func_has_no_args(func): args = inspect.getargspec(func)[0] if six.PY2 else [ p for p in inspect.signature(func).parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD and p.default is p.empty if p.kind == p.POSITIONAL_OR_KEYWORD ] return len(args) == 1 Loading docs/ref/contrib/admin/admindocs.txt +9 −4 Original line number Diff line number Diff line Loading @@ -60,10 +60,15 @@ Model reference =============== The **models** section of the ``admindocs`` page describes each model in the system along with all the fields and methods (without any arguments) available on it. While model properties don't have any arguments, they are not listed. Relationships to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` attributes on fields or from docstrings on model methods. system along with all the fields and methods available on it. Relationships to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` attributes on fields or from docstrings on model methods. .. versionchanged:: 1.9 The **models** section of the ``admindocs`` now describes methods that take arguments as well. In previous versions it was restricted to methods without arguments. A model with useful documentation might look like this:: Loading docs/releases/1.9.txt +6 −0 Original line number Diff line number Diff line Loading @@ -163,6 +163,12 @@ Minor features * JavaScript slug generation now supports Romanian characters. :mod:`django.contrib.admindocs` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The model section of the ``admindocs`` now also describes methods that take arguments, rather than ignoring them. :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading Loading
django/contrib/admindocs/templates/admin_doc/model_detail.html +25 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ {{ description }} <h3>{% trans 'Fields' %}</h3> <div class="module"> <table class="model"> <thead> Loading @@ -48,6 +49,30 @@ </table> </div> {% if methods %} <h3>{% trans 'Methods with arguments' %}</h3> <div class="module"> <table class="model"> <thead> <tr> <th>{% trans 'Method' %}</th> <th>{% trans 'Arguments' %}</th> <th>{% trans 'Description' %}</th> </tr> </thead> <tbody> {% for method in methods|dictsort:"name" %} <tr> <td>{{ method.name }}</td> <td>{{ method.arguments }}</td> <td>{{ method.verbose }}</td> </tr> {% endfor %} </tbody> </table> </div> {% endif %} <p class="small"><a href="{% url 'django-admindocs-models-index' %}">‹ {% trans 'Back to Model Documentation' %}</a></p> </div> {% endblock %}
django/contrib/admindocs/views.py +31 −8 Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ from django.db import models from django.http import Http404 from django.template.engine import Engine from django.utils.decorators import method_decorator from django.utils.inspect import func_has_no_args from django.utils.inspect import ( func_accepts_kwargs, func_accepts_var_args, func_has_no_args, get_func_full_args, ) from django.utils.translation import ugettext as _ from django.views.generic import TemplateView Loading Loading @@ -219,7 +222,7 @@ class ModelDetailView(BaseAdminDocsView): fields.append({ 'name': field.name, 'data_type': data_type, 'verbose': verbose, 'verbose': verbose or '', 'help_text': field.help_text, }) Loading @@ -242,9 +245,10 @@ class ModelDetailView(BaseAdminDocsView): 'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name), }) methods = [] # Gather model methods. for func_name, func in model.__dict__.items(): if inspect.isfunction(func) and func_has_no_args(func): if inspect.isfunction(func): try: for exclude in MODEL_METHODS_EXCLUDE: if func_name.startswith(exclude): Loading @@ -254,10 +258,28 @@ class ModelDetailView(BaseAdminDocsView): verbose = func.__doc__ if verbose: verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.model_name) # If a method has no arguments, show it as a 'field', otherwise # as a 'method with arguments'. if func_has_no_args(func) and not func_accepts_kwargs(func) and not func_accepts_var_args(func): fields.append({ 'name': func_name, 'data_type': get_return_data_type(func_name), 'verbose': verbose, 'verbose': verbose or '', }) else: arguments = get_func_full_args(func) print_arguments = arguments # Join arguments with ', ' and in case of default value, # join it with '='. Use repr() so that strings will be # correctly displayed. print_arguments = ', '.join([ '='.join(list(arg_el[:1]) + [repr(el) for el in arg_el[1:]]) for arg_el in arguments ]) methods.append({ 'name': func_name, 'arguments': print_arguments, 'verbose': verbose or '', }) # Gather related objects Loading @@ -282,6 +304,7 @@ class ModelDetailView(BaseAdminDocsView): 'summary': title, 'description': body, 'fields': fields, 'methods': methods, }) return super(ModelDetailView, self).get_context_data(**kwargs) Loading
django/utils/inspect.py +52 −1 Original line number Diff line number Diff line Loading @@ -43,6 +43,44 @@ def get_func_args(func): ] def get_func_full_args(func): """ Return a list of (argument name, default value) tuples. If the argument does not have a default value, omit it in the tuple. Arguments such as *args and **kwargs are also included. """ if six.PY2: argspec = inspect.getargspec(func) args = argspec.args[1:] # ignore 'self' defaults = argspec.defaults or [] # Split args into two lists depending on whether they have default value no_default = args[:len(args) - len(defaults)] with_default = args[len(args) - len(defaults):] # Join the two lists and combine it with default values args = [(arg,) for arg in no_default] + zip(with_default, defaults) # Add possible *args and **kwargs and prepend them with '*' or '**' varargs = [('*' + argspec.varargs,)] if argspec.varargs else [] kwargs = [('**' + argspec.keywords,)] if argspec.keywords else [] return args + varargs + kwargs sig = inspect.signature(func) args = [] for arg_name, param in sig.parameters.items(): name = arg_name # Ignore 'self' if name == 'self': continue if param.kind == inspect.Parameter.VAR_POSITIONAL: name = '*' + name elif param.kind == inspect.Parameter.VAR_KEYWORD: name = '**' + name if param.default != inspect.Parameter.empty: args.append((name, param.default)) else: args.append((name,)) return args def func_accepts_kwargs(func): if six.PY2: # Not all callables are inspectable with getargspec, so we'll Loading @@ -64,10 +102,23 @@ def func_accepts_kwargs(func): ) def func_accepts_var_args(func): """ Return True if function 'func' accepts positional arguments *args. """ if six.PY2: return inspect.getargspec(func)[1] is not None return any( p for p in inspect.signature(func).parameters.values() if p.kind == p.VAR_POSITIONAL ) def func_has_no_args(func): args = inspect.getargspec(func)[0] if six.PY2 else [ p for p in inspect.signature(func).parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD and p.default is p.empty if p.kind == p.POSITIONAL_OR_KEYWORD ] return len(args) == 1 Loading
docs/ref/contrib/admin/admindocs.txt +9 −4 Original line number Diff line number Diff line Loading @@ -60,10 +60,15 @@ Model reference =============== The **models** section of the ``admindocs`` page describes each model in the system along with all the fields and methods (without any arguments) available on it. While model properties don't have any arguments, they are not listed. Relationships to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` attributes on fields or from docstrings on model methods. system along with all the fields and methods available on it. Relationships to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` attributes on fields or from docstrings on model methods. .. versionchanged:: 1.9 The **models** section of the ``admindocs`` now describes methods that take arguments as well. In previous versions it was restricted to methods without arguments. A model with useful documentation might look like this:: Loading
docs/releases/1.9.txt +6 −0 Original line number Diff line number Diff line Loading @@ -163,6 +163,12 @@ Minor features * JavaScript slug generation now supports Romanian characters. :mod:`django.contrib.admindocs` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The model section of the ``admindocs`` now also describes methods that take arguments, rather than ignoring them. :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading