Skip to content

Commit

Permalink
Merge branch 'master' into migrate_setuppy_to_pryoject.toml
Browse files Browse the repository at this point in the history
  • Loading branch information
deronnax committed Oct 5, 2023
2 parents fd1932c + d181511 commit b16826c
Show file tree
Hide file tree
Showing 32 changed files with 328 additions and 33 deletions.
26 changes: 25 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- '3.11'

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
with:
Expand All @@ -46,3 +46,27 @@ jobs:
- name: Upload coverage
run: |
codecov -e TOXENV,DJANGO
test-docs:
name: Test documentation links
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v4
with:
python-version: '3.9'

- name: Install dependencies
run: pip install -r requirements/requirements-documentation.txt

# Start mkdocs server and wait for it to be ready
- run: mkdocs serve &
- run: WAIT_TIME=0 && until nc -vzw 2 localhost 8000 || [ $WAIT_TIME -eq 5 ]; do sleep $(( WAIT_TIME++ )); done
- run: if [ $WAIT_TIME == 5 ]; then echo cannot start mkdocs server on http://localhost:8000; exit 1; fi

- name: Check links
continue-on-error: true
run: pylinkvalidate.py -P http://localhost:8000/

- run: echo "Done"
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to REST framework

At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes.
At this point in its lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes.

Apart from minor documentation changes, the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.

Expand Down
2 changes: 1 addition & 1 deletion docs/api-guide/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ More information can be found in the [Documentation](https://django-rest-durin.r
[basicauth]: https://tools.ietf.org/html/rfc2617
[permission]: permissions.md
[throttling]: throttling.md
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax
[mod_wsgi_official]: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPassAuthorization.html
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
[django-rest-framework-oauth]: https://jpadilla.github.io/django-rest-framework-oauth/
Expand Down
2 changes: 1 addition & 1 deletion docs/api-guide/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Note that the exception handler will only be called for responses generated by r

The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.

To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class.
To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `.default_code` attributes on the class.

For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so:

Expand Down
14 changes: 14 additions & 0 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ When serializing the instance, default will be used if the object attribute or d

Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.

Notes regarding default value propagation from model to serializer:

All the default values from model will pass as default to the serializer and the options method.

If the default is callable then it will be propagated to & evaluated every time in the serializer but not in options method.

If the value for given field is not given then default value will be present in the serializer and available in serializer's methods. Specified validation on given field will be evaluated on default value as that field will be present in the serializer.

### `allow_null`

Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value.
Expand Down Expand Up @@ -550,6 +558,12 @@ The `HiddenField` class is usually only needed if you have some validation that

For further examples on `HiddenField` see the [validators](validators.md) documentation.

---

**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).

---

## ModelField

A generic field that can be tied to any arbitrary model field. The `ModelField` class delegates the task of serialization/deserialization to its associated model field. This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field.
Expand Down
6 changes: 6 additions & 0 deletions docs/api-guide/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ If you want the date field to be entirely hidden from the user, then use `Hidden

---

---

**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).

---

# Advanced field defaults

Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator.
Expand Down
7 changes: 7 additions & 0 deletions docs/api-guide/viewsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ The `action` decorator will route `GET` requests by default, but may also accept
def unset_password(self, request, pk=None):
...

Argument `methods` also supports HTTP methods defined as [HTTPMethod](https://docs.python.org/3/library/http.html#http.HTTPMethod). Example below is identical to the one above:

from http import HTTPMethod

@action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE])
def unset_password(self, request, pk=None):
...

The decorator allows you to override any viewset-level configuration such as `permission_classes`, `serializer_class`, `filter_backends`...:

Expand Down
3 changes: 3 additions & 0 deletions docs/community/project-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ The following template should be used for the description of the issue, and serv
- [ ] `docs` Python & Django versions
- [ ] Update the translations from [transifex](https://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py).
- [ ] Ensure documentation validates
- Build and serve docs `mkdocs serve`
- Validate links `pylinkvalidate.py -P http://127.0.0.1:8000`
- [ ] Confirm with @tomchristie that release is finalized and ready to go.
- [ ] Ensure that release date is included in pull request.
- [ ] Merge the release pull request.
Expand Down
2 changes: 2 additions & 0 deletions docs/community/third-party-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [fast-drf] - A model based library for making API development faster and easier.
* [django-requestlogs] - Providing middleware and other helpers for audit logging for REST framework.
* [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints.
* [drf-api-action][drf-api-action] - uses the power of DRF also as a library functions

[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
Expand Down Expand Up @@ -241,3 +242,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[fast-drf]: https://github.com/iashraful/fast-drf
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
[drf-api-action]: https://github.com/Ori-Roza/drf-api-action
5 changes: 5 additions & 0 deletions docs/community/tutorials-and-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ There are a wide range of resources available for learning and using Django REST
</a>
</div>

## Courses

* [Developing RESTful APIs with Django REST Framework][developing-restful-apis-with-django-rest-framework]

## Tutorials

* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
Expand Down Expand Up @@ -130,3 +134,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[pycon-us-2017]: https://www.youtube.com/watch?v=Rk6MHZdust4
[django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/
[doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/
[developing-restful-apis-with-django-rest-framework]: https://testdriven.io/courses/django-rest-framework/
2 changes: 1 addition & 1 deletion docs/topics/ajax-csrf-cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response

[cite]: https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax
[cors]: https://www.w3.org/TR/cors/
[adamchainz]: https://github.com/adamchainz
[django-cors-headers]: https://github.com/adamchainz/django-cors-headers
3 changes: 3 additions & 0 deletions requirements/requirements-documentation.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# MkDocs to build our documentation.
mkdocs>=1.1.2,<1.2
jinja2>=2.10,<3.1.0 # contextfilter has been renamed

# pylinkvalidator to check for broken links in documentation.
pylinkvalidator==0.3
4 changes: 4 additions & 0 deletions rest_framework/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils.encoding import force_str

from rest_framework import exceptions, serializers
from rest_framework.fields import empty
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict

Expand Down Expand Up @@ -149,4 +150,7 @@ def get_field_info(self, field):
for choice_value, choice_name in field.choices.items()
]

if getattr(field, 'default', None) and field.default != empty and not callable(field.default):
field_info['default'] = field.default

return field_info
2 changes: 1 addition & 1 deletion rest_framework/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def __init__(self, trailing_slash=True, use_regex_path=True):
self._url_conf = re_path
else:
self._base_pattern = '<{lookup_value}:{lookup_prefix}{lookup_url_kwarg}>'
self._default_value_pattern = 'path'
self._default_value_pattern = 'str'
self._url_conf = path
# remove regex characters from routes
_routes = []
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/schemas/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def map_serializer(self, serializer):
if isinstance(field, serializers.HiddenField):
continue

if field.required:
if field.required and not serializer.partial:
required.append(self.get_field_name(field))

schema = self.map_field(field)
Expand Down
2 changes: 0 additions & 2 deletions rest_framework/static/rest_framework/js/jquery-3.5.1.min.js

This file was deleted.

2 changes: 0 additions & 2 deletions rest_framework/static/rest_framework/js/jquery-3.6.4.min.js

This file was deleted.

2 changes: 2 additions & 0 deletions rest_framework/static/rest_framework/js/jquery-3.7.1.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion rest_framework/templates/rest_framework/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ <h4 class="modal-title" id="myModalLabel">{{ error_title }}</h4>
"csrfToken": "{{ csrf_token }}"
}
</script>
<script src="{% static "rest_framework/js/jquery-3.6.4.min.js" %}"></script>
<script src="{% static "rest_framework/js/jquery-3.7.1.min.js" %}"></script>
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/templates/rest_framework/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ <h1>{{ name }}</h1>
"csrfToken": "{% if request %}{{ csrf_token }}{% endif %}"
}
</script>
<script src="{% static "rest_framework/js/jquery-3.6.4.min.js" %}"></script>
<script src="{% static "rest_framework/js/jquery-3.7.1.min.js" %}"></script>
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/templates/rest_framework/docs/error.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ <h2>Overriding this template</h2>



<script src="{% static 'rest_framework/js/jquery-3.6.4.min.js' %}"></script>
<script src="{% static 'rest_framework/js/jquery-3.7.1.min.js' %}"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion rest_framework/templates/rest_framework/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
{% include "rest_framework/docs/auth/basic.html" %}
{% include "rest_framework/docs/auth/session.html" %}

<script src="{% static 'rest_framework/js/jquery-3.6.4.min.js' %}"></script>
<script src="{% static 'rest_framework/js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'rest_framework/js/bootstrap.min.js' %}"></script>
<script src="{% static 'rest_framework/docs/js/jquery.json-view.min.js' %}"></script>
<script src="{% static 'rest_framework/docs/js/api.js' %}"></script>
Expand Down
4 changes: 4 additions & 0 deletions rest_framework/utils/field_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.utils.text import capfirst

from rest_framework.compat import postgres_fields
from rest_framework.fields import empty
from rest_framework.validators import UniqueValidator

NUMERIC_FIELD_TYPES = (
Expand Down Expand Up @@ -127,6 +128,9 @@ def get_field_kwargs(field_name, model_field):
kwargs['read_only'] = True
return kwargs

if model_field.default is not None and model_field.default != empty and not callable(model_field.default):
kwargs['default'] = model_field.default

if model_field.has_default() or model_field.blank or model_field.null:
kwargs['required'] = False

Expand Down
2 changes: 1 addition & 1 deletion rest_framework/utils/model_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""
from collections import namedtuple

FieldInfo = namedtuple('FieldResult', [
FieldInfo = namedtuple('FieldInfo', [
'pk', # Model field instance
'fields', # Dict of field name -> model field instance
'forward_relations', # Dict of field name -> RelationInfo
Expand Down
13 changes: 13 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#! /usr/bin/env python3
import os
import sys

import pytest
Expand Down Expand Up @@ -34,6 +35,18 @@ def is_class(string):
'--cov-report', 'xml',
] + pytest_args

try:
pytest_args.remove('--no-pkgroot')
except ValueError:
pass
else:
sys.path.pop(0)

# import rest_framework before pytest re-adds the package root directory.
import rest_framework
package_dir = os.path.join(os.getcwd(), 'rest_framework')
assert not rest_framework.__file__.startswith(package_dir)

if first_arg.startswith('-'):
# `runtests.py [flags]`
pytest_args = ['tests'] + pytest_args
Expand Down
19 changes: 5 additions & 14 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import os
import sys

import django
from django.core import management


def pytest_addoption(parser):
parser.addoption('--no-pkgroot', action='store_true', default=False,
help='Remove package root directory from sys.path, ensuring that '
'rest_framework is imported from the installed site-packages. '
'Used for testing the distribution.')
parser.addoption('--staticfiles', action='store_true', default=False,
help='Run tests with static files collection, using manifest '
'staticfiles storage. Used for testing the distribution.')
Expand Down Expand Up @@ -87,19 +82,15 @@ def pytest_configure(config):
'guardian',
)

if config.getoption('--no-pkgroot'):
sys.path.pop(0)

# import rest_framework before pytest re-adds the package root directory.
import rest_framework
package_dir = os.path.join(os.getcwd(), 'rest_framework')
assert not rest_framework.__file__.startswith(package_dir)

# Manifest storage will raise an exception if static files are not present (ie, a packaging failure).
if config.getoption('--staticfiles'):
import rest_framework
settings.STATIC_ROOT = os.path.join(os.path.dirname(rest_framework.__file__), 'static-root')
settings.STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
backend = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
if django.VERSION < (4, 2):
settings.STATICFILES_STORAGE = backend
else:
settings.STORAGES['staticfiles']['BACKEND'] = backend

django.setup()

Expand Down
50 changes: 50 additions & 0 deletions tests/schemas/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,56 @@ class View(generics.GenericAPIView):
assert list(schema['properties']['nested']['properties'].keys()) == ['number']
assert schema['properties']['nested']['required'] == ['number']

def test_response_body_partial_serializer(self):
path = '/'
method = 'GET'

class ItemSerializer(serializers.Serializer):
text = serializers.CharField()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.partial = True

class View(generics.GenericAPIView):
serializer_class = ItemSerializer

view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view

responses = inspector.get_responses(path, method)
assert responses == {
'200': {
'description': '',
'content': {
'application/json': {
'schema': {
'type': 'array',
'items': {
'$ref': '#/components/schemas/Item'
},
},
},
},
},
}
components = inspector.get_components(path, method)
assert components == {
'Item': {
'type': 'object',
'properties': {
'text': {
'type': 'string',
},
},
}
}

def test_list_response_body_generation(self):
"""Test that an array schema is returned for list views."""
path = '/'
Expand Down
Loading

0 comments on commit b16826c

Please sign in to comment.