python - Custom Hyperlinked URL field for more than one lookup field in a serializer of DRF -
i using django rest framework developing web api project. in project need build nested api's endpoint this:
/users/ - users /users/<user_pk> - details of particular user /users/<user_pk>/mails/ - mails sent user /users/<user_pk>/mails/<pk> - details of mail sent user
so, using drf-nested-routers ease of writing & maintaing these nested resources.
i want output of endpoints have hyperlink getting details of each nested resource alongwith other details this:
[ { "url" : "http://localhost:8000/users/1", "first_name" : "name1", "last_name": "lastname" "email" : "name1@xyz.com", "mails": [ { "url": "http://localhost:8000/users/1/mails/1", "extra_data": "this data", "mail":{ "url": "http://localhost:8000/mails/3" "to" : "abc@xyz.com", "from": "name1@xyz.com", "subject": "this subject text", "message": "this message text" } }, { .......... } .......... ] } ......... ]
to this, write serializers inherit hyperlinkedmodelserializer
per drf docs, automatically adds url
field in response during serialization.
but, default drf serializers not support generation of url nested resource above mentioned or can more single lookup field. handle situation, recommended create custom hyperlinked field.
i followed doc, , write custom code handling url generation of nested resource. code snippets follows:
models.py
from django.contrib.auth.models import abstractuser django.db import models # user model class user(models.abstractuser): mails = models.manytomanyfield('mail', through='usermail', through_fields=('user', 'mail')) # mail model class mail(models.model): = models.emailfield() = models.emailfield() subject = models.charfield() message = models.charfield() # user mail model class usermail(models.model): user = models.foreignkey('user') mail = models.foreignkey('mail') extra_data = models.charfield()
serializers.py
from rest_framework import serializers .models import user, mail, usermail .serializers_fields import usermailhyperlink # mail serializer class mailserializer(serializers.hyperlinkedmodelserializer): class meta: model = mail fields = ('url', 'to', 'from', 'subject', 'message' ) # user mail serializer class usermailserializer(serializers.hyperlinkedmodelserializer): url = usermailhyperlink() mail = mailserializer() class meta: model = usermail fields = ('url', 'extra_data', 'mail') # user serializer class userserializer(serializers.hyperlinkedmodelserializer): mails = usermailserializer(source='usermail_set', many=true) class meta: model = user fields = ('url', 'first_name', 'last_name', 'email', 'mails')
serializers_fields.py
from rest_framework import serializers rest_framework.reverse import reverse .models import usermail class usermailhyperlink(serializers.hyperlinkedrelatedfield): view_name = 'user-mail-detail' queryset = usermail.objects.all() def get_url(self, obj, view_name, request, format): url_kwargs = { 'user_pk' : obj.user.pk, 'pk' : obj.pk } return reverse(view_name, kwargs=url_kwargs, request=request, format=format) def get_object(self, view_name, view_args, view_kwargs): lookup_kwargs = { 'user_pk': view_kwargs['user_pk'], 'pk': view_kwargs['pk'] } return self.get_queryset().get(**lookup_kwargs)
views.py
from rest_framework import viewsets rest_framework.response import response django.shortcuts import get_object_or_404 .models import user, usermail .serializers import userserializer, mailserializer class userviewset(viewsets.modelviewset): queryset = user.objects.all() serializer_class = userserializer class usermailviewset(viewsets.viewset): queryset = usermail.objects.all() serializer_class = usermailserializer def list(self, request, user_pk=none): mails = self.queryset.filter(user=user_pk) serializer = self.serializer_class(mails, many=true, context={'request': request} ) return response(serializer.data) def retrieve(self, request, pk=none, user_pk=none): queryset = self.queryset.filter(pk=pk, user=user_pk) mail = get_object_or_404(queryset, pk=pk) serializer = self.serializer_class(mail, context={'request': request} ) return response(serializer.data)
urls.py
from rest_framework.routers import defaultrouter rest_framework_nested import routers django.conf.urls import include, url import views router = defaultrouter() router.register(r'users', views.userviewset, base_name='user') user_router = routers.nestedsimplerouter(router, r'users', lookup='user' ) user_router.register(r'mails', views.usermailviewset, base_name='user-mail' ) urlpatterns = [ url(r'^', include(router.urls)), url(r'^', include(user_router.urls)), ]
now, after doing when run project , ping /users/
api endpoint, got error:
attributeerror : 'usermail' object has no attribute 'url'
i couldn't understand why error came, because in usermailserializer
added url
field attribute of serializer, when has serialize why takes url
field attribute of usermail
model. please me out away problem.
p.s: please don't suggest refactoring in models. as, here disguised project real idea user
& mail
thing. so, take test case , suggest me solution.
i needed similar lately. solution ended making custom relations field. save space, ill (and shamelessly) point source code. important part adding lookup_fields
, lookup_url_kwargs
class attributes used internally both lookup objects , construct uris:
class multiplepkshyperlinkedidentityfield(hyperlinkedidentityfield): lookup_fields = ['pk'] def __init__(self, view_name=none, **kwargs): self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields) self.lookup_url_kwargs = kwargs.pop('lookup_url_kwargs', self.lookup_fields) ...
that in turn allows usage like:
class myserializer(serializers.modelserializer): url = multiplepkshyperlinkedidentityfield( view_name='api:my-resource-detail', lookup_fields=['form_id', 'pk'], lookup_url_kwargs=['form_pk', 'pk'] )
here how use source code.
hopefully can started.
Comments
Post a Comment