how to create model instance in drf serializers - python

I am new to DRF. I want to get saved the model.
In models.py, PackageDetails and PhysicalDetail have foreignkey relationship to Member
My serializers.py is as follows:
from rest_framework import serializers
from .models import Member, PackageDetails, PhysicalDetail
class PackageDetailsSerializer(serializers.ModelSerializer):
is_expired = serializers.SerializerMethodField()
members_expiry_date = serializers.SerializerMethodField()
class Meta:
model = PackageDetails
exclude = ['id']
extra_fields = ['is_expired', 'members_expiry_date']
def get_is_expired(self, instance):
return instance.is_expired
def get_members_expiry_date(self, instance):
return instance.members_expiry_date
class PhysicalDetailSerializer(serializers.ModelSerializer):
class Meta:
model = PhysicalDetail
exclude = ['id']
class MemberSerializer(serializers.ModelSerializer):
physical_details = PhysicalDetailSerializer(many=True)
package_details = PackageDetailsSerializer(many=True)
class Meta:
model = Member
fields = '__all__'
extra_fields = ['physical_details', 'package_details']
def create(self, validated_data):
physical_detail_data = validated_data.pop("physical_details")
package_detail_data = validated_data.pop("package_details")
member = Member.objects.create(**validated_data)
PhysicalDetail.objects.create(member=member, **physical_detail_data)
PackageDetails.objects.create(member=member, **package_detail_data)
return member
views.py :
class MemberViewset(viewsets.ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class PackageDetailViewset(viewsets.ModelViewSet):
queryset = PackageDetails.objects.all()
serializer_class = PackageDetailsSerializer
class PhysicalDetailViewset(viewsets.ModelViewSet):
queryset = PhysicalDetail.objects.all()
serializer_class = PhysicalDetailSerializer
In GET request it worked well.. but in POST request with the same json format it responses the following:
{
"physical_details": [
"This field is required."
],
"package_details": [
"This field is required."
]
}
I've provided the fields.. so why this happening..

You removed those from dict using pop()
The pop() method removes and returns an element from a dictionary having the given key.
Try using get() instead
The get() method returns the value for the specified key if the key is in the dictionary.

Related

'QuerySet' object has no attribute 'simplified'

I am trying to do a fairly simple GET request that leads to a query with DRF:
def get(self, request, character):
char_entry = Dictionary.objects.filter(Q(simplified=character) | Q(traditional=character))
serializer = DictionarySerializer(char_entry)
return Response({"character": serializer.data})
My DictionarySerializer looks like this:
from rest_framework import serializers
from .models import Dictionary
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin_numbers", "pinyin_marks", "translation", "level", "traditional", ]
And my Dictionary model looks like this:
class Dictionary(models.Model):
traditional = models.CharField(max_length=50)
simplified = models.CharField(max_length=50)
pinyin_numbers = models.CharField(max_length=50)
pinyin_marks = models.CharField(max_length=50)
translation = models.TextField()
level = models.IntegerField()
class Meta:
db_table = 'dictionary'
indexes = [
models.Index(fields=['simplified', ]),
models.Index(fields=['traditional', ]),
]
As far as I can tell, this should serialize all the fields from the Dictionary table, including simplified.
Why can't Django find the attribute? What am I missing?
A QuerySet is a collection of items. It can thus contain zero, one or more items. YOu need to serialize with the many=True parameter:
def get(self, request, character):
char_entry = Dictionary.objects.filter(
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})
or if we know there is only one item, we should retrieve that single item:
from django.shortcuts import get_object_or_404
def get(self, request, character):
char_entry = get_object_or_404(
Dictionary,
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})

How to pack model queryset in serializer into one field DRF?

For example i have a few models:
class Parent(Model):
api_key = CharField(max_length=250)
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
status = CharField(max_length=250)
I wrote view based on ListAPIView, and serializer:
class ChildSerializer(ModelSerializer):
class Meta:
fields = ['id']
I need to take all Children with parent by find Parent by api_key and return this as:
{
children:[
{'id':1},
{'id':2}
]}
But i take this:
[
{'id':1},
{'id':2}
]
Well, you can define a ParentSerializer which will also return the child objects data with it:
class ParentSerializer(ModelSerializer):
child = ChildSerializer(many=True)
class Meta:
fields = ['api_key', 'child']
Then you need to find the parent by api_key and pass the Parent object to this serializer. Like this:
class ParentView(RetrieveAPIView):
queryset = Parent.objects.all()
lookup_field = 'api_key'
serializer_class = ParentSerializer
lookup_url_kwarg = 'api_key'
Finally set the url to:
path('/parent/<str:api_key>/', ParentView.as_view(), name='parent-detail')
Update
If you need to get the data from request.GET(url querystring), then it much simpler. Try like this:
class ParentView(ListAPIView):
queryset = Parent.objects.all()
serializer_class = ParentSerializer
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
api_key = request.GET.get('api_key')
if api_key:
return queryset.filter(api_key=api_key)
return queryset

Python DRF PrimaryKeyRelatedField to use uuid instead of PK

I'm writing a Django REST Framework API.
My models have default Django PK for internal use AND uuid field for external reference.
class BaseModel(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
class Event(BaseModel):
title = models.TextField()
location = models.ForeignKey('Location', null=True, on_delete=models.SET_NULL)
class Location(BaseModel):
latitude = models.FloatField()
longitude = models.FloatField()
And my serializers:
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
class EventSerializer(BaseSerializer):
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)
class LocationSerializer(BaseSerializer):
class Meta:
model = Location
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('latitude', 'longitude',)
This works fine, here is what I got when I retrieve an Event:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": 1 # Note here I have the PK, not UUID
}
But what I would like is:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": "2454abe7-7cde-4bcb-bf6d-aaff91c107bf" # I want UUID here
}
And of course I want this behavior to work for all my ForeignKeys and ManyToMany fields.
Is there a way to customize the field used by DRF for nested models ?
Thanks !
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.relations import RelatedField
from django.utils.encoding import smart_text
class UUIDRelatedField(RelatedField):
"""
A read-write field that represents the target of the relationship
by a unique 'slug' attribute.
"""
default_error_messages = {
'does_not_exist': _('Object with {uuid_field}={value} does not exist.'),
'invalid': _('Invalid value.'),
}
def __init__(self, uuid_field=None, **kwargs):
assert uuid_field is not None, 'The `uuid_field` argument is required.'
self.uuid_field = uuid_field
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.uuid_field: data})
except ObjectDoesNotExist:
self.fail('does_not_exist', uuid_field=self.uuid_field, value=smart_text(data))
except (TypeError, ValueError):
self.fail('invalid')
def to_representation(self, obj):
return getattr(obj, self.uuid_field)
Sample Usage:
class ProductSerializer(serializers.ModelSerializer):
category = UUIDRelatedField(
queryset=Category.objects.all(),
uuid_field='alias'
)
class Meta:
model = Product
fields = (
'id',
'alias',
'name',
'category',
)
read_only_fields = (
'id',
'alias',
)
Note that as of Django version 4, smart_text and ugettext_lazy were removed, use smart_str and gettext_lazy instead of them:
from django.utils.encoding import gettext_lazy
from django.utils.encoding import smart_str
A friend of mine send me this solution:
It works with all my related objects.
from rest_framework import serializers
from rest_framework.relations import SlugRelatedField
class UuidRelatedField(SlugRelatedField):
def __init__(self, slug_field=None, **kwargs):
slug_field = 'uuid'
super().__init__(slug_field, **kwargs)
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
serializer_related_field = UuidRelatedField
class Meta:
pass
For nested model fields you can use the source argument in a serializer like this
class EventSerializer(BaseSerializer):
location = serializers.CharField(source='location.uuid')
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)

How to assemble different serializers to a Json response in Django RFW?

This is my serializers.py,
class MalbSerializer(serializers.ModelSerializer):
class Meta:
model = malb
fields = ('zoning', 'zoningdesc', )
class MasrSerializer(serializers.ModelSerializer):
class Meta:
model = masr
fields = ('solddate', 'soldprice', )
class MataSerializer(serializers.ModelSerializer):
class Meta:
model = mata
fields = ('assessyear', 'landvalue', )
class TotalSerializer(serializers.ModelSerializer):
LandBuilding = serializers.SerializerMethodField()
SalesRecord = serializers.SerializerMethodField()
TaxAssessment = serializers.SerializerMethodField()
def get_LandBuilding(self, number):
queryset_lb = malb.objects.filter(maid=number)
serializer = MalbSerializer(queryset_lb, many=True)
return serializer.data
def get_SalesRecord(self, number):
queryset_sr = masr.objects.filter(maid=number)
serializer = MasrSerializer(queryset_sr, many=True)
return serializer.data
def get_TaxAssessment(self, number):
queryset_ta = mata.objects.filter(maid=number)
serializer = MataSerializer(queryset_ta, many=True)
return serializer.data
class Meta:
fields = ('LandBuilding', 'SalesRecord', 'TaxAssessment', )
I want to assemble these three serializers to one serializer in TotalSerializer, But it has an error:
Class TotalSerializer missing "Meta.model" attribute
I don't know add which models to here, because I have already add models in MalbSerializer, MasrSerializer, MataSerializer.
So How can I do to show MalbSerializer, MasrSerializer, MataSerializer together in TotalSerializer?
TotalSerializer should subclass serializers.Serializer, not serializers.ModelSerializer.

Passing URL parameters to a nested serializer

I have two serializers, one of which is nested:
class PaperSerializer(serializers.ModelSerializer):
class Meta:
model = Paper
class AuthorSerializer(serializers.ModelSerializer):
papers = PaperSerializer(
many=True,
read_only=True,
source='paper_set'
)
class Meta:
model = Author
I want to get a list of Authors which shows only their published Papers (Boolean field exists on the model).
I would like to call the API like /api/v1/authors/?show_published_only=true.
After some digging around, I discovered that you can pass the context from the ViewSet to the Serializer:
views.py
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
filter_fields = (
'show_published_only',
)
def get_serializer_context(self):
return {'request': self.request}
Now, create a new serializer FilteredPaperSerializer which inherits from serializers.ListSerializer, then override the to_representation() method to filter the queryset:
serializers.py
class FilteredPaperSerializer(serializers.ListSerializer):
def to_representation(self, data):
# Get the parameter from the URL
show_published_only = self.context['request'].query_params['show_published_only']
data = data.filter(is_published=show_published_only)
return super(FilteredPaperSerializer, self).to_representation(data)
class AuthorSerializer(serializers.ModelSerializer):
papers = FilteredPaperSerializer(
many=True,
read_only=True,
source='paper_set'
)
class Meta:
model = Author
NB: Don't forget to convert the fetched URL parameter to a Boolean or relevant data type for your model, I neglected to do it in the write-up above.

Categories