Search code examples
pythonhtmldjango-modelsdjango-formsforeign-keys

Django: Validate ModelForm's Field dependent on another Field of that Model


I have these two Django Models:

class Listing(models.Model):
    ...
    price = models.DecimalField(
        decimal_places=2, 
        max_digits=20, 
        default=0, 
        validators=[MinValueValidator(Decimal('0.01'))]
    )

class Bid(models.Model):
    ...
    listing = models.ForeignKey(Listing, default=None, on_delete=models.CASCADE)
    amount = models.DecimalField(
        decimal_places=2,
        max_digits=20, 
        default=0
        validators=[MinValueValidator(Decimal(listing.price))]
    )

I want to validate that Bid.amount > Bid.listing.price.

When I try to do it the with the help of MinValueValidator from django.core.validators I get this error:

    validators=[MinValueValidator(Decimal(listing.price))])
                                          ^^^^^^^^^^^^^
    AttributeError: 'ForeignKey' object has no attribute 'price'

I also tried referencing listing.price with a self attribute and got the following error:

validators=[MinValueValidator(Decimal(self.listing.price))])
                                          ^^^^
NameError: name 'self' is not defined

I know I can check the above condition in "views.py" once the Model.Form is submitted, but I wonder if there is a way to validate it with Django Validators.

Any help would be appreciated!


Solution

  • I figured it out by creating a custom validator in forms.py:

    from .models import Listing, Bid
    from django.forms import ModelForm
    from decimal import Decimal
    from django.core.exceptions import ValidationError
    
    
    class BidForm(ModelForm):
        class Meta():
            model = Bid
            fields = ["amount"]
    
        def clean_amount(self):
            amount = self.cleaned_data["amount"]
            if Decimal(amount) < Decimal("0.01"):
                raise ValidationError(
                    "Ensure this value is greater than or equal to 0.01."
                )
            elif Decimal(amount) <= Decimal(self.instance.listing.price):
                raise ValidationError("Ensure this value exceeds the price.")
            return amount
    

    Then, in views.py:

    from django.http import HttpResponseRedirect
    from .models import Listing
    from .forms import BidForm
    from decimal import Decimal
    
    def listing(request, listing_id):
        listing = Listing.objects.get(pk=listing_id)
        if request.method == "POST":
            form = BidForm(request.POST)
            form.instance.holder = request.user
            form.instance.listing = listing
            if form.is_valid():
                form.save()
                Listing.objects.filter(pk=listing_id).update(
                    price=Decimal(request.POST["amount"])
                )
                return HttpResponseRedirect(
                    f"/listing/{listing_id}", 
                    {"form":form, "listing":listing}
                )
        else:
            form = BidForm()
        return render(request, "auctions/listing.html", {"form": form, "listing": listing})
    

    Instead of validating Django Model's fields, I validate ModelForm submission with the help of clean_amount() method. Per Django's Documentation:

    This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is.

    I hope this answer helps someone.