Overwrite fields in Django Serializer - django

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'
]

Related

Passing nested data to Django ModelSerializer

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", ...]
...

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

Nested JSON and HyperlinkedModelSerializer problem

I'm working on a tweet App. I have 2 main models : Tweets and Comments. I'm using HyperlinkedModelSerializer to get absolute url for my comments instances with the "url" added in the field. But when It comes to display the comments inside my tweet JSON format, i have this error :
`HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
This error is gone when I remove the url from the field.
Here is my Comment Model :
class CommentManager(models.Manager):
def filter_by_instance(self, instance):
content_type = ContentType.objects.get_for_model(instance.__class__)
obj_id = instance.id
qs = super(CommentManager, self).filter(content_type=content_type, object_id=obj_id)
return qs
class Comment(models.Model):
content = models.TextField(max_length=150)
author = models.ForeignKey(
User,
on_delete = models.CASCADE
)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True)
content_object = GenericForeignKey('content_type', 'object_id')
parent = models.ForeignKey(
"self",
on_delete = models.CASCADE,
blank=True,
null=True
)
datestamp = models.DateTimeField(auto_now_add=True)
objects = CommentManager()
def __str__(self):
return str(self.content[:30])
def save(self):
self.object_id = self.parent.id
super(Comment, self).save()
def children(self):
return Comment.objects.filter(parent=self)
#property
def is_parent(self):
if self.parent is None:
return False
return True
Here is my comment serializer :
class CommentSerializer(serializers.HyperlinkedModelSerializer):
children = serializers.SerializerMethodField()
datestamp = serializers.SerializerMethodField()
def get_datestamp(self, obj):
return obj.datestamp.date()
def get_children(self, obj):
qs = obj.children()
return ChildrenCommentSerializer(qs, many=True).data
class Meta:
model = Comment
fields = [
"url",
"datestamp",
"content",
"is_parent",
"object_id",
"children"
]
class ChildrenCommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [
"content"
]
And finally my tweet serializer :
class TweetSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(slug_field="username", queryset=User.objects.all())
comments = serializers.SerializerMethodField()
datestamp = serializers.SerializerMethodField()
def get_datestamp(self, obj):
return obj.datestamp.date()
def get_comments(self, obj):
qs = Comment.objects.filter_by_instance(obj)
print()
print()
print(CommentSerializer(qs, many=True))
print()
print()
return CommentSerializer(qs, many=True).data
class Meta:
model = Tweet
fields = ["datestamp", "id", "content", "author", "nb_likes", "nb_comments", "slug", "comments" ]
def to_representation(self, obj):
representation = super().to_representation(obj)
if obj.nb_likes > 50:
representation['super_popular'] = True
return representation
I do not understand how and where in the code i should add the "context={'request': request}"
place it when instantiating the serializer
class ViewExample(APIView):
def get(self, request, pk, format=None):
listexample = Example.objects.all()
serializer = ExampleSerializer(listexample, many=True, context={'request':request})
return Response(serializer.data, status=status.HTTP_200_OK)

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()
...

Django Rest Framework serilize relations

How to serialize a fields in related models.
I got a models:
class Order(models.Model):
order_id = models.BigIntegerField(verbose_name='Order ID', unique=True)
order_name = models.CharField(verbose_name='Order name', max_length=255)
order_type = models.IntegerField(verbose_name='Campaign type')
class Types(models.Model):
delimiter = models.CharField(verbose_name='Delimiter', max_length=255)
status = models.BooleanField(verbose_name='Status', default=True)
title = models.CharField(verbose_name='Title', max_length=255)
class User(models.Model):
name = models.CharField(verbose_name='User name', max_length=200, unique=True)
class Report(models.Model):
order = models.ForeignKey(Order, to_field='order_id', verbose_name='Order ID')
user = models.ForeignKey(User, verbose_name='User ID')
ad_type = models.ForeignKey(Types, verbose_name='Type')
imp = models.IntegerField(verbose_name='Total imp')
month = models.DateField(verbose_name='Month', default=datetime.datetime.today)
View:
class ReportLisAPIView(ListAPIView):
serializer_class = ReportSerializer
def get_queryset(self):
month = parse_date(self.kwargs['month']) - relativedelta(day=1)
queryset = (
Report.objects.filter(month=month)
.values_list(
'user', 'user__name', 'order__order_id',
'order__order_name', 'order__order_type'
).all().annotate(Sum('imp'))
)
return queryset
Serializer:
class ReportSerializer(ModelSerializer):
class Meta:
model = Report
depth = 1
I need to get all field like in 'queryset' in get_queryset()
but I got an error:
Got AttributeError when attempting to get a value for field imp on
serializer ReportSerializer. The serializer field might be named
incorrectly and not match any attribute or key on the tuple
instance. Original exception text was: 'tuple' object has no attribute
'imp'.
But if I return in get_queryset() just Report.objects.filter(month=month).all() I'll get all objects and related object with all field, without aggregate of imp and not grouping.
So the question is how to make serializer return structure that set in queryset?
The get_queryset method requires to return a queryset but you are returning a tuple beacause of values_list. Either drop it to return a queryset or go with a more generic view like APIView.
I found a way how to do it.
As I use .values_list() it return list object instead of queryset object. So for serializer do understand what is inside the list I defined all fields in serializer. And in to_representation() I return dictionary like it should be.
Serializer:
class ReportSerializer(serializers.ModelSerializer):
user = serializers.IntegerField()
user_name = serializers.CharField()
order_id = serializers.IntegerField()
order_name = serializers.CharField()
order_type = serializers.IntegerField()
imp = serializers.IntegerField()
class Meta:
model = Report
fields = [
'user', 'user_name', 'order_id', 'order_name',
'order_type', 'imp'
]
depth = 1
def to_representation(self, instance):
Reports = namedtuple('Reports', [
'user',
'user_name',
'order_id',
'order_name',
'order_type',
'imp',
])
return super(ReportSerializer, self).to_representation(
Reports(*instance)._asdict()
)
View:
class ReportLisAPIView(ListAPIView):
serializer_class = ReportSerializer
def get_queryset(self):
month = parse_date(self.kwargs['month']) - relativedelta(day=1)
queryset = (
Report.objects.filter(month=month)
.values_list(
'user', 'user__name', 'order__order_id',
'order__order_name', 'order__order_type'
).all().annotate(Sum('imp'))
)
return queryset
def list(self, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset, many=True)
# actualy that's it! part of which is below can be pass and just
# return Response(serializer.data)
result = {
'month': parse_date(self.kwargs['month']).strftime('%Y-%m'),
'reports': []
}
inflcr = {}
for item in serializer.data:
inflcr.setdefault(item['user'], {
'id': item['user'],
'name': item['user_name'],
'campaigns': []
})
orders = {
'id': item['order_id'],
'name': item['order_name'],
'type': item['order_type'],
'impressions': item['imp'],
}
inflcr[item['user']]['campaigns'].append(orders)
result['reports'] = inflcr.values()
return Response(result)