diff --git a/CHANGELOG.md b/CHANGELOG.md index ce154cbb..7a1c3444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ any parts of the framework not mentioned in the documentation should generally b * Added support to overwrite serializer methods in customized schema class * Adjusted some still old formatted strings to f-strings. +* Replaced `OrderedDict` with `dict` which is also ordered since Python 3.7. ### Fixed diff --git a/rest_framework_json_api/metadata.py b/rest_framework_json_api/metadata.py index 14b9f69b..c7f7b7b4 100644 --- a/rest_framework_json_api/metadata.py +++ b/rest_framework_json_api/metadata.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from django.db.models.fields import related from django.utils.encoding import force_str from rest_framework import serializers @@ -65,7 +63,7 @@ class JSONAPIMetadata(SimpleMetadata): ) def determine_metadata(self, request, view): - metadata = OrderedDict() + metadata = {} metadata["name"] = view.get_view_name() metadata["description"] = view.get_view_description() metadata["renders"] = [ @@ -92,19 +90,17 @@ def get_serializer_info(self, serializer): # Remove the URL field if present serializer.fields.pop(api_settings.URL_FIELD_NAME, None) - return OrderedDict( - [ - (format_field_name(field_name), self.get_field_info(field)) - for field_name, field in serializer.fields.items() - ] - ) + return { + format_field_name(field_name): self.get_field_info(field) + for field_name, field in serializer.fields.items() + } def get_field_info(self, field): """ Given an instance of a serializer field, return a dictionary of metadata about it. """ - field_info = OrderedDict() + field_info = {} serializer = field.parent if isinstance(field, serializers.ManyRelatedField): diff --git a/rest_framework_json_api/pagination.py b/rest_framework_json_api/pagination.py index 6cbd744c..0c0d3bb9 100644 --- a/rest_framework_json_api/pagination.py +++ b/rest_framework_json_api/pagination.py @@ -1,7 +1,6 @@ """ Pagination fields """ -from collections import OrderedDict from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination from rest_framework.utils.urls import remove_query_param, replace_query_param @@ -36,22 +35,18 @@ def get_paginated_response(self, data): { "results": data, "meta": { - "pagination": OrderedDict( - [ - ("page", self.page.number), - ("pages", self.page.paginator.num_pages), - ("count", self.page.paginator.count), - ] - ) + "pagination": { + "page": self.page.number, + "pages": self.page.paginator.num_pages, + "count": self.page.paginator.count, + } + }, + "links": { + "first": self.build_link(1), + "last": self.build_link(self.page.paginator.num_pages), + "next": self.build_link(next), + "prev": self.build_link(previous), }, - "links": OrderedDict( - [ - ("first", self.build_link(1)), - ("last", self.build_link(self.page.paginator.num_pages)), - ("next", self.build_link(next)), - ("prev", self.build_link(previous)), - ] - ), } ) @@ -97,21 +92,17 @@ def get_paginated_response(self, data): { "results": data, "meta": { - "pagination": OrderedDict( - [ - ("count", self.count), - ("limit", self.limit), - ("offset", self.offset), - ] - ) + "pagination": { + "count": self.count, + "limit": self.limit, + "offset": self.offset, + } + }, + "links": { + "first": self.get_first_link(), + "last": self.get_last_link(), + "next": self.get_next_link(), + "prev": self.get_previous_link(), }, - "links": OrderedDict( - [ - ("first", self.get_first_link()), - ("last", self.get_last_link()), - ("next", self.get_next_link()), - ("prev", self.get_previous_link()), - ] - ), } ) diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 6eb69bdb..bb360ebb 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -1,5 +1,4 @@ import json -from collections import OrderedDict import inflection from django.core.exceptions import ImproperlyConfigured @@ -104,7 +103,7 @@ def get_url(self, name, view_name, kwargs, request): def get_links(self, obj=None, lookup_field="pk"): request = self.context.get("request", None) view = self.context.get("view", None) - return_data = OrderedDict() + return_data = {} kwargs = { lookup_field: getattr(obj, lookup_field) @@ -257,7 +256,7 @@ def to_representation(self, value): if resource_type is None or not self._skip_polymorphic_optimization: resource_type = get_resource_type_from_instance(value) - return OrderedDict([("type", resource_type), ("id", str(pk))]) + return {"type": resource_type, "id": str(pk)} def get_resource_type_from_included_serializer(self): """ @@ -301,12 +300,10 @@ def get_choices(self, cutoff=None): if cutoff is not None: queryset = queryset[:cutoff] - return OrderedDict( - [ - (json.dumps(self.to_representation(item)), self.display_value(item)) - for item in queryset - ] - ) + return { + json.dumps(self.to_representation(item)): self.display_value(item) + for item in queryset + } class PolymorphicResourceRelatedField(ResourceRelatedField): diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 0540a39f..7263b96b 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -2,13 +2,13 @@ Renderers """ import copy -from collections import OrderedDict, defaultdict +from collections import defaultdict from collections.abc import Iterable import inflection from django.db.models import Manager from django.template import loader -from django.utils import encoding +from django.utils.encoding import force_str from rest_framework import relations, renderers from rest_framework.fields import SkipField, get_attribute from rest_framework.relations import PKOnlyObject @@ -56,7 +56,7 @@ def extract_attributes(cls, fields, resource): """ Builds the `attributes` object of the JSON:API resource object. """ - data = OrderedDict() + data = {} for field_name, field in iter(fields.items()): # ID is always provided in the root of JSON:API so remove it from attributes if field_name == "id": @@ -89,7 +89,7 @@ def extract_relationships(cls, fields, resource, resource_instance): # Avoid circular deps from rest_framework_json_api.relations import ResourceRelatedField - data = OrderedDict() + data = {} # Don't try to extract relationships from a non-existent resource if resource_instance is None: @@ -125,16 +125,10 @@ def extract_relationships(cls, fields, resource, resource_instance): relation_instance if relation_instance is not None else list() ) - for related_object in relation_queryset: - relation_data.append( - OrderedDict( - [ - ("type", relation_type), - ("id", encoding.force_str(related_object.pk)), - ] - ) - ) - + relation_data = [ + {"type": relation_type, "id": force_str(related_object.pk)} + for related_object in relation_queryset + ] data.update( { field_name: { @@ -171,18 +165,12 @@ def extract_relationships(cls, fields, resource, resource_instance): if not resolved: continue relation_id = relation if resource.get(field_name) else None - relation_data = { - "data": ( - OrderedDict( - [ - ("type", relation_type), - ("id", encoding.force_str(relation_id)), - ] - ) - if relation_id is not None - else None - ) - } + relation_data = {"data": None} + if relation_id is not None: + relation_data["data"] = { + "type": relation_type, + "id": force_str(relation_id), + } if isinstance( field, relations.HyperlinkedRelatedField @@ -233,12 +221,10 @@ def extract_relationships(cls, fields, resource, resource_instance): ) relation_data.append( - OrderedDict( - [ - ("type", nested_resource_instance_type), - ("id", encoding.force_str(nested_resource_instance.pk)), - ] - ) + { + "type": nested_resource_instance_type, + "id": force_str(nested_resource_instance.pk), + } ) data.update( { @@ -419,7 +405,7 @@ def extract_meta(cls, serializer, resource): else: meta = getattr(serializer, "Meta", None) meta_fields = getattr(meta, "meta_fields", []) - data = OrderedDict() + data = {} for field_name in meta_fields: data.update({field_name: resource.get(field_name)}) return data @@ -457,37 +443,33 @@ def build_json_resource_obj( # 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) - resource_data = [ - ("type", resource_name), - ( - "id", - encoding.force_str(resource_instance.pk) if resource_instance else None, - ), - ("attributes", cls.extract_attributes(fields, resource)), - ] + resource_id = force_str(resource_instance.pk) if resource_instance else None + resource_data = { + "type": resource_name, + "id": resource_id, + "attributes": cls.extract_attributes(fields, resource), + } relationships = cls.extract_relationships(fields, resource, resource_instance) if relationships: - resource_data.append(("relationships", relationships)) + resource_data["relationships"] = relationships # Add 'self' link if field is present and valid if api_settings.URL_FIELD_NAME in resource and isinstance( fields[api_settings.URL_FIELD_NAME], relations.RelatedField ): - resource_data.append( - ("links", {"self": resource[api_settings.URL_FIELD_NAME]}) - ) + resource_data["links"] = {"self": resource[api_settings.URL_FIELD_NAME]} meta = cls.extract_meta(serializer, resource) if meta: - resource_data.append(("meta", utils.format_field_names(meta))) + resource_data["meta"] = utils.format_field_names(meta) - return OrderedDict(resource_data) + return resource_data def render_relationship_view( self, data, accepted_media_type=None, renderer_context=None ): # Special case for RelationshipView view = renderer_context.get("view", None) - render_data = OrderedDict([("data", data)]) + render_data = {"data": data} links = view.get_links() if links: render_data.update({"links": links}), @@ -615,7 +597,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None): ) # Make sure we render data in a specific order - render_data = OrderedDict() + render_data = {} if isinstance(data, dict) and data.get("links"): render_data["links"] = data.get("links") diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index 91f86464..a288471e 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from collections.abc import Mapping import inflection @@ -95,7 +94,7 @@ def __init__(self, *args, **kwargs): pass else: fieldset = request.query_params.get(param_name).split(",") - # iterate over a *copy* of self.fields' underlying OrderedDict, because we may + # iterate over a *copy* of self.fields' underlying dict, because we may # modify the original during the iteration. # self.fields is a `rest_framework.utils.serializer_helpers.BindingDict` for field_name, _field in self.fields.fields.copy().items(): @@ -305,7 +304,7 @@ def get_field_names(self, declared_fields, info): """ meta_fields = getattr(self.Meta, "meta_fields", []) - declared = OrderedDict() + declared = {} for field_name in set(declared_fields.keys()): field = declared_fields[field_name] if field_name not in meta_fields: diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index b7f2f9a0..3d374eed 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -1,6 +1,5 @@ import inspect import operator -from collections import OrderedDict import inflection from django.conf import settings @@ -107,11 +106,7 @@ def format_field_names(obj, format_type=None): format_type = json_api_settings.FORMAT_FIELD_NAMES if isinstance(obj, dict): - formatted = OrderedDict() - for key, value in obj.items(): - key = format_value(key, format_type) - formatted[key] = value - return formatted + return {format_value(key, format_type): value for key, value in obj.items()} return obj diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 0b3df693..90bb5574 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -23,7 +23,6 @@ from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer from rest_framework_json_api.utils import ( Hyperlink, - OrderedDict, get_included_resources, get_resource_type_from_instance, undo_format_link_segment, @@ -275,7 +274,7 @@ def get_url(self, name, view_name, kwargs, request): return Hyperlink(url, name) def get_links(self): - return_data = OrderedDict() + return_data = {} self_link = self.get_url( "self", self.self_link_view_name, self.kwargs, self.request ) @@ -284,9 +283,9 @@ def get_links(self): "related", self.related_link_view_name, related_kwargs, self.request ) if self_link: - return_data.update({"self": self_link}) + return_data["self"] = self_link if related_link: - return_data.update({"related": related_link}) + return_data["related"] = related_link return return_data def get(self, request, *args, **kwargs): diff --git a/tests/test_pagination.py b/tests/test_pagination.py index c09ac71c..10f0ebbf 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from rest_framework.request import Request from rest_framework_json_api.pagination import JsonApiLimitOffsetPagination @@ -27,28 +25,18 @@ def test_get_paginated_response(self, rf): expected_content = { "results": list(range(11, 16)), - "links": OrderedDict( - [ - ("first", "http://testserver/?page%5Blimit%5D=5"), - ( - "last", - "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=100", - ), - ( - "next", - "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=15", - ), - ("prev", "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=5"), - ] - ), + "links": { + "first": "http://testserver/?page%5Blimit%5D=5", + "last": "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=100", + "next": "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=15", + "prev": "http://testserver/?page%5Blimit%5D=5&page%5Boffset%5D=5", + }, "meta": { - "pagination": OrderedDict( - [ - ("count", count), - ("limit", limit), - ("offset", offset), - ] - ) + "pagination": { + "count": count, + "limit": limit, + "offset": offset, + } }, }