Alex
Alex

Reputation: 193

How to validate form field in django?

I want to make shure that the current value of the bid field is not less than current biggest bid. This is my form with a custom clean method.

Form:

class Place_A_Bid_Form(forms.Form):
    listing = forms.CharField(widget=forms.TextInput(attrs={"type":"hidden"}))
    bid = forms.IntegerField(widget=forms.NumberInput(attrs={"class":"form-control"}), min_value=1)

    def clean_bid(self, biggestBid):
        bid = self.cleaned_data["bid"]
        if bid < biggestBid:
            raise ValidationError("""New bid shouldn't be less than starting bid, or 
                                    if any bids have been placed then new bid 
                                    should be greater than current biggest bid""")
        return bid

View:

def place_a_bid(request):
    if request.method == "POST":
        form = Place_A_Bid_Form(request.POST)
        user = User.objects.get(username=request.user.username)
        biggest_bid = Bid.objects.filter(user=user).aggregate(Max("amount"))
        if form.is_valid():
            data = form.cleaned_data
            user_obj = User.objects.get(username=request.user.username)
            listing_obj = Listing.objects.get(title=data["listing"])
            Bid.objects.update_or_create(
                user=user_obj,
                listing=listing_obj,
                amount=data["bid"]
            )
        return redirect(listing_obj)

In view I am extracting current value that I am going to compare to, and I can't figure out how to pass this value to my form field's clean method. Or maybe I'm doing this wrong? So how to do properly this sort of validation?

Upvotes: 2

Views: 2419

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476614

Django's IntegerField [Django-doc] can validate a minimum value without any extra logic, you can set the .min_value and add a validator with:

from django.core.validators import MinValueValidator

class Place_A_Bid_Form(forms.Form):
    listing = forms.CharField(widget=forms.TextInput(attrs={'type': 'hidden'}))
    bid = forms.IntegerField(widget=forms.NumberInput(attrs={'class':'form-control'}))

    def __init__(self, *args, **kwargs, min_bid=0):
        super().__init__(*args, **kwargs)
        bid = self.fields['bid']
        min_bid += 1
        bid.min_value = min_bid
        bid.validators.add(MinValidator(min_bid))

so then you only need to pass the min_bid to the form:

from django.contrib.auth.decorators import login_required

@login_required
def place_a_bid(request):
    biggest_bid = Bid.objects.filter(user=request.user).aggregate(
        min_bid=Max('amount')
    )['min_bid'] or 0
    if request.method == 'POST':
        form = Place_A_Bid_Form(request.POST, min_bid=biggest_bid)
        if form.is_valid():
            data = form.cleaned_data
            listing_obj = Listing.objects.get(title=data['listing'])
            Bid.objects.update_or_create(
                user=request.user,
                listing=listing_obj,
                amount=data['bid']
            )
            return redirect(listing_obj)
    else:
        form = Place_A_Bid_Form(min_bid=biggest_bid)
    return render(request, 'app_name/name-of-template.html', {'form': form})

This also construct a form in case of a GET request with the biggest bidding as minimum bid. This will also add this as min="biggest-bid" in the HTML so the browser can validate this. This can thus also validate the bid already at client side before submitting the form, but will also properly validate the form at the server side.

It might however be better to pass the listing not through the form, but as a URL parameter.


Note: You can limit views to a view to authenticated users with the @login_required decorator [Django-doc].

Upvotes: 0

eisa nahardani
eisa nahardani

Reputation: 401

class Place_A_Bid_Form(forms.Form):
    listing = forms.CharField(widget=forms.TextInput(attrs={"type":"hidden"}))
    bid = forms.IntegerField(widget=forms.NumberInput(attrs={"class":"form-control"}),
    min_value=1)

    def __init__(self,biggestBid=0 *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.biggestBid = biggestBid

    def clean_bid(self):
        bid = self.cleaned_data["bid"]
        if bid < self.biggestBid:
            raise ValidationError("""New bid shouldn't be less than starting bid, or 
                                    if any bids have been placed then new bid 
                                    should be greater than current biggest bid""")
        return bid

and then in views.py:

def place_a_bid(request):
    if request.method == "POST":
        dict = Bid.objects.filter(user=user).aggregate(Max("amount"))
        form = Place_A_Bid_Form(biggestBid=dict['amount__max'], data=request.POST)
        user = User.objects.get(username=request.user.username)
        if form.is_valid():
            data = form.cleaned_data
            user_obj = User.objects.get(username=request.user.username)
            listing_obj = Listing.objects.get(title=data["listing"])
            Bid.objects.update_or_create(
                user=user_obj,
                listing=listing_obj,
                amount=data["bid"]
            )
        return redirect(listing_obj)

Upvotes: 4

Related Questions