diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbce7ce..43919ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ any parts of the framework not mentioned in the documentation should generally b * Removed support for Django 3.0. * Removed support for Django 3.1. * Removed support for Python 3.6. +* Removed obsolete method `utils.get_included_serializers`. +* Removed optional `format_type` argument of `utils.format_link_segment`. +* Removed `format_type`s default argument of `utils.format_value`. `format_type` is now required. ## [4.3.0] - 2021-12-10 diff --git a/example/factories.py b/example/factories.py index 96b85cad..4ca1e0b1 100644 --- a/example/factories.py +++ b/example/factories.py @@ -42,7 +42,7 @@ class Meta: email = factory.LazyAttribute(lambda x: faker.email()) bio = factory.RelatedFactory("example.factories.AuthorBioFactory", "author") - type = factory.SubFactory(AuthorTypeFactory) + author_type = factory.SubFactory(AuthorTypeFactory) class AuthorBioFactory(factory.django.DjangoModelFactory): diff --git a/example/fixtures/blogentry.json b/example/fixtures/blogentry.json index 464573e4..ceb4a9fe 100644 --- a/example/fixtures/blogentry.json +++ b/example/fixtures/blogentry.json @@ -77,7 +77,7 @@ "modified_at": "2016-05-02T10:09:48.277", "name": "Alice", "email": "alice@example.com", - "type": null + "author_type": null } }, { @@ -88,7 +88,7 @@ "modified_at": "2016-05-02T10:09:57.133", "name": "Bob", "email": "bob@example.com", - "type": null + "author_type": null } }, { diff --git a/example/migrations/0011_rename_type_author_author_type_and_more.py b/example/migrations/0011_rename_type_author_author_type_and_more.py new file mode 100644 index 00000000..191e901c --- /dev/null +++ b/example/migrations/0011_rename_type_author_author_type_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.0 on 2021-12-29 13:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("example", "0010_auto_20210714_0809"), + ] + + operations = [ + migrations.RenameField( + model_name="author", + old_name="type", + new_name="author_type", + ), + migrations.AlterField( + model_name="project", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), + ), + ] diff --git a/example/models.py b/example/models.py index 63d93e33..c3850076 100644 --- a/example/models.py +++ b/example/models.py @@ -54,7 +54,7 @@ class Meta: class Author(BaseModel): name = models.CharField(max_length=50) email = models.EmailField() - type = models.ForeignKey(AuthorType, null=True, on_delete=models.CASCADE) + author_type = models.ForeignKey(AuthorType, null=True, on_delete=models.CASCADE) def __str__(self): return self.name diff --git a/example/serializers.py b/example/serializers.py index 0e1022cc..b9bf71d6 100644 --- a/example/serializers.py +++ b/example/serializers.py @@ -252,10 +252,13 @@ class AuthorSerializer(serializers.ModelSerializer): help_text="help for defaults", ) initials = serializers.SerializerMethodField() - included_serializers = {"bio": AuthorBioSerializer, "type": AuthorTypeSerializer} + included_serializers = { + "bio": AuthorBioSerializer, + "author_type": AuthorTypeSerializer, + } related_serializers = { "bio": "example.serializers.AuthorBioSerializer", - "type": "example.serializers.AuthorTypeSerializer", + "author_type": "example.serializers.AuthorTypeSerializer", "comments": "example.serializers.CommentSerializer", "entries": "example.serializers.EntrySerializer", "first_entry": "example.serializers.EntrySerializer", @@ -270,7 +273,7 @@ class Meta: "entries", "comments", "first_entry", - "type", + "author_type", "secrets", "defaults", "initials", diff --git a/example/tests/__snapshots__/test_openapi.ambr b/example/tests/__snapshots__/test_openapi.ambr index 48aa21fa..cb03ca73 100644 --- a/example/tests/__snapshots__/test_openapi.ambr +++ b/example/tests/__snapshots__/test_openapi.ambr @@ -124,6 +124,9 @@ }, "relationships": { "properties": { + "authorType": { + "$ref": "#/components/schemas/reltoone" + }, "bio": { "$ref": "#/components/schemas/reltoone" }, @@ -135,9 +138,6 @@ }, "firstEntry": { "$ref": "#/components/schemas/reltoone" - }, - "type": { - "$ref": "#/components/schemas/reltoone" } }, "type": "object" @@ -532,6 +532,9 @@ }, "relationships": { "properties": { + "authorType": { + "$ref": "#/components/schemas/reltoone" + }, "bio": { "$ref": "#/components/schemas/reltoone" }, @@ -543,9 +546,6 @@ }, "firstEntry": { "$ref": "#/components/schemas/reltoone" - }, - "type": { - "$ref": "#/components/schemas/reltoone" } }, "type": "object" diff --git a/example/tests/test_format_keys.py b/example/tests/test_format_keys.py index 8d99eec7..b91cf595 100644 --- a/example/tests/test_format_keys.py +++ b/example/tests/test_format_keys.py @@ -59,7 +59,7 @@ def test_options_format_field_names(db, client): "bio", "entries", "firstEntry", - "type", + "authorType", "comments", "secrets", "defaults", diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index c58b5131..21a641f8 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -1,4 +1,3 @@ -import pytest from django.contrib.auth import get_user_model from django.test import override_settings from django.urls import reverse @@ -216,28 +215,3 @@ def test_404_error_pointer(self): response = self.client.get(not_found_url) assert 404 == response.status_code assert errors == response.json() - - -@pytest.mark.django_db -def test_patch_allow_field_type(author, author_type_factory, client): - """ - Verify that type field may be updated. - """ - # TODO remove in next major version 5.0.0 see serializers.ReservedFieldNamesMixin - with pytest.deprecated_call(): - author_type = author_type_factory() - url = reverse("author-detail", args=[author.id]) - - data = { - "data": { - "id": author.id, - "type": "authors", - "relationships": { - "data": {"id": author_type.id, "type": "author-type"} - }, - } - } - - response = client.patch(url, data=data) - - assert response.status_code == 200 diff --git a/example/tests/test_views.py b/example/tests/test_views.py index 560f7547..a3aa1444 100644 --- a/example/tests/test_views.py +++ b/example/tests/test_views.py @@ -423,7 +423,7 @@ def test_get_related_serializer_class_many(self): self.assertEqual(got, EntrySerializer) def test_get_serializer_comes_from_included_serializers(self): - kwargs = {"pk": self.author.id, "related_field": "type"} + kwargs = {"pk": self.author.id, "related_field": "author_type"} view = self._get_view(kwargs) related_serializers = view.get_serializer_class().related_serializers delattr(view.get_serializer_class(), "related_serializers") @@ -470,14 +470,14 @@ def test_retrieve_related_single_reverse_lookup(self): def test_retrieve_related_single(self): url = reverse( "author-related", - kwargs={"pk": self.author.type.pk, "related_field": "type"}, + kwargs={"pk": self.author.author_type.pk, "related_field": "author_type"}, ) resp = self.client.get(url) expected = { "data": { "type": "authorTypes", - "id": str(self.author.type.id), - "attributes": {"name": str(self.author.type.name)}, + "id": str(self.author.author_type.id), + "attributes": {"name": str(self.author.author_type.name)}, } } self.assertEqual(resp.status_code, 200) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 61824749..f77a6501 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -4,7 +4,7 @@ from rest_framework import parsers from rest_framework.exceptions import ParseError -from rest_framework_json_api import exceptions, renderers, serializers +from rest_framework_json_api import exceptions, renderers from rest_framework_json_api.utils import get_resource_name, undo_format_field_names @@ -160,12 +160,8 @@ def parse(self, stream, media_type=None, parser_context=None): ) # Construct the return data - serializer_class = getattr(view, "serializer_class", None) parsed_data = {"id": data.get("id")} if "id" in data else {} - # TODO remove in next major version 5.0.0 see serializers.ReservedFieldNamesMixin - if serializer_class is not None: - if issubclass(serializer_class, serializers.PolymorphicModelSerializer): - parsed_data["type"] = data.get("type") + parsed_data["type"] = data.get("type") parsed_data.update(self.parse_attributes(data)) parsed_data.update(self.parse_relationships(data)) parsed_data.update(self.parse_metadata(result)) diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index cfc6cf3f..bfdfec71 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -1,4 +1,3 @@ -import warnings from collections import OrderedDict from collections.abc import Mapping @@ -157,7 +156,7 @@ def validate_path(serializer_class, field_path, path): class ReservedFieldNamesMixin: """Ensures that reserved field names are not used and an error raised instead.""" - _reserved_field_names = {"meta", "results"} + _reserved_field_names = {"meta", "results", "type"} def get_fields(self): fields = super().get_fields() @@ -171,18 +170,6 @@ def get_fields(self): f"{', '.join(sorted(found_reserved_field_names))}" ) - if "type" in fields: - # see https://jsonapi.org/format/#document-resource-object-fields - warnings.warn( - DeprecationWarning( - f"Field name 'type' found in serializer class " - f"{self.__class__.__module__}.{self.__class__.__qualname__} " - f"which is not allowed according to the JSON:API spec and " - f"won't be supported anymore in the next major DJA release. " - f"Rename 'type' field to something else. " - ) - ) - return fields diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index b15f4c6c..aba9de5a 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -1,6 +1,5 @@ import inspect import operator -import warnings from collections import OrderedDict import inflection @@ -147,23 +146,14 @@ def undo_format_field_name(field_name): return field_name -def format_link_segment(value, format_type=None): +def format_link_segment(value): """ Takes a string value and returns it with formatted keys as set in `format_type` or `JSON_API_FORMAT_RELATED_LINKS`. :format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore' """ - if format_type is None: - format_type = json_api_settings.FORMAT_RELATED_LINKS - else: - warnings.warn( - DeprecationWarning( - "Using `format_type` argument is deprecated." - "Use `format_value` instead." - ) - ) - + format_type = json_api_settings.FORMAT_RELATED_LINKS return format_value(value, format_type) @@ -179,15 +169,7 @@ def undo_format_link_segment(value): return value -def format_value(value, format_type=None): - if format_type is None: - warnings.warn( - DeprecationWarning( - "Using `format_value` without passing on `format_type` argument is deprecated." - "Use `format_field_name` instead." - ) - ) - format_type = json_api_settings.FORMAT_FIELD_NAMES +def format_value(value, format_type): if format_type == "dasherize": # inflection can't dasherize camelCase value = inflection.underscore(value) @@ -342,17 +324,6 @@ def get_default_included_resources_from_serializer(serializer): return list(getattr(meta, "included_resources", [])) -def get_included_serializers(serializer): - warnings.warn( - DeprecationWarning( - "Using of `get_included_serializers(serializer)` function is deprecated." - "Use `serializer.included_serializers` instead." - ) - ) - - return getattr(serializer, "included_serializers", dict()) - - def get_relation_instance(resource_instance, source, serializer): try: relation_instance = operator.attrgetter(source)(resource_instance) diff --git a/setup.cfg b/setup.cfg index afe7faab..527ddd6b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,10 +58,6 @@ DJANGO_SETTINGS_MODULE=example.settings.test filterwarnings = error::DeprecationWarning error::PendingDeprecationWarning - # TODO remove in next major version of DJA 5.0.0 - # this deprecation warning filter needs to be added as AuthorSerializer is used in - # too many tests which introduced the type field name in tests - ignore:Field name 'type' testpaths = example tests diff --git a/tests/test_parsers.py b/tests/test_parsers.py index f1207757..45ac5232 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -63,6 +63,7 @@ def test_parse_formats_field_names( result = parse(data, parser_context) assert result == { "id": "123", + "type": "BasicModel", "test_attribute": "test-value", "test_relationship": {"id": "123", "type": "TestRelationship"}, } @@ -85,7 +86,7 @@ def test_parse_with_default_arguments(self, parse): }, } result = parse(data, None) - assert result == {} + assert result == {"type": "BasicModel"} def test_parse_preserves_json_value_field_names( self, settings, parse, parser_context diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 19ee23d6..e1b14ed8 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -50,12 +50,3 @@ class ReservedFieldNamesSerializer(serializers.Serializer): "ReservedFieldNamesSerializer uses following reserved field name(s) which is " "not allowed: meta, results" ) - - -def test_serializer_fields_deprecated_field_name_type(): - with pytest.deprecated_call(): - - class TypeFieldNameSerializer(serializers.Serializer): - type = serializers.CharField() - - TypeFieldNameSerializer().fields diff --git a/tests/test_utils.py b/tests/test_utils.py index d8810c0b..efb1325d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,4 @@ import pytest -from django.db import models from rest_framework import status from rest_framework.fields import Field from rest_framework.generics import GenericAPIView @@ -13,7 +12,6 @@ format_link_segment, format_resource_type, format_value, - get_included_serializers, get_related_resource_type, get_resource_name, get_resource_type_from_serializer, @@ -23,13 +21,12 @@ ) from tests.models import ( BasicModel, - DJAModel, ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, ) -from tests.serializers import BasicModelSerializer, ManyToManyTargetSerializer +from tests.serializers import BasicModelSerializer def test_get_resource_name_no_view(): @@ -256,11 +253,6 @@ def test_format_link_segment(settings, format_type, output): assert format_link_segment("first_Name") == output -def test_format_link_segment_deprecates_format_type_argument(): - with pytest.deprecated_call(): - assert "first-name" == format_link_segment("first_name", "dasherize") - - @pytest.mark.parametrize( "format_links,output", [ @@ -289,11 +281,6 @@ def test_format_value(settings, format_type, output): assert format_value("first_name", format_type) == output -def test_format_value_deprecates_default_format_type_argument(): - with pytest.deprecated_call(): - assert "first_name" == format_value("first_name") - - @pytest.mark.parametrize( "resource_type,pluralize,output", [ @@ -347,40 +334,6 @@ class PlainRelatedResourceTypeSerializer(serializers.Serializer): assert get_related_resource_type(field) == output -def test_get_included_serializers(): - class DeprecatedIncludedSerializersModel(DJAModel): - self = models.ForeignKey("self", on_delete=models.CASCADE) - target = models.ForeignKey(ManyToManyTarget, on_delete=models.CASCADE) - other_target = models.ForeignKey(ManyToManyTarget, on_delete=models.CASCADE) - - class Meta: - app_label = "tests" - - class DeprecatedIncludedSerializersSerializer(serializers.ModelSerializer): - included_serializers = { - "self": "self", - "target": ManyToManyTargetSerializer, - "other_target": "tests.serializers.ManyToManyTargetSerializer", - } - - class Meta: - model = DeprecatedIncludedSerializersModel - fields = ("self", "other_target", "target") - - with pytest.deprecated_call(): - included_serializers = get_included_serializers( - DeprecatedIncludedSerializersSerializer - ) - - expected_included_serializers = { - "self": DeprecatedIncludedSerializersSerializer, - "target": ManyToManyTargetSerializer, - "other_target": ManyToManyTargetSerializer, - } - - assert included_serializers == expected_included_serializers - - def test_get_resource_type_from_serializer_without_resource_name_raises_error(): class SerializerWithoutResourceName(serializers.Serializer): something = Field()