Skip to content

Commit b9ccd9d

Browse files
committed
v2.0.0-beta.2 -> v2.0.0
* docs: note about importing serializers * Document ResourceRelatedField and RelationshipView * Updated pip install instructions for 2.0.0-beta.2 * Add LimitOffsetPagination * Dont let the offset go into negative space * Add basic unit test for LimitOffsetPagination * Support deeply nested includes Allow skipping of intermediate included models * Add current tox.ini directory to PYTHONPATH in order to use imports form there Fix regression on PY3 caused by unicode_literals * [FEATURE]: support using get_serializer_class on view * fixed extract_root_meta for lists * Fixed get_resource_name in case of non-model backed serializer. Closes #219 * ResourceRelatedField now accepts serializer methods when many=True * Rename "suggested" posts to "featured" so we can use suggested as many=True * Updated SerializerMethodResourceRelatedField to allow many=True Issue #151 Closes #220 * Correct error responses for projects with different DRF-configurations (#222) * [#214] Add error messages tests. * [#214] Extract formatting DRF errors. * Add example view with custom handle_exception. * Use HTTP 422 for validation error responses. * Add full example of class-configured json api view. * Fixed naming that suggested settings were used to inflect relationship names. JSON_API_FORMAT_RELATION_NAME actually inflected the `type` instead. The relation name is not changable at this time although if it woudl be useful to someone it would be fine to implement it. Closes #136. * Updated changelog * Added a doc note to prefer setting resource_name on serializers or models. Closes #207 * Added get_related_field_name method to RelationshipView * Added get_related_field_name method to RelationshipView * Added docs about field_name_mapping * Updated the readme for testing (#234) * Allow exception handler to be used by normal DRF views: (#233) * Add top-level 'errors' object to non-JSON-API responses * Allow configuring the exception handler to be used _only_ in JSON API views or uniformly across all views * Fix included resource type inconsistency (#229) When setting `resource_name = None`, the related instance's resource name is used in `relationships`, but `None` is used in `included`. This is related to #94 and #124 * Fixes #230. Keep write only fields from having an attribute key * Release v2.0.0 * Update setup.py to classify as production/stable
1 parent 8c4db64 commit b9ccd9d

31 files changed

+851
-209
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ pip-delete-this-directory.txt
3030

3131
# Tox
3232
.tox/
33+
34+
# VirtualEnv
35+
.venv/

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11

2+
v2.0.0
3+
4+
* Fixed bug where write_only fields still had their keys rendered
5+
* Exception handler can now easily be used on DRF-JA views alongside regular DRF views
6+
* Added `get_related_field_name` for views subclassing RelationshipView to override
7+
* Renamed `JSON_API_FORMAT_RELATION_KEYS` to `JSON_API_FORMAT_TYPES` to match what it was actually doing
8+
* Renamed `JSON_API_PLURALIZE_RELATION_TYPE` to `JSON_API_PLURALIZE_TYPES`
9+
* Documented ResourceRelatedField and RelationshipView
10+
* Added LimitOffsetPagination
11+
* Support deeply nested `?includes=foo.bar.baz` without returning intermediate models (bar)
12+
* Allow a view's serializer_class to be fetched at runtime via `get_serializer_class`
13+
* Added support for `get_root_meta` on list serializers
14+
15+
216
v2.0.0-beta.2
317

418
* Added JSONAPIMeta class option to models for overriding `resource_name`. #197

README.rst

+7-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ From PyPI
8080

8181
::
8282

83-
$ pip install djangorestframework-jsonapi==2.0.0-beta.1
83+
$ pip install djangorestframework-jsonapi
8484

8585

8686
From Source
@@ -107,9 +107,14 @@ Browse to http://localhost:8000
107107
Running Tests
108108
^^^^^^^^^^^^^
109109

110+
It is recommended to create a virtualenv for testing. Assuming it is already
111+
installed and activated:
112+
110113
::
111114

112-
$ python runtests.py
115+
$ pip install -e .
116+
$ pip install -r requirements-development.txt
117+
$ py.test
113118

114119

115120
-----

docs/api.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Gathers the data from serializer fields specified in `meta_fields` and adds it t
3838

3939
#### extract_root_meta
4040

41-
`extract_root_meta(serializer, resource, meta)`
41+
`extract_root_meta(serializer, resource)`
4242

4343
Calls a `get_root_meta` function on a serializer, if it exists.
4444

@@ -47,4 +47,3 @@ Calls a `get_root_meta` function on a serializer, if it exists.
4747
`build_json_resource_obj(fields, resource, resource_instance, resource_name)`
4848

4949
Builds the resource object (type, id, attributes) and extracts relationships.
50-

docs/usage.md

+195-13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ record count and a `links` object with the next, previous, first, and last links
3434
Pages can be selected with the `page` GET parameter. Page size can be controlled
3535
per request via the `PAGINATE_BY_PARAM` query parameter (`page_size` by default).
3636

37+
### Serializers
38+
39+
It is recommended to import the base serializer classes from this package
40+
rather than from vanilla DRF. For example,
41+
42+
```python
43+
from rest_framework_json_api import serializers
44+
45+
class MyModelSerializer(serializers.ModelSerializers):
46+
# ...
47+
```
48+
3749
### Setting the resource_name
3850

3951
You may manually set the `resource_name` property on views, serializers, or
@@ -75,7 +87,10 @@ class Me(models.Model):
7587
If you set the `resource_name` on a combination of model, serializer, or view
7688
in the same hierarchy, the name will be resolved as following: view >
7789
serializer > model. (Ex: A view `resource_name` will always override a
78-
`resource_name` specified on a serializer or model)
90+
`resource_name` specified on a serializer or model). Setting the `resource_name`
91+
on the view should be used sparingly as serializers and models are shared between
92+
multiple endpoints. Setting the `resource_name` on views may result in a different
93+
`type` being set depending on which endpoint the resource is fetched from.
7994

8095

8196
### Inflecting object and relation keys
@@ -143,20 +158,20 @@ Example - With format conversion set to `dasherize`:
143158
}
144159
```
145160

146-
#### Relationship types
161+
#### Types
147162

148-
A similar option to JSON\_API\_FORMAT\_RELATION\_KEYS can be set for the relationship names:
163+
A similar option to JSON\_API\_FORMAT\_KEYS can be set for the types:
149164

150165
``` python
151-
JSON_API_FORMAT_RELATION_KEYS = 'dasherize'
166+
JSON_API_FORMAT_TYPES = 'dasherize'
152167
```
153168

154169
Example without format conversion:
155170

156171
``` js
157172
{
158173
"data": [{
159-
"type": "identities",
174+
"type": "blog_identity",
160175
"id": 3,
161176
"attributes": {
162177
...
@@ -179,7 +194,7 @@ When set to dasherize:
179194
``` js
180195
{
181196
"data": [{
182-
"type": "identities",
197+
"type": "blog-identity",
183198
"id": 3,
184199
"attributes": {
185200
...
@@ -198,7 +213,7 @@ When set to dasherize:
198213
It is also possible to pluralize the types like so:
199214

200215
```python
201-
JSON_API_PLURALIZE_RELATION_TYPE = True
216+
JSON_API_PLURALIZE_TYPES = True
202217
```
203218
Example without pluralization:
204219

@@ -245,8 +260,168 @@ When set to pluralize:
245260
}
246261
```
247262

248-
Both `JSON_API_PLURALIZE_RELATION_TYPE` and `JSON_API_FORMAT_RELATION_KEYS` can be combined to
249-
achieve different results.
263+
### Related fields
264+
265+
Because of the additional structure needed to represent relationships in JSON
266+
API, this package provides the `ResourceRelatedField` for serializers, which
267+
works similarly to `PrimaryKeyRelatedField`. By default,
268+
`rest_framework_json_api.serializers.ModelSerializer` will use this for
269+
related fields automatically. It can be instantiated explicitly as in the
270+
following example:
271+
272+
```python
273+
from rest_framework_json_api import serializers
274+
from rest_framework_json_api.relations import ResourceRelatedField
275+
276+
from myapp.models import Order, LineItem, Customer
277+
278+
279+
class OrderSerializer(serializers.ModelSerializer):
280+
class Meta:
281+
model = Order
282+
283+
line_items = ResourceRelatedField(
284+
queryset=LineItem.objects,
285+
many=True # necessary for M2M fields & reverse FK fields
286+
)
287+
288+
customer = ResourceRelatedField(
289+
queryset=Customer.objects # queryset argument is required
290+
) # except when read_only=True
291+
292+
```
293+
294+
In the [JSON API spec](http://jsonapi.org/format/#document-resource-objects),
295+
relationship objects contain links to related objects. To make this work
296+
on a serializer we need to tell the `ResourceRelatedField` about the
297+
corresponding view. Use the `HyperlinkedModelSerializer` and instantiate
298+
the `ResourceRelatedField` with the relevant keyword arguments:
299+
300+
```python
301+
from rest_framework_json_api import serializers
302+
from rest_framework_json_api.relations import ResourceRelatedField
303+
304+
from myapp.models import Order, LineItem, Customer
305+
306+
307+
class OrderSerializer(serializers.ModelSerializer):
308+
class Meta:
309+
model = Order
310+
311+
line_items = ResourceRelatedField(
312+
queryset=LineItem.objects,
313+
many=True,
314+
related_link_view_name='order-lineitems-list',
315+
related_link_url_kwarg='order_pk',
316+
self_link_view_name='order_relationships'
317+
)
318+
319+
customer = ResourceRelatedField(
320+
queryset=Customer.objects,
321+
related_link_view-name='order-customer-detail',
322+
related_link_url_kwarg='order_pk',
323+
self_link_view_name='order-relationships'
324+
)
325+
```
326+
327+
* `related_link_view_name` is the name of the route for the related
328+
view.
329+
330+
* `related_link_url_kwarg` is the keyword argument that will be passed
331+
to the view that identifies the 'parent' object, so that the results
332+
can be filtered to show only those objects related to the 'parent'.
333+
334+
* `self_link_view_name` is the name of the route for the `RelationshipView`
335+
(see below).
336+
337+
In this example, `reverse('order-lineitems-list', kwargs={'order_pk': 3}`
338+
should resolve to something like `/orders/3/lineitems`, and that route
339+
should instantiate a view or viewset for `LineItem` objects that accepts
340+
a keword argument `order_pk`. The
341+
[drf-nested-routers](https://github.com/alanjds/drf-nested-routers) package
342+
is useful for defining such nested routes in your urlconf.
343+
344+
The corresponding viewset for the `line-items-list` route in the above example
345+
might look like the following. Note that in the typical use case this would be
346+
the same viewset used for the `/lineitems` endpoints; when accessed through
347+
the nested route `/orders/<order_pk>/lineitems` the queryset is filtered using
348+
the `order_pk` keyword argument to include only the lineitems related to the
349+
specified order.
350+
351+
```python
352+
from rest_framework import viewsets
353+
354+
from myapp.models import LineItem
355+
from myapp.serializers import LineItemSerializer
356+
357+
358+
class LineItemViewSet(viewsets.ModelViewSet):
359+
queryset = LineItem.objects
360+
serializer_class = LineItemSerializer
361+
362+
def get_queryset(self):
363+
queryset = self.queryset
364+
365+
# if this viewset is accessed via the 'order-lineitems-list' route,
366+
# it wll have been passed the `order_pk` kwarg and the queryset
367+
# needs to be filtered accordingly; if it was accessed via the
368+
# unnested '/lineitems' route, the queryset should include all LineItems
369+
if 'order_pk' in self.kwargs:
370+
order_pk = self.kwargs['order_pk']
371+
queryset = queryset.filter(order__pk=order_pk])
372+
373+
return queryset
374+
```
375+
376+
### RelationshipView
377+
`rest_framework_json_api.views.RelationshipView` is used to build
378+
relationship views (see the
379+
[JSON API spec](http://jsonapi.org/format/#fetching-relationships)).
380+
The `self` link on a relationship object should point to the corresponding
381+
relationship view.
382+
383+
The relationship view is fairly simple because it only serializes
384+
[Resource Identifier Objects](http://jsonapi.org/format/#document-resource-identifier-objects)
385+
rather than full resource objects. In most cases the following is sufficient:
386+
387+
```python
388+
from rest_framework_json_api.views import RelationshipView
389+
390+
from myapp.models import Order
391+
392+
393+
class OrderRelationshipView(RelationshipView):
394+
queryset = Order.objects
395+
396+
```
397+
398+
The urlconf would need to contain a route like the following:
399+
400+
```python
401+
url(
402+
regex=r'^orders/(?P<pk>[^/.]+/relationships/(?P<related_field>[^/.]+)$',
403+
view=OrderRelationshipView.as_view(),
404+
name='order-relationships'
405+
)
406+
```
407+
408+
The `related_field` kwarg specifies which relationship to use, so
409+
if we are interested in the relationship represented by the related
410+
model field `Order.line_items` on the Order with pk 3, the url would be
411+
`/order/3/relationships/line_items`. On `HyperlinkedModelSerializer`, the
412+
`ResourceRelatedField` will construct the url based on the provided
413+
`self_link_view_name` keyword argument, which should match the `name=`
414+
provided in the urlconf, and will use the name of the field for the
415+
`related_field` kwarg.
416+
Also we can override `related_field` in the url. Let's say we want the url to be:
417+
`/order/3/relationships/order_items` - all we need to do is just add `field_name_mapping`
418+
dict to the class:
419+
```python
420+
field_name_mapping = {
421+
'line_items': 'order_items'
422+
}
423+
```
424+
250425

251426
### Meta
252427

@@ -260,10 +435,17 @@ added to the `meta` object within the same `data` as the serializer.
260435
To add metadata to the top level `meta` object add:
261436

262437
``` python
263-
def get_root_meta(self, obj):
264-
return {
265-
'size': len(obj)
266-
}
438+
def get_root_meta(self, resource, many):
439+
if many:
440+
# Dealing with a list request
441+
return {
442+
'size': len(resource)
443+
}
444+
else:
445+
# Dealing with a detail request
446+
return {
447+
'foo': 'bar'
448+
}
267449
```
268450
to the serializer. It must return a dict and will be merged with the existing top level `meta`.
269451

example/factories/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# -*- encoding: utf-8 -*-
2-
from __future__ import unicode_literals
32

43
import factory
54
from faker import Factory as FakerFactory
@@ -22,6 +21,7 @@ class Meta:
2221
name = factory.LazyAttribute(lambda x: faker.name())
2322
email = factory.LazyAttribute(lambda x: faker.email())
2423

24+
bio = factory.RelatedFactory('example.factories.AuthorBioFactory', 'author')
2525

2626
class AuthorBioFactory(factory.django.DjangoModelFactory):
2727
class Meta:

0 commit comments

Comments
 (0)