Search code examples
pythondjangoapiresttastypie

django-tastypie : Related data not saving


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 ??????


Solution

  • 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",
    }