验证一个基于为另一个模型设置的值的模型

huangapple go评论62阅读模式
英文:

Validating a model based on the value set for a separate model

问题

背景

我正在创建一个电子商务应用程序,允许用户在任何活动商品列表上提交竞标,其中BidListing是**models.py**中不同的Django模型(如下所示)。

对于每个新的出价,出价金额Bid.amount)是通过模型表单设置的。如果出价金额大于当前出价Listing.current_bid):

  1. 出价被视为有效,并保存对象。
  2. Listing.current_bid 更新为等于新的Bid.amount

问题

目前,我的解决方案(请参见下面的**views.py**)实现了期望的结果——它成功地验证并更新每个新的出价,使用与is_valid()的调用连接的条件:

if new_bid_form.is_valid() and new_bid_form.cleaned_data["current_bid"] > previous_bid:

然而,尽管这个设计有效,但它感觉很“hackish”,因为验证逻辑不发生在模型内部。因此,在调用clean()时隐式检查条件,并且我可以预见这会引发以后的问题。相反,我希望在模型内部实现验证逻辑,以便它在Django的内置验证调用时被调用并引发ValidationError

问题

  1. 我是否正确地说我的当前“验证”解决方案设计得很差?
  2. 如果是这样,我如何在Bid模型内部验证表单中输入的Bid.amount大于Listing.current_bid的值?(请参见下面的解决方案尝试)

models.py

Listing Model

class Listing(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="usernames")
    title = models.CharField(max_length=80)
    description = models.CharField(max_length=800)
    starting_bid = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal('0.00'))])
    current_bid = models.DecimalField(default=0, max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal('0.00'))])
    image = models.URLField(blank=True)

Bid Model

class Bid(models.Model):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="owners")
    listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="listings")
    amount = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal('0.00'))])

views.py

listing() 函数:为每个列表定义一个视图,并实现一个ModelForm以提交出价。

def listing(request, listing_id):
    class NewBidForm(ModelForm):
        template_name = "auctions/form_template.html"
        class Meta:
            model = Bid
            fields = ["amount"]
            widgets = {
                "amount":forms.NumberInput(attrs={"placeholder": "提交高于当前列表价格的报价"})
            }
            labels = {
                "amount":_("提交报价:")
            }
    
    # 获取当前列表的当前出价
    listing = Listing.objects.get(pk=listing_id)
    current_bid = listing.current_bid

    # 如果提交的表单数据有效,则更新并保存新的出价
    if request.method == "POST":
        new_bid_form = NewBidForm(request.POST)
        if new_bid_form.is_valid() and new_bid_form.cleaned_data["amount"] > current_bid:
            new_bid_form.instance.amount = new_bid_form.cleaned_data["amount"]
            new_bid_form.instance.owner = request.user
            new_bid_form.instance.listing = listing
            new_bid_form.save()

            # 将列表的当前出价设置为新的出价金额
            listing.current_bid = new_bid_form.instance.amount
            listing.save()
            return HttpResponseRedirect(reverse("listing", args=[listing_id]))
        else:
            return render(request, "auctions/listing.html", {
                "listing": listing,
                "form": new_bid_form,
                "message": messages.add_message(request, messages.ERROR, "出价必须高于当前的最高出价。"),
            })

解决方案尝试

到目前为止,我一直无法找到类似的示例,提供了我可以迭代的解决方案的任何提示。从Django文档中看来,覆盖clean()方法可能是一种实现这一目标的方法,但我还没有找到一种方法,在其中一个(未保存的)模型实例依赖于另一个不同模型的实例的情况下执行此操作。

我尝试了在我的Bid模型中使用以下clean()方法的解决方案:

def clean(self):
    super().clean()
    if self.amount < self.listing.current_bid:
        raise ValidationError("出价必须高于之前的最高出价")

然而,这返回了一个错误,我认为这是因为新的Bid尚未保存,因此没有引用.listing

RelatedObjectDoesNotExist at /listing/1. 出价没有列表。

任何见解将非常感激!

英文:

Background

I am creating an E-commerce application that allows users to submit bids on any active item listings – where Bid & Listing are distinct Django models in models.py (below).

For each new bid, the bid amount (Bid.amount) is set via a Model Form. If the bid amount is greater than the current bid (Listing.current_bid), then:

  1. The Bid is considered valid and the object is saved.
  2. Listing.current_bid is updated to equal the new Bid.amount.

Issue

Currently, my solution (see views.py below) achieves the desired result – it successfully validates and updates each new bid using a conditional concatenated with the call to is_valid():

if new_bid_form.is_valid() and new_bid_form.cleaned_data[&quot;current_bid&quot;] &gt; previous_bid:

However, even though this design works, it feels "hackish", because the validation logic does not occur within the model. Therefore, the condition is not implicitly checked on the call to clean(), and I can foresee that causing issues going forward. Instead, I want to implement the validation logic within the model so that its called with Django's built-in validation and will raise a ValidationError.


Question

  1. Am I correct in saying that my current "validation" solution is poorly designed?
  2. If so, how can I validate within the Bid model, that the value entered into the form for Bid.amount is greater than Listing.current_bid? (See Solution Attempts below)

models.py

Listing Model

class Listing(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name=&quot;usernames&quot;)
    title = models.CharField(max_length=80)
    description = models.CharField(max_length=800)
    starting_bid = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal(&#39;0.00&#39;))])
    current_bid = models.DecimalField(default=0, max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal(&#39;0.00&#39;))])
    image = models.URLField(blank=True)

Bid Model

 class Bid(models.Model):
        owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name=&quot;owners&quot;)
        listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name=&quot;listings&quot;)
        amount = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal(&#39;0.00&#39;))])

views.py

listing() Function: Defines a view for each listing & implements a ModelForm to submit bids.

def listing(request, listing_id):
    class NewBidForm(ModelForm):
        template_name = &quot;auctions/form_template.html&quot;
        class Meta:
            model = Bid
            fields = [&quot;amount&quot;]
            widgets = {
                &quot;amount&quot;:forms.NumberInput(attrs={&quot;placeholder&quot;: &quot;Submit an offer greater than the current listing price&quot;})
            }
            labels = {
                &quot;amount&quot;:_(&quot;Submit an Offer:&quot;)
            }
    
    # Get the current bid for the current listing
    listing = Listing.objects.get(pk=listing_id)
    current_bid = listing.current_bid

    # If the submitted form data is valid, update &amp; save new bid
    if request.method == &quot;POST&quot;:
        new_bid_form = NewBidForm(request.POST)
        if new_bid_form.is_valid() and new_bid_form.cleaned_data[&quot;amount&quot;] &gt; current_bid:
            new_bid_form.instance.amount = new_bid_form.cleaned_data[&quot;amount&quot;]
            new_bid_form.instance.owner = request.user
            new_bid_form.instance.listing = listing
            new_bid_form.save()

            # Set the listing&#39;s current bid to the new bid amount
            listing.current_bid = new_bid_form.instance.amount
            listing.save()
            return HttpResponseRedirect(reverse(&quot;listing&quot;, args=[listing_id]))
        else:
            return render(request, &quot;auctions/listing.html&quot;, {
                &quot;listing&quot;: listing,
                &quot;form&quot;: new_bid_form,
                &quot;message&quot;: messages.add_message(request, messages.ERROR, &quot;Bid must be greater than the current highest bid.&quot;),
            })

Solution Attempts

So far, I've been unable to find a similar example that provided any hints at a solution that I could iterate on. From the Django documentation, it seems overriding the clean() method might potentially be a way to achieve this, but I have not found a way to do this when one (unsaved) model instance, depends on an instance of another distinct model.

I attempted a solution that used the clean() method below in my Bid model:

def clean(self):
        super().clean()
        if self.amount &lt; self.listing.current_bid:
            raise ValidationError(&quot;Bid must be greater than the previous current bid&quot;)

However, this returned an error, which I believe is because the new Bid is not yet saved, thus does not have a reference to .listing:

RelatedObjectDoesNotExist at /listing/1. Bid has no listing.

Any insight would be massively appreciated!

答案1

得分: 0

对于可能有类似问题的其他人,我成功找到了解决方案(就在我眼前),通过覆盖clean()方法,正如我所猜测的那样。

错误是因为在调用覆盖的clean()方法时,Bid对象尚未引用Listing,因此,通过将Bid实例化为引用列表对象,我能够解决这个问题:

def listing(request, listing_id):
    class NewBidForm(ModelForm):
        template_name = "auctions/form_template.html"
        class Meta:
            model = Bid
            fields = ["amount"]
            widgets = {
                "amount": forms.NumberInput(attrs={"placeholder": "Submit an offer greater than the current listing price"})
            }
            labels = {
                "amount":_("Submit an Offer:")
            }
    
    # 获取要竞拍的当前列表。
    listing = Listing.objects.get(pk=listing_id)

    # 验证新出价是否高于当前最高出价。
    if request.method == "POST":
        new_bid_form = NewBidForm(request.POST, instance=Bid(listing=listing))
        if new_bid_form.is_valid():
            new_bid_form.instance.amount = new_bid_form.cleaned_data["amount"]
            new_bid_form.instance.owner = request.user
            new_bid_form.save()

            # 将列表的当前出价设置为新出价金额。
            listing.current_bid = new_bid_form.instance.amount
            listing.save()
            return HttpResponseRedirect(reverse("listing", args=[listing_id]))
        else:
            return render(request, "auctions/listing.html", {
                "listing": listing,
                "form": new_bid_form,
            })

供参考,在Bid模型的clean()方法中略微修订:

class Bid(models.Model):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="owners")
    listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="listings")
    amount = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal('0.00'))])

    def clean(self):
        super().clean()
        if self.amount <= self.listing.current_bid:
            raise ValidationError(
                _("Unable to place new bid! Please enter a minimum bid of at least $%(value)s!"),
                params={"value":f"{float(self.listing.current_bid) + 1.0:.2f}"})
英文:

For anyone else that might have a similar issue, I was able to find a solution (which was staring me in the face) by overriding the clean() method as I had guessed.

The error was raised because the Bid object did not yet have a reference to Listing at the time the overridden clean() method was being invoked.

Therefore, by instantiating the Bid with a reference to the listing object, I was able to solve the issue:

def listing(request, listing_id):
    class NewBidForm(ModelForm):
        template_name = &quot;auctions/form_template.html&quot;
        class Meta:
            model = Bid
            fields = [&quot;amount&quot;]
            widgets = {
                &quot;amount&quot;:forms.NumberInput(attrs={&quot;placeholder&quot;: &quot;Submit an offer greater than the current listing price&quot;})
            }
            labels = {
                &quot;amount&quot;:_(&quot;Submit an Offer:&quot;)
            }
    
    # Get the current listing to bid on.
    listing = Listing.objects.get(pk=listing_id)

    # Validate that the new bid is greater than the current highest bid.
    if request.method == &quot;POST&quot;:
        new_bid_form = NewBidForm(request.POST, instance=Bid(listing=listing))
        if new_bid_form.is_valid():
            new_bid_form.instance.amount = new_bid_form.cleaned_data[&quot;amount&quot;]
            new_bid_form.instance.owner = request.user
            new_bid_form.save()

            # Set the listing&#39;s current bid to the new bid amount.
            listing.current_bid = new_bid_form.instance.amount
            listing.save()
            return HttpResponseRedirect(reverse(&quot;listing&quot;, args=[listing_id]))
        else:
            return render(request, &quot;auctions/listing.html&quot;, {
                &quot;listing&quot;: listing,
                &quot;form&quot;: new_bid_form,
            })

For reference, a slight revision to the clean() method in the Bid model:

class Bid(models.Model):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name=&quot;owners&quot;)
    listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name=&quot;listings&quot;)
    amount = models.DecimalField(max_digits=11, decimal_places=2,validators=[MinValueValidator(Decimal(&#39;0.00&#39;))])

    def clean(self):
        super().clean()
        if self.amount &lt;= self.listing.current_bid:
            raise ValidationError(
                _(&quot;Unable to place new bid! Please enter a minimum bid of at least $%(value)s!&quot;), 
                params={&quot;value&quot;:f&quot;{float(self.listing.current_bid) + 1.0:.2f}&quot;})

huangapple
  • 本文由 发表于 2023年6月8日 13:45:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76428906.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定