【问题标题】:Django get instance in inline form adminDjango 在内联表单管理员中获取实例
【发布时间】:2014-04-23 09:07:17
【问题描述】:

有一个内联表单类:

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

内联类:

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

管理类

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

问题:如何获取ItemColorSelectForm中的当前对象。

print kwargs返回:

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

【问题讨论】:

    标签: django python-2.7 django-forms django-admin inline


    【解决方案1】:

    解决方案: 覆盖 Inline 类中的 formset 方法

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

    【讨论】:

    • 如果你在多线程模式下运行,你可能会遇到麻烦,因为 InlineForm.obj 是在线程之间共享的,所以值可能会在设置和获取它之间发生变化。
    • 也就是说,Django 似乎没有给我们任何选择,而且在任何时候管理员中的用户可能都不多,因此请将此阅读为“你实际上不应该这样做” ,但我现在要自己使用它。
    • 在多线程运行时会中断的可怕解决方案,例如使用 uwsgi。
    • 既然可以分配给实例,为什么还要分配给类? self.instance = obj
    【解决方案2】:

    当前接受的解决方案不是线程安全的。如果您关心线程安全,请永远不要将实例分配给静态类属性。

    线程安全的解决方案是:

    对于 Django 1.7 (可能是更早的版本,不清楚):

    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
    

    Django >= 1.9 开始,也可以传递 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
    

    以上解决方案将使实例 kwarg 以模型形式提供:

    class InlineForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super(InlineForm, self).__init__(*args, **kwargs)
            print('instance', kwargs['instance'])
    

    【讨论】:

      【解决方案3】:

      修复:当前接受的解决方案在多线程模式下不安全

      Arti 的解决方案有效,另一个更好的选择可能是:

      不是将当前对象 id 传递到内联表单中,
      使用对象 id 在 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)
      

      因此,不再需要覆盖表单中的__init__(),也不需要覆盖当前对象。

      在 Django 2.1.7 中工作

      【讨论】:

        猜你喜欢
        • 2017-08-22
        • 1970-01-01
        • 2011-01-23
        • 2015-11-15
        • 2011-10-15
        • 2015-06-21
        • 1970-01-01
        • 1970-01-01
        • 2012-09-05
        相关资源
        最近更新 更多