I've just created a forms.models.BaseInlineFormSet
to override the default formset for a TabularInline model. I need to evaluate the user's group in formset validation (clean
) because some groups must write a number inside a range (0,20).
I'm using django admin to autogenerate the interface.
I've tried getting the request and the user from the kwargs in the init method, but I couldn't get the reference.
This is what I have now:
class OrderInlineFormset(forms.models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(OrderInlineFormset, self).__init__(*args, **kwargs)
def clean(self):
# get forms that actually have valid data
count = 0
for form in self.forms:
try:
if form.cleaned_data:
count += 1
if self.user.groups.filter(name='Seller').count() == 1:
if form.cleaned_data['discount'] > 20:
raise forms.ValidationError('Not authorized to specify a discount greater than 20%')
except AttributeError:
# annoyingly, if a subform is invalid Django explicity raises
# an AttributeError for cleaned_data
pass
if count < 1:
raise forms.ValidationError('You need to specify at least one item')
class OrderItemInline(admin.TabularInline):
model = OrderItem
formset = OrderInlineFormset
Then I use it as inlines = [OrderItemInline,]
in my ModelAdmin.
Unfortunatly self.user is always None
so I cannot compare the user group and the filter is not applied. I need to filter it because other groups should be able to specify any discount percent.
How can I do? If you also need the ModelAdmin code I'll publish it (I just avoided to copy the whole code to avoid confusions).
Well, I recognise my code there in your question, so I guess I'd better try and answer it. But I would say first of all that that snippet is really only for validating a minimum number of forms within the formset. Your use case is different - you want to check something within each form. That should be done with validation at the level of the form, not the formset.
That said, the trouble is not actually with the code you've posted, but with the fact that that's only part of it. Obviously, if you want to get the user from the kwargs when the form or formset is initialized, you need to ensure that the user is actually passed into that initialization - which it isn't, by default.
Unfortunately, Django's admin doesn't really give you a proper hook to intercept the initialization itself. But you can cheat by overriding the get_form
function and using functools.partial
to wrap the form class with the request argument (this code is reasonably untested, but should work):
from functools import partial
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(OrderForm, self).__init__(*args, **kwargs)
def clean(self)
if self.user.groups.filter(name='Seller').count() == 1:
if self.cleaned_data['discount'] > 20:
raise forms.ValidationError('Not authorized to specify a discount greater than 20%')
return self.cleaned_data
class MyAdmin(admin.ModelAdmin):
form = OrderForm
def get_form(self, request, obj=None, **kwargs):
form_class = super(MyAdmin, self).get_form(request, obj, **kwargs)
return functools.partial(form_class, user=request.user)
Here's another option without using partials. First override the get_formset
method in your TabularInline
class.
Assign request.user
or what ever extra varaibles you need to be available in the formset as in example below:
class OrderItemInline(admin.TabularInline):
model = OrderItem
formset = OrderInlineFormset
def get_formset(self, request, obj=None, **kwargs):
formset = super(OrderProductsInline, self).get_formset(request, obj, **kwargs)
formset.user = request.user
return formset
Now the user is available in the formset as self.user
class OrderInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
print(self.user) # is available here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With