Skip to content

Instantly share code, notes, and snippets.

@rodrigogadea
Last active December 23, 2015 03:48
Show Gist options
  • Save rodrigogadea/6575452 to your computer and use it in GitHub Desktop.
Save rodrigogadea/6575452 to your computer and use it in GitHub Desktop.
Problem understanding how DRF works
class BookmarkViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions for Bookmarks
"""
queryset = Bookmark.objects.all()
serializer_class = BookmarkSerializer2
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly, IsAuthorizedOr404)
def pre_save(self, obj):
obj.owner = self.request.user
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
serializer.restore_object(request.DATA) # Call the restore_object method "manually"
if serializer.is_valid():
#print "th:is_valid", serializer.object.thumbnail
self.pre_save(serializer.object)
#print "th:pre_save", serializer.object.thumbnail
self.object = serializer.save(force_insert=True)
#print "th:save", serializer.object.thumbnail
self.post_save(self.object, created=True)
#print "th post_save:", serializer.object.thumbnail
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Hi! (I'm leaving this Gist because it may be easier to read than in the mailing list: https://gist.github.com/rodrigogadea/6575452)
The past days I have been struggling with understanding how DRF works. Coming from tastypie, I don't know if my head keeps thinking in tastypie or I'm not getting the internals or how it should be coded with DRF (probably the 3 of them).
We have these 4 models (in models.py): Bookmark, URL, ImageThumbnail and Topic (we have more, but they are not relevant =P)
class Bookmark(models.Model):
url = models.ForeignKey(URL)
owner = models.ForeignKey(User, related_name="bookmarks")
thumbnail = models.ForeignKey(ImageThumbnail, null=True, blank=True)
title = models.CharField(null=True, blank=True, max_length=767)
topic = models.ForeignKey(Topic, null=True, blank=True)
class URL(models.Model):
url = models.URLField(max_length=2083)
class ImageThumbnail(models.Model):
height = models.PositiveIntegerField(default=0)
width = models.PositiveIntegerField(default=0)
image = models.ImageField(upload_to=get_image_path, height_field='height',
width_field='width', null=True, blank=True,
max_length=767)
class Topic(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User)
And I want to create and endpoint for Bookmarks in which our client will POST something like:
POST /api/bookmarks
{
"url" : "http://myspace.com/RebeccaBlack",
"topic": "Awesome!",
"title": "I <3 Rebecca",
"image": "/api/thumbnails/4"
}
The problem is that "url", "topic" and "image" are FK to other models, so this would lead to doing 3 requests per new bookmark, one per each model to create the objects, then get their pks /urls to finally create a bookmark.
Then, the goal is having in return a json like:
RESP 201
{
"url" : "/api/urls/3",
"topic": "/api/topics/2",
"title": "I <3 Rebecca and I <3 Friday",
"image": "/api/thumbnails/4"
}
So, what I wanted to do is to use Django's get_or_create method to create the other objects if they don't exists and create the bookmark with only 1 request (save bandwidth in expense of server processing - this may lead to a "lean" client).
In order to do so (as for what I read, there is no nested write support), I tried to write a serializer to do so, and from the documentation, the restore_object method is what should be re-written, so I ended up with this:
class BookmarkSerializer2(serializers.Serializer):
pk = serializers.Field(source='pk')
title = serializers.CharField()
owner = serializers.Field(source='owner.username')
url = serializers.SlugRelatedField(slug_field="url",
queryset=URL.objects.all())
topic = serializers.SlugRelatedField(slug_field="name",
queryset=Topic.objects.all())
thumbnail = serializers.PrimaryKeyRelatedField(
#view_name="imagethumbnail-detail",
required=False,
queryset=ImageThumbnail.objects.all())
def restore_object(self, attrs, instance=None):
"""
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
"""
if instance:
instance.url, url_created = \
URL.objects.get_or_create(url=attrs.get('url', instance.url))
instance.topic, topic_created = \
Topic.objects.get_or_create(
name=attrs.get('topic', instance.topic),
owner=self.context['request'].user)
instance.thumbnail = attrs.get('thumbnail', instance.thumbnail)
instance.title = attrs.get('title', instance.title)
instance.owner = attrs.get('owner', instance.owner)
url, url_created = URL.objects.get_or_create(url=attrs.get('url'))
topic, topic_created = Topic.objects.get_or_create(
name=attrs.get('topic'),
owner=self.context['request'].user)
try:
thumbnail = \
ImageThumbnail.objects.get(pk = int(attrs.get('thumbnail')))
except:
thumbnail = None
title = attrs.get('title', None)
bmk = Bookmark(title=title, url=url, topic=topic,
thumbnail=thumbnail)
return bmk
Which lead me to this view (with a "default" Router):
class BookmarkViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions for Bookmarks
"""
queryset = Bookmark.objects.all()
serializer_class = BookmarkSerializer2
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly, IsAuthorizedOr404)
def pre_save(self, obj):
obj.owner = self.request.user
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
serializer.restore_object(request.DATA) # Call the restore_object method "manually"
if serializer.is_valid():
#print "th:is_valid", serializer.object.thumbnail
self.pre_save(serializer.object)
#print "th:pre_save", serializer.object.thumbnail
self.object = serializer.save(force_insert=True)
#print "th:save", serializer.object.thumbnail
self.post_save(self.object, created=True)
#print "th post_save:", serializer.object.thumbnail
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And I encountered a lot of problems with this approach and it is not fully functional yet, so I wanted to ask for help about how to achieve it.
First, I found that restore_object was not called at all when the request was processed (the POST for creation), which lead to me to copy the create method of the CreateModelMixin and try to call it "manually".
This addressed some of the problems, because now the Topic and the URL objects gets created before .is_valid? being called, which makes the method execute, and pre_save, save et al. are called.
Then, I tried with different fields for serializing a FK (pk is the easiest, because of Model.objects.get(pk=) but if you are returning url (because of caching) there is no (or I couldn't find it) get_by_uri(Model, uri) (or just get_by_uri(uri)) and you may end up asking the client to parse the pk of the uri to post), with no much success.
Finally, the only problem left is that the Thumbnail object doesn't get assigned to the Bookmark, and I think it's because the restore_object method doesn't get called in the view when created. The is_valid method doesn't use it nor the get_serializer, so where should I put this? The Bookmark gets created, but "thumbnail" gets always "null". I can assign it via the web interface later, though...
My approach seems too complicated, which leads me to think that I am not getting it right ("not thinking with a "drf" head").
Any insight / guidance on this would be highly appreciated, and thanks a lot in advance!!
Cheers,
Rodrigo
class Bookmark(models.Model):
url = models.ForeignKey(URL)
owner = models.ForeignKey(User, related_name="bookmarks")
thumbnail = models.ForeignKey(ImageThumbnail, null=True, blank=True)
title = models.CharField(null=True, blank=True, max_length=767)
topic = models.ForeignKey(Topic, null=True, blank=True)
class URL(models.Model):
url = models.URLField(max_length=2083)
class ImageThumbnail(models.Model):
height = models.PositiveIntegerField(default=0)
width = models.PositiveIntegerField(default=0)
image = models.ImageField(upload_to=get_image_path, height_field='height',
width_field='width', null=True, blank=True,
max_length=767)
class Topic(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User)
class BookmarkSerializer2(serializers.Serializer):
pk = serializers.Field(source='pk')
title = serializers.CharField()
owner = serializers.Field(source='owner.username')
url = serializers.SlugRelatedField(slug_field="url",
queryset=URL.objects.all())
topic = serializers.SlugRelatedField(slug_field="name",
queryset=Topic.objects.all())
thumbnail = serializers.PrimaryKeyRelatedField(
#view_name="imagethumbnail-detail",
required=False,
queryset=ImageThumbnail.objects.all())
def restore_object(self, attrs, instance=None):
"""
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
"""
if instance:
instance.url, url_created = \
URL.objects.get_or_create(url=attrs.get('url', instance.url))
instance.topic, topic_created = \
Topic.objects.get_or_create(
name=attrs.get('topic', instance.topic),
owner=self.context['request'].user)
instance.thumbnail = attrs.get('thumbnail', instance.thumbnail)
instance.title = attrs.get('title', instance.title)
instance.owner = attrs.get('owner', instance.owner)
url, url_created = URL.objects.get_or_create(url=attrs.get('url'))
topic, topic_created = Topic.objects.get_or_create(
name=attrs.get('topic'),
owner=self.context['request'].user)
try:
thumbnail = \
ImageThumbnail.objects.get(pk = int(attrs.get('thumbnail')))
except:
thumbnail = None
title = attrs.get('title', None)
bmk = Bookmark(title=title, url=url, topic=topic,
thumbnail=thumbnail)
return bmk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment