1

I have a model that represents a shopping item with a "choose-your-own-price" model. The seller can opt in to allow customers to pick from a low, medium, or high price. Thus, I need to validate that the user sends a valid option between these three values for each shopping item.

Currently, my model looks like this:

class PurchaseItem(models.Model):
    """
    PurchaseItem.
    "price" should be treated as a suggestion. We allow users to choose their own price, so we need to validate that
    what comes here is a valid choice from the Listing. Otherwise, a malicious user might be able to charge whatever
    they want. The Listing model has a ListingPrice with a required 'suggested_cost' field and optional low, medium,
    high costs.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    item = models.ForeignKey(Listing, on_delete=models.DO_NOTHING)
    price = models.DecimalField(max_digits=8, decimal_places=2, blank=False, null=False)
    currency = models.CharField(max_length=30, choices=SUPPORTED_CURRENCIES, default='USD')
    created_date = models.DateTimeField(auto_now_add=True)
    purchased_by = models.ForeignKey(User, on_delete=models.CASCADE)

    def clean(self):
        listing_price = self.item.price
        valid_prices = [listing_price.suggested_cost]
        if listing_price.supports_cost_choice is True:
            valid_prices = valid_prices + [listing_price.low_cost, listing_price.medium_cost, listing_price.high_cost]
        if self.price not in valid_prices:
            raise ValidationError("Selected price is not a valid option.")
        super(PurchaseItem, self).clean()

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.clean() # <---- Workaround. I'm trying to remove this.
        super(PurchaseItem, self).save(force_insert, force_update, using, update_fields)

When saving directly into the database or interacting via the Django admin panel, Django will call clean, I will do my validation, throw errors if it's not a valid price, and all is good. Works great.

Issue:
My issue is in the Create ViewSet. I call the POST endpoint and I expect it to throw an error when saving the item, but instead it skips the clean stage. I add breakpoints and it never calls clean. Thus, I added a workaround to manually call clean on save.

Questions:
How can I get rid of the manual call to self.clean()? Or how can I make sure the viewset throws errors if the price is not valid? Currently, the serializer's is_valid is returning true, and I'm not sure if it's the serializer's responsibility to look up valid prices on submit. And why is the serializer creating invalid objects instead of running the clean method and throwing the Validation Error?

2
  • 1
    You can move PurchasteItem.clean to the serializer's validate, or add a method validate_price to run specific validations for the price field. For details have a look here Commented Jul 25, 2021 at 0:51
  • 1
    Django by default, will not call the clean method or full_clean for that matter outside of using forms. The reason why clean was used in the admin panel is because the admin panel uses django forms and forms will call clean. Unfortunately, when switching to DRF, the clean method will not be called. Doc reference Commented Jul 25, 2021 at 5:43

1 Answer 1

1

In your serializer file:

class NameOfYOurModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = PurchaseItem
        fields = (
            'price',
        )

    def validate_price(self, valor):  # noqa
        # Just an example! I do not know the name of the field and other things; but this will work as a validation to your data.
        if value > 0:
        # Here is another example: only you can add the condition!
            return valor
        raise serializers.ValidationError('Description of the error!')
Sign up to request clarification or add additional context in comments.

1 Comment

I ended up solving this with a validate method as opposed to validating a single field since I needed the whole object, but your answer was just what I needed to get unstuck. Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.