Skip to content

Commit d05c9d8

Browse files
committed
new style serializers (squash for rebase)
1 parent 726a6d5 commit d05c9d8

File tree

8 files changed

+3163
-5
lines changed

8 files changed

+3163
-5
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,31 @@ py.test
228228
```
229229

230230

231+
New-Style Serializers
232+
================
233+
234+
In 2020, an enhanced set of mixins were added that permit fine-grained control of nested
235+
Serializer behavior using a `match_on` argument. New-style serializers delegate control
236+
of the Create/Update behavior to the nested Serializer. The parent Serializer need only
237+
resolve nested serializers in the right order; this is handled by the `RelatedSaveMixin`.
238+
239+
New-style Serializers provide the following semantics:
240+
241+
- Get: retrieve a matching object (but DO NOT update)
242+
- Update: retrieve and update a matching object
243+
- Create: create an object using the entire payload
244+
- Combinations of the above e.g. GetOrCreate and UpdateOrCreate
245+
246+
The matching of `data` to a specific `instance` is driven by a list of fields found in
247+
`match_on`. This value is obtained from:
248+
249+
- the `match_on` kwarg provided when the field is initialized
250+
- the DEFAULT_MATCH_ON class attribute
251+
252+
The new-style Serializers may be used as top-level Serializers to provide get-or-create
253+
behaviors to DRF endpoints. Examples of use can be found in
254+
`test_nested_serializer_mixins.py`.
255+
231256
Known problems with solutions
232257
=============================
233258

drf_writable_nested/mixins.py

Lines changed: 528 additions & 4 deletions
Large diffs are not rendered by default.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Generated by Django 2.1.3 on 2021-02-01 14:52
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('tests', '0001_initial'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Child',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('name', models.TextField()),
21+
],
22+
),
23+
migrations.CreateModel(
24+
name='ContextChild',
25+
fields=[
26+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
27+
('name', models.TextField()),
28+
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
29+
],
30+
),
31+
migrations.CreateModel(
32+
name='GrandParent',
33+
fields=[
34+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35+
],
36+
),
37+
migrations.CreateModel(
38+
name='LookupChild',
39+
fields=[
40+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
41+
('name', models.TextField()),
42+
],
43+
),
44+
migrations.CreateModel(
45+
name='LookupGrandParent',
46+
fields=[
47+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
48+
],
49+
),
50+
migrations.CreateModel(
51+
name='LookupOneToOneChild',
52+
fields=[
53+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
54+
('name', models.TextField()),
55+
],
56+
),
57+
migrations.CreateModel(
58+
name='LookupParent',
59+
fields=[
60+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
61+
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parent', to='tests.LookupChild')),
62+
('child2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parent2', to='tests.LookupChild')),
63+
],
64+
),
65+
migrations.CreateModel(
66+
name='LookupReverseChild',
67+
fields=[
68+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
69+
('name', models.TextField()),
70+
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='tests.LookupParent')),
71+
],
72+
),
73+
migrations.CreateModel(
74+
name='M2MSource',
75+
fields=[
76+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
77+
('name', models.TextField()),
78+
],
79+
),
80+
migrations.CreateModel(
81+
name='M2MTarget',
82+
fields=[
83+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
84+
('name', models.TextField()),
85+
],
86+
),
87+
migrations.CreateModel(
88+
name='NewProfile',
89+
fields=[
90+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
91+
('age', models.IntegerField()),
92+
],
93+
),
94+
migrations.CreateModel(
95+
name='NewUser',
96+
fields=[
97+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
98+
('username', models.TextField()),
99+
],
100+
),
101+
migrations.CreateModel(
102+
name='Parent',
103+
fields=[
104+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
105+
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.Child')),
106+
],
107+
),
108+
migrations.CreateModel(
109+
name='ParentMany',
110+
fields=[
111+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
112+
('children', models.ManyToManyField(to='tests.Child')),
113+
],
114+
),
115+
migrations.CreateModel(
116+
name='ReadOnlyChild',
117+
fields=[
118+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
119+
('name', models.TextField()),
120+
],
121+
),
122+
migrations.CreateModel(
123+
name='ReadOnlyParent',
124+
fields=[
125+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
126+
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.ReadOnlyChild')),
127+
],
128+
),
129+
migrations.CreateModel(
130+
name='ReverseChild',
131+
fields=[
132+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
133+
('name', models.TextField()),
134+
],
135+
),
136+
migrations.CreateModel(
137+
name='ReverseManyChild',
138+
fields=[
139+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
140+
('name', models.TextField()),
141+
],
142+
),
143+
migrations.CreateModel(
144+
name='ReverseManyParent',
145+
fields=[
146+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
147+
],
148+
),
149+
migrations.CreateModel(
150+
name='ReverseParent',
151+
fields=[
152+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
153+
],
154+
),
155+
migrations.AddField(
156+
model_name='reversemanychild',
157+
name='parent',
158+
field=models.ManyToManyField(related_name='children', to='tests.ReverseManyParent'),
159+
),
160+
migrations.AddField(
161+
model_name='reversechild',
162+
name='parent',
163+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='tests.ReverseParent'),
164+
),
165+
migrations.AddField(
166+
model_name='newprofile',
167+
name='user',
168+
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to='tests.NewUser'),
169+
),
170+
migrations.AddField(
171+
model_name='m2msource',
172+
name='forward',
173+
field=models.ManyToManyField(related_name='reverse', to='tests.M2MTarget'),
174+
),
175+
migrations.AddField(
176+
model_name='lookuponetoonechild',
177+
name='parent',
178+
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='one_to_one', to='tests.LookupParent'),
179+
),
180+
migrations.AddField(
181+
model_name='lookupgrandparent',
182+
name='child',
183+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.LookupParent'),
184+
),
185+
migrations.AddField(
186+
model_name='grandparent',
187+
name='child',
188+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.Parent'),
189+
),
190+
]

tests/models.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import uuid
2+
3+
from django.conf import settings
24
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
35
from django.contrib.contenttypes.models import ContentType
46
from django.db import models
@@ -147,3 +149,90 @@ class I86Name(models.Model):
147149
class I86Genre(models.Model):
148150
pass
149151

152+
153+
class ReadOnlyChild(models.Model):
154+
name = models.TextField()
155+
156+
157+
class ReadOnlyParent(models.Model):
158+
child = models.ForeignKey(ReadOnlyChild, on_delete=models.CASCADE)
159+
160+
161+
class Child(models.Model):
162+
name = models.TextField()
163+
164+
165+
class Parent(models.Model):
166+
child = models.ForeignKey(Child, on_delete=models.CASCADE)
167+
168+
169+
class ParentMany(models.Model):
170+
children = models.ManyToManyField(Child)
171+
172+
173+
class ReverseParent(models.Model):
174+
pass
175+
176+
177+
class ReverseChild(models.Model):
178+
name = models.TextField()
179+
parent = models.ForeignKey(ReverseParent, on_delete=models.CASCADE, related_name='children')
180+
181+
182+
class ReverseManyParent(models.Model):
183+
pass
184+
185+
186+
class ReverseManyChild(models.Model):
187+
name = models.TextField()
188+
parent = models.ManyToManyField(ReverseManyParent, related_name='children')
189+
190+
191+
class LookupChild(models.Model):
192+
name = models.TextField()
193+
194+
195+
class LookupParent(models.Model):
196+
child = models.ForeignKey(LookupChild, on_delete=models.CASCADE, related_name='parent')
197+
child2 = models.ForeignKey(LookupChild, on_delete=models.CASCADE, related_name='parent2')
198+
199+
200+
class LookupReverseChild(models.Model):
201+
name = models.TextField()
202+
parent = models.ForeignKey(LookupParent, on_delete=models.CASCADE, related_name='children')
203+
204+
205+
class LookupOneToOneChild(models.Model):
206+
name = models.TextField()
207+
parent = models.OneToOneField(LookupParent, on_delete=models.CASCADE, related_name='one_to_one')
208+
209+
210+
class LookupGrandParent(models.Model):
211+
child = models.ForeignKey(LookupParent, on_delete=models.CASCADE)
212+
213+
214+
class M2MTarget(models.Model):
215+
name = models.TextField()
216+
217+
218+
class M2MSource(models.Model):
219+
forward = models.ManyToManyField(M2MTarget, related_name='reverse')
220+
name = models.TextField()
221+
222+
223+
class GrandParent(models.Model):
224+
child = models.ForeignKey(Parent, on_delete=models.CASCADE)
225+
226+
227+
class ContextChild(models.Model):
228+
name = models.TextField()
229+
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
230+
231+
232+
class NewUser (models.Model):
233+
username = models.TextField()
234+
235+
236+
class NewProfile(models.Model):
237+
user = models.OneToOneField(NewUser, on_delete=models.CASCADE, related_name='profile')
238+
age = models.IntegerField()

0 commit comments

Comments
 (0)