I'm getting a Json response to save it in my database, I need to get the items in line_items object. My Serializer and View works fine if I remove the line_items attribute in the model, but when I try to get that object and save it in the database nothing happens. Maybe I'm missing something in my serializer?
Json Structure:
{
"id": 123456,
"email": "jon#doe.ca",
"created_at": "2017-03-29T15:56:48-04:00",
"line_items": [
{
"id": 56789,
"title": "Aviator sunglasses",
"quantity": 1
},
{
"id": 98765,
"title": "Mid-century lounger",
"quantity": 1
}
]
}
My model:
class Line(models.Model):
title = models.CharField(max_length=100)
quantity = models.IntegerField()
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ForeignKey(Line)
My Serializer:
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
My View:
#api_view(['POST'])
def orders(request):
if request.method == 'POST':
json_str = json.dumps(request.data)
resp = json.loads(json_str)
serializer = OrderSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
You have a list in the field-key line_items in your response, as per your models you can't accommodate the data in the tables, what you need is ManyToMany relation between Order and Line,
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ManyToManyField(Line)
and in your serializer,
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = [f.name for f in model._meta.fields] + ['line_items']
def create(self, validated_data):
line_items = validated_data.pop('line_items')
instance = super(OrderSerializer, self).create(validated_data)
for item in line_items:
instance.line_items.add(item['id'])
instance.save()
return instance
Just add depth=1 in your serializer. It will do-
class OrderSerializer(ModelSerializer):
class Meta:
depth = 1
fields = '__all__'
I think you should add an explicit line_items field to OrderSerializer. Like this:
class OrderSerializer(ModelSerializer):
line_items = LineSerializer(many=True)
class Meta:
model = Order
fields = '__all__'
Related
I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 12,
"package": self.package.id,
"is_enabled": False,
"location": self.location
}
response = self.client.post(path=self.url, data=data, format='json')
locations doesn't appear in validated_data?
How to get it to assign locations to the instance with post requests?
I also tried to send it with as ids list, but non works. I only field price, package, is_enabled in validated}_data, but location doesn't appear!
read_only=True means the field will be neglected in request body and will only appear in response body
locations = LocationSerializer(many=True, read_only=True)
so, remove it and you can access locations in validated_data
I am trying to wrap my head around this for too long already :(
I need the following output for my frontend (specially the ID & name field in combination):
{
"serial": "e3461fb0",
"shipment": {
"id": 1,
"name": "via rotterdam"
}
}
model:
class Shipment(models.Model):
name = models.CharField("name", max_length = 128)
date = models.DateField()
class Product(models.Model):
serial = models.CharField("serial", max_length = 31, unique = True)
shipment = models.ForeignKey(Shipment, on_delete = models.CASCADE, blank = True, null = True)
serializer:
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = ["id", "name",]
class ProductSerializer(serializers.ModelSerializer):
shipment = ShipmentSerializer()
def update(self, instance, validated_data):
print("TEST:", instance, validated_data)
return super().update(instance, validated_data)
class Meta:
model = Product
fields = ["serial", "shipment",]
lookup_field = "serial"
read_only_fields = ["serial",]
ViewSet:
class ProductViewSet(ModelViewSet):
serializer_class = ProductSerializer
lookup_field = "serial"
http_method_names = ["get", "patch", "put"]
def get_queryset(self):
return Product.objects.all()
my problem here is the following: Lets say a product ist linked to a shipment and what I want now is to update that product by linking it to another shipment by using the id. But even in the HTML view of DRF I only get to see the name attribute of shipment. How can I only show/use the id here? I know I could modify the __str__ method of the model to return a string representation and split it later in the frontend, but I would like to avoid that if possible.
I was thinking of something like this:
Id field is what DRF make itself, and it's read-only by default
If you want to update that, you should override this field. Something like:
class ShipmentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
name = serializers.CharField(read_only=True)
class Meta:
model = Shipment
fields = ["id", "name",]
I have created model with many to many relationship and I have join table when I keep additional variable for it:
class BorderStatus(models.Model):
STATUS_CHOICES = [("OP", "OPEN"), ("SEMI", "CAUTION"), ("CLOSED", "CLOSED")]
origin_country = models.ForeignKey(OriginCountry, on_delete=models.CASCADE, default="0")
destination = models.ForeignKey(Country, on_delete=models.CASCADE, default="0")
status = models.CharField(max_length=6, choices=STATUS_CHOICES, default="CLOSED")
extra = 1
class Meta:
unique_together = [("destination", "origin_country")]
verbose_name_plural = "Border Statuses"
def __str__(self):
return (
f"{self.origin_country.origin_country.name} -> {self.destination.name}"
f" ({self.status})"
)
Other models:
# Create your models here.
class Country(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name='Country')
class Meta:
verbose_name_plural = "Countries"
def __str__(self):
return self.name
class OriginCountry(models.Model):
origin_country = models.ForeignKey(
Country, related_name="origins", on_delete=models.CASCADE
)
destinations = models.ManyToManyField(
Country, related_name="destinations", through="BorderStatus"
)
class Meta:
verbose_name_plural = "Origin Countries"
def __str__(self):
return self.origin_country.name
Here is my serializer for the endpoint:
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""Create serializer for editing single connection based on origin and destination name- to change status"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
And my endpoint:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
The problem Im having is that I cant create any new combination for the BorderStatus model in this serializer via post request.
If I remove the lines:
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
Then the form will work, but then I wont have the string representation of those variables, instead I get IDs.
Is there any way to allow request to accept origin_country and destination while being related fields?
EDIT:
To clarify how OriginCountry works, it is has a nested field:
[{ "id": 1
"origin_country": "Canada",
"dest_country": [
{
"id": 1,
"name": "France",
"status": "CLOSED"
},
{
"id": 2,
"name": "Canada",
"status": "OP"
}
]
},
]
You can try to override perform_create method of the viewset to make the necessary adjustments on-the-fly when new entry is posted:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
def perform_create(self, serializer):
origin_country, _ = models.Country.get_or_create(name=self.request.data.get('origin_country')
destination, _ = models.Country.get_or_create(name=self.request.data.get('destination')
return serializer.save(origin_country=origin_country, destination=destination)
Maybe you will also need to adjust your serializer to have:
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ['name']
class BorderStatusEditorSerializer(serializers.ModelSerializer):
origin_country = CountrySerializer()
destination = CountrySerializer()
...
Yes, I will try to give this combination.
You get this error because of Incorrect Type exception. Django checks data type validation on the serializer. For example here your dest_country returns a list of dicts but in your model it is a primary key (pk)
That's why on post django says : pk value expected, list received
But you can solve this error by using two different serializers (one to post another by default)
1. Create two different serializers
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""The First serialiser by default"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
class BorderStatusEditorCreateSerializer(serializers.ModelSerializer):
"""The Second serialiser for create"""
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
2.Add get_serializer_class method to for Viewset
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
serializer_classes = {
'create': BorderStatusEditorCreateSerializer, # serializer used on post
}
default_serializer_class = BorderStatusEditorSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)
Hi I want to deserialize only using 1 field. However, I want to serialize it as an object depending on the model.
Suppose I have:
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
class Query(models.Model):
name = models.CharField()
product = models.ForeignKey(Product)
...
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class QuerySerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = Query
fields = '__all__'
I want to POST/deserialize something like this on the QuerySerializer:
{
"name": "Query 1",
"product": "Banana",
...
}
and I want something like this in return for serializer:
{
"name": "Query 1",
"product": {
"name": "Banana",
"amount": 1,
"description": "Banana description"
}
...
}
I know a way is overriding to_internal_value but I do not like it since it messes up with ValidationErrrors.
I also get this as a result:
{'product': {'non_field_errors':
['Invalid data. Expected a dictionary, but got str.']}}
First of all, make the name field of Product as unique to avoid unnecessary complications.
class Product(models.Model):
name = models.CharField(unique=True)
amount = models.IntegerField()
description = models.TextField()
and change your serializer as,
class QuerySerializer(serializers.ModelSerializer):
product = serializers.CharField(write_only=True)
class Meta:
model = Query
fields = '__all__'
def create(self, validated_data):
product_name = validated_data.pop('product')
product_instance = Product.objects.get(name=product_name)
return Query.objects.create(product=product_instance, **validated_data)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['product'] = ProductSerializer(instance.product).data
return rep
Reference: DRF: Simple foreign key assignment with nested serializers?
I have the model structure like below
class BaseProduct:
id = models.CharField(max_length=15)
name = models.CharField(max_length=20)
class Product
base_product = ForeigKey(BaseProduct)
name = models.CharField(max_length=20)
class Condition:
category = models.ForeignKey(Product, related_name='allowed_product')
check = models.IntegerField(default=0)
allow = models.PositiveSmallIntegerField(default=1)
The query:
Product.objects.filter(condition__allow=1, condition__check=1)
I want format something like below
Base Product and inside that list of products based on allow and check filter
[
{
"name": "BaseProduct 1",
"products": [
{
"name": "TV",
}, {}, ....
]
},
........
]
Try it, if you use django rest framework
from rest_framework import serializers
from rest_framework.fields import empty
from django.utils.functional import cached_property
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('name')
class BaseProductSerializer(serializers.ModelSerializer):
products = serializers.SerializerMethodField()
class Meta:
model = BaseProduct
fields = ('name', 'products')
def __init__(self, instance=None, data=empty, **kwargs):
self._condition_allow = kwargs.pop('condition_allow', 1)
super(BaseProductSerializer, self).__init__(instance=None, data=empty, **kwargs)
#cached_property
def _request_data(self):
request = self.context.get('request')
# if POST
# return request.data if request else {}
# if GET params
return request.query_params if request else {}
#cached_property
def _condition(self):
return self._request_data.get('CONDITION_PARAM_NAME')
def get_products(self, obj):
qs = obj.product_set.filter(condition__allow=self._condition_allow, condition__check=1)
serializer = ProductSerializer(qs, many=True)
# ^^^^^
return serializer.data
in view
serialiser(qs, condition_allow=5)
Change your models to have related_name for the foreignkeys to have reverse relationship:
class BaseProduct:
id = models.CharField(max_length=15)
name = models.CharField(max_length=20)
class Product
base_product = ForeigKey(BaseProduct, related_name='products')
name = models.CharField(max_length=20)
class Condition:
category = models.ForeignKey(Product, related_name='conditions')
check = models.IntegerField(default=0)
allow = models.PositiveSmallIntegerField(default=1)
So now you can use it in your serializers:
class BaseProductSerializer:
class Meta:
model = BaseProduct
fields = ('name', 'products',)
class ProductSerializer:
class Meta:
model = Product
fields = ('conditions',)
class ConditionSerializer:
class Meta:
model = Condition
fields = '__all__'
Finally in your views, change this:
Product.objects.filter(condition__allow=1, condition__check=1)
into this:
BaseProduct.objects.filter(products__conditions__allow=1, products__conditions__allow=1)
And hopefully, this should generate JSON data in the format that you asked for.