9

Have a inline form class:

class ItemColorSelectForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ItemColorSelectForm, self).__init__(*args, **kwargs)
        #here i need current object

Inline class:

class ItemColorSelectInline(generic.GenericTabularInline):
    model = ColorSelect
    extra = 1
    form = ItemColorSelectForm

Admin class

class ItemAdmin(admin.ModelAdmin):
    inlines = [ItemColorInline,]

Question: how can a get current object in ItemColorSelectForm.

print kwargs return:

{'auto_id': u'id_%s', 'prefix': u'catalog-colorselect-content_type-object_id-__prefix__', 'empty_permitted': True}

3 Answers 3

6

Currently accepted solution is not thread safe. If you care about thread safety, never, ever assign an instance to a static class property.

Thread safe solutions are:

For Django 1.7 < 1.9 (possibly earlier versions, unclear):

from django.utils.functional import cached_property

def get_formset(self, *args, **kwargs):
    FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)

    class ProxyFormSet(FormSet):
        def __init__(self, *args, **kwargs):
            self.instance = kwargs['instance']
            super(ProxyFormSet, self).__init__(*args, **kwargs)

        @cached_property
        def forms(self):
            kwargs = {'instance': self.instance}
            forms = [self._construct_form(i, **kwargs) 
                    for i in xrange(self.total_form_count())]
            return forms
    return ProxyFormSet

As of Django >= 1.9 it's also possible to pass form_kwargs:

def get_formset(self, *args, **kwargs):
    FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)

    class ProxyFormSet(FormSet):
        def __init__(self, *args, **kwargs):
            form_kwargs = kwargs.pop('form_kwargs', {})
            form_kwargs['instance'] = kwargs['instance']
            super(ProxyFormSet, self).__init__(
                *args, form_kwargs=form_kwargs, **kwargs)
    return ProxyFormSet

Above solutions will make an instance kwarg available in the model form:

class InlineForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(InlineForm, self).__init__(*args, **kwargs)
        print('instance', kwargs['instance'])
Sign up to request clarification or add additional context in comments.

Comments

3

Solution: Override the formset method in Inline class

def get_formset(self, request, obj=None, **kwargs):
        InlineForm.obj = obj
        return super(InlineAdmin, self).get_formset(request, obj, **kwargs)

4 Comments

If you're running in multi-threaded mode, you might run into trouble since InlineForm.obj is shared between threads, so the value could change between setting and getting it.
That said, it doesn't seem like Django gives us any choice, and there probably aren't many users in the admin at any time, so read this as 'you actually shouldn't do this, but I'm going to use it myself right now'.
Horrible solution that will break when run multi threaded, for example using uwsgi.
Why assign to class when you can assign to instance? self.instance = obj
3

To fix: currently accepted solution not safe in multi-thread mode

Arti's solution works, another better option could be:

Instead of passing the current object id into the inline form,
use the object id to create a inline form field within the get_formset().

# admin.py
class TransactionInline(admin.TabularInline):
    model = Transaction
    form = TransactionInlineForm

    def get_formset(self, request, obj=None, **kwargs):
        # comment Arti's solution
        # TransactionInlineForm.project_id = obj.id

        formset = super().get_formset(request, obj, **kwargs)
        field = formset.form.declared_fields['purchase']
        field.queryset = get_object_or_404(Project, pk=obj.id).products.all()
        return formset
# forms.py
class TransactionInlineForm(ModelForm):
    purchase = ModelChoiceField(queryset=None, label='Purchase', required=False)

So, there is no need to override the __init__() in form anymore, neither the current object.

works in Django 2.1.7

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.