2012-02-23 14 views
13

Estoy usando un TabularInline en el administrador de Django, configurado para mostrar un formulario en blanco adicional.Accediendo a la instancia del modelo principal de modelform of admin inline

class MyChildInline(admin.TabularInline): 
    model = MyChildModel 
    form = MyChildInlineForm 
    extra = 1 

El modelo se parece MyParentModel-> MyChildModel-> MyInlineForm.

Estoy usando un formulario personalizado para poder buscar dinámicamente los valores y completar las opciones en un campo. p.ej.

class MyChildInlineForm(ModelForm): 

    my_choice_field = forms.ChoiceField() 

    def __init__(self, *args, **kwargs): 
     super(MyChildInlineForm, self).__init__(*args, **kwargs) 

     # Lookup ID of parent model. 
     parent_id = None 
     if "parent_id" in kwargs: 
      parent_id = kwargs.pop("parent_id") 
     elif self.instance.parent_id: 
      parent_id = self.instance.parent_id 
     elif self.is_bound: 
      parent_id = self.data['%s-parent'% self.prefix] 

     if parent_id: 
      parent = MyParentModel.objects.get(id=parent_id) 
      if rev: 
       qs = parent.get_choices() 
       self.fields['my_choice_field'].choices = [(r.name,r.value) for r in qs] 

Esto funciona bien para los registros en línea con destino a un registro real, sino por la forma en blanco adicional, no se muestra ningún valor en mi campo de elección, ya que no tiene ningún ID de registro y hay no se puede buscar el registro MyParentModel asociado.

He inspeccionado todos los valores que pude encontrar (args, kwargs, self.data, self.instance, etc.) pero no encuentro ninguna forma de acceder al objeto principal al que está vinculado el tabular en línea. ¿Hay alguna manera de hacer esto?

Respuesta

1

AdminModel tiene algunos métodos como get_formsets. Recibe un objeto y devuelve un conjunto de conjuntos de formularios. Creo que se puede añadir un poco de información sobre el objeto principal de que las clases de formset y utilizarla más tarde en __init__ de juego de formularios

22

Actualización: A partir de Django 1.9, hay un método en la clase def get_form_kwargs(self, index)BaseFormSet. Por lo tanto, reemplazando que pasa los datos a la forma.

Esta sería la versión Python 3/Django 1.9+:

class MyFormSet(BaseInlineFormSet): 
    def get_form_kwargs(self, index): 
     kwargs = super().get_form_kwargs(index) 
     kwargs['parent_object'] = self.instance 
     return kwargs 


class MyForm(forms.ModelForm): 
    def __init__(self, *args, parent_object, **kwargs): 
     self.parent_object = parent_object 
     super(MyForm, self).__init__(*args, **kwargs) 


class MyChildInline(admin.TabularInline): 
    formset = MyFormSet 
    form = MyForm 

Para Django 1.8 y por debajo de:

para pasar un valor de un formset a las formas individuales, tendrías que ver cómo están construidos. Un editor/IDE con "saltar a la definición" realmente ayuda aquí a sumergirse en el código ModelAdmin, y aprender sobre el inlineformset_factory y su clase BaseInlineFormSet.

A partir de ahí, encontrará que el formulario está construido en _construct_form() y puede anularlo para pasar parámetros adicionales. Es probable que algo como esto:

class MyFormSet(BaseInlineFormSet): 
    def _construct_form(self, i, **kwargs): 
     kwargs['parent_object'] = self.instance 
     return super(MyFormSet, self)._construct_form(i, **kwargs) 

    @property 
    def empty_form(self): 
     form = self.form(
      auto_id=self.auto_id, 
      prefix=self.add_prefix('__prefix__'), 
      empty_permitted=True, 
      parent_object=self.instance, 
     ) 
     self.add_fields(form, None) 
     return form 

class MyForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     self.parent_object = kwargs.pop('parent_object', None) 
     super(MyForm, self).__init__(*args, **kwargs) 


class MyChildInline(admin.TabularInline): 
    formset = MyFormSet 
    form = MyForm 

Sí, esto implica una función privada _construct_form.

actualización Nota: Esta no cubre el empty_form, por lo tanto, el código del formulario tiene que aceptar los parámetros opcionalmente.

+0

+1, aunque no estoy seguro de lo que era exactamente @Cerin tratando de resolver, la mayor parte del tiempo debería ser más fácil de usar inlines genéricos y el uso la instancia de formset para verificar el tipo de contenido y el id_objeto. –

+3

Intenté esto, pero obtengo un KeyError para 'parent_object' en la línea de MyForm donde intenta mostrar el valor parent_object. – platzhersh

+1

Acabo de probar esto. No funciona – Cerin

1

Ampliando la respuesta de ilvar un poco, si el campo de formulario de interés se construye a partir de un campo modelo, puede utilizar la siguiente construcción para aplicar un comportamiento personalizado a ella:

class MyChildInline(admin.TabularInline): 
    model = MyChildModel 
    extra = 1 
    def get_formset(self, request, parent=None, **kwargs): 
     def formfield_callback(db_field): 
      """ 
      Constructor of the formfield given the model field. 
      """ 
      formfield = self.formfield_for_dbfield(db_field, request=request) 
      if db_field.name == 'my_choice_field' and parent is not None: 
       formfield.choices = parent.get_choices() 
      return formfield 
     return super(MyChildInline, self).get_formset(
      request, obj=obj, formfield_callback=formfield_callback, **kwargs) 
     return result 
3

estoy usando Django 1 .10 y funciona para mí:
Crear una FormSet y puso el objeto padre en kwargs:

class MyFormSet(BaseInlineFormSet): 

    def get_form_kwargs(self, index): 
     kwargs = super(MyFormSet, self).get_form_kwargs(index) 
     kwargs.update({'parent': self.instance}) 
     return kwargs 

crear un formulario y hacer estallar un atributo antes super llamados

class MyForm(forms.ModelForm): 

    def __init__(self, *args, **kwargs): 
     parent = kwargs.pop('parent') 
     super(MyForm, self).__init__(*args, **kwargs) 
     # do whatever you need to with parent 

poner eso en el admin en línea :

class MyModelInline(admin.StackedInline): 

    model = MyModel 
    fields = ('my_fields',) 
    form = MyFrom 
    formset = MyFormSet 
+1

Agradable ver la solución Django 1.9+ :-) En lugar de 'self.form_kwargs.copy()' Recomiendo llamar a 'super()' en su lugar. – vdboor

+0

@vdboor hola, gracias por el asesoramiento! –

+0

2 preguntas: 1) ¿qué es FormFieldMetaForm? ¿No debería ser súper (MyForm, self)? 2) en MyForm .__ init __(), al hacer uso de parent, ¿debería hacerse antes o después de la llamada a super() .__ init __()? – ckot

Cuestiones relacionadas