form example dynamically python django forms django-forms

python - dynamically - formset django example



Django pasando los parámetros de formulario personalizados a Formset (12)

Esto fue arreglado en Django 1.9 con form_kwargs .

Tengo un formulario de Django que se ve así:

class ServiceForm(forms.Form): option = forms.ModelChoiceField(queryset=ServiceOption.objects.none()) rate = forms.DecimalField(widget=custom_widgets.SmallField()) units = forms.IntegerField(min_value=1, widget=custom_widgets.SmallField()) def __init__(self, *args, **kwargs): affiliate = kwargs.pop(''affiliate'') super(ServiceForm, self).__init__(*args, **kwargs) self.fields["option"].queryset = ServiceOption.objects.filter(affiliate=affiliate)

Llamo a este formulario con algo como esto:

form = ServiceForm(affiliate=request.affiliate)

Donde request.affiliate es el usuario conectado. Esto funciona según lo previsto.

Mi problema es que ahora quiero convertir este único formulario en un formset. Lo que no puedo entender es cómo puedo pasar la información del afiliado a los formularios individuales al crear el formset. De acuerdo con los documentos para hacer un formset de esto, necesito hacer algo como esto:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)

Y luego necesito crearlo así:

formset = ServiceFormSet()

Ahora, ¿cómo puedo pasar affiliate = request.affiliate a los formularios individuales de esta manera?


A partir del compromiso e091c18f50266097f648efc7cac2503968e9d217 el martes 14 de agosto, 23:44:46 2012 +0200 la solución aceptada ya no puede funcionar.

La versión actual de la función django.forms.models.modelform_factory () usa una "técnica de construcción de tipo", llamando a la función tipo () en el formulario pasado para obtener el tipo de metaclase, luego usando el resultado para construir un objeto de clase de su escribe sobre la marcha ::

# Instatiate type(form) in order to use the same metaclass as form. return type(form)(class_name, (form,), form_class_attrs)

Esto significa que incluso un objeto curry o partial pasado en lugar de una forma "hace que el pato te muerda" por así decirlo: llamará a una función con los parámetros de construcción de un objeto ModelFormClass , devolviendo el mensaje de error ::

function() argument 1 must be code, not str

Para solucionar este problema, escribí una función de generador que usa un cierre para devolver una subclase de cualquier clase especificada como primer parámetro, que luego llama a super.__init__ después de update los kwargs con los proporcionados en la llamada de la función del generador ::

def class_gen_with_kwarg(cls, **additionalkwargs): """class generator for subclasses with additional ''stored'' parameters (in a closure) This is required to use a formset_factory with a form that need additional initialization parameters (see http://.com/questions/622982/django-passing-custom-form-parameters-to-formset) """ class ClassWithKwargs(cls): def __init__(self, *args, **kwargs): kwargs.update(additionalkwargs) super(ClassWithKwargs, self).__init__(*args, **kwargs) return ClassWithKwargs

Luego, en su código, llamará a la fábrica de formularios como ::

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))

advertencias:

  • esto recibió muy pocas pruebas, al menos por ahora
  • los parámetros suministrados podrían colisionar y sobrescribir aquellos usados ​​por cualquier código que use el objeto devuelto por el constructor

Construiría la clase de formulario dinámicamente en una función, para que tenga acceso al afiliado a través del cierre:

def make_service_form(affiliate): class ServiceForm(forms.Form): option = forms.ModelChoiceField( queryset=ServiceOption.objects.filter(affiliate=affiliate)) rate = forms.DecimalField(widget=custom_widgets.SmallField()) units = forms.IntegerField(min_value=1, widget=custom_widgets.SmallField()) return ServiceForm

Como beneficio adicional, no es necesario volver a escribir el conjunto de preguntas en el campo de opciones. El inconveniente es que las subclases son un poco extravagantes. (Cualquier subclase debe hacerse de forma similar).

editar:

En respuesta a un comentario, puede llamar a esta función sobre cualquier lugar donde use el nombre de clase:

def view(request): affiliate = get_object_or_404(id=request.GET.get(''id'')) formset_cls = formset_factory(make_service_form(affiliate)) formset = formset_cls(request.POST) ...


Django 1.9:

ArticleFormSet = formset_factory(MyArticleForm) formset = ArticleFormSet(form_kwargs={''user'': request.user})

form_kwargs


Esto es lo que funcionó para mí, Django 1.7:

from django.utils.functional import curry lols = {''lols'':''lols''} formset = modelformset_factory(MyModel, form=myForm, extra=0) formset.form = staticmethod(curry(MyForm, lols=lols)) return formset #form.py class MyForm(forms.ModelForm): def __init__(self, lols, *args, **kwargs):

Espero que ayude a alguien, me tomó el tiempo suficiente para resolverlo;)


La solución de Carl Meyer se ve muy elegante. Intenté implementarlo para modelformsets. Tenía la impresión de que no podía llamar a métodos estáticos dentro de una clase, pero lo siguiente inexplicablemente funciona:

class MyModel(models.Model): myField = models.CharField(max_length=10) class MyForm(ModelForm): _request = None class Meta: model = MyModel def __init__(self,*args,**kwargs): self._request = kwargs.pop(''request'', None) super(MyForm,self).__init__(*args,**kwargs) class MyFormsetBase(BaseModelFormSet): _request = None def __init__(self,*args,**kwargs): self._request = kwargs.pop(''request'', None) subFormClass = self.form self.form = curry(subFormClass,request=self._request) super(MyFormsetBase,self).__init__(*args,**kwargs) MyFormset = modelformset_factory(MyModel,formset=MyFormsetBase,extra=1,max_num=10,can_delete=True) MyFormset.form = staticmethod(curry(MyForm,request=MyFormsetBase._request))

En mi opinión, si hago algo como esto:

formset = MyFormset(request.POST,queryset=MyModel.objects.all(),request=request)

Luego, la palabra clave "solicitud" se propaga a todos los formularios miembros de mi formset. Estoy contento, pero no tengo idea de por qué está funcionando, parece estar equivocado. ¿Alguna sugerencia?


Me gusta la solución de cierre para ser "más limpio" y más Pythonic (por lo que +1 a mmarshall answer), pero los formularios de Django también tienen un mecanismo de devolución de llamada que puede usar para filtrar los conjuntos de consultas en los conjuntos de formularios.

Tampoco está documentado, lo que creo que es un indicador de que a los desarrolladores de Django podría no gustarles tanto.

Así que, básicamente, crea su formset igual pero agrega la devolución de llamada:

ServiceFormSet = forms.formsets.formset_factory( ServiceForm, extra=3, formfield_callback=Callback(''option'', affiliate).cb)

Esto crea una instancia de una clase que se ve así:

class Callback(object): def __init__(self, field_name, aff): self._field_name = field_name self._aff = aff def cb(self, field, **kwargs): nf = field.formfield(**kwargs) if field.name == self._field_name: # this is ''options'' field nf.queryset = ServiceOption.objects.filter(affiliate=self._aff) return nf

Esto debería darte la idea general. Es un poco más complejo hacer que la devolución de llamada sea un método de objeto como este, pero le da un poco más de flexibilidad en lugar de hacer una devolución de llamada de función simple.


Pasé algún tiempo tratando de resolver este problema antes de ver esta publicación.

La solución que se me ocurrió fue la solución de cierre (y es una solución que he utilizado antes con los formularios del modelo Django).

Probé el método curry () como se describió anteriormente, pero simplemente no pude hacer que funcionara con Django 1.0, así que al final volví al método de cierre.

El método de cierre es muy limpio y la única pequeña rareza es que la definición de clase está anidada dentro de la vista u otra función. Creo que el hecho de que esto me parezca extraño es una interrupción de mi experiencia previa en programación y creo que alguien con experiencia en idiomas más dinámicos no se inmutó.


Quería poner esto como un comentario a la respuesta de Carl Meyers, pero como eso requiere puntos, acabo de colocarlo aquí. Esto me llevó 2 horas averiguarlo, así que espero que ayude a alguien.

Una nota sobre el uso de inlineformset_factory.

Usé esa solución yo mismo y funcionó perfectamente, hasta que lo intenté con inlineformset_factory. Estaba ejecutando Django 1.0.2 y obtuve una extraña excepción KeyError. Actualicé al último trunk y funcionó directo.

Ahora puedo usarlo de manera similar a esto:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm) BookFormSet.form = staticmethod(curry(BookForm, user=request.user))


Soy un novato aquí, así que no puedo agregar ningún comentario. Espero que este código también funcione:

ServiceFormSet = formset_factory(ServiceForm, extra=3) ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))

como para agregar parámetros adicionales al BaseFormSet del formset en lugar de a la forma.


Tenía que hacer algo similar. Esto es similar a la solución de curry :

def form_with_my_variable(myvar): class MyForm(ServiceForm): def __init__(self, myvar=myvar, *args, **kwargs): super(SeriveForm, self).__init__(myvar=myvar, *args, **kwargs) return MyForm factory = inlineformset_factory(..., form=form_with_my_variable(myvar), ... )


Yo usaría functools.partial y functools.wraps :

from functools import partial, wraps from django.forms.formsets import formset_factory ServiceFormSet = formset_factory(wraps(ServiceForm)(partial(ServiceForm, affiliate=request.affiliate)), extra=3)

Creo que este es el enfoque más limpio y no afecta a ServiceForm de ninguna manera (es decir, al dificultar la subclase).


basado en esta respuesta , encontré una solución más clara:

class ServiceForm(forms.Form): option = forms.ModelChoiceField( queryset=ServiceOption.objects.filter(affiliate=self.affiliate)) rate = forms.DecimalField(widget=custom_widgets.SmallField()) units = forms.IntegerField(min_value=1, widget=custom_widgets.SmallField()) @staticmethod def make_service_form(affiliate): self.affiliate = affiliate return ServiceForm

Y ejecutarlo a la vista como

formset_factory(form=ServiceForm.make_service_form(affiliate))