How to validate data of two models in the django-admin when using inlines?
Update: Reading directly the django source code i got one undocumented missing piece to solve my problem. Thanks to Brandon that solved half of the problem by giving me one of the开发者_如何学编程 missing pieces. See my own answer to see my solution (i dont want to mix things here).
I have the following (simplified) models:
Order(models.Model):
status = models.CharField( max_length=25, choices=STATUS_CHOICES, default='PENDING')
total = models.DecimalField( max_digits=22, decimal_places=2)
def clean(self):
if self.estatus == 'PAID' or self.estatus == 'SENT':
if len(self.payment.all()) > 0:
raise ValidationError("The status cannot be SENT or PAID if there is no payment for the order")
Payment(models.Model):
amount = models.DecimalField( max_digits=22, decimal_places=2 )
order = models.ForeignKey(Order, related_name="payment")
def clean(self):
if self.amount < self.order.total or self.amount <= 0:
ValidationError("The payment cannot be less than the order total")
In my admin.py i have:
class paymentInline(admin.StackedInline):
model = Payment
max_num = 1
class OrderAdmin(admin.ModelAdmin):
model = Order
inlines = [ paymentInline, ]
The validation in the clean method of the Order does not work because there is no payment saved when the validation occurs (obviously it has not been saved to the database).
The validation inside the payment works fine (if editing or adding a new payment).
I want to validate if the order has a payment if the status is 'PAID' or 'SENT', but as i cannot doit the way is in the clean method.
My question is, how can i access the 'payment.amount' value entered by the user in the inline (payment) of the Order form, to accomplish my validation? (considering im in the clean method of the Order model)
After reading the django source code i found one property of the BaseInlineFormSet that contains the Parent Instance of the Inline, in my case, the Order instance being edited.
Brandon gave me another important piece, iterating over the self.forms of the BaseInlineFormSet to get each of the instances (even not saved or not cleaned or empty), in my case, each Payment Instance being edited.
These are the two pieces of information needed to check if the Order with status 'PAID' or 'SENT' has a payment or not. Iterate over the cleaned_data of the formset would not give Order data (i.e. when not changing the Order, just changing the Payment, or when not adding a Payment -and an empty Payment- but changing the Order) which is needed to decide to save the model if the order status is different than 'PAID' or 'SENT', so this method was discarded before.
The models are keep the same, I only modified the admin.py to add the next:
class PaymentInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
order = None
payment = None
if any(self.errors):
return
# django/forms/models.py Line # 672,674 .. class BaseInlineFormSet(BaseModelFormSet) . Using Django 1.3
order = self.instance
#There should be only one form in the paymentInline (by design), so in the iteration below we end with a valid payment data.
if len(self.forms) > 1:
raise forms.ValidationError(u'Only one payment per order allowed.')
for f in self.forms:
payment = f.save(commit=False)
if payment.amount == None:
payment.amount = 0
if order != None:
if order.status in ['PAID', 'SENT'] and payment.amount <= 0:
raise forms.ValidationError(u'The order with %s status must have an associated payment.'%order.status)
class pymentInline(admin.StackedInline):
model = Payment
max_num = 1
formset = PaymentInlineFormset
class OrderAdmin(admin.ModelAdmin):
inlines = [ paymentInline, ]
admin.site.register(Order, OrderAdmin)
admin.site.register(Payment)
It sounds like you just need to validate that there is at least one valid formset in the inlines...you might give this code a try: http://wadofstuff.blogspot.com/2009/08/requiring-at-least-one-inline-formset.html
Hope that gets you going.
[Edit]
I took a look at some code I had written for another app that had custom validation on inlines based on a property on the related model. Of course, you may need to make some adjustments, but give this a try. You'll need to specify an inline to use in your admin model too.
#I would put this in your app's admin.py
class PaymentInline(admin.TabularInline):
model = Payment
formset = PaymentInlineFormset
#I would put this in the app's forms.py
class PaymentInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
order = None
valid_forms = 0
for error in self.errors:
if error:
return
for cleaned_data in self.cleaned_data:
amount = cleaned_data.get('amount', 0)
if order == None:
order = cleaned_data.get('order')
if amount > 0:
valid_forms += 1
if order.status in ['PAID', 'SENT'] and len(valid_forms) > 0:
raise forms.ValidationError(u'Your error message')
精彩评论