diff --git a/CHANGELOG.md b/CHANGELOG.md index a626fff7..348fe53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ any parts of the framework not mentioned in the documentation should generally b ### Changed * Replaced binary `drf_example` sqlite3 db with a [fixture](example/fixtures/drf_example.json). See [getting started](docs/getting-started.md#running-the-example-app). +* Replaced unmaintained [API doc](docs/api.md) with [auto-generated API reference](docs/api.rst). ### Fixed diff --git a/docs/.gitignore b/docs/.gitignore index e35d8850..70a87b2c 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ _build +apidoc diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 86116d5c..00000000 --- a/docs/api.md +++ /dev/null @@ -1,60 +0,0 @@ - -# API - -## mixins -### MultipleIDMixin - -Add this mixin to a view to override `get_queryset` to automatically filter -records by `ids[]=1&ids[]=2` in URL query params. - -## rest_framework_json_api.renderers.JSONRenderer - -The `JSONRenderer` exposes a number of methods that you may override if you need -highly custom rendering control. - -#### extract_attributes - -`extract_attributes(fields, resource)` - -Builds the `attributes` object of the JSON API resource object. - -#### extract_relationships - -`extract_relationships(fields, resource, resource_instance)` - -Builds the `relationships` top level object based on related serializers. - -#### extract_included - -`extract_included(fields, resource, resource_instance, included_resources)` - -Adds related data to the top level `included` key when the request includes `?include=example,example_field2` - -#### extract_meta - -`extract_meta(serializer, resource)` - -Gathers the data from serializer fields specified in `meta_fields` and adds it to the `meta` object. - -#### extract_root_meta - -`extract_root_meta(serializer, resource)` - -Calls a `get_root_meta` function on a serializer, if it exists. - -#### build_json_resource_obj - -`build_json_resource_obj(fields, resource, resource_instance, resource_name)` - -Builds the resource object (type, id, attributes) and extracts relationships. - -## rest_framework_json_api.parsers.JSONParser - -Similar to `JSONRenderer`, the `JSONParser` you may override the following methods if you need -highly custom parsing control. - -#### parse_metadata - -`parse_metadata(result)` - -Returns a dictionary which will be merged into parsed data of the request. By default, it reads the `meta` content in the request body and returns it in a dictionary with a `_meta` top level key. diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..0f633cd8 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,9 @@ +API Reference +============= + +This API reference is autogenerated from the Python docstrings -- which need to be improved! + +.. toctree:: + :maxdepth: 4 + + apidoc/rest_framework_json_api diff --git a/docs/conf.py b/docs/conf.py index 7a61ea8a..5b2f42de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,11 +17,18 @@ import sys import os import shlex +import django # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) +os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' +django.setup() + +# Auto-generate API documentation. +from sphinx.apidoc import main +main(['sphinx-apidoc', '-e', '-T', '-M', '-f', '-o', 'apidoc', '../rest_framework_json_api']) # -- General configuration ------------------------------------------------ @@ -31,7 +38,9 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = ['sphinx.ext.autodoc'] +autodoc_member_order = 'bysource' +autodoc_inherit_docstrings = False # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -62,9 +71,10 @@ # built documents. # # The short X.Y version. -version = '2.0' +from rest_framework_json_api import VERSION +version = VERSION # The full version, including alpha/beta/rc tags. -release = '2.0.0-alpha.1' +release = VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/rest_framework_json_api/django_filters/backends.py b/rest_framework_json_api/django_filters/backends.py index 61148266..c9268eed 100644 --- a/rest_framework_json_api/django_filters/backends.py +++ b/rest_framework_json_api/django_filters/backends.py @@ -20,17 +20,32 @@ class DjangoFilterBackend(DjangoFilterBackend): chaining. It also returns a 400 error for invalid filters. Filters can be: - - A resource field equality test: - `?filter[qty]=123` - - Apply other [field lookup](https://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups) # noqa: E501 + + - A resource field + equality test: + + ``?filter[qty]=123`` + + - Apply other + https://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups operators: - `?filter[name.icontains]=bar` or `?filter[name.isnull]=true...` - - Membership in a list of values: - `?filter[name.in]=abc,123,zzz (name in ['abc','123','zzz'])` - - Filters can be combined for intersection (AND): - `?filter[qty]=123&filter[name.in]=abc,123,zzz&filter[...]` - - A related resource path can be used: - `?filter[inventory.item.partNum]=123456 (where `inventory.item` is the relationship path)` + + ``?filter[name.icontains]=bar`` or ``?filter[name.isnull]=true...`` + + - Membership in + a list of values: + + ``?filter[name.in]=abc,123,zzz`` (name in ['abc','123','zzz']) + + - Filters can be combined + for intersection (AND): + + ``?filter[qty]=123&filter[name.in]=abc,123,zzz&filter[...]`` + + - A related resource path + can be used: + + ``?filter[inventory.item.partNum]=123456`` (where `inventory.item` is the relationship path) If you are also using rest_framework.filters.SearchFilter you'll want to customize the name of the query parameter for searching to make sure it doesn't conflict @@ -65,12 +80,11 @@ def _validate_filter(self, keys, filterset_class): def get_filterset(self, request, queryset, view): """ - Sometimes there's no filterset_class defined yet the client still + Sometimes there's no `filterset_class` defined yet the client still requests a filter. Make sure they see an error too. This means - we have to get_filterset_kwargs() even if there's no filterset_class. - - TODO: .base_filters vs. .filters attr (not always present) + we have to `get_filterset_kwargs()` even if there's no `filterset_class`. """ + # TODO: .base_filters vs. .filters attr (not always present) filterset_class = self.get_filterset_class(view, queryset) kwargs = self.get_filterset_kwargs(request, queryset, view) self._validate_filter(kwargs.pop('filter_keys'), filterset_class) @@ -112,8 +126,8 @@ def get_filterset_kwargs(self, request, queryset, view): def filter_queryset(self, request, queryset, view): """ - Backwards compatibility to 1.1 (required for Python 2.7) - In 1.1 filter_queryset does not call get_filterset or get_filterset_kwargs. + This is backwards compatibility to django-filter 1.1 (required for Python 2.7). + In 1.1 `filter_queryset` does not call `get_filterset` or `get_filterset_kwargs`. """ # TODO: remove when Python 2.7 support is deprecated if VERSION >= (2, 0, 0): diff --git a/rest_framework_json_api/filters.py b/rest_framework_json_api/filters.py new file mode 100644 index 00000000..f0b95a35 --- /dev/null +++ b/rest_framework_json_api/filters.py @@ -0,0 +1,97 @@ +import re + +from rest_framework.exceptions import ValidationError +from rest_framework.filters import BaseFilterBackend, OrderingFilter + +from rest_framework_json_api.utils import format_value + + +class OrderingFilter(OrderingFilter): + """ + A backend filter that implements http://jsonapi.org/format/#fetching-sorting and + raises a 400 error if any sort field is invalid. + + If you prefer *not* to report 400 errors for invalid sort fields, just use + :py:class:`rest_framework.filters.OrderingFilter` with + :py:attr:`~rest_framework.filters.OrderingFilter.ordering_param` = "sort" + + Also applies DJA format_value() to convert (e.g. camelcase) to underscore. + (See JSON_API_FORMAT_FIELD_NAMES in docs/usage.md) + """ + #: override :py:attr:`rest_framework.filters.OrderingFilter.ordering_param` + #: with JSON:API-compliant query parameter name. + ordering_param = 'sort' + + def remove_invalid_fields(self, queryset, fields, view, request): + """ + Extend :py:meth:`rest_framework.filters.OrderingFilter.remove_invalid_fields` to + validate that all provided sort fields exist (as contrasted with the super's behavior + which is to silently remove invalid fields). + + :raises ValidationError: if a sort field is invalid. + """ + valid_fields = [ + item[0] for item in self.get_valid_fields(queryset, view, + {'request': request}) + ] + bad_terms = [ + term for term in fields + if format_value(term.replace(".", "__").lstrip('-'), "underscore") not in valid_fields + ] + if bad_terms: + raise ValidationError('invalid sort parameter{}: {}'.format( + ('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms))) + # this looks like it duplicates code above, but we want the ValidationError to report + # the actual parameter supplied while we want the fields passed to the super() to + # be correctly rewritten. + # The leading `-` has to be stripped to prevent format_value from turning it into `_`. + underscore_fields = [] + for item in fields: + item_rewritten = item.replace(".", "__") + if item_rewritten.startswith('-'): + underscore_fields.append( + '-' + format_value(item_rewritten.lstrip('-'), "underscore")) + else: + underscore_fields.append(format_value(item_rewritten, "underscore")) + + return super(OrderingFilter, self).remove_invalid_fields( + queryset, underscore_fields, view, request) + + +class QueryParameterValidationFilter(BaseFilterBackend): + """ + A backend filter that performs strict validation of query parameters for + JSON:API spec conformance and raises a 400 error if non-conforming usage is + found. + + If you want to add some additional non-standard query parameters, + override :py:attr:`query_regex` adding the new parameters. Make sure to comply with + the rules at http://jsonapi.org/format/#query-parameters. + """ + #: compiled regex that matches the allowed http://jsonapi.org/format/#query-parameters: + #: `sort` and `include` stand alone; `filter`, `fields`, and `page` have []'s + query_regex = re.compile(r'^(sort|include)$|^(filter|fields|page)(\[[\w\.\-]+\])?$') + + def validate_query_params(self, request): + """ + Validate that query params are in the list of valid query keywords in + :py:attr:`query_regex` + + :raises ValidationError: if not. + """ + # TODO: For jsonapi error object conformance, must set jsonapi errors "parameter" for + # the ValidationError. This requires extending DRF/DJA Exceptions. + for qp in request.query_params.keys(): + if not self.query_regex.match(qp): + raise ValidationError('invalid query parameter: {}'.format(qp)) + if len(request.query_params.getlist(qp)) > 1: + raise ValidationError( + 'repeated query parameter not allowed: {}'.format(qp)) + + def filter_queryset(self, request, queryset, view): + """ + Overrides :py:meth:`BaseFilterBackend.filter_queryset` by first validating the + query params with :py:meth:`validate_query_params` + """ + self.validate_query_params(request) + return queryset diff --git a/rest_framework_json_api/filters/__init__.py b/rest_framework_json_api/filters/__init__.py deleted file mode 100644 index 37c21e2e..00000000 --- a/rest_framework_json_api/filters/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .sort import OrderingFilter # noqa: F401 -from .queryvalidation import QueryParameterValidationFilter # noqa: F401 diff --git a/rest_framework_json_api/filters/queryvalidation.py b/rest_framework_json_api/filters/queryvalidation.py deleted file mode 100644 index 746ab787..00000000 --- a/rest_framework_json_api/filters/queryvalidation.py +++ /dev/null @@ -1,37 +0,0 @@ -import re - -from rest_framework.exceptions import ValidationError -from rest_framework.filters import BaseFilterBackend - - -class QueryParameterValidationFilter(BaseFilterBackend): - """ - A backend filter that performs strict validation of query parameters for - jsonapi spec conformance and raises a 400 error if non-conforming usage is - found. - - If you want to add some additional non-standard query parameters, - override :py:attr:`query_regex` adding the new parameters. Make sure to comply with - the rules at http://jsonapi.org/format/#query-parameters. - """ - #: compiled regex that matches the allowed http://jsonapi.org/format/#query-parameters - #: `sort` and `include` stand alone; `filter`, `fields`, and `page` have []'s - query_regex = re.compile(r'^(sort|include)$|^(filter|fields|page)(\[[\w\.\-]+\])?$') - - def validate_query_params(self, request): - """ - Validate that query params are in the list of valid query keywords - Raises ValidationError if not. - """ - # TODO: For jsonapi error object conformance, must set jsonapi errors "parameter" for - # the ValidationError. This requires extending DRF/DJA Exceptions. - for qp in request.query_params.keys(): - if not self.query_regex.match(qp): - raise ValidationError('invalid query parameter: {}'.format(qp)) - if len(request.query_params.getlist(qp)) > 1: - raise ValidationError( - 'repeated query parameter not allowed: {}'.format(qp)) - - def filter_queryset(self, request, queryset, view): - self.validate_query_params(request) - return queryset diff --git a/rest_framework_json_api/filters/sort.py b/rest_framework_json_api/filters/sort.py deleted file mode 100644 index f74b2c1c..00000000 --- a/rest_framework_json_api/filters/sort.py +++ /dev/null @@ -1,44 +0,0 @@ -from rest_framework.exceptions import ValidationError -from rest_framework.filters import OrderingFilter - -from rest_framework_json_api.utils import format_value - - -class OrderingFilter(OrderingFilter): - """ - This implements http://jsonapi.org/format/#fetching-sorting and raises 400 - if any sort field is invalid. If you prefer *not* to report 400 errors for - invalid sort fields, just use OrderingFilter with `ordering_param='sort'` - - Also applies DJA format_value() to convert (e.g. camelcase) to underscore. - (See JSON_API_FORMAT_FIELD_NAMES in docs/usage.md) - """ - ordering_param = 'sort' - - def remove_invalid_fields(self, queryset, fields, view, request): - valid_fields = [ - item[0] for item in self.get_valid_fields(queryset, view, - {'request': request}) - ] - bad_terms = [ - term for term in fields - if format_value(term.replace(".", "__").lstrip('-'), "underscore") not in valid_fields - ] - if bad_terms: - raise ValidationError('invalid sort parameter{}: {}'.format( - ('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms))) - # this looks like it duplicates code above, but we want the ValidationError to report - # the actual parameter supplied while we want the fields passed to the super() to - # be correctly rewritten. - # The leading `-` has to be stripped to prevent format_value from turning it into `_`. - underscore_fields = [] - for item in fields: - item_rewritten = item.replace(".", "__") - if item_rewritten.startswith('-'): - underscore_fields.append( - '-' + format_value(item_rewritten.lstrip('-'), "underscore")) - else: - underscore_fields.append(format_value(item_rewritten, "underscore")) - - return super(OrderingFilter, self).remove_invalid_fields( - queryset, underscore_fields, view, request) diff --git a/rest_framework_json_api/pagination.py b/rest_framework_json_api/pagination.py index 281c78f2..db09bb7d 100644 --- a/rest_framework_json_api/pagination.py +++ b/rest_framework_json_api/pagination.py @@ -12,7 +12,6 @@ class JsonApiPageNumberPagination(PageNumberPagination): """ A json-api compatible pagination format. - Use a private name for the implementation because the public name is pending deprecation. """ page_query_param = 'page[number]' page_size_query_param = 'page[size]' @@ -54,10 +53,12 @@ def get_paginated_response(self, data): class JsonApiLimitOffsetPagination(LimitOffsetPagination): """ A limit/offset based style. For example: - http://api.example.org/accounts/?page[limit]=100 - http://api.example.org/accounts/?page[offset]=400&page[limit]=100 - Use a private name for the implementation because the public name is pending deprecation. + .. code:: + + http://api.example.org/accounts/?page[limit]=100 + http://api.example.org/accounts/?page[offset]=400&page[limit]=100 + """ limit_query_param = 'page[limit]' offset_query_param = 'page[offset]' @@ -105,7 +106,15 @@ def get_paginated_response(self, data): class PageNumberPagination(JsonApiPageNumberPagination): """ - A soon-to-be-changed paginator that uses non-JSON:API query parameters (default: + .. warning:: + + PageNumberPagination is deprecated. Use JsonApiPageNumberPagination instead. + If you want to retain current defaults you will need to implement custom + pagination class explicitly setting `page_query_param = "page"` and + `page_size_query_param = "page_size"`. + See changelog for more details. + + A paginator that uses non-JSON:API query parameters (default: 'page' and 'page_size' instead of 'page[number]' and 'page[size]'). """ page_query_param = 'page' @@ -119,7 +128,7 @@ def __init__(self): 'page_size_query_param' not in type(self).__dict__) if warn: warnings.warn( - 'PageNumberPagination is deprecated use JsonApiPageNumberPagination instead. ' + 'PageNumberPagination is deprecated. Use JsonApiPageNumberPagination instead. ' 'If you want to retain current defaults you will need to implement custom ' 'pagination class explicitly setting `page_query_param = "page"` and ' '`page_size_query_param = "page_size"`. ' @@ -131,7 +140,14 @@ def __init__(self): class LimitOffsetPagination(JsonApiLimitOffsetPagination): """ - Deprecated paginator that uses a different max_limit + .. warning:: + + LimitOffsetPagination is deprecated. Use JsonApiLimitOffsetPagination instead. + If you want to retain current defaults you will need to implement custom + pagination class explicitly setting `max_limit = None`. + See changelog for more details. + + A paginator that uses a different max_limit from `JsonApiLimitOffsetPagination`. """ max_limit = None @@ -142,7 +158,7 @@ def __init__(self): warn = 'max_limit' not in type(self).__dict__ if warn: warnings.warn( - 'LimitOffsetPagination is deprecated use JsonApiLimitOffsetPagination instead. ' + 'LimitOffsetPagination is deprecated. Use JsonApiLimitOffsetPagination instead. ' 'If you want to retain current defaults you will need to implement custom ' 'pagination class explicitly setting `max_limit = None`. ' 'See changelog for more details.', diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 8459f3ba..3ce39bbb 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -11,8 +11,14 @@ class JSONParser(parsers.JSONParser): """ + Similar to `JSONRenderer`, the `JSONParser` you may override the following methods if you + need highly custom parsing control. + A JSON API client will send a payload that looks like this: + .. code:: json + + { "data": { "type": "identities", @@ -65,6 +71,11 @@ def parse_relationships(data): @staticmethod def parse_metadata(result): + """ + Returns a dictionary which will be merged into parsed data of the request. By default, + it reads the `meta` content in the request body and returns it in a dictionary with + a `_meta` top level key. + """ metadata = result.get('meta') if metadata: return {'_meta': metadata} diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 040a84f1..044b6f9f 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -152,10 +152,12 @@ def many_init(cls, *args, **kwargs): and child classes in order to try to cover the general case. If you're overriding this method you'll probably want something much simpler, eg: - @classmethod - def many_init(cls, *args, **kwargs): - kwargs['child'] = cls() - return CustomManyRelatedField(*args, **kwargs) + .. code:: python + + @classmethod + def many_init(cls, *args, **kwargs): + kwargs['child'] = cls() + return CustomManyRelatedField(*args, **kwargs) """ list_kwargs = {'child_relation': cls(*args, **kwargs)} for key in kwargs: diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 60836e97..9fee611c 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -18,22 +18,29 @@ class JSONRenderer(renderers.JSONRenderer): """ + The `JSONRenderer` exposes a number of methods that you may override if you need highly + custom rendering control. + Render a JSON response per the JSON API spec: - { - "data": [{ - "type": "companies", - "id": 1, - "attributes": { - "name": "Mozilla", - "slug": "mozilla", - "date-created": "2014-03-13 16:33:37" - } - }, { - "type": "companies", - "id": 2, - ... - }] - } + + .. code:: json + + { + "data": [{ + "type": "companies", + "id": 1, + "attributes": { + "name": "Mozilla", + "slug": "mozilla", + "date-created": "2014-03-13 16:33:37" + } + }, { + "type": "companies", + "id": 2, + ... + }] + } + """ media_type = 'application/vnd.api+json' @@ -41,6 +48,9 @@ class JSONRenderer(renderers.JSONRenderer): @classmethod def extract_attributes(cls, fields, resource): + """ + Builds the `attributes` object of the JSON API resource object. + """ data = OrderedDict() for field_name, field in six.iteritems(fields): # ID is always provided in the root of JSON API so remove it from attributes @@ -72,6 +82,9 @@ def extract_attributes(cls, fields, resource): @classmethod def extract_relationships(cls, fields, resource, resource_instance): + """ + Builds the relationships top level object based on related serializers. + """ # Avoid circular deps from rest_framework_json_api.relations import ResourceRelatedField @@ -318,6 +331,10 @@ def extract_relation_instance(cls, field_name, field, resource_instance, seriali @classmethod def extract_included(cls, fields, resource, resource_instance, included_resources, included_cache): + """ + Adds related data to the top level included key when the request includes + ?include=example,example_field2 + """ # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return @@ -442,6 +459,10 @@ def extract_included(cls, fields, resource, resource_instance, included_resource @classmethod def extract_meta(cls, serializer, resource): + """ + Gathers the data from serializer fields specified in meta_fields and adds it to + the meta object. + """ if hasattr(serializer, 'child'): meta = getattr(serializer.child, 'Meta', None) else: @@ -456,6 +477,9 @@ def extract_meta(cls, serializer, resource): @classmethod def extract_root_meta(cls, serializer, resource): + """ + Calls a `get_root_meta` function on a serializer, if it exists. + """ many = False if hasattr(serializer, 'child'): many = True @@ -471,6 +495,9 @@ def extract_root_meta(cls, serializer, resource): @classmethod def build_json_resource_obj(cls, fields, resource, resource_instance, resource_name, force_type_resolution=False): + """ + Builds the resource object (type, id, attributes) and extracts relationships. + """ # Determine type from the instance if the underlying model is polymorphic if force_type_resolution: resource_name = utils.get_resource_type_from_instance(resource_instance) diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index d525af15..5085eeec 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -127,6 +127,7 @@ class HyperlinkedModelSerializer( * Relationships to other instances are hyperlinks, instead of primary keys. Included Mixins: + * A mixin class to enable sparse fieldsets is included * A mixin class to enable validation of included resources is included """ @@ -150,6 +151,7 @@ class ModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, Mo Included Mixins: + * A mixin class to enable sparse fieldsets is included * A mixin class to enable validation of included resources is included """ diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 39000216..033a5730 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -129,6 +129,13 @@ def _format_object(obj, format_type=None): def format_keys(obj, format_type=None): """ + .. warning:: + + `format_keys` function and `JSON_API_FORMAT_KEYS` setting are deprecated and will be + removed in the future. + Use `format_field_names` and `JSON_API_FIELD_NAMES` instead. Be aware that + `format_field_names` only formats keys and preserves value. + Takes either a dict or list and returns it with camelized keys only if JSON_API_FORMAT_KEYS is set. @@ -190,6 +197,13 @@ def format_value(value, format_type=None): def format_relation_name(value, format_type=None): + """ + .. warning:: + + The 'format_relation_name' function has been renamed 'format_resource_type' and the + settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES' instead of + 'JSON_API_FORMAT_RELATION_KEYS' and 'JSON_API_PLURALIZE_RELATION_TYPE' + """ warnings.warn( "The 'format_relation_name' function has been renamed 'format_resource_type' and the " "settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES' instead of " diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index b77e6a99..f8766bc4 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -30,20 +30,22 @@ class PrefetchForIncludesHelperMixin(object): def get_queryset(self): - """This viewset provides a helper attribute to prefetch related models + """ + This viewset provides a helper attribute to prefetch related models based on the include specified in the URL. __all__ can be used to specify a prefetch which should be done regardless of the include - @example - # When MyViewSet is called with ?include=author it will prefetch author and authorbio - class MyViewSet(viewsets.ModelViewSet): - queryset = Book.objects.all() - prefetch_for_includes = { - '__all__': [], - 'author': ['author', 'author__authorbio'], - 'category.section': ['category'] - } + .. code:: python + + # When MyViewSet is called with ?include=author it will prefetch author and authorbio + class MyViewSet(viewsets.ModelViewSet): + queryset = Book.objects.all() + prefetch_for_includes = { + '__all__': [], + 'author': ['author', 'author__authorbio'], + 'category.section': ['category'] + } """ qs = super(PrefetchForIncludesHelperMixin, self).get_queryset() if not hasattr(self, 'prefetch_for_includes'): diff --git a/tox.ini b/tox.ini index e242ba2a..e8cf36c2 100644 --- a/tox.ini +++ b/tox.ini @@ -34,3 +34,9 @@ commands = # example has extra dependencies that are installed in a dev environment # but are not installed in CI. Explicitly set those packages. isort --check-only --verbose --recursive --diff --thirdparty pytest --thirdparty polymorphic --thirdparty pytest_factoryboy --thirdparty packaging example + +[testenv:sphinx] +deps = + -rrequirements-development.txt +commands = + sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html