Avoid repeated entries from user

I’m trying to build an auction site. I’ve set up my placebid view and I’m quite happy with how it functions but the problem is the same user is able to bid on the same listing multiple times. This is okay but instead of it being stored in the database as several separate bids all by the same user, I want the initial bid to be updated or initial bid removed and new bid created.

I did try UniqueConstraint and added to my Bid model (fields: bidder and bid_input) but it made no difference plus when I deleted it from my Bid model and makemigrations there were no changes detected so it seems the database was dismissing it anyway.

I tried to do a .filter().exists() check before saving the form in my placebid view and now the form lets me place an initial bid but when I do a second bid it returns a Page Not Found error:

Page not found (404)
Request Method: POST
Request URL:    http://127.0.0.1:8000/listing/3/bid
Raised by:  auctions.views.placebid
No Bid matches the given query.

This is my functional views.py with no form validation checking:

def placebid(request, id):
    listing_bid = get_object_or_404(Listing, id=id)
    highest_bid = Bid.objects.filter(bid_item_id=id).aggregate(Max('bid_input'))['bid_input__max'] or Decimal('0')
    currentHighest = Bid.objects.filter(bid_item_id=id).aggregate(Max('bid_input'))['bid_input__max']
    listing = Listing.objects.get(pk=id)
    if request.method == "POST":
        bidform = BidForm(request.POST)
        if bidform.is_valid() and highest_bid == 0:
            bid_placed = bidform.cleaned_data['bid_input']
            if bid_placed <= listing.start_price:
                messages.error(request, 'Make sure your bid is greater than the start price')
                return HttpResponseRedirect(reverse("listing", args=(id,)))
            else:
                newbid = bidform.save(commit=False)
                newbid.bidder = request.user
                newbid.bid_input = bidform.cleaned_data['bid_input']
                newbid.bid_item = listing_bid
                newbid.time = timezone.now()
                newbid.save()
                messages.success(request, 'Bid placed succesfully')
                return HttpResponseRedirect(reverse("listing", args=(id,)))
        if bidform.is_valid() and highest_bid > 0:
            bid_placed = bidform.cleaned_data['bid_input']
            if bid_placed <= highest_bid:
                messages.error(request, 'Make sure your bid is greater than the current highest bid')
                return HttpResponseRedirect(reverse("listing", args=(id,)))
            else:
                newbid = bidform.save(commit=False)
                newbid.bidder = request.user
                newbid.bid_input = bidform.cleaned_data['bid_input']
                newbid.bid_item = listing_bid
                newbid.time = timezone.now()
                newbid.save()
                messages.success(request, 'Bid placed succesfully')
                return HttpResponseRedirect(reverse("listing", args=(id,)))
    else:
        bidform = BidForm()
    return HttpResponseRedirect(reverse("listing", args=(id,)))

I understand that I am doing my form validation in views.py and it’s best to do it in forms.py using clean() but I don’t know how to because I’m depending on the highest_bid variable to validate my forms and that needs the id that is passed through with the request.

This is the .filter().exists() checking that I added:

                bid = get_object_or_404(Bid, id=id)
                if bid.bid_input.filter(bidder=request.user.id).exists():
                    bid.bid_input.remove(request.user)
                    newbid = bidform.save(commit=False)
                    newbid.bidder = request.user
                    newbid.bid_input = bidform.cleaned_data['bid_input']
                    newbid.bid_item = listing_bid
                    newbid.time = timezone.now()
                    newbid.save()
                    messages.success(request, 'Bid placed succesfully')
                    return HttpResponseRedirect(reverse("listing", args=(id,)))
                else:
                    newbid = bidform.save(commit=False)
                    newbid.bidder = request.user
                    newbid.bid_input = bidform.cleaned_data['bid_input']
                    newbid.bid_item = listing_bid
                    newbid.time = timezone.now()
                    newbid.save()
                    messages.success(request, 'Bid placed succesfully')
                    return HttpResponseRedirect(reverse("listing", args=(id,)))

Bid model and Bid form

class Bid(models.Model):
    bidder = models.ForeignKey(User, on_delete=models.CASCADE, related_name="bidders")
    bid_item = models.ForeignKey(Listing, on_delete=models.CASCADE, 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"{self.bidder}, bid amount: {self.bid_input}"
class BidForm(forms.ModelForm):
    class Meta:
        model = Bid
        fields = ["bid_input"]
        labels = {"bid_input": ""}
        widgets = {
            "bid_input": forms.NumberInput(attrs={'placeholder': 'Enter bid (£)'})
        }

On your GET handling for the form, you want to retrieve the current instance of the Bid for that person for that item, if one exists. That allows the form to update that Bid instance rather than creating a new one.

When working with a model form, Django keeps track of which instance of the model is being used. If no instance was previously retrieved, it’s going to create a new instance. So at the time the page is being requested, what you want to do is create an instance of the form already using the model to be updated. (See the instance parameter of the ModelForm constructor.)