Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c503748

Browse files
Support formatting URL segments via new FORMAT_LINKS setting (#876)
Fixes #790.
1 parent 3833271 commit c503748

File tree

10 files changed

+200
-8
lines changed

10 files changed

+200
-8
lines changed

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Jason Housley <housleyjk@gmail.com>
1515
Jerel Unruh <mail@unruhdesigns.com>
1616
Jonathan Senecal <contact@jonathansenecal.com>
1717
Joseba Mendivil <git@jma.email>
18+
Kevin Partington <platinumazure@gmail.com>
1819
Kieran Evans <keyz182@gmail.com>
1920
Léo S. <leo@naeka.fr>
2021
Luc Cary <luc.cary@gmail.com>

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ any parts of the framework not mentioned in the documentation should generally b
1313
### Added
1414

1515
* Ability for the user to select `included_serializers` to apply when using `BrowsableAPI`, based on available `included_serializers` defined for the current endpoint.
16+
* Ability for the user to format serializer properties in URL segments using the `JSON_API_FORMAT_LINKS` setting.
1617

1718
### Fixed
1819

‎docs/usage.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,44 @@ When set to pluralize:
477477
}
478478
```
479479

480+
#### Related URL segments
481+
482+
Serializer properties in relationship and related resource URLs may be infected using the `JSON_API_FORMAT_LINKS` setting.
483+
484+
``` python
485+
JSON_API_FORMAT_LINKS = 'dasherize'
486+
```
487+
488+
For example, with a serializer property `created_by` and with `'dasherize'` formatting:
489+
490+
```json
491+
{
492+
"data": {
493+
"type": "comments",
494+
"id": "1",
495+
"attributes": {
496+
"text": "Comments are fun!"
497+
},
498+
"links": {
499+
"self": "/comments/1"
500+
},
501+
"relationships": {
502+
"created_by": {
503+
"links": {
504+
"self": "/comments/1/relationships/created-by",
505+
"related": "/comments/1/created-by"
506+
}
507+
}
508+
}
509+
},
510+
"links": {
511+
"self": "/comments/1"
512+
}
513+
}
514+
```
515+
516+
The relationship name is formatted by the `JSON_API_FORMAT_FIELD_NAMES` setting, but the URL segments are formatted by the `JSON_API_FORMAT_LINKS` setting.
517+
480518
### Related fields
481519

482520
#### ResourceRelatedField

‎rest_framework_json_api/relations.py‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from rest_framework_json_api.exceptions import Conflict
1616
from rest_framework_json_api.utils import (
1717
Hyperlink,
18+
format_link_segment,
1819
get_included_serializers,
1920
get_resource_type_from_instance,
2021
get_resource_type_from_queryset,
@@ -112,14 +113,10 @@ def get_links(self, obj=None, lookup_field="pk"):
112113
else view.kwargs[lookup_field]
113114
}
114115

116+
field_name = self.field_name if self.field_name else self.parent.field_name
117+
115118
self_kwargs = kwargs.copy()
116-
self_kwargs.update(
117-
{
118-
"related_field": self.field_name
119-
if self.field_name
120-
else self.parent.field_name
121-
}
122-
)
119+
self_kwargs.update({"related_field": format_link_segment(field_name)})
123120
self_link = self.get_url("self", self.self_link_view_name, self_kwargs, request)
124121

125122
# Assuming RelatedField will be declared in two ways:

‎rest_framework_json_api/settings.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
DEFAULTS = {
1313
"FORMAT_FIELD_NAMES": False,
1414
"FORMAT_TYPES": False,
15+
"FORMAT_LINKS": False,
1516
"PLURALIZE_TYPES": False,
1617
"UNIFORM_EXCEPTIONS": False,
1718
}

‎rest_framework_json_api/utils.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,19 @@ def format_resource_type(value, format_type=None, pluralize=None):
148148
return inflection.pluralize(value) if pluralize else value
149149

150150

151+
def format_link_segment(value, format_type=None):
152+
"""
153+
Takes a string value and returns it with formatted keys as set in `format_type`
154+
or `JSON_API_FORMAT_LINKS`.
155+
156+
:format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore'
157+
"""
158+
if format_type is None:
159+
format_type = json_api_settings.FORMAT_LINKS
160+
161+
return format_value(value, format_type)
162+
163+
151164
def get_related_resource_type(relation):
152165
from rest_framework_json_api.serializers import PolymorphicModelSerializer
153166

‎rest_framework_json_api/views.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from rest_framework_json_api.utils import (
2626
Hyperlink,
2727
OrderedDict,
28+
format_value,
2829
get_included_resources,
2930
get_resource_type_from_instance,
3031
)
@@ -185,7 +186,8 @@ def get_related_serializer_class(self):
185186
return parent_serializer_class
186187

187188
def get_related_field_name(self):
188-
return self.kwargs["related_field"]
189+
field_name = self.kwargs["related_field"]
190+
return format_value(field_name, "underscore")
189191

190192
def get_related_instance(self):
191193
parent_obj = self.get_object()

‎tests/test_relations.py‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pytest
2+
from django.conf.urls import re_path
3+
from rest_framework.routers import SimpleRouter
4+
5+
from rest_framework_json_api.relations import HyperlinkedRelatedField
6+
from rest_framework_json_api.views import ModelViewSet, RelationshipView
7+
8+
from .models import BasicModel
9+
10+
11+
@pytest.mark.urls(__name__)
12+
@pytest.mark.parametrize(
13+
"format_links,expected_url_segment",
14+
[
15+
(None, "relatedField_name"),
16+
("dasherize", "related-field-name"),
17+
("camelize", "relatedFieldName"),
18+
("capitalize", "RelatedFieldName"),
19+
("underscore", "related_field_name"),
20+
],
21+
)
22+
def test_relationship_urls_respect_format_links(
23+
settings, format_links, expected_url_segment
24+
):
25+
settings.JSON_API_FORMAT_LINKS = format_links
26+
27+
model = BasicModel(text="Some text")
28+
29+
field = HyperlinkedRelatedField(
30+
self_link_view_name="basic-model-relationships",
31+
related_link_view_name="basic-model-related",
32+
read_only=True,
33+
)
34+
field.field_name = "relatedField_name"
35+
36+
expected = {
37+
"self": f"/basic_models/{model.pk}/relationships/{expected_url_segment}/",
38+
"related": f"/basic_models/{model.pk}/{expected_url_segment}/",
39+
}
40+
41+
actual = field.get_links(model)
42+
43+
assert expected == actual
44+
45+
46+
# Routing setup
47+
48+
49+
class BasicModelViewSet(ModelViewSet):
50+
class Meta:
51+
model = BasicModel
52+
53+
54+
class BasicModelRelationshipView(RelationshipView):
55+
queryset = BasicModel.objects
56+
57+
58+
router = SimpleRouter()
59+
router.register(r"basic_models", BasicModelViewSet, basename="basic-model")
60+
61+
urlpatterns = [
62+
re_path(
63+
r"^basic_models/(?P<pk>[^/.]+)/(?P<related_field>[^/.]+)/$",
64+
BasicModelViewSet.as_view({"get": "retrieve_related"}),
65+
name="basic-model-related",
66+
),
67+
re_path(
68+
r"^basic_models/(?P<pk>[^/.]+)/relationships/(?P<related_field>[^/.]+)/$",
69+
BasicModelRelationshipView.as_view(),
70+
name="basic-model-relationships",
71+
),
72+
]
73+
74+
urlpatterns += router.urls

‎tests/test_utils.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from rest_framework_json_api import serializers
99
from rest_framework_json_api.utils import (
1010
format_field_names,
11+
format_link_segment,
1112
format_resource_type,
1213
format_value,
1314
get_included_serializers,
@@ -197,6 +198,21 @@ def test_format_field_names(settings, format_type, output):
197198
assert format_field_names(value, format_type) == output
198199

199200

201+
@pytest.mark.parametrize(
202+
"format_type,output",
203+
[
204+
(None, "first_Name"),
205+
("camelize", "firstName"),
206+
("capitalize", "FirstName"),
207+
("dasherize", "first-name"),
208+
("underscore", "first_name"),
209+
],
210+
)
211+
def test_format_field_segment(settings, format_type, output):
212+
settings.JSON_API_FORMAT_LINKS = format_type
213+
assert format_link_segment("first_Name") == output
214+
215+
200216
@pytest.mark.parametrize(
201217
"format_type,output",
202218
[

‎tests/test_views.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import pytest
2+
3+
from rest_framework_json_api import serializers, views
4+
from rest_framework_json_api.relations import ResourceRelatedField
5+
from rest_framework_json_api.utils import format_value
6+
7+
from .models import BasicModel
8+
9+
related_model_field_name = "related_field_model"
10+
11+
12+
@pytest.mark.parametrize(
13+
"format_links",
14+
[
15+
None,
16+
"dasherize",
17+
"camelize",
18+
"capitalize",
19+
"underscore",
20+
],
21+
)
22+
def test_get_related_field_name_handles_formatted_link_segments(format_links, rf):
23+
url_segment = format_value(related_model_field_name, format_links)
24+
25+
request = rf.get(f"/basic_models/1/{url_segment}")
26+
27+
view = BasicModelFakeViewSet()
28+
view.setup(request, related_field=url_segment)
29+
30+
assert view.get_related_field_name() == related_model_field_name
31+
32+
33+
class BasicModelSerializer(serializers.ModelSerializer):
34+
related_model_field = ResourceRelatedField(queryset=BasicModel.objects)
35+
36+
def __init__(self, *args, **kwargs):
37+
# Intentionally setting field_name property to something that matches no format
38+
self.related_model_field.field_name = related_model_field_name
39+
super(BasicModelSerializer, self).__init(*args, **kwargs)
40+
41+
class Meta:
42+
model = BasicModel
43+
44+
45+
class BasicModelFakeViewSet(views.ModelViewSet):
46+
serializer_class = BasicModelSerializer
47+
48+
def retrieve(self, request, *args, **kwargs):
49+
pass

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /