Praveen
Praveen

Reputation: 11

Django admin save_model

i'm new to django, please help me in below scenoria,

when i save the order inn admin panel the same_model function not working the order is saving with out a function.

The requirement is when i add order in admin panel when the quantity of orderedproduct is graeter the actual quantity it should through an error else it should reduce the ordered quantity from the product.

The order is working but save_model function is not working

admin.py

from django.contrib import admin
from django.contrib import messages
from django.utils.translation import ngettext
from .models import Category, Product, Order, OrderItem
from django.utils.html import format_html


class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 1

class OrderAdmin(admin.ModelAdmin):
    inlines = [OrderItemInline]
    list_display = ('id', 'user', 'order_date')

    def save_model(self, request, obj, form, change):
        errors = []
        super().save_model(request, obj, form, change)

        for order_item in obj.orderitem_set.all():
            print(order_item.quantity)
            if order_item.quantity > order_item.product.quantity:
                errors.append(
                    f"Order quantity ({order_item.quantity}) for {order_item.product.name} exceeds available quantity ({order_item.product.quantity})."
                )
            else:
                order_item.product.quantity -= order_item.quantity
                order_item.product.save()

        if errors:
            # If there are errors, display a message and rollback the order creation
            message = ngettext(
                "The order was not saved due to the following error:",
                "The order was not saved due to the following errors:",
                len(errors),
            )
            messages.error(request, f"{message} {', '.join(errors)}")
            obj.delete()  # Rollback the order creation

   
admin.site.register(Category)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['display_image', 'name', 'quantity', 'price', 'category']

    def display_image(self, obj):
        if obj.image:
            return format_html('<img src="{}" width="50" height="50" />'.format(obj.image.url))
        return "No Image"

    display_image.short_description = 'Image'
admin.site.register(Product,ProductAdmin)
admin.site.register(Order, OrderAdmin) 

models.py

from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name


class Product(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products', null=True)
    image = models.ImageField(upload_to='static/product_images/', null=True, blank=True)
    quantity = models.PositiveIntegerField(default=0)  

    def __str__(self):
        return f"{self.name} - Quantity: {self.quantity}"


class Cart(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    products = models.ManyToManyField(Product, through='CartItem')

    def __str__(self):
        return f"Cart for {self.user.username}"


class CartItem(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=1)

    def __str__(self):
        return f"{self.quantity} x {self.product.name} in Cart"


class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    products = models.ManyToManyField(Product, through='OrderItem')
    order_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Order #{self.id} by {self.user.username}"


class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField()

    def __str__(self):
        return f"{self.quantity} x {self.product.name} in Order"

Upvotes: 1

Views: 174

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476493

You need to trigger this after .save_related(…) [Django-doc], since at that point, the many to many fields are saved:

class OrderAdmin(admin.ModelAdmin):
    inlines = [OrderItemInline]
    list_display = ('id', 'user', 'order_date')

    def save_related(self, request, form, formsets, change):
        super().save_related(request, request, form, formsets, change)
        errors = []
        for order_item in obj.orderitem_set.all():
            print(order_item.quantity)
            if order_item.quantity > order_item.product.quantity:
                errors.append(
                    f"Order quantity ({order_item.quantity}) for {order_item.product.name} exceeds available quantity ({order_item.product.quantity})."
                )
            else:
                order_item.product.quantity -= order_item.quantity
                order_item.product.save()

        if errors:
            # If there are errors, display a message and rollback the order creation
            message = ngettext(
                'The order was not saved due to the following error:',
                'The order was not saved due to the following errors:',
                len(errors),
            )
            messages.error(request, f"{message} {', '.join(errors)}")
            obj.delete()  # Rollback the order creation

That being said, validation should not be done as a post-processing step: you can inject a custom ModelForm in the `ModelAdmin and formsets, and do the validation at that point.

Here you will for example subtract the quantities of all items, and if then an error occurs, you forget to increase it back. While you can fix that, it is likely that in the future, more such things will fail (and race conditions will occur), so usually it is better to Look Before You Leap (LBYL) [wiki] here.

Upvotes: 0

Related Questions