Why does my "id" field disappear in my serializer (manytomany)? - django

I have a "PriceTable" object that can contain several "PriceLine" objects with a manytomany relationship.
I use django rest framework to publish an api and I would like that when I use PUT or PATCH on PriceTable, I can also modify the content of PriceLine.
The goal is to have a unique UPDATE method to be able to modify the instances of the 2 objects.
My models:
class PriceTable(models.Model):
name = models.CharField(_('Name'), max_length=255)
client = models.ForeignKey(
"client.Client",
verbose_name=_('client'),
related_name="price_table_client",
on_delete=models.CASCADE
)
lines = models.ManyToManyField(
'price.PriceLine',
verbose_name=_('lines'),
related_name="price_table_lines",
blank=True,
)
standard = models.BooleanField(_('Standard'), default=False)
class Meta:
verbose_name = _("price table")
verbose_name_plural = _("price tables")
def __str__(self):
return self.name
class PriceLine(models.Model):
price = models.FloatField(_('Price'))
product = models.ForeignKey(
"client.Product",
verbose_name=_('product'),
related_name="price_line_product",
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("price line")
verbose_name_plural = _("price line")
def __str__(self):
return f"{self.product.name} : {self.price} €"
I want to be able to send a JSON of this format to modify both the table and its lines:
{
"id": 16,
"lines": [
{
"id": 1,
"price": 20.0,
"product": 1
},
{
"id": 2,
"price": 45.0,
"product": 2
}
],
"name": "test"
}
For this, I try to override the update method of my serializer:
class PriceTableSerializer(serializers.ModelSerializer):
"""
PriceTableSerializer
"""
lines = PriceLineSerializerTest(many=True)
class Meta:
model = PriceTable
exclude = ['standard', 'client',]
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
lines = validated_data.get('lines')
print(lines)
# not python code
# for target_line in lines:
# if instance.id == target_line.id
# instance.price = target_line.price
# ...
return instance
The 5 commented lines, are the logic I would like to implement.
I want to browse the received array of rows and if the id of this row is equal to the id of a row in my instance, I change the values of this row.
The problem is that the id disappears. When I print the lines variable, I get this:
[OrderedDict([('price', 20.0), ('product', <Product: Test import2>)])]
OrderedDict([('price', 20.0), ('product', <Product: Test import2>)])
What happened to the id?

As docs says in https://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-update:
You will need to add an explicit id field to the instance serializer. The default implicitly-generated id field is marked as read_only. This causes it to be removed on updates. Once you declare it explicitly, it will be available in the list serializer's update method.
So you should declare id yourself for being able to use that:
class PriceLineSerializerTest(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = PriceLine
exclude = ['id', ...]

Related

Deserialize Nested Object in Django-Rest-Framework

Good day,
I'm trying to do a PUT request that contains a nested-object and I can't get the province to update correctly. There didn't seem to be anything obvious in the django-rest-framework docs to help with this and I've investigated the solutions of a few other similar problems but none have helped (set many=false, change to ModelSerializer, specialized serializers, etc.).
Everything else about the address will update correctly and return a 200 response (no errors in django logs either). Am I wrong to assume that django-rest-framework handles this all for me for free? Do I have to override the update and create methods within the serializer to validate and save the nested-object?
I'm thinking it's because I have the province serializer set to read_only within the address serializer. However, if I remove the read_only modifier on the province serializer, it gives an error about the province already existing:
{
"province": {
"province": [
"valid province with this province already exists."
]
}
}
Which is behaviour I do not expect and don't know how to resolve. I am not trying to add or update the province. I just want to change the province code in the address.province field and I can't use a string "MB" because it expects an object. I effectively want this behaviour:
UPDATE agent_business_address
SET province = 'MB'
WHERE agent_id = 12345;
-- agent_business_address.province has a foreign key constraint on valid_province.province
-- valid_province is populated with all the 2-letter abbreviations for provinces(
I make this PUT request to /api/agent-business-address/
{
"address": "123 Fake St",
"agent_id": 12345,
"city": "Calgary",
"dlc": "2021-10-11 14:03:03",
"operator_id": 4,
"postal_code": "A1B 2C3",
"province": {
"description": "Manitoba",
"province": "MB"
},
"valid_address": "N"
}
That is received by this ViewSet:
class AgentBusinessAddressViewSet(viewsets.ModelViewSet):
queryset = AgentBusinessAddress.objects.all()
serializer_class = AgentBusinessAddressSerializer
Relevant serializers:
class AgentBusinessAddressSerializer(serializers.HyperlinkedModelSerializer):
province = ValidProvinceSerializer(read_only=True) # Removing the read_only causes the error above.
class Meta:
model = AgentBusinessAddress
fields = ('agent_id', 'operator_id', 'dlc', 'address', 'city', 'province', 'postal_code', 'valid_address')
class ValidProvinceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ValidProvince
read_only_fields = ('operator_id', 'dlc')
fields = ('province', 'description')
Relevant models:
class AgentBusinessAddress(models.Model):
agent = models.OneToOneField(Agent, models.DO_NOTHING, primary_key=True)
operator_id = models.SmallIntegerField()
dlc = models.DateTimeField()
address = models.CharField(max_length=100)
city = models.CharField(max_length=80)
province = models.ForeignKey('ValidProvince', models.DO_NOTHING, db_column='province')
postal_code = models.CharField(max_length=7)
valid_address = models.CharField(max_length=1)
class Meta:
managed = False
db_table = 'agent_business_address'
class ValidProvince(models.Model):
province = models.CharField(primary_key=True, max_length=2)
operator_id = models.SmallIntegerField()
dlc = models.DateTimeField()
description = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'valid_province'
Any help would be appreciated.
Solved it with the help of the specialized serializers post after a few re-reads.
I updated the serializers to this:
# This gets called for non-GET requests.
class AgentBusinessAddressSerializer(serializers.ModelSerializer):
class Meta:
model = AgentBusinessAddress
fields = ('__all__')
# This get called for GET requests.
class AgentBusinessAddressReadSerializer(AgentBusinessAddressSerializer):
province = ValidProvinceSerializer(read_only=True)
class Meta:
model = AgentBusinessAddress
fields = ('__all__')
I updated the viewset to this:
class AgentBusinessAddressViewSet(viewsets.ModelViewSet):
queryset = AgentBusinessAddress.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
return AgentBusinessAddressReadSerializer
return AgentBusinessAddressSerializer
Now I just send the primary key for the province in the PUT request:
{
"address": "123 Fake St",
"agent": 10000003,
"city": "Calgary",
"dlc": "2021-10-11 19:47:38",
"operator_id": 4,
"postal_code": "A1B 2C3",
"province": "NS",
"valid_address": "N"
}
I get a PUT 200 response back and verify in the DB that the province is now 'NS'.
{
"agent": 10000003,
"operator_id": 4,
"dlc": "2021-10-11T19:47:38",
"address": "123 Fake St",
"city": "Calgary",
"postal_code": "A1B 2C3",
"valid_address": "N",
"province": "NS",
}

ManyToMany django field not working, not taking any input in POST request?

I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
But when I define it in my module/models.py file, it is not taking any input as rooms. here are my files -
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
rooms = models.ManyToManyField(Rooms)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
is_deleted = models.BooleanField(default=False)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'], status = status.HTTP_201_CREATED)
return Response(module_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
POST request body for creating a module in POSTMAN -
{
"rooms": [
{
"room_id": 2,
"title": "4",
"desc": "22",
"level": "2",
}
],
"title": "4",
"desc": "22",
}
With this request, module is being created, but no room is getting added in it.
Can someone tell me why my rooms are not getting added while creating modules?
You need to write an explicit serializer create method to support writable nested representations.
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
Example:
Given a RoomSerializer like this:
class RoomSerializer(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = models.Rooms
fields = '__all__'
We explicitly set room_id in RoomSerializer, to avoid <room_id> stripped away from the input data, and a MultipleObjectsReturned raised.
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(many=True) # remove *read_only=True*
class Meta:
model = Module
fields = "__all__"
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room, created = Rooms.objects.get_or_create(**data)
module.rooms.add(room)
return module

IntegrityError: null value in column "invoiceOwner_id" violates not-null constraint

In my django app, i'm having difficulties whenever i want to add a new object that uses the table paymentInvoice.
The error i'm getting from my api looks like this
IntegrityError at /api/clients/invoice/
null value in column "invoiceOwner_id" violates not-null constraint
DETAIL: Failing row contains (10, INV-0006, Lix, 2020-08-04, 1, Pending, 3000, null).
NB: I haven't created the field invoiceOwner_id, postgres automatically added it or rather is using it as a representation for my invoiceOwner field
class Purchaser(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.date.today)
def __str__(self):
return self.name
class paymentInvoice(models.Model):
invoiceNo = models.CharField(max_length=50, unique=True, default=increment_invoice_number)
invoiceOwner = models.ForeignKey(Purchaser, on_delete=models.CASCADE, related_name="invoice_detail")
product = models.CharField(max_length=50, blank=True)
date = models.DateField(default=datetime.date.today)
quantity = models.PositiveSmallIntegerField(blank=True, default=1)
payment_made = models.IntegerField(default=0)
def __str__(self):
return self.invoiceOwner.name
serilizers file
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
views file
class paymentInvoiceListCreateView(ListCreateAPIView):
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by('-date')
GET result from api call.
{
"id": 1,
"invoiceOwner": "Martin",
"invoiceNo": "INV-0001",
"product": "",
"date": "2020-08-04",
"quantity": 1,
"payment_made": 0
}
Tried passing below as POST but got the main error
{
"invoiceOwner": "Becky",
"product": "Lix",
"quantity": 1,
"payment_made": 3000
}
"invoiceOwner" in your serializers.py is a SerializerMethodField which is readonly
that's why you get an error, you have to define the create method yourself
As I said in comment: You need to explicitly override the create method in your serializer since your model has foreign key invoiceOwner, just to create that instance first as a Purchaser instance.
You can try the code below:
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
def create(self, validated_data):
purchaser_name = validated_data.get("invoiceOwner")
purchaser = Purchaser(name=purchaser_name,
# you need to have phone, email, since these fields are unique,
# they can't remain null
)
purchaser.save()
return paymentInvoice.objects.create(invoiceOwner = purchaser, **validated_data)

filter multiple objects and returns 'new field' in Serializer

Just like Youtube, I want to see what categories my post(outfit) is in.
My approach is...
Get whole list of categories belong to user
Get whole list of categories belong to user and the post
compare them and return JSON
While building a Seriailizer, I feel like I'm totally stock. So frustraing... :(
Please feel free to change my approach if you have any better idea.
Expected JSON outputis below
{
...
"categories": [
{
"id": 1,
"name": "asd"
"added": True <- since the post is added to 'asd'
},
{
"id": 2,
"name": "workout"
"added": True
},
...
{
"id": 5,
"name": "bgm"
"added": False <- since the post is not added to 'bgm'
},
]
}
Here is views.py
class OutfitDetailView(generics.RetrieveAPIView):
queryset = Outfit.objects.all()
serializer_class = OutfitDetailSerializer
lookup_field = 'pk'
Here is serializers.py (this is a problem)
class OutfitDetailSerializer(serializers.ModelSerializer):
...
def get_categories(self, obj):
# Right Here! Get whole categories associated with user
all_categories = Category.objects.filter(owner=self.context['request'].user)
# And the categories associated with user AND the post
categories = obj.categories.filter(owner=self.context['request'].user)
return CategorySerializer(categories, many=True).data
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = (
'id',
'name',
'added' <- here I want to add 'added' field but I don't know how.
)
In case you need a model
class Outfit(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
content = models.CharField(max_length=30)
...
class Category(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
outfits = models.ManyToManyField(Outfit, related_name="categories", blank=True)
main_img = models.ImageField(
upload_to=upload_location_category,
null=True,
blank=True)
...
if i understand you correct, you can try:
class OutfitDetailSerializer(serializers.ModelSerializer):
def get_categories(self, obj):
user = self.context['request'].user
categories = Category.objects.filter(owner=user)
added = categories.extra(select={'added': '1'}).filter(outfits__pk=obj.pk)
added = list(added.values('added', 'name', 'id'))
added_f = categories.extra(select={'added': '0'}).exclude(outfits__pk=obj.pk)
added_f = list(added_f.values('added', 'name', 'id'))
categories = added + added_f
return CategorySerializer(categories, many=True).data
Note in the values you need add all fields you need for the CategorySerializer
class CategorySerializer(serializers.ModelSerializer):
added = serializers.BooleanField()
class Meta:
model = Category
fields = (
'id',
'name',
'added'
)

Django nested serializer allow_null=True

I have a nested serializer and I want to activate the allow_null to true, but it doesn't work.
TOP object have a nested Down object, the related_name must be present in the TOP object but with a null value. If the down object is not null all down object fields are required.
Example request with all fields in down object (this one works fine) :
{
"title": "Titre new rgfdgfdgthtrh",
"downs": {
"type": "Type example",
"is_external": true,
},
}
Example that i tryed to do : request when down object is null (this one doesn't work)
{
"title": "Titre new ",
"downs": {},
}
I have tryed with "downs": None or Null without success.
My views :
# My Views.py
class Top(models.Model):
class Meta:
verbose_name = _('Top')
verbose_name_plural = _('Tops')
top_guid = models.UUIDField(
primary_key=True,
unique=True,
default=uuid.uuid4,
editable=False)
title = models.CharField(
help_text=_('Title'),
verbose_name=_('title'),
max_length=100,
blank=False
)
class Down(models.Model):
top = models.OneToOneField(
Top,
on_delete=models.CASCADE,
help_text=_('Top'),
verbose_name="top",
related_name="downs"
)
type = models.CharField(
help_text=_('Type'),
verbose_name=_('type'),
max_length=30,
blank=False
)
is_external = models.BooleanField(
help_text=_('external (default = false)'),
verbose_name=_('external'),
blank=False,
default=False
)
and my serializers
# My serializers.py
class DownSerializer(serializers.ModelSerializer):
class Meta:
model = Down
fields = '__all__'
class TopSerializer(serializers.ModelSerializer):
downs = DownSerializer(many=False, required=False, allow_null=True)
class Meta:
model = Top
fields = ('top_guid', 'title', 'downs',)
def create(self, validated_data):
"""
Create and return a new `Topic` instance.
"""
downs_data = validated_data.pop('downs')
top = Top.objects.create(**validated_data)
Down.objects.create(top=top, **downs_data)
return top
def update(self, instance, validated_data):
"""
Update and return an existing `Topic` instance.
"""
# get bim_snippet data and bim_snippet object
downs_data = validated_data.pop('downs')
downs = instance.downs
# update top data and save top object
instance.title = validated_data.get('title', instance.title)
instance.top_type = validated_data.get('top_type', instance.top_type)
instance.save()
# update down data and save down object
downs.snippet_type = downs_data.get('type', downs.snippet_type)
downs.is_external = downs_data.get('is_external', downs.is_external)
downs.save()
return instance
Thank's a lot.
I think that if you add arguments like allow_null=True or read_only=False in your serializer class, you need to recreate your sqlite3 database. read_only was not working, but just after recreate the db it works fine. (makemigrations and migrate seem's to be not enought)