Pass request to inline form in Django Admin?
Asked Answered
S

2

6

I'm trying to get the user from the request in Django Admin. What I need is access to the request's user in the inline form's clean() method. I've done a procedure similar to the one described below with a normal ModelForm (i.e. not an inline one) and I was successful. However, with inlines I'm having a lot of issues. I have:

class SaleFormset(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request')
        super(SaleFormset, self).__init__(*args, **kwargs)

    def _construct_form(self, i, **kwargs):
        kwargs['request'] = self.request
        super(SaleFormset, self)._construct_form(i, **kwargs)


class SaleProductItemInlineForm(ModelForm):
    """
    Custom form for the Sale Product Item Inline used by the
    Sale Admin form.
    """

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request')
        super(SaleProductItemInlineForm, self).__init__(*args, **kwargs)

    class Meta:
        model = SaleProductItem
        fields = "__all__"  

And in the admin.py, I have:

class SaleProductItemInline(admin.TabularInline):
    """
    Tabular inline for a SaleProductItem used in the Sale Admin.
    """
    model = models.SaleProductItem
    form = SaleProductItemInlineForm
    formset = SaleFormset

    def get_formset(self, request, obj=None, **kwargs):
        formset_class = super(SaleProductItemInline, self).get_formset(request, obj, **kwargs)

        class Subset(formset_class):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return formset_class(*args, **kwargs)

        return Subset

However, I'm getting an error saying that 'NoneType' object has no attribute 'media' because of this section:

@property
def media(self):
    # All the forms on a FormSet are the same, so you only need to
    # interrogate the first form for media.
    if self.forms:
        return self.forms[0].media
Samples answered 20/8, 2016 at 4:48 Comment(0)
A
6

an approach similar to this worked for me:

def SaleProductItemInlineFormFactory(request):
    class SaleProductItemInlineForm(ModelForm):
        def __init__(self, *args, **kwargs):
            # here finally you can do something with your request
            super(SaleProductItemInlineForm, self).__init__(*args, **kwargs)

        class Meta:
            model = SaleProductItem
            fields = "__all__"
    return SaleProductItemInlineForm

def SaleProductItemInlineFactory(request):
    class SaleProductItemInline(admin.TabularInline):
        model = models.SaleProductItem
        form = SaleProductItemInlineFormFactory(request)

    return SaleProductItemInline

class SaleAdmin(admin.ModelAdmin):
    inlines = ()

    # such way we send request to main form
    def get_form(self, request, obj=None, **kwargs):
        form = super(SaleAdmin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form

    # we define inlines with factory to create Inline class with request inside
    def change_view(self, request, object_id, form_url='', extra_context=None):
        self.inlines = (SaleProductItemInlineFactory(request), )
        return super(SaleAdmin, self).change_view(request, object_id)

    # we define inlines with factory to create Inline class with request inside
    def add_view(self, request, form_url='', extra_context=None):
        self.inlines = (SaleProductItemInlineFactory(request), )
        return super(SaleAdmin, self).add_view(request)
Almazan answered 28/2, 2017 at 16:15 Comment(3)
where in add_view() would you be able to access the object_id to pass to change_view()Wearing
for sure add_view() should call add_view(). I fixed it. thanksAlmazan
Can't imagine such a nasty and complicated work in order to achieve this...Femi
S
5

A shortcut of your solution is:

class SaleFormset(BaseInlineFormSet):

    def _construct_form(self, i, **kwargs):
        form = super(SaleFormset, self)._construct_form(i, **kwargs)
        form.request = self.request
        return form

class SaleProductItemInline(admin.TabularInline):
    model = models.SaleProductItem
    form = SaleProductItemInlineForm
    formset = SaleFormset

    def get_formset(self, request, obj=None, **kwargs):
        formset = super(SaleProductItemInline, self).get_formset(request, obj, **kwargs)
        formset.request = request
        return formset
Silvereye answered 8/3, 2018 at 13:24 Comment(2)
is _construct_form part of public API?Exhibitioner
A shorter and thread-safe alternative using partialmethod: formset._construct_form = partialmethod(formset._construct_form, user=request.user). No need to derive BaseInlineFormSet.Farmann

© 2022 - 2024 — McMap. All rights reserved.