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?
PurchasteItem.cleanto the serializer'svalidate, or add a methodvalidate_priceto run specific validations for thepricefield. For details have a look herecleanmethod orfull_cleanfor that matter outside of using forms. The reason whycleanwas used in the admin panel is because the admin panel uses django forms and forms will callclean. Unfortunately, when switching to DRF, thecleanmethod will not be called. Doc reference