0
# Relations
1
2
Resource relationship fields that handle JSON:API resource identifier objects, hyperlinked relationships, and relationship data management with support for both to-one and to-many relationships.
3
4
## Capabilities
5
6
### ResourceRelatedField
7
8
Main relationship field for JSON:API resource relationships.
9
10
```python { .api }
11
class ResourceRelatedField(RelatedField):
12
"""
13
JSON:API resource relationship field.
14
15
Handles relationships as resource identifier objects:
16
{"type": "resource-type", "id": "123"}
17
18
Supports both to-one and to-many relationships with proper
19
JSON:API formatting and validation.
20
"""
21
22
queryset = None # QuerySet for related objects
23
many = False # True for to-many relationships
24
read_only = False # Whether field is read-only
25
required = True # Whether field is required
26
allow_null = False # Whether null values are allowed
27
28
def __init__(self, **kwargs):
29
"""
30
Initialize resource relationship field.
31
32
Args:
33
queryset: QuerySet for related objects
34
many: True for to-many relationships
35
read_only: Whether field is read-only
36
required: Whether field is required
37
allow_null: Whether null values allowed
38
"""
39
40
def to_representation(self, value):
41
"""
42
Convert related object(s) to resource identifier object(s).
43
44
Args:
45
value: Related Django model instance or QuerySet
46
47
Returns:
48
dict or list: Resource identifier object(s)
49
"""
50
51
def to_internal_value(self, data):
52
"""
53
Convert resource identifier object(s) to Django model instance(s).
54
55
Args:
56
data: Resource identifier object(s)
57
58
Returns:
59
Model instance or list of instances
60
61
Raises:
62
ValidationError: If resource type doesn't match or objects don't exist
63
"""
64
65
def get_attribute(self, instance):
66
"""Get related object(s) from model instance."""
67
```
68
69
Usage example:
70
71
```python
72
from rest_framework_json_api import serializers
73
from rest_framework_json_api.relations import ResourceRelatedField
74
from myapp.models import Article, Author, Tag
75
76
class ArticleSerializer(serializers.ModelSerializer):
77
author = ResourceRelatedField(queryset=Author.objects.all())
78
tags = ResourceRelatedField(queryset=Tag.objects.all(), many=True)
79
80
class Meta:
81
model = Article
82
fields = ['title', 'content', 'author', 'tags']
83
84
# Generates relationships:
85
# {
86
# "relationships": {
87
# "author": {
88
# "data": {"type": "authors", "id": "5"}
89
# },
90
# "tags": {
91
# "data": [
92
# {"type": "tags", "id": "1"},
93
# {"type": "tags", "id": "2"}
94
# ]
95
# }
96
# }
97
# }
98
```
99
100
### HyperlinkedMixin
101
102
Mixin that adds hyperlink support to relationship fields.
103
104
```python { .api }
105
class HyperlinkedMixin:
106
"""
107
Mixin for hyperlinked relationship fields.
108
109
Adds self and related links to relationship objects:
110
{
111
"data": {"type": "authors", "id": "5"},
112
"links": {
113
"self": "/articles/1/relationships/author",
114
"related": "/articles/1/author"
115
}
116
}
117
"""
118
119
self_link_view_name = None # View name for self link
120
related_link_view_name = None # View name for related link
121
related_link_lookup_field = 'pk' # Lookup field for related link
122
related_link_url_kwarg = None # URL kwarg name
123
124
def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwargs):
125
"""
126
Initialize hyperlinked mixin.
127
128
Args:
129
self_link_view_name: View name for relationship self link
130
related_link_view_name: View name for related resource link
131
"""
132
133
def get_url(self, name, view_name, kwargs, request):
134
"""
135
Generate URL for hyperlinked relationship.
136
137
Args:
138
name: Link name ('self' or 'related')
139
view_name: Django URL view name
140
kwargs: URL kwargs
141
request: HTTP request object
142
143
Returns:
144
str or None: Generated URL
145
"""
146
147
def get_links(self, obj=None, lookup_field='pk'):
148
"""
149
Generate links dict for relationship.
150
151
Args:
152
obj: Related object instance
153
lookup_field: Field to use for lookup
154
155
Returns:
156
dict: Links with self and/or related URLs
157
"""
158
```
159
160
### SerializerMethodResourceRelatedField
161
162
Resource relationship field that uses a serializer method.
163
164
```python { .api }
165
class SerializerMethodResourceRelatedField(Field):
166
"""
167
Resource relationship field that gets its value from a serializer method.
168
169
Similar to SerializerMethodField but for relationships.
170
Returns resource identifier objects.
171
"""
172
173
def __init__(self, method_name=None, **kwargs):
174
"""
175
Initialize method-based resource field.
176
177
Args:
178
method_name: Name of serializer method to call
179
"""
180
181
def to_representation(self, obj):
182
"""
183
Get relationship value from serializer method.
184
185
Args:
186
obj: Object being serialized
187
188
Returns:
189
dict or list: Resource identifier object(s)
190
"""
191
```
192
193
Usage example:
194
195
```python
196
class ArticleSerializer(serializers.ModelSerializer):
197
featured_comments = SerializerMethodResourceRelatedField()
198
199
def get_featured_comments(self, obj):
200
# Return featured comments for this article
201
return obj.comments.filter(featured=True)
202
```
203
204
### ManySerializerMethodResourceRelatedField
205
206
Many-to-many version of SerializerMethodResourceRelatedField.
207
208
```python { .api }
209
class ManySerializerMethodResourceRelatedField(SerializerMethodResourceRelatedField):
210
"""
211
Many-to-many version of SerializerMethodResourceRelatedField.
212
213
Always returns a list of resource identifier objects.
214
"""
215
216
many = True # Always True for this field type
217
```
218
219
### SkipDataMixin
220
221
Mixin that skips data rendering for performance optimization.
222
223
```python { .api }
224
class SkipDataMixin:
225
"""
226
Mixin that skips "data" rendering in relationships for performance.
227
228
Useful when you only want to include relationship links
229
without the actual relationship data to save database queries.
230
"""
231
232
def get_attribute(self, instance):
233
"""Skip field to avoid database queries."""
234
raise SkipField
235
236
def to_representation(self, *args):
237
"""Not implemented - data is skipped."""
238
raise NotImplementedError
239
```
240
241
### PolymorphicResourceRelatedField
242
243
Resource relationship field for polymorphic models.
244
245
```python { .api }
246
class PolymorphicResourceRelatedField(ResourceRelatedField):
247
"""
248
Resource relationship field for polymorphic models.
249
250
Handles relationships to polymorphic models that can represent
251
multiple resource types with proper type detection.
252
"""
253
```
254
255
### HyperlinkedRelatedField
256
257
Hyperlinked relationship field that only provides links.
258
259
```python { .api }
260
class HyperlinkedRelatedField(HyperlinkedMixin, SkipDataMixin, RelatedField):
261
"""
262
Hyperlinked relationship field that skips data and only provides links.
263
264
Useful for providing relationship links without fetching relationship data
265
for performance optimization.
266
"""
267
```
268
269
### SerializerMethodHyperlinkedRelatedField
270
271
Hyperlinked relationship field that uses a serializer method.
272
273
```python { .api }
274
class SerializerMethodHyperlinkedRelatedField(HyperlinkedMixin, SerializerMethodFieldBase):
275
"""
276
Hyperlinked relationship field that gets its value from a serializer method.
277
278
Combines method-based value retrieval with hyperlink generation.
279
"""
280
```
281
282
### ManySerializerMethodHyperlinkedRelatedField
283
284
Many-to-many version of SerializerMethodHyperlinkedRelatedField.
285
286
```python { .api }
287
class ManySerializerMethodHyperlinkedRelatedField(SerializerMethodHyperlinkedRelatedField):
288
"""
289
Many-to-many version of SerializerMethodHyperlinkedRelatedField.
290
291
Always returns a list of hyperlinked relationships.
292
"""
293
294
many = True
295
```
296
297
### ManyRelatedFieldWithNoData
298
299
Many-to-many field that skips data rendering.
300
301
```python { .api }
302
class ManyRelatedFieldWithNoData(SkipDataMixin, DRFManyRelatedField):
303
"""
304
Many-to-many relationship field that skips data for performance.
305
306
Only includes relationship links, not the actual relationship data.
307
Useful for relationships that would cause expensive database queries.
308
"""
309
```
310
311
## Relationship Types
312
313
### To-One Relationships
314
315
```python
316
# ForeignKey field
317
class ArticleSerializer(serializers.ModelSerializer):
318
author = ResourceRelatedField(queryset=Author.objects.all())
319
320
# Generates:
321
# {
322
# "relationships": {
323
# "author": {
324
# "data": {"type": "authors", "id": "5"}
325
# }
326
# }
327
# }
328
```
329
330
### To-Many Relationships
331
332
```python
333
# ManyToMany or reverse ForeignKey field
334
class ArticleSerializer(serializers.ModelSerializer):
335
tags = ResourceRelatedField(queryset=Tag.objects.all(), many=True)
336
comments = ResourceRelatedField(source='comment_set', many=True, read_only=True)
337
338
# Generates:
339
# {
340
# "relationships": {
341
# "tags": {
342
# "data": [
343
# {"type": "tags", "id": "1"},
344
# {"type": "tags", "id": "2"}
345
# ]
346
# },
347
# "comments": {
348
# "data": [
349
# {"type": "comments", "id": "10"},
350
# {"type": "comments", "id": "11"}
351
# ]
352
# }
353
# }
354
# }
355
```
356
357
### Hyperlinked Relationships
358
359
```python
360
class ArticleAuthorField(HyperlinkedMixin, ResourceRelatedField):
361
self_link_view_name = 'article-relationships-author'
362
related_link_view_name = 'article-author'
363
364
class ArticleSerializer(serializers.ModelSerializer):
365
author = ArticleAuthorField(queryset=Author.objects.all())
366
367
# Generates:
368
# {
369
# "relationships": {
370
# "author": {
371
# "data": {"type": "authors", "id": "5"},
372
# "links": {
373
# "self": "/articles/1/relationships/author",
374
# "related": "/articles/1/author"
375
# }
376
# }
377
# }
378
# }
379
```
380
381
### Null Relationships
382
383
```python
384
# Allow null relationships
385
class ArticleSerializer(serializers.ModelSerializer):
386
author = ResourceRelatedField(
387
queryset=Author.objects.all(),
388
allow_null=True,
389
required=False
390
)
391
392
# When author is None:
393
# {
394
# "relationships": {
395
# "author": {
396
# "data": null
397
# }
398
# }
399
# }
400
```
401
402
## Performance Optimization
403
404
```python
405
# Skip relationship data for performance
406
class ArticleSerializer(serializers.ModelSerializer):
407
# Only include links, not data
408
expensive_relation = ManyRelatedFieldWithNoData()
409
410
# Or use custom field with SkipDataMixin
411
class OptimizedRelationField(SkipDataMixin, ResourceRelatedField):
412
pass
413
414
author = OptimizedRelationField()
415
416
# Generates relationship with links only:
417
# {
418
# "relationships": {
419
# "author": {
420
# "links": {
421
# "self": "/articles/1/relationships/author",
422
# "related": "/articles/1/author"
423
# }
424
# }
425
# }
426
# }
427
```
428
429
## Error Handling
430
431
Relationship fields provide proper validation errors:
432
433
```python
434
# Invalid resource type:
435
# {
436
# "errors": [{
437
# "detail": "Incorrect model type. Expected authors, received users.",
438
# "source": {"pointer": "/data/relationships/author"}
439
# }]
440
# }
441
442
# Object doesn't exist:
443
# {
444
# "errors": [{
445
# "detail": "Invalid pk \"999\" - object does not exist.",
446
# "source": {"pointer": "/data/relationships/author"}
447
# }]
448
# }
449
```
450
451
## Types
452
453
```python { .api }
454
from rest_framework_json_api.relations import (
455
ResourceRelatedField,
456
HyperlinkedMixin,
457
SerializerMethodResourceRelatedField,
458
ManySerializerMethodResourceRelatedField,
459
SkipDataMixin,
460
ManyRelatedFieldWithNoData
461
)
462
463
# Constants
464
LINKS_PARAMS = [
465
"self_link_view_name",
466
"related_link_view_name",
467
"related_link_lookup_field",
468
"related_link_url_kwarg"
469
]
470
```