(Question before: Django Rest Framework nested relationship)
I've made serializer like this:
serializers.py
from rest_framework import serializers, fields
from .models import Pegawai,Barang
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = instance.pegawai.name
return rep
class PegawaiSerializer(serializers.ModelSerializer):
barangs = BarangSerializer(read_only=True, many=True)
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
'barangs',
)
Results :
{
"pegawai": "Ryan",
"nama_barang": "burjo",
"harga_barang": "1234"
},
And How to make the result like this in the barang API when posted the data:
{
"pegawai": {"id" : 1,
"name" : "Ryan",
"alias" : "R"}
"nama_barang": "burjo",
"harga_barang": "1234"
},
Please help, cheers.
Write extra serializer and wire-up it in to_representation(..) method,
class PegawaiShortSerializer(serializers.ModelSerializer):
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
)
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = PegawaiShortSerializer(instance.pegawai).data
return rep
Have you try:
rep = super().to_representation(instance)
pegawai_obj = instance.pegawai
pegawai_data = {"id":pegawai_obj.id, "name":pegawai_obj.name, "alias":pegawai_obj.alias}
rep['pegawai'] = pegawai_data
return rep
But I don't think this is the best solution.
Related
I have a nested serializes, which content I need to return as Null in case of the parent-serializer field "is_profile_private" (a boolean) is True.
I tried using get_queryset in order to filter the User Profile but no progress was made.
Tried using SerializerMethordField() and get_profile() but Django complained about UserProfileSerializer type of object not being allowed to be serialized.
serializers.py
class UserProfileSerializer(UserSerializer):
height = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = (
"bio",
"gender",
"custom_gender",
"non_binary_list",
"birthdate",
"avatar",
"height",
"hometown",
"zodiac_sign",
"language",
)
#staticmethod
def get_height(obj):
return {"value": obj.height, "unit": obj.height_unit}
class SimpleUserSerializer(UserSerializer):
profile = UserProfileSerializer(source="user", required=False)
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
views.py
class UserProfileAPIView(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
lookup_field = "id"
queryset = User.objects.all()
serializer_class = SimpleUserSerializer
http_method_names = ["get"]
#staticmethod
def get(request, *args, **kwargs):
return User.objects.get(id=str(request.data))
You can use SerializerMethodField:
class SimpleUserSerializer(UserSerializer):
profile = serializers.SerializerMethodField()
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
def get_profile(self, obj):
if obj.is_profile_private:
return None
return UserProfileSerializer(obj.user).data
please note that you should return serializer's data, not serializere itself.
I have spent already a couple of days researching about this issue in similar questions and I am not able to get a solution. This should be something simple, I have a model:
model.py
class Item(models.Model):
"""Class to represent an item..."""
label = models.TextField(null=True)
name = models.TextField()
category = models.ForeignKey( "Category", on_delete=models.SET_NULL,
null=True, default=DEFAULT_CATEGORY_ID)
class Category(models.Model):
"""Class to represent the category of an Item. Like plants, bikes..."""
name = models.TextField()
description = models.TextField(null=True)
view.py
class ItemViewset(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
"""API Endpoint to return the list of items"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializer.py
class ItemSerializer(serializers.ModelSerializer):
"""Serializer for Item."""
category = CategorySerializer(read_only=True)
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Item
fields = [ 'id', 'label', 'name', 'category']
read_only_fields = ['id']
# def to_representation(self, instance):
# ret = super().to_representation(instance)
# ret['category'] = CategorySerializer(instance.category).data
# return ret
def create(self, request):
# Look up objects by arbitrary attributes.
# You can check here if your students are participating
# the classes and have taken the subjects they sign up for.
category = get_object_or_404(Category(), id=request.data.get('category'))
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(category=category)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
If I have the line category = CategorySerializer(read_only=True) commented the nested get won't work, for example for a curl to an object. I will get just de id of the structure. If I uncomment it like it is in the example I get the right response:
{
"id": 60,
"label": null,
"name": "Delete me",
"category": {
"id": 1,
"name": "IT"
}
}
But then the post with id will not work, the category will be set to null.
How can I get both working at the same time?
Update: This is the post I want to do:
{
"label": "00000003",
"name": "Delete me",
"category": 1,
}
You can override the to_represention method of your serializer
class ItemSerializer(serializers.ModelSerializer):
"""Serializer for Item."""
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Item
fields = [ 'id', 'label', 'name', 'category']
read_only_fields = ['id']
def to_representation(self, instance):
data = super().to_representation(instance)
data["category"] = CategorySerializer(instance.category).data
return data
I am new to this django-rest-framework, my problem is how am I going to insert a data with one-to-one relationship in the views.py.
I can do the normal way of inserting data like the one below,
def post(self, request,format=None):
p = Person()
p.firstname = "John"
p.middlename = "K"
p.lastname = "Rambo"
p.save()
e = Employee()
e.code = "emp-002"
e.person_id = p.id
e.save()
I cannot do the approach above because I want to learn more about DRF.
To give you more idea please see below :
I have this model
class Person(models.Model):
firstname = models.CharField(max_length=150, default="")
middlename = models.CharField(max_length=150, default="")
lastname = models.CharField(max_length=150, default="")
class Employee(models.Model):
person = models.OneToOneField(Person, on_delete=models.CASCADE,primary_key=True)
code = models.CharField(max_length=100, default="")
I have this serializer
class PersonSer(serializers.ModelSerializer):
class Meta:
model = Person
fields = (
'id',
'firstname',
'middlename',
'lastname',)
class EmployeeSer(serializers.ModelSerializer):
person = PersonSer()
class Meta:
model = Employee
fields = ('code','person')
I have this view
class SaveData(APIView):
#transaction.atomic
def post(self,request, format=None):
p = PersonSer(data=request.data)
if p.is_valid():
p.save()
request.data['person'] = p.data['id']
request.data['code'] = "Emp-"+str(p.data['id'])
"""
I expect request.data now equal to
{
'person' : 102, # we assume the newly created person id == 102
'code' : 'Emp-102',
'firstname' : 'John',
'middlename' : 'K.',
'lastname' : 'Rambo'
}
"""
es = EmployeeSer(data=request.data)
if es.is_valid():
es.save()
else:
print(es.errors) # {'person': [ErrorDetail(string='This field is required.', code='required')]}
"""
I also tried this,
es = EmployeeSer(data={'person' : p.data, 'code' : 'Sample code'})
if es.is_valid():
es.save()
else:
print(es.errors) #{'person': {'fullname': [ErrorDetail(string='This field may not be blank.',code='blank')], ]}}
"""
return Response({'message':'okay'})
The request.data is a formData, the value is
{
'firstname' : 'John',
'middlename' : 'K.',
'lastname' : 'Rambo'
}
You can override the create method of the PersonSer to achieve this.
class PersonSer(serializers.ModelSerializer):
...
def create(self, validated_data):
person = Person.objects.create(**validated_data)
Employee.objects.create(person=person, code=f"Emp-{person.id}")
return person
And you can simplify your view to:
...
#transaction.atomic
def post(self,request, format=None):
p = PersonSer(data=request.data)
p.is_valid(raise_exception=True)
p.save()
return Response({'message':'okay'})
...
raise_expection=True will cause serializer to raise ValidationError exception if there are validation errors.
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',)
I want to create a udemy like web app and using django rest framework for the backend and mysql as database.
i have a model named 'Lessons' that contains list of all lessons and one of the fields is 'video-link'. also i have another model names Purchases that have two ForeignKey fields :Users and Lessons. i want to show the Lessons to all users but for the download field i have to lookup the pair (User , Lesson) in Purchases and if He has the course i will show him the download field.
My View Set
class LessonsViewSet(viewsets.ModelViewSet):
queryset = models.Assignments.objects.all()
authentication_classes = (TokenAuthentication,)
def get_serializer_class(self):
if self.request.user.is_staff :
print(self.request.user)
return serializers.FullAccessLessonsSerializer
elif self.request.user.is_active:
return serializers.PartialAccessLessonsSerializer
print(self.request.user)
return serializers.BasicAccessLessonsSerializer
My Serializers
Full access for admins:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )
Basic access for unauthenticated users:
class BasicAccessLessonsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description')
and partial access for students :
class PartialAccessAssignmentsSerializer(serializers.ModelSerializer):
"""A serializer for all Lessons"""
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
super(PartialAccessAssignmentsSerializer, self).__init__(*args,**kwargs)
print(self.fields.get('id'))
self.fields.pop('dllink')
class Meta:
model = models.Assignments
fields = ('id','title','description','dllink' )
I have done anything I could found but I can not figure it out. I either an get error or removing all dl links.
If you want dllink is None when user can't access.use this:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
dllink = serializers.SerializerMethodField()
def get_prescription_accept(self, instance):
result = True # lookup the pair (User , Lesson) in Purchases
if result:
return instance.dllink
else:
return ''
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )
If you want pop dllink from data,use:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
data = super(FullAccessLessonsSerializer, self).to_representation(instance)
result = True # lookup the pair (User , Lesson) in Purchases
if not result:
data.pop('dllink')
return data
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )