diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 8ccdc342c..60d7f7097 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -12,6 +12,7 @@ from django.core.paginator import Paginator as DjangoPaginator from django.core.paginator import InvalidPage from django.template import loader from django.utils import six +from django.utils.encoding import force_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ @@ -178,10 +179,12 @@ class PageNumberPagination(BasePagination): # Client can control the page using this query parameter. page_query_param = 'page' + page_query_description = _('A page number within the paginated result set.') # Client can control the page size using this query parameter. # Default is 'None'. Set to eg 'page_size' to enable usage. page_size_query_param = None + page_size_query_description = _('Number of results to return per page.') # Set to an integer to limit the maximum page size the client may request. # Only relevant if 'page_size_query_param' has also been set. @@ -287,11 +290,21 @@ class PageNumberPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' fields = [ - coreapi.Field(name=self.page_query_param, required=False, location='query') + coreapi.Field( + name=self.page_query_param, + required=False, + location='query', + description=force_text(self.page_query_description) + ) ] if self.page_size_query_param is not None: fields.append( - coreapi.Field(name=self.page_size_query_param, required=False, location='query') + coreapi.Field( + name=self.page_size_query_param, + required=False, + location='query', + description=force_text(self.page_size_query_description) + ) ) return fields @@ -305,7 +318,9 @@ class LimitOffsetPagination(BasePagination): """ default_limit = api_settings.PAGE_SIZE limit_query_param = 'limit' + limit_query_description = _('Number of results to return per page.') offset_query_param = 'offset' + offset_query_description = _('The initial index from which to return the results.') max_limit = None template = 'rest_framework/pagination/numbers.html' @@ -425,8 +440,18 @@ class LimitOffsetPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' return [ - coreapi.Field(name=self.limit_query_param, required=False, location='query'), - coreapi.Field(name=self.offset_query_param, required=False, location='query') + coreapi.Field( + name=self.limit_query_param, + required=False, + location='query', + description=force_text(self.limit_query_description) + ), + coreapi.Field( + name=self.offset_query_param, + required=False, + location='query', + description=force_text(self.offset_query_description) + ) ] @@ -437,6 +462,7 @@ class CursorPagination(BasePagination): http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api """ cursor_query_param = 'cursor' + cursor_query_description = _('The pagination cursor value.') page_size = api_settings.PAGE_SIZE invalid_cursor_message = _('Invalid cursor') ordering = '-created' @@ -739,5 +765,10 @@ class CursorPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' return [ - coreapi.Field(name=self.cursor_query_param, required=False, location='query') + coreapi.Field( + name=self.cursor_query_param, + required=False, + location='query', + description=force_text(self.cursor_query_description) + ) ] diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 773df6261..c9655fb20 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -5,9 +5,11 @@ from importlib import import_module from django.conf import settings from django.contrib.admindocs.views import simplify_regex from django.core.exceptions import PermissionDenied +from django.db import models from django.http import Http404 from django.utils import six from django.utils.encoding import force_text, smart_text +from django.utils.translation import ugettext_lazy as _ from rest_framework import exceptions, renderers, serializers from rest_framework.compat import ( @@ -113,6 +115,19 @@ def endpoint_ordering(endpoint): return (path, method_priority) +def get_pk_description(model, model_field): + if isinstance(model_field, models.AutoField): + value_type = _('unique integer value') + elif isinstance(model_field, models.UUIDField): + value_type = _('UUID string') + else: + value_type = _('unique value') + + return _('A {value_type} identifying this {name}.').format( + value_type=value_type, + name=model._meta.verbose_name, + ) + class EndpointInspector(object): """ A class to determine the available API endpoints that a project exposes. @@ -450,10 +465,30 @@ class SchemaGenerator(object): Return a list of `coreapi.Field` instances corresponding to any templated path variables. """ + model = getattr(getattr(view, 'queryset', None), 'model', None) fields = [] for variable in uritemplate.variables(path): - field = coreapi.Field(name=variable, location='path', required=True) + description = None + if model is not None: + # Attempt to infer a field description if possible. + try: + model_field = model._meta.get_field(variable) + except: + pass + + if model_field is not None and model_field.help_text: + description = force_text(model_field.help_text) + elif model_field is not None and model_field.primary_key: + description = get_pk_description(model, model_field) + + + field = coreapi.Field( + name=variable, + location='path', + required=True, + description='' if (description is None) else description + ) fields.append(field) return fields