Skip to content

autogenerate API documentation #479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7b29f36
initial integration of JSONAPIDjangoFilter
n2ygk Aug 23, 2018
dc5ca38
documentation, isort, flake8
n2ygk Aug 24, 2018
6b0dc8c
Forgot to add django_filters to installed_apps
n2ygk Aug 24, 2018
d4fbf24
backwards compatibility for py27 + django-filter
n2ygk Aug 24, 2018
d86d217
handle optional django-filter package
n2ygk Aug 24, 2018
83c4cc0
fix travis to match new TOXENVs due to django-filter
n2ygk Aug 24, 2018
f5792c1
fixed a typo
n2ygk Aug 24, 2018
cbc9d55
add a warning if django-filter is missing and JSONAPIDjangoFilter is …
n2ygk Aug 25, 2018
2f6ba1d
JSONAPIQueryValidationFilter implementation
n2ygk Aug 27, 2018
4f2b75b
improve filter_regex
n2ygk Aug 28, 2018
48b4c51
Merge branch 'JSONAPIDjangoFilter' into JSONAPIQueryValidationFilter
n2ygk Aug 28, 2018
2742d60
rename tests from filter to param
n2ygk Aug 29, 2018
6a8d7ae
easy changes recommended by @sliverc review
n2ygk Aug 29, 2018
db9e1f9
resolve @sliverc review method of using optional django-filter.
n2ygk Aug 29, 2018
68f5e02
Merge branch 'JSONAPIDjangoFilter' into JSONAPIQueryValidationFilter
n2ygk Aug 30, 2018
9eec004
initial attempt at sphinx-apidoc
n2ygk Sep 14, 2018
1475643
each module on a separate page and force overwrite of autogenerated a…
n2ygk Sep 15, 2018
eb486cd
move documentation from api.md into appropriate module docstrings
n2ygk Sep 15, 2018
a475425
minor reStructuredText tweaks to docstrings for sphinx-apidoc
n2ygk Sep 15, 2018
211577c
docs/apidoc needs to be in the repo
n2ygk Sep 15, 2018
6378985
rst tweaks for sphinx
n2ygk Sep 15, 2018
f0bdbd4
Merge branch 'master' into JSONAPIQueryValidationFilter
n2ygk Sep 17, 2018
64d4af0
remove JSONAPI prefix per #471
n2ygk Sep 17, 2018
23616a2
inadvertently removed when merging master
n2ygk Sep 17, 2018
2c476d9
add QueryValidation filter to NonPaginatedEntryViewset to avoid break…
n2ygk Sep 17, 2018
9b5ab9d
flake8
n2ygk Sep 17, 2018
dbd3d32
100% test coverage for QueryParamaterValidationFilter
n2ygk Sep 17, 2018
11aaf06
move QueryValidationFilter earlier and document how to extend query_r…
n2ygk Sep 17, 2018
57e95cc
QueryValidationFilter to README
n2ygk Sep 17, 2018
a22ca21
py2.7 fix for a non-ASCII quotation mark
n2ygk Sep 17, 2018
0252096
ugh I added back this junk file by mistake again
n2ygk Sep 17, 2018
e43e641
Merge branch 'JSONAPIQueryValidationFilter' into sphinx-apidoc
n2ygk Sep 17, 2018
642b2e0
merge in QueryValidationFilter
n2ygk Sep 17, 2018
7880bdb
run sphinx-autodoc as part of the RTD sphinx-build
n2ygk Sep 17, 2018
b8b5a78
change member order to bysource
n2ygk Sep 17, 2018
b6eb33c
don't inherit missing docstrings
n2ygk Sep 17, 2018
6c653fb
reST touchups
n2ygk Sep 17, 2018
9e715fa
Change "invalid filter" to "invalid query parameter" for malformed fi…
n2ygk Sep 18, 2018
af10543
renamed to QueryParameterValidationFilter to be clear that this is qu…
n2ygk Sep 18, 2018
c928d72
clearer language
n2ygk Sep 18, 2018
6e008ad
flake8 line length after renaming the class
n2ygk Sep 18, 2018
08518ac
Merge branch 'JSONAPIQueryValidationFilter' into sphinx-apidoc
n2ygk Sep 18, 2018
fc7d4f2
changelog
n2ygk Sep 18, 2018
8ad9bf5
Merge branch 'master' into sphinx-apidoc
n2ygk Sep 19, 2018
1427b5a
refactor filters subpackage into a single module to be consistent wit…
n2ygk Sep 19, 2018
0ac6c25
`autodoc_inherit_docstrings = False` fixes unwanted inherited docstri…
n2ygk Sep 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
_build
apidoc
60 changes: 0 additions & 60 deletions docs/api.md

This file was deleted.

9 changes: 9 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -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
16 changes: 13 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ------------------------------------------------

Expand All @@ -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']
Expand Down Expand Up @@ -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.
Expand Down
46 changes: 30 additions & 16 deletions rest_framework_json_api/django_filters/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down
97 changes: 97 additions & 0 deletions rest_framework_json_api/filters.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 0 additions & 2 deletions rest_framework_json_api/filters/__init__.py

This file was deleted.

37 changes: 0 additions & 37 deletions rest_framework_json_api/filters/queryvalidation.py

This file was deleted.

44 changes: 0 additions & 44 deletions rest_framework_json_api/filters/sort.py

This file was deleted.

Loading