I'm trying to expose an API to my Django model through Django REST framework.
I have an object Observation
. An observation can contain multiple things that have been observed. So I represented it like this:
class Observation(models.Model):
photo_file = models.ImageField( upload_to=img_dir, blank=True, null=True )
titestamp = models.DateTimeField(blank=True, null=True)
latitude = models.FloatField()
longitude = models.FloatField()
class ObservedThing(models.Model):
thing = models.ForeignKey(Thing) # the thing being observed
observation = models.ForeignKey(Observation, related_name='observed_thing')
value = models.FloatField()
As I understand this is a one-to-many relationship.
I now have an API View:
class ObsvList(generics.ListCreateAPIView):
"""
API endpoint that represents a list of observations.
"""
model = Observation
serializer_class = ObsvSerializer
and the corresponding serialiser:
class ObsvSerializer(serializers.ModelSerializer):
observed_thing = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Observation
What do I have to do to be able to POST an observation with several things detected? I cannot figure it out. Many thanks.
(answer more or less copied from another similar but less clear question)
To create multiple related objects in a single POST requires writable nested serializers which are not yet available.
Full support is a work in progress, but in the mean time one (hacky) solution is to override the create
method in the view in each case:
class FooListCreateView(ListCreateAPIView):
model = Foo
serializer_class = FooSerializer
def create(self, request, *args, **kwargs):
data=request.DATA
f = Foo.objects.create()
# ... create nested objects from request data ...
# ...
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
Probably not ideal, but it works for me until the proper way comes along.
The other option is to create the related Observation
objects individually with separate POSTs, and the use PrimaryKeyRelatedField or HyperlinkedRelatedField to make the associations in the final ObservedThing
POST.
I know this thread has already an answer but I started working to solve this problem, and since this post was one of my inspirations, I would like to share my final solution. It can be useful to someone. I have the models, so the parent class:
#parent model class
class Parent(models.Model):
id = models.AutoField(primary_key=True)
field = models.CharField(max_length=45)
class Meta:
managed = False
db_table = 'parent'
then, the child class:
#child model class
class Child(models.Model):
id = models.AutoField(primary_key=True)
field = models.CharField(max_length=45)
parent = models.ForeignKey(Parent, related_name='children')
class Meta:
managed = False
db_table = 'child'
I had to define the serializers, since I didn't want to create a router accessible url to directly manage Children objects, but I wanted to create them through the ModelViewSet of the parent ModelViewSet, this is what I needed:
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = Child
read_only_fields = ('id',)
class ParentSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
read_only_fields = ('id',)
class ParentSerializerNested(ParentSerializer):
children = ChildSerializer(many=True)
I was then ready to create the ModelViewSet, overriding/extending the create/update mixins, and make it generic in order to reuse it for other cases:
class ParentChildViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
serializer = self.serializer_parent(data=request.DATA,
files=request.FILES)
try:
if serializer.is_valid():
with transaction.commit_on_success():
self.pre_save(serializer.object)
parent = serializer.save(force_insert=True)
self.post_save(parent, created=True)
# need to insert children records
for child in request.DATA[self.child_field]:
child[self.parent_field] = parent.id
child_record = self.serializer_child(data=child)
if child_record.is_valid():
child_record.save(force_insert=True)
else:
raise ValidationError('Child validation failed')
headers = self.get_success_headers(serializer.data)
serializer.data[self.child_field] = self.serializer_child(
self.model_child.objects.filter(
**{self.parent_field: parent.id}).all(),
many=True).data
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
except ValidationError:
pass
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So I can reuse it for every nested relationship case I have in my app like this:
class ParentViewSet(ParentChildViewSet):
child_field = 'children'
parent_field = 'parent'
model = Parent
model_child = Child
serializer_class = ParentSerializerNested
serializer_parent = ParentSerializer
serializer_child = ChildSerializer
And in the end, the routing:
router = routers.DefaultRouter()
router.register(r'parents', ParentViewSet)
It works like a charm!
thing = models.ManyToManyField('Thing')
you need to use many to many relationship for creating a temporary table that will store the keys and associate data automatically.