Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django admin - access request.user in BaseInlineFormSet

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).

like image 274
Ale A Avatar asked Jan 18 '23 01:01

Ale A


2 Answers

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)
like image 51
Daniel Roseman Avatar answered Jan 25 '23 15:01

Daniel Roseman


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 
like image 27
bhaskarc Avatar answered Jan 25 '23 14:01

bhaskarc