#27240 closed New feature (duplicate)

Allow passing custom parameters to formset forms in admin

Reported by: Alexey Rogachev Owned by: nobody
Component: contrib.admin Version: master
Severity: Normal Keywords: admin, form, formset, parameter
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Alexey Rogachev)

I have Enterprise model and related model Attachment. Attachments are managed using formsets in both frontend and backend.

I use custom form to handle attachments, and it uses additional user kwarg:

def __init__(self, *args, **kwargs):
    self.user = kwargs.pop('user', None)
    super(AttachmentForm, self).__init__(*args, **kwargs)

I need to fill this parameter with request.user value. In frontend I use the method recommended in docs (code omitted for brevity).

enterprise = Enterprise()
AttachmentFormSet = generic_inlineformset_factory(Attachment, form=AttachmentForm, max_num=3, validate_max=True)
if request.method == 'POST':
    attachment_formset = AttachmentFormSet(request.POST, request.FILES, instance=enterprise, form_kwargs = {'user': request.user})
else:
    attachment_formset = AttachmentFormSet(instance=enterprise, form_kwargs = {'user': request.user})

But docs do not cover how achieve the same thing in backend using Django Admin.

In admin I have:

class AttachmentInline(GenericTabularInline):
    model = Attachment
    form = AttachmentForm

class EnterpriseAdmin(MyCompareVersionAdmin):
    inlines = [AttachmentInline]

It's unclear where and how we need to grab and pass request.user parameter to AttachmentForm. After some trial and error, I ended up with this workaround:

class EnterpriseAdmin(admin.ModelAdmin):
    inlines = [AttachmentInline]

    def save_related(self, request, form, formsets, change):
        AttachmentForm.user = request.user
        super(EnterpriseAdmin, self).save_related(request, form, formsets, change)

Related modifications in AttachmentForm:

user = None

def __init__(self, *args, **kwargs):
    user = kwargs.pop('user', None)
    if user:
        self.user = user
    super(AttachmentForm, self).__init__(*args, **kwargs)

So I turned user to class attribute and set it from kwargs only if it's passed and it's not None.

It works, but I don't like this approach and It looks like a hack for me.

I think the solution on how to properly do it should be added to the docs in the same section along with frontend solution.

I'm not sure If I can't find the proper way to do that or it's really not implemented for admin.

Change History (14)

comment:1 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:2 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:3 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:4 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:5 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:6 Changed 16 months ago by Alexey Rogachev

Version: 1.10master

comment:7 Changed 16 months ago by Alexey Rogachev

Component: contrib.adminDocumentation
Description: modified (diff)
Summary: Docs - Passing custom parameters to formset forms in adminPassing custom parameters to formset forms in admin

comment:8 Changed 16 months ago by Alexey Rogachev

Description: modified (diff)

comment:9 Changed 16 months ago by Marten Kenbeek

I'm currently using the following pattern to solve the same problem with forms:

def get_form(self, request, obj=None, **kwargs):
    Form = super().get_form(request, obj=None, **kwargs)
    return functools.partial(Form, user=request.user)

Using a class attribute can suffer from thread-safety issues when handling concurrent requests.

comment:10 Changed 16 months ago by Alexey Rogachev

@knbk I understand that approach with class attribute is not elegant. As for your soultion - I need to do it for formset form (AttachmentForm), not admin form. Admin form is different in my case and called EnterpriseAdminForm. Formset has no get_form related method, only get_form_kwargs(self, index): method. which has no reference to request.

comment:11 Changed 16 months ago by Marten Kenbeek

InlineModelAdmin has a get_formset() method in which you can do the same.

comment:12 Changed 16 months ago by Alexey Rogachev

@knbk Thanks, I think this can be also done in def get_formsets_with_inlines(self, request, obj=None): or def get_inline_formsets(self, request, formsets, inline_instances, obj=None): in regular admin.ModelAdmin.

comment:13 Changed 16 months ago by Tim Graham

Did you come up with a solution? If so, maybe you can submit a patch if you still think an example is needed?

comment:14 Changed 16 months ago by Tim Graham

Component: Documentationcontrib.admin
Resolution: duplicate
Status: newclosed
Summary: Passing custom parameters to formset forms in adminAllow passing custom parameters to formset forms in admin
Type: UncategorizedNew feature

Closing as a duplicate of #26607 (see :ticket:26607#comment:3).

Note: See TracTickets for help on using tickets.
Back to Top