質問

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}
役に立ちましたか?

解決 2

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)

他のヒント

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'])

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top