django-tastypie : Related data not saving - python

My models.py
class Orders(models.Model):
order_id = models.CharField(max_length=7, primary_key=True)
users = models.ForeignKey(ProductUsers, on_delete=models.DO_NOTHING)
address = models.ForeignKey(ProductUsersAddress, on_delete=models.DO_NOTHING)
payment_method = models.CharField(default='COD', max_length=20, choices=PAYMENT_METHOD)
class OrderedProduct(models.Model):
products = models.ForeignKey(Products, on_delete=models.DO_NOTHING)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
price = models.DecimalField(default=0.00, max_digits=5, decimal_places=2, blank=False)
My resources.py
class OrdersResource(ModelResource):
ordered_products = fields.ToManyField('orders.resources.OrderedProductResource',
attribute=lambda bundle: OrderedProduct.objects.filter(orders=bundle.obj),
related_name='orders', full=True, null=True)
contact_no = fields.ForeignKey(ProductUsersResource, 'users')
address = fields.ForeignKey(ProductUsersAddressResource, 'address')
class Meta:
queryset = Orders.objects.all()
resource_name = 'orders'
include_resource_uri = False
collection_name = 'orders'
allowed_methods = ['get', 'post']
always_return_data = True
class OrderedProductResource(ModelResource):
products = fields.ForeignKey(ProductsResource, 'products')
orders = fields.ForeignKey(OrdersResource, 'orders')
class Meta:
queryset = OrderedProduct.objects.all()
resource_name = 'ordered_products'
excludes = ['id']
include_resource_uri = False
I entered data using Django-Admin.
When I hit, http://localhost:8000/orders/, I get,
{
"orders": [
{
"address": "/api/v1/address/1",
"contact_no": "/api/v1/users/8269661606",
"order_id": "KJLSWI",
"ordered_products": [
{
"orders": "/api/v1/orders/KJLSWI",
"price": "40.00",
"products": "/api/v1/products/1",
"quantity": 2
},
{
"orders": "/api/v1/orders/KJLSWI",
"price": "70.00",
"products": "/api/v1/products/2",
"quantity": 4
},
{
"orders": "/api/v1/orders/KJLSWI",
"price": "67.00",
"products": "/api/v1/products/3",
"quantity": 7
}
],
"payment_method": "COD",
}
]
}
Now according to tasty documentation,
Tastypie encourages “round-trippable” data, which means the data you
can GET should be able to be POST/PUT’d back to recreate the same
object.
If you’re ever in question about what you should send, do a GET on
another object & see what Tastypie thinks it should look like.
But when I post same data by just changing primary-key,
{
"address": "/api/v1/address/1",
"contact_no": "/api/v1/users/8269661606",
"order_id": "ABCDE",
"ordered_products": [
{
"orders": "/api/v1/orders/ABCDE",
"price": "40.00",
"products": "/api/v1/products/1",
"quantity": 2
},
{
"orders": "/api/v1/orders/ABCDE",
"price": "70.00",
"products": "/api/v1/products/2",
"quantity": 4
},
{
"orders": "/api/v1/orders/ABCDE",
"price": "67.00",
"products": "/api/v1/products/3",
"quantity": 7
}
],
"payment_method": "COD",
}
I get response,
{
"address": "/api/v1/address/1",
"contact_no": "/api/v1/users/8269661606",
"ordered_products": [],
"payment_method": "COD",
}
My data in model OrderedProduct, is not getting saved. WHYYYYYYY ??????

Try this:
def hydrate_m2m(self, bundle):
for ordered_product in bundle.data['ordered_products']:
if isinstance(ordered_product, dict):
ordered_product.update({'orders': bundle.obj})
return super(OrdersResource, self).hydrate_m2m(bundle)
and remove orders key from ordered_products JSON
and replace attribute=lambda bundle: OrderedProduct.objects.filter(orders=bundle.obj) with orders
and move related_name='orders' from resource to model.
Finally:
model:
class OrderedProduct(models.Model):
products = models.ForeignKey(Products, on_delete=models.DO_NOTHING)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE, related_name='orders')
quantity = models.IntegerField(default=0)
price = models.DecimalField(default=0.00, max_digits=5, decimal_places=2, blank=False)
resource:
class OrdersResource(ModelResource):
ordered_products = fields.ToManyField('orders.resources.OrderedProductResource',
'orders', full=True, null=True)
contact_no = fields.ForeignKey(ProductUsersResource, 'users')
address = fields.ForeignKey(ProductUsersAddressResource, 'address')
class Meta:
queryset = Orders.objects.all()
resource_name = 'orders'
include_resource_uri = False
collection_name = 'orders'
allowed_methods = ['get', 'post']
always_return_data = True
def hydrate_m2m(self, bundle):
for ordered_product in bundle.data['ordered_products']:
if isinstance(ordered_product, dict):
ordered_product.update({'orders': bundle.obj})
return super(OrdersResource, self).hydrate_m2m(bundle)
and POST data:
{
"address": "/api/v1/address/1",
"contact_no": "/api/v1/users/8269661606",
"order_id": "ABCDE",
"ordered_products": [
{
"price": "40.00",
"products": "/api/v1/products/1",
"quantity": 2
},
{
"price": "70.00",
"products": "/api/v1/products/2",
"quantity": 4
},
{
"price": "67.00",
"products": "/api/v1/products/3",
"quantity": 7
}
],
"payment_method": "COD",
}

Related

How to GET multipule ModelSrializer in one APIVIEW using Django Rest Framework

I have UserModel each user has multiple package and each package have price and program.
model.py:
class User(models.Model):
name= models.CharField(max_length=100)
class Package(models.Model):
package_name = models.CharField(max_length=100)
user= models.ForeignKey(User, on_delete=models.CASCADE,related_name='user_package')
class Program(models.Model):
title = models.CharField(max_length=100)
user_package = models.ForeignKey(Package, on_delete=models.CASCADE)
class Price(models.Model):
price = models.FloatField()
user_package = models.ForeignKey(Package, on_delete=models.CASCADE)
serializer look like:
class UserSerializer(NestedCreateMixin, NestedUpdateMixin,serializers.ModelSerializer):
program = ProgramSerializer(source='user_program', many=True, read_only=True)
package = PackageSerializer(source='user_package', many=True, read_only=True)
price = PriceSerializer(source='price', many=True, read_only=True)
class Meta:
model = User
fields= ("__all__")
views.py:
class user_apiView(APIView):
def get(self, request):
user= user.objects.all()
serializer = UserSerializer(user, many = True)
return Response(serializer.data)
and that what I get:
{
"id": 1,
"package": [
{
"id": 1,
"package_name": "wfe",
"user": 1
},
{
"id": 2,
"package_name": "wfe",
"user": 1
}
]
}
how can GET this RESULT?
{
"id": 1,
"package": [
{
"id": 1,
"package_name": "wfe",
"user": 1
},
{
"id": 2,
"package_name": "wfe",
"user": 1
}
],
"price": [
{
"id": 1,
"price": "wfe",
"package": 1
},
{
"id": 2,
"price": "wfe",
"package": 2
}
]
"program": [
{
"id": 1,
"title": "wfe",
"package": 1
},
{
"id": 2,
"title": "wfe",
"package": 2
}
]
}
The problem is that your price and program models are not directly related to your user model. If you consider the relations it is like price -> package -> user, so you will have to get those relations in the package serializer instead like this
serializers.py
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
fields= ("__all__")
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields= ("__all__")
class PackageSerializer(serializers.ModelSerializer):
program = ProgramSerializer(source='program_set', many=True, read_only=True)
price = PriceSerializer(source='price_set', many=True, read_only=True)
class Meta:
model = Package
fields= ("__all__")
class UserSerializer(serializers.ModelSerializer):
package = PackageSerializer(source='user_package', many=True, read_only=True)
class Meta:
model = User
fields= ("__all__")
Note that this will however not give you the output in the format you mentioned though, the price and program fields will instead be nested under package.
[
{
"id": 1,
"package": [
{
"id": 1,
"program": [
{
"id": 1,
"title": "program1",
"user_package": 1
}
],
"price": [
{
"id": 1,
"price": 1000.0,
"user_package": 1
}
],
"package_name": "package1",
"user": 1
},
{
"id": 2,
"program": [
{
"id": 2,
"title": "program2",
"user_package": 2
}
],
"price": [
{
"id": 2,
"price": 1500.0,
"user_package": 2
}
],
"package_name": "package2",
"user": 1
}
],
"name": "user1"
}
]
If you really want the format in the way you posted you might instead want to take a look at https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

how to create multiple objects with one request DRF

I have the following models
class Product(models.Model):
name = models.CharField(null=True, blank=True, max_length=500)
category = models.CharField(null=True, blank=True, max_length=120)
class SpecificationName(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, related_name='specifications')
name = models.CharField(max_length=125)
class Attribute(models.Model):
spec_name = models.ForeignKey(SpecificationName, on_delete=models.CASCADE, null=True, related_name='attributes')
index = models.CharField(max_length=200, blank=True, null=True)
value = models.CharField(max_length=250, blank=True, null=True)
after saving objects in Django admin I have an example
{
"name": "Apple Smart Watch",
"category": "IT",
"specifications": [
{
"name": "Test Data",
"attributes": [
{
"index": "test",
"value": "test2"
},
{
"index": "test7",
"value": "test8"
},
{
"index": "test9",
"value": "test10"
}
]
},
{
"name": "Test Data Continued",
"attributes": [
{
"index": "bla",
"value": "bla1"
},
{
"index": "bla 2",
"value": "bla 4"
},
{
"index": "test9",
"value": "test10"
}
]
},
{
"name": "Test Spec",
"attributes": []
}
]
}
I need to save this kind of object with one request but I am failing to do this
my serializer looks like this
class ProductSerializer(serializers.ModelSerializer):
specifications = SpecNameSerializer(many=True)
# attributes = AttributeSerializer(many=True, required=False)
class Meta:
model = Product
fields = ['name', 'category', 'brand', 'price', 'specifications']
def create(self, validated_data):
specs = validated_data.pop('specifications')
instance = Product.objects.create(**validated_data)
for spec in specs:
SpecificationName.objects.create(product=instance, **spec)
print(spec)
return instance
with this code, I am getting the following result but not as expected
{
"name": "Appel watch series",
"specifications": [
{
"name": "Test Data",
"attributes": []
},
{
"name": "Test Data comn",
"attributes": []
},
{
"name": "Test Spec",
"attributes": []
}
]
}
it cannot write into attributes
I searched for many answers but I did not find or applied some of them, again it did not help me. I am using just ListCreateView in the views. Please is there anybody who can help solve this problem. Thanks in advance!
SOLVED
I am using here ModelSerializer instead I used Serializer and added some changes here my answer and it worked
class AttributeSerializer(serializers.Serializer):
index = serializers.CharField(max_length=200)
value = serializers.CharField(max_length=200)
class SpecNameSerializer(serializers.ModelSerializer):
attributes = AttributeSerializer(many=True)
class Meta:
model = SpecificationName
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
specifications = SpecNameSerializer(many=True)
class Meta:
model = Product
fields = ['name', 'category', 'brand', 'price', 'specifications']
def create(self, validated_data):
specs = validated_data.pop('specifications')
instance = Product.objects.create(**validated_data)
for spec in specs:
SpecificationName.objects.create(product=instance, **spec)
attrs = spec.pop('attributes')
for attr in attrs:
Attribute.objects.create(spec_name=spec, **attr)
return instance

Django Rest Framework relationship queries in nested serializer

I am having difficulties in implementing nested serializers in Django REST Framework.
I'm building an Online score board, for which I currently have three models and I'm trying to serialize it into a single response.
models.py
class Player(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
return self.first_name
class Event(models.Model):
user = models.ManyToManyField("Player")
name = models.CharField(max_length=50)
desc = models.CharField(max_length=225)
def __str__(self):
return self.name
class LeadBoard(models.Model):
"""model for LeadBoard"""
player = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='leadboard_player', null=True, blank=True)
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='leadboard_event', null=True, blank=True)
score = models.IntegerField()
def __str__(self):
return self.score
The event model represent some kind of sports events and each Event can have multiple Players (user filed in Event model), the LeadBoard model stores the score of players in each event.
serializer.py
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ('id','first_name', 'last_name', 'bio')
class EventSerializer(serializers.ModelSerializer):
user = PlayerSerializer(read_only=True, many=True)
class Meta:
model = Event
fields = ('id', 'name', 'user', 'desc')
class LeadBoardSerializer(serializers.ModelSerializer):
event = EventSerializer(read_only=True)
class Meta:
model = LeadBoard
fields = ('id', 'event', 'score')
I have added two Players
[
{
"id": 1,
"first_name": "Jhon",
"last_name": "Doe"
},
{
"id": 3,
"first_name": "Anna",
"last_name": "Doe"
}
]
and their scores in LeadBoard model
[
{
"id": 1,
"player": 1,
"event": 1,
"score": 20
},
{
"id": 2,
"player": 3,
"event": 1,
"score": 90
}
]
This is the response of LeadBoard,
[
{
"id": 1,
"event": {
"id": 1,
"name": "event2020",
"user": [
{
"id": 1,
"first_name": "Jhon",
"last_name": "Doe"
},
{
"id": 3,
"first_name": "Anna",
"last_name": "Doe"
}
],
"desc": "event description"
},
"score": 20
},
{
"id": 2,
"event": {
"id": 1,
"name": "event2020",
"user": [
{
"id": 1,
"first_name": "Jhon",
"last_name": "Doe"
},
{
"id": 3,
"first_name": "Anna",
"last_name": "Doe"
}
],
"desc": "event description"
},
"score": 90
}
]
But what I'm expecting to get is a response like this, which returns the Players(users) of events and their scores correctly.
[
{
"id": 1,
"event": {
"id": 1,
"name": "event2020",
"user": [
{
"id": 1,
"first_name": "Jhon",
"last_name": "Doe",
"score": 20
},
{
"id": 3,
"first_name": "Anna",
"last_name": "Doe",
"score": 90
}
],
"desc": "event description"
}
}
]
What am I missing here?
I'm new to Django and Django Rest Framework.
You need make response by event not by leadboard.
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ('id','first_name', 'last_name', 'bio')
class LeadBoardSerializer(serializers.ModelSerializer):
player = PlayerSerializer(read_only=True)
class Meta:
model = LeadBoard
fields = ('id', 'player', 'score')
class EventSerializer(serializers.ModelSerializer):
leadboard_event = LeadBoardSerializer(read_only=True, many=True)
class Meta:
model = Event
fields = ('id', 'name', 'desc', 'leadboard_event')
now use view to get event list
Update 1
if you want get score in player.
class PlayerSerializer(serializers.Serializer):
player_id = serializers.IntegerField()
first_name = serializers.CharField(max_length=256)
last_name = serializers.CharField(max_length=256)
score = serializers.IntegerField()
class LeadBoardSerializer(serializers.ModelSerializer):
player = serializers.SerializerMethodField()
class Meta:
model = LeadBoard
fields = ('id', 'player')
def get_player(self,obj):
player_dict = {'player_id': obj.player.id, 'first_name': obj.player.first_name, 'last_name': obj.player.last_name, 'score': obj.score}
result = PlayerSerializer(data=player_dict)
return result
class EventSerializer(serializers.ModelSerializer):
leadboard_event = LeadBoardSerializer(read_only=True, many=True)
class Meta:
model = Event
fields = ('id', 'name', 'desc', 'leadboard_event')
Try it.

ManyToMany Through Field Serialization

I am having problems serializing my items/orders with quantity.
Results I am getting:
{
"id": 1,
"_current_status": null,
"orderitem_set": [
{
"item_id": 2,
"quantity": 5
},
{
"item_id": 1,
"quantity": 1
}
],
"items": [
{
"id": 2,
"name": "Blue Shoe",
"description": "Sweet blue shoe bro",
"weight": "99.99",
"price": "99.99"
},
{
"id": 1,
"name": "Red Shoe",
"description": "sweet red shoe bro",
"weight": "1.00",
"price": "100.00"
}
],
"_last_modified": "2015-06-10T22:32:08.007833Z",
"_weight": "500.95",
"_total_price": "599.95",
"placed": false,
"date_placed": null
}
Results I want:
{
"id": 1,
"_current_status": null,
"items": [
{
"id": 2,
"name": "Blue Shoe",
"description": "Sweet blue shoe bro",
"weight": "99.99",
"price": "99.99",
"quantity": 5
},
{
"id": 1,
"name": "Red Shoe",
"description": "sweet red shoe bro",
"weight": "1.00",
"price": "100.00",
"quantity": 1
}
],
"_last_modified": "2015-06-10T22:32:08.007833Z",
"_weight": "500.95",
"_total_price": "599.95",
"placed": false,
"date_placed": null
}
Classes
class Order(models.Model):
_current_status = models.ForeignKey("main.Status", blank=True, null=True)
_last_modified = models.DateTimeField(auto_now=True)
_weight = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
_total_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
user = models.ForeignKey("main.ShopUser")
items = models.ManyToManyField("main.Item", through='main.OrderItem')
placed = models.BooleanField(default=False)
date_placed = models.DateTimeField(null=True, blank=True)
class OrderItem(models.Model):
order = models.ForeignKey('main.Order')
item = models.ForeignKey('main.Item')
quantity = models.IntegerField(default=1)
Serializers
I realize that my serializer is what is putting the orderitem_set into my json but I couldn't figure out how to get the quantity data into items other than in a custom view that loops through the items and the orderitem_set and adds a quantity to items. But that is just terrible and I was hoping there was a way to do this with the built in stuff with the framework or at least make it so I don't have to do that for every view.
class OrderItemField(serializers.RelatedField):
def to_representation(self, value):
return {'item_id': value.item_id, 'quantity': value.quantity}
class OrderSerializer(serializers.ModelSerializer):
_current_status = StatusSerializer(required=False, many=False)
orderitem_set = OrderItemField(read_only=True, many=True)
items = ItemSerializer(required=True, many=True)
class Meta:
model = Order
exclude = ('user',)
The view
This view gives me what I am looking for, but it is just terrible. Especially when I have other views that would be giving me a list of orders vs just a single order.
def set_quantities(data):
# Gets the quantity for each item and then appends it to each item instead of having 'orderitem_set'
for order_item in data['orderitem_set']:
for item in data['items']:
if item['id'] == order_item['item_id']:
item['quantity'] = order_item['quantity']
del data['orderitem_set']
return data
def get(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_object())
data = set_quantities(serializer.data)
return Response(data, status=status.HTTP_200_OK)
By changing the OrderSerializer items field to be an OrderItemField I was able to get back to root Order by using self.root.instance and then using that in conjuction with the value, which is an Item instance, build a query to find the OrderItem object to get the quantity. Then I can get a serialized Item using my ItemSerializer and just append the quantity to that data.
Doing it this way applies to all of my order views and it is a lot less messy. If there is a better way please let me know!
Serializers
class OrderItemField(serializers.RelatedField):
def to_representation(self, value):
if type(self.root.instance) == list:
self.root.instance = self.root.instance[0]
order_item = OrderItem.objects.filter(item=value, order=self.root.instance).first()
quantity = order_item.quantity
value = ItemSerializer(value).data
value['quantity'] = quantity
return value
class OrderSerializer(serializers.ModelSerializer):
_current_status = StatusSerializer(required=False, many=False)
items = OrderItemField(many=True, read_only=True)
class Meta:
model = Order
exclude = ('user',)
You can just use it like this:
class OrderItemSerializer(serializers.ModelSerializer):
class Meta:
model = Order.items.through
fields = ('order', 'item', 'quantity')
class OrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(source='orderitem_set', many=True)
class Meta:
model = Order
fields = ('your_order_fields', 'items')
Note: you have to add items field into OrderSerializer.

How to get the fields of Foreign key field in a queryset

I have the following models:
class Product(models.Model):
objects = ProductManger()
name = models.CharField(max_length=200)
brand_name = models.CharField(max_length=100)
description = models.TextField()
image_url = models.CharField(max_length=200)
class Cart(models.Model):
user = models.ForeignKey(User)
creation_date = models.DateTimeField(auto_now_add=True)
modification_date = models.DateTimeField(auto_now=True, auto_now_add=True)
is_check_out = models.BooleanField(default=False)
class CartItem(models.Model):
objects = CartItemManager()
cart = models.ForeignKey(Cart)
product = models.ForeignKey(Product)
quantity = models.PositiveSmallIntegerField(default=1)
I want to get a JSON of all the cartItems in a cart (where is_check_out =False), and for that i have the following code:
def get_user_cart(self, request, **kwargs):
self.method_check(request, allowed=['get'])
self.is_authenticated(request)
cart, created = Cart.objects.get_or_create(user__exact=request.user, is_check_out=False)
if not created:
items = CartItem.objects.filter(cart=cart)
d = []
for item in items:
print item.product
d.append(model_to_dict(item))
data = [ { 'cart_id':cart.id, 'items':d} ]
else:
data = [ { 'cart_id':cart.id, 'items':[]} ]
data_string = json.dumps(data, cls=DjangoJSONEncoder)
return HttpResponse(data_string, mimetype='application/json')
This gives me the following JSON:
[
{
"items": [
{
"product": 48,
"price": "0.69",
"tax": "0.09",
"cart": 169,
"note": null,
"id": 467,
"quantity": 1
}
],
"cart_id": 169
}
]
But what i am looking for is something like :
[
{
"items": [
{
"product": {
"id": 1,
"name": "apple",
"image_url": "http://localhost"
},
"price": "0.69",
"tax": "0.09",
"cart": 169,
"note": null,
"id": 467,
"quantity": 1
}
],
"cart_id": 169
}
]
UPDATE:
I changed my get_user_cart to the following to achieve what i was looking for; if there is a better way of doing this please comment or answer.
def get_user_cart(self, request, **kwargs):
self.method_check(request, allowed=['get'])
self.is_authenticated(request)
cart, created = Cart.objects.get_or_create(user__exact=request.user, is_check_out=False)
if not created:
items = CartItem.objects.select_related('product').filter(cart=cart)
d = []
for item in items:
d.append({
"product": {
"id": item.product.id,
"name": item.product.name,
"image_url": item.product.image_url
},
'price': item.price,
'tax': item.tax,
'cart':item.cart.id,
'note': item.note,
'id':item.id,
'quantity':item.quantity
})
data = [ { 'cart_id':cart.id, 'items':d} ]
else:
data = [ { 'cart_id':cart.id, 'items':[]} ]
data_string = json.dumps(data, cls=DjangoJSONEncoder)
return HttpResponse(data_string, mimetype='application/json')
Any help is appreciated
Instead of using filter, it would appear that you need to use select_related so that the query gets that data as well.
Based on your code, you only need to change one line in the loop:
items = CartItem.objects.select_related('product').filter(cart=cart)

Categories