I have a realy simple API Endpoint which return a list of MyModel.
views.py:
#api_view(['GET'])
def index(request):
myModel = MyModel.objects.all()
serializer = MyModelSerializer(myModel, many=True)
return Response(serializer.data)
serializers.py
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field1', 'field2']
Now I'd like to return a serialized dictionary of models indexed by the primary key (which is a CharField).
{
'pk1':{
'field1': 'fo',
'field2': 'bar',
},
'pk2':{
'field1': 'fobar',
'field2': 'foo',
}
}
[EDIT] As requested, here is the model:
from django.db import models
class MyModel(models.Model):
id = models.CharField(primary_key=True)
field1 = models.FloatField(null=True)
field2 = models.FloatField(null=True)
You can override to_representation method in your serializer
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field1', 'field2']
def to_representation(self, instance):
ret = super().to_representation(instance)
return {instance.pk: ret}
You'll need to create a custom ListSerializer that will return a dictionary.
from django.db import models
from rest_framework import serializers
class DictSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
return [
(item.id, self.child.to_representation(item)) for item in iterable
]
#property
def data(self):
ret = super(DictSerializer, self).data
return serializers.ReturnDict(ret, serializer=self)
Then you'll have to specify that your ModelSerializer should use this custom when many=True. You do this by setting the list_serializer_class in the Meta of your ModelSerializer.
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field1', 'field2']
list_serializer_class = DictSerializer
If you want to be efficient following his Gabriel Muj solution is better but will not provide what you want out of the box. His solution will provide output like:
[{
'pk1': {
'field1': 'fo',
'field2': 'bar',
}
},
{
'pk2': {
'field1': 'fobar',
'field2': 'foo',
}
}
]
First change the serializer according to His solution:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field1', 'field2']
def to_representation(self, instance):
ret = super().to_representation(instance)
return {instance.pk: ret}
But to get what you asked you need to use ChainMap from collections. Now you need to change your views like this:
from collections import ChainMap
#api_view(['GET'])
def index(request):
myModel = MyModel.objects.all()
serializer = MyModelSerializer(myModel, many=True)
data_dict = dict(ChainMap(*serializer.data))
return Response(data_dict)
For this snippet you need to make sure its python 3.3+ . This will provide good performance. While optimizing my code I have found this solution is less heavy.
You can iterate over the query set. In your index view:
#api_view(['GET'])
def index(request)
data = dict()
for element in MyModel.objects.all():
dict[element.id] = {'field1': element.field1, 'field2': element.field2}
return Response(data)
Related
I am using django rest framework. I want to include the url of an action defined in a view in its serializer.
My serializers.py:
from rest_framework import serializers
class CommentSerializer(serializers.ModelSerializer):
"""Serializer for comments."""
class Meta:
model = Comment
fields = ["id", "item", "author", "content", "date_commented", "parent"]
class ItemDetailSerializer(serializers.ModelSerializer):
"""Serializer for items (for retrieving/detail purpose)."""
category = CategorySerializer(many=True, read_only=True)
media = MediaSerializer(many=True, read_only=True)
brand = BrandSerializer(many=False, read_only=True)
specifications = serializers.SerializerMethodField(source="get_specifications")
comments = ??????????????????????????????????????????????????
class Meta:
model = Item
fields = [
"id",
"slug",
"name",
"description",
"brand",
"show_price",
"location",
"specifications",
"is_visible",
"is_blocked",
"created_at",
"updated_at",
"seller",
"category",
"media",
"comments",
"users_wishlist",
"reported_by",
]
read_only = True
editable = False
lookup_field = "slug"
def get_specifications(self, obj):
return ItemSpecificationSerializer(obj.item_specification.all(), many=True).data
My views.py:
from rest_framework import viewsets, mixins, status
from ramrobazar.inventory.models import Item, Comment
from ramrobazar.drf.serializers ItemSerializer, ItemDetailSerializer, CommentSerializer
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.filters import SearchFilter
from django_filters.rest_framework import DjangoFilterBackend
class ItemList(viewsets.GenericViewSet, mixins.ListModelMixin):
"""View for listing and retrieving all items for sale."""
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializer_action_classes = {
"retrieve": ItemDetailSerializer,
}
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = "slug"
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = [
"category__slug",
"brand__name",
]
search_fields = ["name", "description", "category__name", "brand__name", "location"]
def get_serializer_class(self):
try:
return self.serializer_action_classes[self.action]
except:
return self.serializer_class
def retrieve(self, request, slug=None):
item = self.get_object()
serializer = self.get_serializer(item)
return Response(serializer.data)
#action(detail=True, methods=['GET'])
def comments(self, request, slug=None):
item = Item.objects.get(slug=slug)
queryset = Comment.objects.filter(item=item)
serializer = CommentSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I have an action named comments in the ItemList view which gives all the comments of a specific item. I can get the details of the item from the url /api/items/<slug>. I can get all the comments of the item from the url api/items/<slug>/comments. I want to include a comments field in the ItemDetailSerializer serializer which is a link to api/items/<slug>/commments. How can I accomplish this?
You can do this via SerializerMethodField and reverse:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.SerializerMethodField(source="get_comments")
...
def get_comments(self, obj):
return reverse(YOUR_URL_NAME, kwargs={'slug': obj.slug})
try this:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
validated_data['comments'] = self.context['request'].build_absolute_uri() + 'comments'
return super(ContentGalleryListSerializer, self).create(validated_data)
You can use the HyperlinkedIdentityField for this:
comments = serializers.HyperlinkedIdentityField(
view_name='item-comments',
lookup_field='slug'
)
This will then render a comments field that contains a URL to the item-comments view, with the slug as lookup parameter.
You will need to register the view with the name item-comments, for example with:
urlpatterns = [
path('items/<slug>/comments/', ItemList.as_view({'get': 'comments'}), name='item-comments')
]
Note that normally nested routes are discouraged, and you'd put the comments in a separate view. But the above should work.
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")
Does ModelSerializer have an option to change the fields dynamically by GET or (POST, PUT, DELETE)?
While GET requires complex fields such as nested serializers, these are not required for (POST, PUT, DELETE).
I think the solution is to use separate serializers for GET and (POST, PUT, DELETE).
But in that case, I'd have to create quite a few useless serializers.
Is there any good solution?
class PlaylistSerializer(serializers.ModelSerializer):
user = UserDetailSerializer(read_only=True)
tracks = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
is_added = serializers.SerializerMethodField()
is_favorited = serializers.BooleanField()
class Meta:
model = Playlist
fields = (
"pk",
"user",
"title",
"views",
"is_public",
"is_wl",
"created_at",
"updated_at",
"tracks",
"is_owner",
"is_added",
"is_favorited",
)
def get_is_owner(self, obj):
return obj.user == self.context["request"].user
def get_tracks(self, obj):
queryset = obj.track_set
if queryset.exists():
tracks = TrackSerializer(queryset, context=self.context, many=True).data
return tracks
else:
return []
def get_is_added(self, obj):
try:
return obj.is_added
except AttributeError:
return False
class PlaylistUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
fields = ("title", "is_public")
first you need to create a class and inherit your serializer from this class as below:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""To be used alongside DRF's serializers.ModelSerializer"""
#classmethod
def default_fieldset(cls):
return cls.Meta.fields
def __init__(self, *args, **kwargs):
self.requested_fields = self._extract_fieldset(**kwargs)
# Fields should be popped otherwise next line complains about
unexpected kwarg
kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
self._limit_fields(self.requested_fields)
def _extract_fieldset(self, **kwargs):
requested_fields = kwargs.pop('fields', None)
if requested_fields is not None:
return requested_fields
context = kwargs.pop('context', None)
if context is None:
return None
return context.get('fields')
def _limit_fields(self, allowed_fields=None):
if allowed_fields is None:
to_exclude = set(self.fields.keys()) - set(self.default_fieldset())
else:
to_exclude = set(self.fields.keys()) - set(allowed_fields)
for field_name in to_exclude or []:
self.fields.pop(field_name)
#classmethod
def all_fields_minus(cls, *removed_fields):
return set(cls.Meta.fields) - set(removed_fields)
then your serializer would be something like this:
class PlaylistSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Playlist
fields = ("pk", "user", "title", "views", "is_public",
"is_wl", "created_at", "updated_at", "tracks", "is_owner",
"is_added", "is_favorited",)
#classmethod
def update_serializer(cls):
return ("title", "is_public")
#classmethod
def view_serializer(cls):
return ("title", "is_public", "is_owner", "is_added")
then you will call your serializer as below:
PlaylistSerializer(instance, fields=PlaylistSerializer.update_serializer()).data
I have an HStoreField in my model. Example:
attributes = HStoreField(default=dict, blank=True)
My view and serializer:
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = "__all__"
class CarViewSet(viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
model = Car
Ok. When I try some tests, like this:
#pytest.fixture
def create_car(client):
response = client.post(
'/myapi/v1/car/',
data={
'name': "Ford Mustang",
'price': 2000,
'attributes': {"key": "value"},
},
format='json',
)
return response
#pytest.mark.django_db
def test_car_view(client, create_car):
response = create_car
response_get = client.get(f'/myapi/v1/car/{response.data["id"]}/')
assert response_get.status_code == 200
I receive this error:
self = HStoreField(required=False), value = '"key"=>NULL'
def to_representation(self, value):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return {
six.text_type(key): self.child.to_representation(val) if val is not None else None
> for key, val in value.items()
}
E AttributeError: 'str' object has no attribute 'items'
Looking for information about this problem I found references to use DictField for working with HStoreField. But I did not find examples. Does someone have an idea or examples?
I got it!
I needed to set attributes as a JSONField.
My solution:
class CarSerializer(serializers.ModelSerializer):
attributes = serializers.JSONField()
class Meta:
model = Car
fields = "__all__"
I have a method field called followers. I get the list of followers in a SerializerMethodField :
followers = serializers.SerializerMethodField()
I want to format the result with a specific serializer called BaseUserSmallSerializer. How should I implement the method get_followers to achieve that ?
Try this;
followers = BaseUserSmallSerializer(source='get_followers', many=True)
OR
You can use serializer inside methodfield;
def get_followers(self, obj):
followers_queryset = #get queryset of followers
return BaseUserSmallSerializer(followers_queryset, many=True).data
If you prefer a more generic solution:
SerializerMethodNestedSerializer which works same as serializers.SerializerMethodField but wraps the result with the passed serializer and returns a dict
class SerializerMethodNestedSerializer(serializers.SerializerMethodField):
"""Returns nested serializer in serializer method field"""
def __init__(self, kls, kls_kwargs=None, **kwargs):
self.kls = kls
self.kls_kwargs = kls_kwargs or {}
super(SerializerMethodNestedSerializer, self).__init__(**kwargs)
def to_representation(self, value):
repr_value = super(SerializerMethodNestedSerializer, self).to_representation(value)
if repr_value is not None:
return self.kls(repr_value, **self.kls_kwargs).data
Usage
class SomeSerializer(serializers.ModelSerializer):
payment_method = SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
def get_payment_method(self, obj):
return PaymentCard.objects.filter(user=obj.user, active=True).first()
class Meta:
model = Profile
fields = ("payment_method",)
class PaymentCardSerializer(serializers.ModelSerializer):
class Meta:
fields = ('date_created', 'provider', 'external_id',)
model = PaymentCard
The expected output of SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
None or {'date_created': '2020-08-31', 'provider': 4, 'external_id': '123'}