Skip to content

Preserve field names when no formatting is configured #909

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 3 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ any parts of the framework not mentioned in the documentation should generally b
### Fixed

* Allow `get_serializer_class` to be overwritten when using related urls without defining `serializer_class` fallback
* Preserve field names when no formatting is configured.

### Deprecated

* Deprecated default `format_type` argument of `rest_framework_json_api.utils.format_value`. Use `rest_framework_json_api.utils.format_field_name` or specify specifc `format_type` instead.
* Deprecated `format_type` argument of `rest_framework_json_api.utils.format_link_segment`. Use `format_value` instead.

## [4.1.0] - 2021-03-08

Expand Down
5 changes: 2 additions & 3 deletions rest_framework_json_api/django_filters/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings

from rest_framework_json_api.utils import format_value
from rest_framework_json_api.utils import undo_format_field_name


class DjangoFilterBackend(DjangoFilterBackend):
Expand Down Expand Up @@ -119,8 +119,7 @@ def get_filterset_kwargs(self, request, queryset, view):
)
# convert jsonapi relationship path to Django ORM's __ notation
key = m.groupdict()["assoc"].replace(".", "__")
# undo JSON_API_FORMAT_FIELD_NAMES conversion:
key = format_value(key, "underscore")
key = undo_format_field_name(key)
data.setlist(key, val)
filter_keys.append(key)
del data[qp]
Expand Down
10 changes: 5 additions & 5 deletions rest_framework_json_api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework.exceptions import ValidationError
from rest_framework.filters import BaseFilterBackend, OrderingFilter

from rest_framework_json_api.utils import format_value
from rest_framework_json_api.utils import undo_format_field_name


class OrderingFilter(OrderingFilter):
Expand All @@ -15,7 +15,7 @@ class OrderingFilter(OrderingFilter):
: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.
Also supports undo of field name formatting
(See JSON_API_FORMAT_FIELD_NAMES in docs/usage.md)
"""

Expand All @@ -38,7 +38,7 @@ def remove_invalid_fields(self, queryset, fields, view, request):
bad_terms = [
term
for term in fields
if format_value(term.replace(".", "__").lstrip("-"), "underscore")
if undo_format_field_name(term.replace(".", "__").lstrip("-"))
not in valid_fields
]
if bad_terms:
Expand All @@ -56,10 +56,10 @@ def remove_invalid_fields(self, queryset, fields, view, request):
item_rewritten = item.replace(".", "__")
if item_rewritten.startswith("-"):
underscore_fields.append(
"-" + format_value(item_rewritten.lstrip("-"), "underscore")
"-" + undo_format_field_name(item_rewritten.lstrip("-"))
)
else:
underscore_fields.append(format_value(item_rewritten, "underscore"))
underscore_fields.append(undo_format_field_name(item_rewritten))

return super(OrderingFilter, self).remove_invalid_fields(
queryset, underscore_fields, view, request
Expand Down
4 changes: 2 additions & 2 deletions rest_framework_json_api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rest_framework.settings import api_settings
from rest_framework.utils.field_mapping import ClassLookupDict

from rest_framework_json_api.utils import format_value, get_related_resource_type
from rest_framework_json_api.utils import format_field_name, get_related_resource_type


class JSONAPIMetadata(SimpleMetadata):
Expand Down Expand Up @@ -93,7 +93,7 @@ def get_serializer_info(self, serializer):

return OrderedDict(
[
(format_value(field_name), self.get_field_info(field))
(format_field_name(field_name), self.get_field_info(field))
for field_name, field in serializer.fields.items()
]
)
Expand Down
28 changes: 7 additions & 21 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from rest_framework import parsers
from rest_framework.exceptions import ParseError

from . import exceptions, renderers, serializers, utils
from .settings import json_api_settings
from rest_framework_json_api import exceptions, renderers, serializers
from rest_framework_json_api.utils import get_resource_name, undo_format_field_names


class JSONParser(parsers.JSONParser):
Expand Down Expand Up @@ -37,27 +37,13 @@ class JSONParser(parsers.JSONParser):

@staticmethod
def parse_attributes(data):
attributes = data.get("attributes")
uses_format_translation = json_api_settings.FORMAT_FIELD_NAMES

if not attributes:
return dict()
elif uses_format_translation:
# convert back to python/rest_framework's preferred underscore format
return utils.format_field_names(attributes, "underscore")
else:
return attributes
attributes = data.get("attributes") or dict()
return undo_format_field_names(attributes)

@staticmethod
def parse_relationships(data):
uses_format_translation = json_api_settings.FORMAT_FIELD_NAMES
relationships = data.get("relationships")

if not relationships:
relationships = dict()
elif uses_format_translation:
# convert back to python/rest_framework's preferred underscore format
relationships = utils.format_field_names(relationships, "underscore")
relationships = data.get("relationships") or dict()
relationships = undo_format_field_names(relationships)

# Parse the relationships
parsed_relationships = dict()
Expand Down Expand Up @@ -130,7 +116,7 @@ def parse(self, stream, media_type=None, parser_context=None):

# Check for inconsistencies
if request.method in ("PUT", "POST", "PATCH"):
resource_name = utils.get_resource_name(
resource_name = get_resource_name(
parser_context, expand_polymorphic_types=True
)
if isinstance(resource_name, str):
Expand Down
85 changes: 70 additions & 15 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import inspect
import operator
import warnings
from collections import OrderedDict

import inflection
Expand Down Expand Up @@ -118,8 +119,76 @@ def format_field_names(obj, format_type=None):
return obj


def undo_format_field_names(obj):
"""
Takes a dict and undo format field names to underscore which is the Python convention
but only in case `JSON_API_FORMAT_FIELD_NAMES` is actually configured.
"""
if json_api_settings.FORMAT_FIELD_NAMES:
return format_field_names(obj, "underscore")

return obj


def format_field_name(field_name):
"""
Takes a field name and returns it with formatted keys as set in
`JSON_API_FORMAT_FIELD_NAMES`
"""
return format_value(field_name, json_api_settings.FORMAT_FIELD_NAMES)


def undo_format_field_name(field_name):
"""
Takes a string and undos format field name to underscore which is the Python convention
but only in case `JSON_API_FORMAT_FIELD_NAMES` is actually configured.
"""
if json_api_settings.FORMAT_FIELD_NAMES:
return format_value(field_name, "underscore")

return field_name


def format_link_segment(value, format_type=None):
"""
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."
)
)

return format_value(value, format_type)


def undo_format_link_segment(value):
"""
Takes a link segment and undos format link segment to underscore which is the Python convention
but only in case `JSON_API_FORMAT_RELATED_LINKS` is actually configured.
"""

if json_api_settings.FORMAT_RELATED_LINKS:
return format_value(value, "underscore")

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
if format_type == "dasherize":
# inflection can't dasherize camelCase
Expand All @@ -142,25 +211,11 @@ def format_resource_type(value, format_type=None, pluralize=None):
pluralize = json_api_settings.PLURALIZE_TYPES

if format_type:
# format_type will never be None here so we can use format_value
value = format_value(value, format_type)

return inflection.pluralize(value) if pluralize else value


def format_link_segment(value, format_type=None):
"""
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

return format_value(value, format_type)


def get_related_resource_type(relation):
from rest_framework_json_api.serializers import PolymorphicModelSerializer

Expand Down Expand Up @@ -348,7 +403,7 @@ def format_drf_errors(response, context, exc):
# handle all errors thrown from serializers
else:
for field, error in response.data.items():
field = format_value(field)
field = format_field_name(field)
pointer = "/data/attributes/{}".format(field)
if isinstance(exc, Http404) and isinstance(error, str):
# 404 errors don't have a pointer
Expand Down
4 changes: 2 additions & 2 deletions rest_framework_json_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
from rest_framework_json_api.utils import (
Hyperlink,
OrderedDict,
format_value,
get_included_resources,
get_resource_type_from_instance,
undo_format_link_segment,
)


Expand Down Expand Up @@ -187,7 +187,7 @@ def get_related_serializer_class(self):

def get_related_field_name(self):
field_name = self.kwargs["related_field"]
return format_value(field_name, "underscore")
return undo_format_link_segment(field_name)

def get_related_instance(self):
parent_obj = self.get_object()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def parser_context(self, rf):
@pytest.mark.parametrize(
"format_field_names",
[
None,
False,
"dasherize",
"camelize",
"capitalize",
Expand Down
2 changes: 1 addition & 1 deletion tests/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def test_to_representation(self, model, field):
@pytest.mark.parametrize(
"format_related_links",
[
None,
False,
"dasherize",
"camelize",
"capitalize",
Expand Down
Loading