Help with form validation

Hello,

I’m trying to build an auctions website that allows users to bid on listings. For a user to successfully place a bid they must input an amount higher than the current highest bid and if there is no current highest bid because no user has placed a bid yet, that first bid input must be higher than the listing start price.

I want to add a form validation to my BidForm to raise an error if the input doesn’t fit these conditions but I have a pylint error on listing.id in def clean_bid_input and it says it is an undefined variable so I feel my form validation isn’t quite right. Please could someone have a look and see if my form validation is following the logic I’m hoping it will?
Thank you in advance

models.py

def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]

class Listing(models.Model):

    class NewManager(models.Manager):
        def get_queryset(self):
            return super().get_queryset().filter(status='active')

    options = (
        ('active', 'Active'),
        ('closed', 'Closed'),
    )

    title = models.CharField(max_length=64)
    description = models.TextField(max_length=64)
    start_price = models.DecimalField(max_digits=9, decimal_places=2, validators=[MinValueValidator(0.99)])
    image = models.URLField(max_length=200, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="listings")
    lister = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="lister_user")
    date_added = models.DateTimeField(default=timezone.now)
    status = models.CharField(max_length=10, choices=options, default="active")
    winner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user), related_name="winner_user", null=True)
    favourites = models.ManyToManyField(User, related_name="favourite", default=None, blank=True)
    objects = models.Manager()
    listingmanager = NewManager()

    def __str__(self): 
        return f"{self.title} ({self.pk}, £{self.start_price}, {self.lister})"


class Bid(models.Model):
    bidder = models.ForeignKey(User, on_delete=models.CASCADE, related_name="bidders")
    bid_item = models.ManyToManyField(Listing, related_name="bid_items", default=None)
    bid_input = models.DecimalField(max_digits=9, decimal_places=2, default=None)
    time = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"Bid amount: {self.bid_input}"

forms.py

class NewListingForm(forms.ModelForm):
    class Meta:
        model = Listing
        fields = ["title", "description", "start_price", "image", "category"]

    title = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}))
    description = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}))
    start_price = forms.DecimalField(label='Starting Bid Price (£)')
    image = forms.URLField(widget=forms.URLInput(attrs={'autocomplete':'off'}))
    category = forms.ModelChoiceField(queryset=Category.objects.all())


class BidForm(forms.ModelForm):
    class Meta:
        model = Bid
        fields = ["bid_input"]
        labels = {"bid_input": ""}
        widgets = {
            "bid_input": forms.NumberInput(attrs={'placeholder': 'Enter bid (£)'})
        }
    
    def clean_bid_input(self):
        data = self.cleaned_data['bid_input']
        highest_bid = Bid.objects.filter(bid_item=listing.id).aggregate(Max('bid_input'))
        listing_price = Listing.get(bid_item=listing.id).start_price
        if highest_bid is None:
            if data < listing_price:
                raise ValidationError('Bid must be higher than listing start price')
        if data < highest_bid:
            raise ValidationError('Bid must be higher than current highest bid')
        return data

You don’t mention which variable it’s complaining about - I’m going to make a guess that it’s referring to Bid.objects.

If so, that’s nothing to worry about. Tools like pylint can have problems with some of the “magic” that Django performs, leading to many spurious warnings.

However, you do have (at least) one issue in your clean_bid_input method, you’re not returning the value - from the docs:

The return value of this method replaces the existing value in cleaned_data , so it must be the field’s value from cleaned_data (even if this method didn’t change it) or a new cleaned value.

Ken

The pylint error is red underlining ‘listing’ in the def clean_bid_input. It is underlining listing in 2 places where I put bid_item = listing.id

And yes, sorry I did forget to add return data right at the end of def clean_bid_input. I will go back and edit.

Ah, yes, that is a problem.

Your view that is handling the submission of the BidForm, how does it know what listing the bid is for? Are you passing it in as a form parameter, or as a URL parameter?
(It might help to see the view at this point, and the urls.py file that calls it.)

Also, in looking at your model a little more closely - are you sure that bid_item should be a many-to-many? That’s saying that one bid of a given amount can apply to multiple Listings. That doesn’t make logical sense to me.

Ok, reading this some more - if bid_item were a ForeignKey instead of a ManyToMany field, your clean_bid_input could use self.cleaned_data['bid_item'] as its reference in the filter.

1 Like