Skip to content

Commit

Permalink
Merge branch 'main' into pyup-scheduled-update-2023-09-18
Browse files Browse the repository at this point in the history
  • Loading branch information
sliverc authored Sep 20, 2023
2 parents 253abc1 + b75255c commit d842ed4
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
env:
PYTHON: ${{ matrix.python-version }}
steps:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Note that in line with [Django REST framework policy](https://www.django-rest-framework.org/topics/release-notes/),
any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.

## [Unreleased]

### Fixed

* Fixed OpenAPI schema generation for `Serializer` when used inside another `Serializer` or as a child of `ListField`.

### Removed

* Removed support for Python 3.7.

## [6.1.0] - 2023-08-25

This is the last release supporting Python 3.7.

### Added

* Added support for Python 3.11.
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ As a Django REST framework JSON:API (short DJA) we are trying to address followi
Requirements
------------

1. Python (3.7, 3.8, 3.9, 3.10, 3.11)
1. Python (3.8, 3.9, 3.10, 3.11)
2. Django (3.2, 4.0, 4.1, 4.2)
3. Django REST framework (3.13, 3.14)

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ like the following:

## Requirements

1. Python (3.7, 3.8, 3.9, 3.10, 3.11)
1. Python (3.8, 3.9, 3.10, 3.11)
2. Django (3.2, 4.0, 4.1, 4.2)
3. Django REST framework (3.13, 3.14)

Expand Down
22 changes: 22 additions & 0 deletions example/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Company,
Entry,
ProjectType,
Questionnaire,
ResearchProject,
TaggedItem,
)
Expand Down Expand Up @@ -140,3 +141,24 @@ def future_projects(self, create, extracted, **kwargs):
if extracted:
for project in extracted:
self.future_projects.add(project)


class QuestionnaireFactory(factory.django.DjangoModelFactory):
class Meta:
model = Questionnaire

name = factory.LazyAttribute(lambda x: faker.text())
questions = [
{
"text": "What is your name?",
"required": True,
},
{
"text": "What is your quest?",
"required": False,
},
{
"text": "What is the air-speed velocity of an unladen swallow?",
},
]
metadata = {"author": "Bridgekeeper"}
28 changes: 28 additions & 0 deletions example/migrations/0013_questionnaire.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.5 on 2023-09-07 02:35

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("example", "0012_author_full_name"),
]

operations = [
migrations.CreateModel(
name="Questionnaire",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("questions", models.JSONField()),
],
),
]
18 changes: 18 additions & 0 deletions example/migrations/0014_questionnaire_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2023-09-12 07:12

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("example", "0013_questionnaire"),
]

operations = [
migrations.AddField(
model_name="questionnaire",
name="metadata",
field=models.JSONField(default={}),
preserve_default=False,
),
]
6 changes: 6 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,9 @@ class Company(models.Model):

def __str__(self):
return self.name


class Questionnaire(models.Model):
name = models.CharField(max_length=100)
questions = models.JSONField()
metadata = models.JSONField()
20 changes: 20 additions & 0 deletions example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
LabResults,
Project,
ProjectType,
Questionnaire,
ResearchProject,
TaggedItem,
)
Expand Down Expand Up @@ -421,3 +422,22 @@ class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = "__all__"


class QuestionSerializer(serializers.Serializer):
text = serializers.CharField()
required = serializers.BooleanField(default=False)


class QuestionnaireMetadataSerializer(serializers.Serializer):
author = serializers.CharField()
producer = serializers.CharField(default=None)


class QuestionnaireSerializer(serializers.ModelSerializer):
questions = serializers.ListField(child=QuestionSerializer())
metadata = QuestionnaireMetadataSerializer()

class Meta:
model = Questionnaire
fields = ("name", "questions", "metadata")
2 changes: 2 additions & 0 deletions example/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CommentFactory,
CompanyFactory,
EntryFactory,
QuestionnaireFactory,
ResearchProjectFactory,
TaggedItemFactory,
)
Expand All @@ -27,6 +28,7 @@
register(ArtProjectFactory)
register(ResearchProjectFactory)
register(CompanyFactory)
register(QuestionnaireFactory)


@pytest.fixture
Expand Down
40 changes: 40 additions & 0 deletions example/tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,46 @@ def test_schema_id_field():
assert "id" not in company_properties["attributes"]["properties"]


def test_schema_subserializers():
"""Schema for child Serializers reflects the actual response structure."""
patterns = [
re_path(
"^questionnaires/?$", views.QuestionnaireViewset.as_view({"get": "list"})
),
]
generator = SchemaGenerator(patterns=patterns)

request = create_request("/")
schema = generator.get_schema(request=request)

assert {
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"author": {"type": "string"},
"producer": {"type": "string"},
},
"required": ["author"],
},
"questions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"text": {"type": "string"},
"required": {"type": "boolean", "default": False},
},
"required": ["text"],
},
},
"name": {"type": "string", "maxLength": 100},
},
"required": ["name", "questions", "metadata"],
} == schema["components"]["schemas"]["Questionnaire"]["properties"]["attributes"]


def test_schema_parameters_include():
"""Include paramater is only used when serializer defines included_serializers."""
patterns = [
Expand Down
33 changes: 33 additions & 0 deletions example/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,39 @@ def test_model_serializer_with_implicit_fields(self, comment, client):
assert response.status_code == 200
assert expected == response.json()

def test_model_serializer_with_subserializers(self, questionnaire, client):
expected = {
"data": {
"type": "questionnaires",
"id": str(questionnaire.pk),
"attributes": {
"name": questionnaire.name,
"questions": [
{
"text": "What is your name?",
"required": True,
},
{
"text": "What is your quest?",
"required": False,
},
{
"text": "What is the air-speed velocity of an unladen swallow?",
"required": False,
},
],
"metadata": {"author": "Bridgekeeper", "producer": None},
},
},
}

response = client.get(
reverse("questionnaire-detail", kwargs={"pk": questionnaire.pk})
)

assert response.status_code == 200
assert expected == response.json()


class TestPolymorphicModelSerializer(TestCase):
def setUp(self):
Expand Down
2 changes: 2 additions & 0 deletions example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
NonPaginatedEntryViewSet,
ProjectTypeViewset,
ProjectViewset,
QuestionnaireViewset,
)

router = routers.DefaultRouter(trailing_slash=False)
Expand All @@ -32,6 +33,7 @@
router.register(r"projects", ProjectViewset)
router.register(r"project-types", ProjectTypeViewset)
router.register(r"lab-results", LabResultViewSet)
router.register(r"questionnaires", QuestionnaireViewset)

urlpatterns = [
path("", include(router.urls)),
Expand Down
2 changes: 2 additions & 0 deletions example/urls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
NonPaginatedEntryViewSet,
ProjectTypeViewset,
ProjectViewset,
QuestionnaireViewset,
)

router = routers.DefaultRouter(trailing_slash=False)
Expand All @@ -38,6 +39,7 @@
router.register(r"projects", ProjectViewset)
router.register(r"project-types", ProjectTypeViewset)
router.register(r"lab-results", LabResultViewSet)
router.register(r"questionnaires", QuestionnaireViewset)

# for the old tests
router.register(r"identities", Identity)
Expand Down
7 changes: 7 additions & 0 deletions example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
LabResults,
Project,
ProjectType,
Questionnaire,
)
from example.serializers import (
AuthorDetailSerializer,
Expand All @@ -43,6 +44,7 @@
LabResultsSerializer,
ProjectSerializer,
ProjectTypeSerializer,
QuestionnaireSerializer,
)

HTTP_422_UNPROCESSABLE_ENTITY = 422
Expand Down Expand Up @@ -292,3 +294,8 @@ class LabResultViewSet(ReadOnlyModelViewSet):
"__all__": [],
"author": ["author__bio", "author__entries"],
}


class QuestionnaireViewset(ModelViewSet):
queryset = Questionnaire.objects.all()
serializer_class = QuestionnaireSerializer
8 changes: 8 additions & 0 deletions rest_framework_json_api/schemas/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,14 @@ def map_serializer(self, serializer):
and 'links'.
"""
# TODO: remove attributes, etc. for relationshipView??
if isinstance(
serializer.parent, (serializers.ListField, serializers.BaseSerializer)
):
# Return plain non-JSON:API serializer schema for serializers nested inside
# a Serializer or a ListField, as those don't use the full JSON:API
# serializer schemas.
return super().map_serializer(serializer)

required = []
attributes = {}
relationships_required = []
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def get_package_data(package):
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist =
py{37,38,39,310}-django32-drf{313,314,master},
py{38,39,310}-django32-drf{313,314,master},
py{38,39,310}-django40-drf{313,314,master},
py{38,39,310,311}-django41-drf{314,master},
py{38,39,310,311}-django42-drf{314,master},
Expand Down Expand Up @@ -50,7 +50,7 @@ deps =
commands =
sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html

[testenv:py{37,38,39,310}-django32-drfmaster]
[testenv:py{38,39,310}-django32-drfmaster]
ignore_outcome = true

[testenv:py{38,39,310}-django40-drfmaster]
Expand Down

0 comments on commit d842ed4

Please sign in to comment.