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']
Related
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", ...]
...
I have the following serializer :
class SalesProjectListSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField('get_custompermissions',read_only=True)
class Meta:
model = SalesProject
fields = ['sales_project_id', 'sales_project_name',
'sales_project_est_rev', 'project_status','permissions']
depth = 2
def get_custompermissions(self, obj):
permission_list = ['add_salesproject']
user_perms = User.get_user_permissions(self.context['request'].user)
return { permission: True if permission in user_perms else False for permission in permission_list }
This serializer is used to serialize the data thats used to render the project listing page.
The serialized data would give me something like :
projects = [{sales_project_id : 1 , sales_project_name = 'test , ... ,permissions: [...]}]
However instead what i wish to return is somthing like this :
projects = {projects:[{sales_project_id : 1 , sales_project_name = 'test , ... }]
,permissions: [...]}
You cand override the method responsible for the response depending on your View type.
I assume you are using a ListAPIView so this is how you would do it:
class YourView(ListAPIView):
model = SalesProject
serializer_class = SalesProjectListSerializer
def list(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
# change the data
# serializer.data is the response that your serializer generates
res = {"projects": serializer.data}
return Response(res)
It's the same for other views such as RetrieveAPIView but you should override the retrieve method instead.
I'm trying to write a test to check that the create method of my Viewset works ok:
#pytest.mark.django_db
def test_create(admin_user):
parent = factories.ParentFactory()
payload = {
'name': 'child',
'parent_id': parent.pk,
}
view = views.ParentViewSet.as_view({'post': 'create'})
request = factory.post('/', payload, format='json')
force_authenticate(request, user=admin_user)
response = view(request)
assert response.status_code == status.HTTP_201_CREATED
assert models.Child.objects.count() == 1
child = models.Child.objects.first()
assert child.name == 'child'
However, I get the following error when I run the code:
psycopg2.errors.NotNullViolation: null value in column "parent_id" violates not-null constraint
But the test for the Parent create method runs fine:
def test_create(admin_user):
payload = {
'name': 'parent',
'children': [
{
'name': 'child',
}
],
}
view = views.ParentViewSet.as_view({'post': 'create'})
request = factory.post('/', payload, format='json')
force_authenticate(request, user=admin_user)
response = view(request)
assert response.status_code == status.HTTP_201_CREATED
assert models.Parent.objects.count() == 1
season = models.Parent.objects.first()
assert season.name == 'parent'
assert season.children.count() == 1
Can someone tell me the correct way to write the payload for the child test create?
Edit:
serializers.py
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = models.Child
fields = ['id', 'name']
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
class Meta:
model = models.Parent
fields = ['id', 'name', 'children']
def create(self, validated_data):
children = validated_data.pop('children')
parent = super().create(validated_data)
for child in children:
child['parent'] = parent
self.fields['children'].create(children)
return parent
views.py
class ParentViewSet(
viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
):
queryset = models.Parent.objects.all().prefetch_related(
'children'
)
serializer_class = serializers.ParentSerializer
class ChildViewSet(
viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
):
queryset = models.Child.objects.all().prefetch_related(
'children'
)
serializer_class = serializers.ChildSerializer
I think you have a field "parent" in the child model, but as you are not including that field in ChildSerializer, even if you send it in the request body, it is not included while creating a Child instance, hence the error you're getting. You have several ways to solve it
1 - You can add parent field to ChildSerializer:
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = models.Child
fields = ['id', 'name', 'parent']
and send the request like this:
payload = {
'name': 'child',
'parent': parent.pk,
}
2 - If you do not want that, you may keep the ChildSerializer as it is now, and add parent only while saving the child instance, with something like this:
class ChildViewSet(
...
):
queryset = models.Child.objects.all().prefetch_related('children')
serializer_class = serializers.ChildSerializer
def perform_create(self, serializer):
serializer.save(parent_id=self.request.data.get('parent'))
Again you'd need to send the request like this:
payload = {
'name': 'child',
'parent': parent.pk,
}
But in this second scenario, you're not including parent field in request validation, and if a client does not send parent field or sends an invalid value there, you'll get an exception. If you are to go with this solution, I'd advise to somehow validate parent field as well.
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'
]
I have a problem with filtering objects in View Set... I am trying to show objects only where field 'point' is null.
I always get error: NameError: name 'null' is not defined
Could you please HELP ME ?
My code:
class CompanyMapSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name', 'point', 'url', 'pk')
extra_kwargs = {
'url': {'view_name': 'api:company-detail'},
}
def to_representation(self, instance):
ret = super(CompanyMapSerializer, self).to_representation(instance)
ret['point'] = {
'latitude': instance.point.x,
'longitude': instance.point.y
}
return ret
And view set code:
class CompanyMapViewSet(viewsets.ModelViewSet):
queryset = Company.objects.filter(point = null)
serializer_class = CompanyMapSerializer
PageNumberPagination.page_size = 10000
Please help me.
You are not defining what null is, and Python doesn't recognize null as a primitive, you've got two options:
queryset = Company.objects.filter(point = None) # using None
queryset = Company.objects.filter(point__isnull = True) # explicitly asking for Null
These two queries are equally valid.