Get relationship attributes with Django's REST API - django

There are two tables user, phone which are linked by an intermediate table owner. Here the goal is to use Rest API to get all phones from a specific user,
http://127.0.0.1/users/alice/phones/.
I use ModelSerializer as serializer and ViewSet as view. Please let me know how to get this done? I have no idea how to route /users/user_name/phones/ to get phones from a specific user.
Thanks.
Code snippet:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model=User
class PhoneSerializer(serializers.ModelSerializer):
class Meta:
model=Phone
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model=Owner
depth=1
// views
class UserViewSet(viewsets.ModelViewSet):
queryset=User.objects.all()
serializer_class=UserSerializer
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
....

I'd suggest you to create a filter.
It will be something like this:
1) Create filter (Make sure that django-filter is installed.):
# filters.py
import django_filters
class PhoneFilter(django_filters.FilterSet):
user = django_filters.Filter(name="user__user_name")
class Meta:
model = Phone
fields = ('user',)
2) Add filter to your ViewSet:
# views.py
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = filters.PhoneFilter
And now you may use this url: /phones/?user=user_name.

Use #detail_route to route URL to your function, e.g.,
#detail_route(methods=['get'])
def phones(self, request, pk=None):
pass
http://127.0.0.1/users/alice/phones will work!

Related

Django REST API Listview

Currently I'm trying to develop personal blog with Django/REST API, and I have trouble for that.
There are a number of posts in blog and I want to control those posts with Hyperlink. I made it by using ModelViewSet, however, whole data in detailView is also shown in ListView.
The thing is, I only want "url" and "title" of posts to be shown in ListView while DetailView contains full data.
Here is my code and current results given by REST framework.
Don't mind IndexView
# serializers
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = '__all__'
# views
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = serializers.PostSerializer
permission_classes = (IsAdminUser, )
Post List in REST API:
Post instance in REST API:
As far as I'm aware, you need a separate serializer for the list view.
You could create a custom serializer that takes in a fields arg to select specific fields. But its probably simpler to just have a separate one for the ListView. Also, for the list view, if you are only showing a subset of the model fields, you can use the only() function on the queryset to only return the model data that you need. For example:
qs = MyModel.objects.all().only('field_a', 'field_b', 'field_c')
Here is the custom serializer if you decide to go that way:
class CustomSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
selected_fields = kwargs.pop('selected_fields', None)
# used pop function so selected_fields is not passed to superclass
super().__init__(*args, **kwargs)
if selected_fields:
# make sure only fields for the model are allowed
fields = set(selected_fields)
current_fields = set(self.fields.keys())
for field in current_fields - fields:
self.fields.pop(field)
class MyModelSerializer(CustomSerializer):
class Meta:
model = MyModel
fields = '__all__'
In the list view:
required_fields = ('field_a', 'field_b', 'field_c')
data_to_return = MyModelSerializer(model_queryset, many=True, fields=required_fields).data
return Response(data)

Extending django-oscarapi API ROOT to custom API class

I have a django oscar application and I use django-oscarapi for my custom APIs. Some things are missing from the oscarapi like category and promotions but I have been able to use django-restframework to create the category API but the challenge I am facing now is how to add it to the API-ROOT. This is my code for rendering categories
customapi serializer class
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'numchild', 'name', 'description', 'image', 'slug')
Views
class CategoryList(generics.ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class CategoryDetail(generics.RetrieveAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
customapi/urls.py
url(r'^caty/$', CategoryList.as_view(), name='category-list'),
url(r'^caty/(?P<category_slug>[\w-]+(/[\w-]+)*)_(?P<pk>\d+)/$',
CategoryDetail.as_view(), name='category'),
Thanks in advance
You have to override the root view of oscarapi. There maybe a way to partially override the module, but I was unsuccessful at this.
module to override:
https://github.com/django-oscar/django-oscar-api/blob/master/oscarapi/views/root.py
In your project add a file yourApp/api/views/root.py and paste in the contents of the source file above.
You can then add end points by adding a tuple to the PUBLIC_APIS or ADMIN_APIS function.

django SearchFilter is not filtering the list of queryset,it returns all of it

this is my view views.py file
class StudentList(generics.ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
#pagination_class = StudentPageNumberPagination
filter_backends = [SearchFilter]
search_fields=['name','mobile']
this is my serializers class
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model=Student
fields=('id','name','mobile','time','late','date')
this is what i type in browser
http://192.168.0.118:8000/students/?name=ket
and i get all of items in the database
If you use SearchFilter your search string must be passed in search parameter, like this:
http://192.168.0.118:8000/students/?search=ket
You can read about it in docs. If you want to split your query parameters into specific fields you can use DjangoFilterBackend instead of SearchFilter.

How to allow generic filtering with djangorestframework on all fields?

My packages:
djangorestframework==3.2.5
django-filter==0.11.0
djangorestframework-filters==0.5.0
I'm using djangorestframework to expose my models as a restful api. To make my and the frontend dev's life easier I want to allow any filter lookup on any model field. But as far as I could see neither of the packages supports such generic filtering out of the box. I can use a AllLookupsFilter but still need to create a filterset class per model and specify each field.
Is there a generic approach to allow filtering on all models and all fields?
I created a little helper function that creates the filterset on-the-fly based on the queryset model of the modelviewset. It adds an AllLookupFilter for each field in the model.
from rest_framework import viewsets
import rest_framework_filters as filters
def create_universal_filter(klass):
"""Creates filterset class with all lookups for all fields of given class"""
field_filters = dict((f, filters.ALL_LOOKUPS)
for f in klass._meta.get_all_field_names())
class MyFilter(filters.FilterSet):
class Meta:
model = klass
fields = field_filters
return MyFilter
class GenericFilterModelViewSet(viewsets.ModelViewSet):
"""Allows all lookups on all fields"""
def __init__(self, *args, **kwargs):
self.filter_class = create_universal_filter(self.queryset.model)
Then I let the modelviewsets where I want to allow generic filtering inherit from it:
class DerivateGroupViewSet(GenericFilterModelViewSet):
queryset = models.DerivateGroup.objects.all()
serializer_class = serializers.DerivateGroupSerializer
You can just add filter_backends to all needed views.
from rest_framework.filters import SearchFilter
class SimpleClass(generics.ListCreateAPIView):
serializer_class = SimpleSerializer
filter_backends = [SearchFilter]

How to represent `self` url in django-rest-framework

I want to add a link to a single resource representation which is an URL to itself, self. Like (taken from documentation):
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing')
{
'album_name': 'The Eraser',
'artist': 'Thom Yorke',
'self': 'http://www.example.com/api/album/2/',
}
How should this be done?
If you inherit serializers.HyperlinkedModelSerializer all you need to do is pass a url field to fields. See the docs here:
http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/
Alright, this solved my problem but if you have a better solution please post an answer:
from django.urls import reverse
from rest_framework import serializers
self_url = serializers.SerializerMethodField('get_self')
def get_self(self, obj):
request = self.context['request']
return reverse('album-detail', kwargs={'id': obj.id}, request=request)
here is my solution,
in your view methods create serilizer object like this:
album = AlbumSerializer(data=data, {"request":request})
in your serilizer class override to_representation method (you can read about this method on DRF docs
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
def to_representation(self, obj):
data = super().to_representation(obj)
request = self.context["request"]
return data
According to this issue, you can just add 'url' in the list of fields.
Here is a little more context than you got in the other answers so far. The key is the context argument passed to the serializer constructor and the 'url' in fields.
http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/
In your viewset:
class AlbumViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Album.objects.all()
serializer = AlbumSerializer(queryset, many=True,
context={'request': request})
return Response(serializer.data)
In the serializer:
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing', 'url')
As stated above the HyperlinkedModelSerializer will convert all your related fields and remove the ID of the resource as well. I use one of the following solutions, depending on the situation.
Solution 1: import api settings and add the url field:
For more details see URL_FIELD_NAME.
from rest_framework.settings import api_settings
class AlbumSerializer(SelfFieldMixin, serializers.ModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing', api_settings.URL_FIELD_NAME)
Solution 2: a simple mixin, which only works if default fields are used:
class SelfFieldMixin:
"""
Adds the self link without converting all relations to HyperlinkedRelatedField
"""
def get_default_field_names(self, declared_fields, model_info):
"""
Return the default list of field names that will be used if the
`Meta.fields` option is not specified.
"""
default_fields = super().get_default_field_names(declared_fields, model_info)
return [self.url_field_name, *default_fields]
And it can be used like
class AlbumSerializer(SelfFieldMixin, serializers.ModelSerializer):
class Meta:
model = Album
fields = '__all__'
NOTE: It requires Python 3 due to the super() call, the a mixin must be placed before any of the serializer classes!
P.S.: To achieve the required response in the question one must also set the URL_FIELD_NAME to 'self'.
Edit: get_default_field_names must return a list object for Meta.exclude to work on ModelSerializers.
You can use the HyperlinkedIdentityField like so:
class ThingSerializer(ModelSerializer):
class Meta:
model = Thing
fields = ['self_link', ...]
self_link = HyperlinkedIdentityField(view_name='thing-detail')
You need to have your routes named appropriately but the Default routers do so automatically (documented here).
As others have pointed out the HyperlinkedModelSerializer also works. This is because it uses this field automatically. See here.