Views.py -
#api_view(['GET'])
def view_items(request):
if request.query_params:
items = Item.objects.filter(**request.query_param.dict()) #error line
else:
items=Item.objects.all()
serializer=ItemSerializer(items,many=True)
if items:
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
The **request.query_param should be changed into **request.query_params.
Let's say you want to filter by name.
#api_view(['GET'])
def view_items(request):
name = request.query_params.get("name", None)
if name:
items = Item.objects.filter(name=name)
I would suggest you to use django-filters, that's a better way to structure your code.
Create Item app
urls.py
app_name = "app-name"
router = SimpleRouter()
router.register("items", ItemViewSet, basename="item")
views.py
class ItemViewSet(views.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filterset_class = ItemFilterSet
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ("id", "name", "label")
filters.py
from django_filters import rest_framework as filters
class ItemFilterSet(filters.FilterSet):
class Meta:
model = Item
fields = ("name", "label")
I'm trying to rebuild a legacy API with DRF and django-filter. One of the field names is from, but
from = DateTimeFilter(field_name="created_dt", lookup_expr="gte")
is not valid Python. Can I name the variable
from_ = DateTimeFilter(...)
but still expose the API parameter as ?from= to users?
Override the __init__() method of FilterSet class
from django_filters import rest_framework as drf_filters
class FooFilterSet(drf_filters.FilterSet):
foo = drf_filters.CharFilter() # some random field
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters["from"] = drf_filters.DateTimeFilter(field_name="created_dt", lookup_expr="gte")
class Meta:
model = Foo
fields = ('foo', 'from')
JPG solution doesn't work for me in django-filter 2.4.0, throws an error
TypeError: 'Meta.fields' must not contain non-model field names: from.
I solved it by overriding the metaclass method:
import django_filters
from django.db.models import QuerySet
from django_filters.filterset import FilterSetMetaclass
class FilterSetMetaclassWithFrom(FilterSetMetaclass):
"""Replaces 'from_' FilterField to 'from'"""
#classmethod
def get_declared_filters(cls, bases, attrs):
filters = super().get_declared_filters(bases, attrs)
if 'from_' in filters:
filters['from'] = filters['from_']
del filters['from_']
return filters
class FromFilterSet(django_filters.FilterSet, metaclass=FilterSetMetaclassWithFrom):
to = django_filters.CharFilter(method='filter_to')
from_ = django_filters.CharFilter(method='filter_from')
class Meta:
model = MySuperModel
fields = []
def filter_to(self, queryset: QuerySet[UserModel], name: str, value: str):
# some logic
return queryset
def filter_from(self, queryset: QuerySet[UserModel], name: str, value: str):
# Note that the name == 'from_', not 'from'!
# some logic
return queryset
My models are:
class User(models.Model):
id = models.AutoField(primary_key=True)
email = models.EmailField()
class Lawyer(models.Model):
user = models.OnetoOneField(User)
class Session(models.Model):
name = models.CharField()
lawyer = models.ForeignKey(Lawyer)
I am trying to create multiple objects with a list serializer for Session.
From the app side they don't have the id of lawyer, but have the email of each lawyer. How can I write a list serializer where I can take the following json input and use email to fetch lawyer to store multiple session objects?
The json input sent will be like:
[
{
"name": "sess1",
"email": "lawyer1#gmail.com",
},
{
"name": "sess1",
"email": "lawyer1#gmail.com",
}
]
You can do it in this way but I think email should be unique=True.
Then use a serializer like this:
from django.utils.translation import ugettext_lazy as _
class SessionCreateManySerializer(serializers.ModelSerializer):
email = serializers.SlugRelatedField(
source='lawyer',
slug_field='user__email',
queryset=Lawyer.objects.all(),
write_only=True,
error_messages={"does_not_exist": _('lawyer with email={value} does not exist.')}
)
class Meta:
model = Session
fields = ('name', 'email')
and a generic create view (just override get_serializer and place many=True in kwargs ):
from rest_framework.response import Response
from rest_framework import generics
class SessionCreateManyView(generics.CreateAPIView):
serializer_class = SessionCreateManySerializer
def get_serializer(self, *args, **kwargs):
kwargs['many'] = True
return super(SessionCreateManyView, self).get_serializer(*args, **kwargs)
You can use bulk creation as well:
# serializers.py
from myapp.models import Session
from rest_framework import serializers
class SessionSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
name = serializers.CharField(required=True)
def validate_email(self, email):
lawyer = Lawyer.objects.filter(user__email=email).first()
if not lawyer:
raise ValidationError(detail="user dose not exist.")
return lawyer
def create(self, validated_data):
return Session.objects.create(name=validated_data.get('name'), lawyer=validated_data.get('email'))
and in your api.py file allow bulk creation:
# api.py
from rest_framework import generics
class SessionCreateAPIView(generics.CreateAPIView):
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
I want to Translate the variables "name" and "description" of my CategorySerializer, when serializing.
from rest_framework import serializers
from api.models import Category
from django.utils.translation import ugettext_lazy as _
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'image', 'description')
Is the serializer method field a good aproach?
PD: this names are translated on the django.po.
Yes, simply define a SerializerMethodField and return the translation on the fly. The following example changes the field 'description' to 'translation':
class CategorySerializer(serializers.ModelSerializer):
translation = SerializerMethodField('get_description_string')
class Meta:
model = Category
fields = ('id','translation',)
def get_description_string(self,obj):
return obj.description
serializers.py
from rest_framework import serializers
from api.models import Category
from django.utils.translation import ugettext_lazy as _
class CategorySerializer(serializers.ModelSerializer):
name_ = serializers.ReadOnlyField(source='get_name')
description_ = serializers.ReadOnlyField(source='get_description')
class Meta:
model = Category
fields = ('id', 'name', 'image', 'description')
def get_name(self):
return _(self.name)
def get_description(self):
return _(self.name)
If you want, you can change fields' name as "name" and "description". And then;
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
# We skip `to_representation` for `None` values so that fields do
# not have to explicitly deal with that case.
#
# For related fields with `use_pk_only_optimization` we need to
# resolve the pk value.
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
# override to_representation function
if field.field_name == "name" or field.field_name == "description":
ret[field.field_name] = _(attribute)
return ret
Problem
As recommended in the blogpost Best Practices for Designing a Pragmatic RESTful API, I would like to add a fields query parameter to a Django Rest Framework based API which enables the user to select only a subset of fields per resource.
Example
Serializer:
class IdentitySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Identity
fields = ('id', 'url', 'type', 'data')
A regular query would return all fields.
GET /identities/
[
{
"id": 1,
"url": "http://localhost:8000/api/identities/1/",
"type": 5,
"data": "John Doe"
},
...
]
A query with the fields parameter should only return a subset of the fields:
GET /identities/?fields=id,data
[
{
"id": 1,
"data": "John Doe"
},
...
]
A query with invalid fields should either ignore the invalid fields or throw a client error.
Goal
Is this possible out of the box somehow? If not, what's the simplest way to implement this? Is there a 3rd party package around that does this already?
You can override the serializer __init__ method and set the fields attribute dynamically, based on the query params. You can access the request object throughout the context, passed to the serializer.
Here is a copy&paste from Django Rest Framework documentation example on the matter:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
This functionality is available from a 3rd-party package.
pip install djangorestframework-queryfields
Declare your serializer like this:
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
Then the fields can now be specified (client-side) by using query arguments:
GET /identities/?fields=id,data
Exclusion filtering is also possible, e.g. to return every field except id:
GET /identities/?fields!=id
disclaimer: I'm the author/maintainer.
serializers.py
class DynamicFieldsSerializerMixin(object):
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsSerializerMixin, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
style={'input_type': 'password'}, write_only=True
)
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class DynamicFieldsViewMixin(object):
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
fields = None
if self.request.method == 'GET':
query_fields = self.request.QUERY_PARAMS.get("fields", None)
if query_fields:
fields = tuple(query_fields.split(','))
kwargs['context'] = self.get_serializer_context()
kwargs['fields'] = fields
return serializer_class(*args, **kwargs)
class UserList(DynamicFieldsViewMixin, ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Configure a new pagination serializer class
from rest_framework import pagination, serializers
class DynamicFieldsPaginationSerializer(pagination.BasePaginationSerializer):
"""
A dynamic fields implementation of a pagination serializer.
"""
count = serializers.Field(source='paginator.count')
next = pagination.NextPageField(source='*')
previous = pagination.PreviousPageField(source='*')
def __init__(self, *args, **kwargs):
"""
Override init to add in the object serializer field on-the-fly.
"""
fields = kwargs.pop('fields', None)
super(pagination.BasePaginationSerializer, self).__init__(*args, **kwargs)
results_field = self.results_field
object_serializer = self.opts.object_serializer_class
if 'context' in kwargs:
context_kwarg = {'context': kwargs['context']}
else:
context_kwarg = {}
if fields:
context_kwarg.update({'fields': fields})
self.fields[results_field] = object_serializer(source='object_list',
many=True,
**context_kwarg)
# Set the pagination serializer setting
REST_FRAMEWORK = {
# [...]
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'DynamicFieldsPaginationSerializer',
}
Make dynamic serializer
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
See:
http://tomchristie.github.io/rest-framework-2-docs/api-guide/serializers
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
# Use it
class MyPonySerializer(DynamicFieldsModelSerializer):
# [...]
Last, use a homemage mixin for your APIViews
class DynamicFields(object):
"""A mixins that allows the query builder to display certain fields"""
def get_fields_to_display(self):
fields = self.request.GET.get('fields', None)
return fields.split(',') if fields else None
def get_serializer(self, instance=None, data=None, files=None, many=False,
partial=False, allow_add_remove=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return serializer_class(instance, data=data, files=files,
many=many, partial=partial,
allow_add_remove=allow_add_remove,
context=context, fields=fields)
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return pagination_serializer_class(instance=page, context=context, fields=fields)
class MyPonyList(DynamicFields, generics.ListAPIView):
# [...]
Request
Now, when you request a resource, you can add a parameter fields to show only specified fields in url.
/?fields=field1,field2
You can find a reminder here : https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a
You could try Dynamic REST, which has support for dynamic fields (inclusion, exclusion), embedded / sideloaded objects, filtering, ordering, pagination, and more.
If you want something flexible like GraphQL, you can use django-restql. It supports nested data (both flat and iterable).
Example
from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin
class UserSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'groups')
A regular request returns all fields.
GET /users
[
{
"id": 1,
"username": "yezyilomo",
"email": "yezileliilomo#hotmail.com",
"groups": [1,2]
},
...
]
A request with the query parameter on the other hand returns only a subset of
the fields:
GET /users/?query={id, username}
[
{
"id": 1,
"username": "yezyilomo"
},
...
]
With django-restql you can access nested fields of any level. E.g
GET /users/?query={id, username, date_joined{year}}
[
{
"id": 1,
"username": "yezyilomo",
"date_joined": {
"year": 2018
}
},
...
]
For iterable nested fields, E.g groups on users.
GET /users/?query={id, username, groups{id, name}}
[
{
"id": 1,
"username": "yezyilomo",
"groups": [
{
"id": 2,
"name": "Auth_User"
}
]
},
...
]
Such functionality we've provided in drf_tweaks / control-over-serialized-fields.
If you use our serializers, all you need is to pass ?fields=x,y,z parameter in the query.
For nested data, I am using Django Rest Framework with the package recommended in the docs, drf-flexfields
This allows you to restrict the fields returned on both the parent and child objects. The instructions in the readme are good, just a few things to watch out for:
The URL seems to need the / like this '/person/?expand=country&fields=id,name,country' instead of as written in the readme '/person?expand=country&fields=id,name,country'
The naming of the nested object and its related name need to be completely consistent, which isn't required otherwise.
If you have 'many' e.g. a country can have many states, you'll need to set 'many': True in the Serializer as described in the docs.
The solution suggested at the [DRF-Documentation][1] worked for me, however when I called the serializer from the View with:
class SomeView(ListAPIView):
def get(self, request, *args, **kwargs):
qry=table.objects.filter(column_value=self.kwargs['urlparameter'])
fields=['DBcol1','DBcol2','DBcol3']
serializer=SomeSerializer(qry,many=True,fields=fields)
I had to add many=True, otherwise it was not working.
[1]: https://www.django-rest-framework.org/api-guide/serializers/#example
Another alternative is to make use of GraphWrap: https://github.com/PaulGilmartin/graph_wrap
By adding /graphql to your urlpatterns, you add layer your REST API with a fully compliant GraphQL queryable API.