Passing nested data to Django ModelSerializer - django

I'm wanting to know how you would pass nested data to a ModelSerializer if the child of the nested data is not a model on its own.
The data that I'm working with looks like this:
{
'leadId': 12345,
'updateTime': 1651250096821,
'changeInfo': {
'oldstage': 'New Leads',
'newstage': 'Attempting Contact'
}
}
From previous experience, I know that if I was only working with the leadId and the updateTime, my serializer would look like this:
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
class Meta:
model = Log
fields = ["leadId", "updateTime"]
Which would then make it possible to do this:
data = {
'leadId': 12345,
'updateTime': 1651250096821
}
serializer = LogSerializer(data=data)
serializer.is_valid()
serializer.save()
If I'm not wanting to turn changeInfo into its own model, is it possible to map the fields to the nested data? Something that might look like this (but this obviously doesn't work):
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
oldstage = serializers.IntegerField(source="oldstage")
newstage = serializers.IntegerField(source="newstage")
class Meta:
model = Log
fields = ["leadId", "updateTime", "oldstage", "newstage]

You can use a custom serializer for your changeInfo field (you don't need to create a model for that):
class ChangeInfoSerializer(serializers.Serializer):
oldstage = serializers.CharField(max_length=100, source="old_stage") # Set max_length to a value that suits your needs
newstage = serializers.CharField(max_length=100, source="new_stage")
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
changeInfo = ChangeInfoSerializer(required=False) # Change to required=True if you want this field to be mandatory
class Meta:
model = Log
fields = ["leadId", "updateTime", "changeInfo"]
def create(self, validated_data):
change_info = validated_data.pop('changeInfo')
for key, value in change_info.items():
if key == "old_stage":
validated_data['old_stage'] = value
elif key == "new_stage":
validated_data['new_stage'] = value
log = Log.objects.create(**validated_data)
return log
def update(self, instance, validated_data):
change_info = validated_data.pop('changeInfo')
instance.lead_id = validated_data.get('leadId', instance.lead_id)
instance.update_time = validated_data.get('updateTime', instance.update_time)
# Here you can use change_info['oldstage'] and change_info['newstage'] if 'changeInfo' is sent (otherwise you'll get a KeyError)
instance.save()
return instance

As mentioned in the comments, a SerializerMethodfield is a good way to go:
serializers.py
class LogSerializer(...):
...
changeInfo = serializers.SerializerMethodField()
def get_changeInfo(self, obj): return {
"leadId" : obj.lead_id,
"updateTime": obj.update_time
}
class Meta:
fields = ["changeInfo", ...]
...

Related

insert nested relationships in DB django

Need help , i am trying to push nested relations inside DB don't know where I am going wrong in this, is there something wrong with validated_data , which is a list of dict here , thanks in advance
class CatalogSerializer(serializers.ModelSerializer):
catalog_products = CatalogProductsSerializer(source = 'catalogproducts_set',many=True)
class Meta:
model = Catalog
fields = ['created_by','client','catalog_products','created_datetime','is_active']
def create(self,validate_data):
client_id = validate_data.pop('id')
client = User.objects.get(id=client_id),
catalog_obj = Catalog.objects.create(
client = client,
created_by = self.context['user'],
is_active =True,
)
for pricelist_ins in validate_data:
CatalogProducts.objects.create(
catalog = catalog_obj,**pricelist_ins)
return catalog_obj
Basic Viewset
class CatalogViewset(viewsets.ModelViewSet):
queryset = Catalog.objects.all()
serializer_class = CatalogSerializer
permission_classes = []
authentication_classes = []
def create(self, request, *args, **kwargs):
if request.data:
try:
serialized_data = self.get_serializer(data = request.data)
if serialized_data.is_valid(raise_exception=True):
serialized_data.save()
return Response(serialized_data.data,status=200)
except Exception as e:
return Response({'error':str(e)},status=400)
return Response({'status':'invalid request'},status=400)
the error I am getting in Postman
{
"error": "{'catalog_products': [ErrorDetail(string='This field is required.', code='required')]}"
}
data i am posting
{
"id":"2",
"pricing_list":[
{
"from_quantity":"101",
"to_quantiy":"34",
"price":"1000"
},
{
"from_quantity":"10",
"to_quantiy":"501",
"price":"2000"
}
]
}
You have catelogue_products in the fields, it is by default required. But you are not posting any catelogue_products. You need to post data based on the fields of the serializer. validated data will not contain any other data, but valid data that was set in serializer.
To make it optional you may try to add required=False in the serialzier like this:
class CatalogSerializer(serializers.ModelSerializer):
catalog_products = CatalogProductsSerializer(source = 'catalogproducts_set',many=True, required=False)
class Meta:
model = Catalog
fields = ['created_by','client','catalog_products','created_datetime','is_active']

How do I get my serializer to return data from a dependent field in my model?

I'm using Django 2 and Python 3.7. I have these models set up. One (Coop) is dependent on the other (CoopType) using Many-To-Many ...
class CoopTypeManager(models.Manager):
def get_by_natural_key(self, name):
return self.get_or_create(name=name)[0]
class CoopType(models.Model):
name = models.CharField(max_length=200, null=False, unique=True)
objects = CoopTypeManager()
class CoopManager(models.Manager):
# Look up by coop type
def get_by_type(self, type):
qset = Coop.objects.filter(type__name=type,
enabled=True)
return qset
# Look up coops by a partial name (case insensitive)
def find_by_name(self, partial_name):
queryset = Coop.objects.filter(name__icontains=partial_name, enabled=True)
print(queryset.query)
return queryset
# Meant to look up coops case-insensitively by part of a type
def contains_type(self, types_arr):
filter = Q(
*[('type__name__icontains', type) for type in types_arr],
_connector=Q.OR
)
queryset = Coop.objects.filter(filter,
enabled=True)
return queryset
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType)
address = AddressField(on_delete=models.CASCADE)
enabled = models.BooleanField(default=True, null=False)
phone = PhoneNumberField(null=True)
email = models.EmailField(null=True)
web_site = models.TextField()
I have the following serializers set up, designed to return the data in JSON form ...
class CoopTypeField(serializers.PrimaryKeyRelatedField):
queryset = CoopType.objects
def to_internal_value(self, data):
if type(data) == dict:
cooptype, created = CoopType.objects.get_or_create(**data)
# Replace the dict with the ID of the newly obtained object
data = cooptype.pk
return super().to_internal_value(data)
...
class CoopTypeSerializer(serializers.ModelSerializer):
class Meta:
model = CoopType
fields = ['id', 'name']
def create(self, validated_data):
"""
Create and return a new `CoopType` instance, given the validated data.
"""
return CoopType.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `CoopType` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class CoopSerializer(serializers.ModelSerializer):
types = CoopTypeSerializer(many=True)
address = AddressTypeField()
class Meta:
model = Coop
fields = ['id', 'name', 'types', 'address', 'phone', 'enabled', 'email', 'web_site']
extra_kwargs = {
'phone': {
'required': False,
'allow_blank': True
}
}
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
def create(self, validated_data):
#"""
#Create and return a new `Snippet` instance, given the validated data.
coop_types = validated_data.pop('types', {})
instance = super().create(validated_data)
for item in coop_types:
coop_type, _ = CoopType.objects.get_or_create(name=item['name']) #**item)
instance.types.add(coop_type)
return instance
However, the dependent field, "type" is always returned as "null," despite the fact that I can see there is valid data in the database. Here is what happens when I run my curl request
curl -v --header "Content-type: application/json" --request GET "http://127.0.0.1:8000/coops/?contains=resource"
[{"id":348,"name":"Garden Resources of Woodlawn (GRoW)","types":{"name":null}
How do I edit my serializer such that it returns the values of the dependent type?
Try to remove rep['types'] = CoopTypeSerializer(instance.types).data from to_representation(...) method,
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
OR
use instance.types.all() instead of instance.types, because, here the instance.types is a Manager method, which doesn't return any QuerySet
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
rep['address'] = AddressSerializer(instance.address).data
return rep

Overwrite fields in Django Serializer

I am new in Django and I would like to overwrite the field value in create and update method of serializer. Here is my model=>
class Holiday(models.Model):
HolidayDay = models.DateField()
Created_DT = models.DateTimeField()
Created_Usr = models.CharField(max_length=20)
LastModified_Usr = models.CharField(max_length=20,blank=True)
LastModified_DT = models.DateTimeField(blank=True,null=True)
def __str__(self):
return str(self.HolidayDay)
Here is my serializer=>
class HolidaySerializer(serializers.ModelSerializer):
class Meta:
model=Holiday
fields = [
'id',
'HolidayDay',
'Created_DT',
'Created_Usr',
'LastModified_Usr',
'LastModified_DT'
]
def create(self,validated_data):
validated_data['Created_Usr'] ="Testing"
return Holiday.objects.create(**validated_data)
I would like to update Create_usr field value in create method and LastModified_usr field in update method. But why I can't overwrite the create_usr field as "Testing"?
Here is my views=>
def post(self,request):
holiday = request.data.get('holiday')
serializer = HolidaySerializer(data=holiday)
serializer.is_valid()
print(serializer.errors)
if serializer.is_valid():
holiday_saved=serializer.save()
return Response({"success":"Holiday '{}' created successfully".format(holiday_saved.HolidayDay)})
def put(self,request,pk):
save_holiday = get_object_or_404(Holiday.objects.all(),pk=pk)
data = request.data.get('holiday')
serializer = HolidaySerializer(instance=save_holiday,data=data,partial=True)
if serializer.is_valid(raise_exception = True):
holiday_saved=serializer.save()
return Response({"sucess": "Holiday '{}' updated successfully".format(holiday_saved.HolidayDay)})
Your create method is not defined in your Serializer class, instead it is part of your Meta class. You should be able to solve it by moving your create method to your HolidaySerializer:
class HolidaySerializer(serializers.ModelSerializer):
def create(self,validated_data):
validated_data['Created_Usr'] = "Testing"
return Holiday.objects.create(**validated_data)
class Meta:
model=Holiday
fields = [
'id',
'HolidayDay',
'Created_DT',
'Created_Usr',
'LastModified_Usr',
'LastModified_DT'
]

How to Change ManyToManyField Name in Serializer

I want to change ManyToManyField Name. user_groups is manytomanyfield. I tried to use ManyToManyRelatedField and also PrimaryKeyRelatedField but it is giving error. How can i change or with data type should i give like for character field i am giving CharField
class EmployeeProfileSerializer(serializers.ModelSerializer):
employee_id = serializers.CharField(source='user_employee_id')
payroll_id = serializers.CharField(source='user_payroll_id')
phone = serializers.CharField(source='user_phone')
hire_date = serializers.DateField(source='user_hire_date')
pay_rate = serializers.IntegerField(source='user_pay_rate')
salaried = serializers.CharField(source='user_salaried')
excempt = serializers.CharField(source='user_excempt')
state = serializers.CharField(source='user_state')
city = serializers.CharField(source='user_city')
zipcode = serializers.IntegerField(source='user_zipcode')
status = serializers.CharField(source='user_status')
class Meta:
model = UserProfile
fields = [
'employee_id',
'phone',
'payroll_id',
'hire_date',
'pay_rate',
'salaried',
'excempt',
'state',
'city',
'zipcode',
'status',
'user_groups',
]
You can use SerializerMethodField:
class EmployeeProfileSerializer(serializers.ModelSerializer):
...
your_custom_name = SerializerMethodField()
class Meta:
model = UserProfile
fields = ['your_custom_name ', ...]
def get_your_custom_name(self, obj):
# Return ids:
return list(obj.user_groups.all().values_list('pk', flat=True))
# Or using a serializer:
return MyUserGroupSerializer(obj.user_groups.all(), many=True).data
For create and update you have to override the create and update method to assign the new field:
class EmployeeProfileSerializer(serializers.ModelSerializer):
...
your_custom_name = IntegerField()
class Meta:
model = UserProfile
fields = ['your_custom_name', ...]
# If you need validation
def validate_your_custom_name(self, value):
if value:
if int(value) > 5:
return value
return None
def create(self, validated_data):
# Get the data for your new field
my_costum_data = validated_data.get('your_custom_name')
# Do something with it
profile_obj = UserProfile.objects.create(...)
if my_costum_data:
user_group = UserGroupModel.objects.get(pk=int(my_costum_data))
profile_obj.user_groups.add(user_group)
def update(self, instance, validated_data):
# Same as create()
...

Serializing a list of object as dictionnary in DjangoRestFramework

Using django & django-rest-framework, I have the following model (this is simplified but it's all there):
class Device(Model):
#stuff
class DeviceInformation(Model):
device = ForeignKey(Device, reverse='infos')
key = CharField(max_length=32)
value = CharField(max_length=1024)
When serializing a device through django-rest-framework's ModelSerializer, I get something like this:
{
//stuff
infos: [{
'key':'BatteryLevel',
'value':'80%'
},{
'key':'DeviceName',
'value':'my device'
}, //etc
]
}
Which is perfectly normal. However, it would make much more sense to serialize into something like this:
{
//stuff
infos: {
'BatteryLevel':'80%',
'DeviceName':'my device',
//etc
}
}
How do I do that? Is it even possible?
Note that I don't need to deserialize any of these information.
EDIT: my serializers are as follows:
class DeviceInfoSerializer(ModelSerializer):
class Meta:
model = DeviceInformation
fields = ('key', 'value')
read_only_fields = fields
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
super(DeviceSerializer, self).__init__(*args, **kwargs)
if hasattr(self, 'object') and self.object and not self.many:
self.data['infos'] = DeviceInfoSerializer(
self.object.infos.all(), many=True).data
class Meta:
model = Device
fields = ['udid', 'model', 'tracked']
read_only_fields = ('model', 'tracked')
slug_field = 'udid'
For your readonly-case, the best way is to use SerializerMethodField.
This would change your DeviceSerializer and remove the need for your DeviceInfoSerializer.
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
infos = serializers.SerializerMethodField('get_infos')
def get_infos(self, obj):
return {
info.key: info.value
for info in obj.infos.all()
}
class Meta:
model = Device
fields = ['udid', 'model', 'tracked', 'infos']
read_only_fields = ('model', 'tracked', 'infos')
slug_field = 'udid'