Skip to content

Commit

Permalink
Minor tweaks and updates version and release notes
Browse files Browse the repository at this point in the history
  • Loading branch information
rsinger86 committed Mar 11, 2023
1 parent 6531c5c commit 9dd6a91
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 32 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ Parameter names and wildcard values can be configured within a Django setting, n
| Option | Description | Default |
|-------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|-----------------|
| EXPAND_PARAM | The name of the parameter with the fields to be expanded | `"expand"` |
| MAXIMUM_EXPANSION_DEPTH | The number of maximum depth permitted expansion | `None` |
| MAXIMUM_EXPANSION_DEPTH | The max allowed expansion depth. By default it's unlimited. Expanding `state.towns` would equal a depth of 2 | `None` |
| FIELDS_PARAM | The name of the parameter with the fields to be included (others will be omitted) | `"fields"` |
| OMIT_PARAM | The name of the parameter with the fields to be omitted | `"omit"` |
| RECURSIVE_EXPANSION_PERMITTED | If `False`, an exception is raised when a recursive pattern is found | `True` |
Expand All @@ -498,13 +498,13 @@ For example, if you want your API to work a bit more like [JSON API](https://jso
REST_FLEX_FIELDS = {"EXPAND_PARAM": "include"}
```

### Defining expansion and recursive limits at serializer level
### Defining Expansion and Recursive Limits on Serializer Classes

`maximum_expansion_depth` property can be overridden at serializer level. It can be configured as `int` or `None`.
A `maximum_expansion_depth` integer property can be set on a serializer class.

`recursive_expansion_permitted` property can be overridden at serializer level. It must be `bool`.
`recursive_expansion_permitted` boolean property can be set on a serializer class.

Both settings raise `serializers.ValidationError` when conditions are met but exceptions can be overridden in `_recursive_expansion_found` and `_expansion_depth_exceeded` methods.
Both settings raise `serializers.ValidationError` when conditions are met but exceptions can be customized by overriding the `recursive_expansion_not_permitted` and `expansion_depth_exceeded` methods.


## Serializer Introspection
Expand Down Expand Up @@ -584,6 +584,10 @@ It will automatically call `select_related` and `prefetch_related` on the curren

# Changelog <a id="changelog"></a>

## 1.0.2 (March 2023)

- Adds control over whether recursive expansions are allowed and allows setting the max expansion depth. Thanks @andruten!

## 1.0.1 (March 2023)

- Various bug fixes. Thanks @michaelschem, @andruten, and @erielias!
Expand Down
4 changes: 3 additions & 1 deletion rest_flex_fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
FIELDS_PARAM = FLEX_FIELDS_OPTIONS.get("FIELDS_PARAM", "fields")
OMIT_PARAM = FLEX_FIELDS_OPTIONS.get("OMIT_PARAM", "omit")
MAXIMUM_EXPANSION_DEPTH = FLEX_FIELDS_OPTIONS.get("MAXIMUM_EXPANSION_DEPTH", None)
RECURSIVE_EXPANSION_PERMITTED = FLEX_FIELDS_OPTIONS.get("RECURSIVE_EXPANSION_PERMITTED", True)
RECURSIVE_EXPANSION_PERMITTED = FLEX_FIELDS_OPTIONS.get(
"RECURSIVE_EXPANSION_PERMITTED", True
)

WILDCARD_ALL = "~all"
WILDCARD_ASTERISK = "*"
Expand Down
17 changes: 9 additions & 8 deletions rest_flex_fields/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
import importlib
from typing import List, Optional, Tuple

from django.conf import settings
from rest_framework import serializers

from rest_flex_fields import (
EXPAND_PARAM,
FIELDS_PARAM,
OMIT_PARAM,
WILDCARD_VALUES,
MAXIMUM_EXPANSION_DEPTH,
RECURSIVE_EXPANSION_PERMITTED,
split_levels,
)

Expand Down Expand Up @@ -65,7 +66,7 @@ def get_maximum_expansion_depth(self) -> Optional[int]:
"""
Defined at serializer level or based on MAXIMUM_EXPANSION_DEPTH setting
"""
return self.maximum_expansion_depth or settings.REST_FLEX_FIELDS.get("MAXIMUM_EXPANSION_DEPTH", None)
return self.maximum_expansion_depth or MAXIMUM_EXPANSION_DEPTH

def get_recursive_expansion_permitted(self) -> bool:
"""
Expand All @@ -74,7 +75,7 @@ def get_recursive_expansion_permitted(self) -> bool:
if self.recursive_expansion_permitted is not None:
return self.recursive_expansion_permitted
else:
return settings.REST_FLEX_FIELDS.get("RECURSIVE_EXPANSION_PERMITTED", True)
return RECURSIVE_EXPANSION_PERMITTED

def to_representation(self, instance):
if not self._flex_fields_rep_applied:
Expand Down Expand Up @@ -280,19 +281,19 @@ def _get_query_param_value(self, field: str) -> List[str]:
values = self.context["request"].query_params.getlist(field)

if not values:
values = self.context["request"].query_params.getlist("{}[]".format(field))
values = self.context["request"].query_params.getlist(f"{field}[]")

if values and len(values) == 1:
values = values[0].split(",")

for expand_path in values:
self._validate_recursive_expansion(expand_path)
self._validate_expansion_depth(expand_path)

if values and len(values) == 1:
return values[0].split(",")

return values or []

def _split_expand_field(self, expand_path: str) -> List[str]:
return expand_path.split('.')
return expand_path.split(".")

def recursive_expansion_not_permitted(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def readme():
]
setup(
name="drf-flex-fields",
version="1.0.1",
version="1.0.2",
description="Flexible, dynamic fields and nested resources for Django REST Framework serializers.",
author="Robert Singer",
author_email="robertgsinger@gmail.com",
Expand Down
14 changes: 10 additions & 4 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,15 @@
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]


Expand Down Expand Up @@ -120,4 +126,4 @@
# of `AutoField`. To avoid introducing migrations and silence the configuration warnings,
# we're setting this to `AutoField`, which is ok for this use case (tests).
# Reference: https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
45 changes: 32 additions & 13 deletions tests/test_flex_fields_model_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,26 +185,33 @@ def test_import_serializer_class(self):
def test_make_expanded_field_serializer(self):
pass

@override_settings(REST_FLEX_FIELDS={"RECURSIVE_EXPANSION_PERMITTED": False})
@patch("rest_flex_fields.serializers.RECURSIVE_EXPANSION_PERMITTED", False)
def test_recursive_expansion(self):
with self.assertRaises(serializers.ValidationError):
FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.dog"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.dog"]}),
)
}
)

@patch('rest_flex_fields.FlexFieldsModelSerializer.recursive_expansion_permitted', new_callable=PropertyMock)
def test_recursive_expansion_serializer_level(self, mock_recursive_expansion_permitted):
@patch(
"rest_flex_fields.FlexFieldsModelSerializer.recursive_expansion_permitted",
new_callable=PropertyMock,
)
def test_recursive_expansion_serializer_level(
self, mock_recursive_expansion_permitted
):
mock_recursive_expansion_permitted.return_value = False

with self.assertRaises(serializers.ValidationError):
FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.dog"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.dog"]}),
)
}
)
Expand All @@ -214,43 +221,55 @@ def test_expansion_depth(self):
serializer = FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.paws"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.paws"]}),
)
}
)
self.assertEqual(serializer._flex_options_all["expand"], ["dog.leg.paws"])

@override_settings(REST_FLEX_FIELDS={"MAXIMUM_EXPANSION_DEPTH": 2})
@patch("rest_flex_fields.serializers.MAXIMUM_EXPANSION_DEPTH", 2)
def test_expansion_depth_exception(self):
with self.assertRaises(serializers.ValidationError):
FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.paws"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.paws"]}),
)
}
)

@patch('rest_flex_fields.FlexFieldsModelSerializer.maximum_expansion_depth', new_callable=PropertyMock)
@patch(
"rest_flex_fields.FlexFieldsModelSerializer.maximum_expansion_depth",
new_callable=PropertyMock,
)
def test_expansion_depth_serializer_level(self, mock_maximum_expansion_depth):
mock_maximum_expansion_depth.return_value = 3
serializer = FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.paws"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.paws"]}),
)
}
)
self.assertEqual(serializer._flex_options_all["expand"], ["dog.leg.paws"])

@patch('rest_flex_fields.FlexFieldsModelSerializer.maximum_expansion_depth', new_callable=PropertyMock)
def test_expansion_depth_serializer_level_exception(self, mock_maximum_expansion_depth):
@patch(
"rest_flex_fields.FlexFieldsModelSerializer.maximum_expansion_depth",
new_callable=PropertyMock,
)
def test_expansion_depth_serializer_level_exception(
self, mock_maximum_expansion_depth
):
mock_maximum_expansion_depth.return_value = 2
with self.assertRaises(serializers.ValidationError):
FlexFieldsModelSerializer(
context={
"request": MockRequest(
method="GET", query_params=MultiValueDict({"expand": ["dog.leg.paws"]})
method="GET",
query_params=MultiValueDict({"expand": ["dog.leg.paws"]}),
)
}
)

0 comments on commit 9dd6a91

Please sign in to comment.