2011-03-03 25 views
5

Si tengo dos formularios, basados ​​en diferentes clases base (por ejemplo, Formulario y Modelo), pero quiero usar algunos campos en ambos, ¿puedo reutilizarlos de manera SECA?Django: ¿Reutilizar campos de formulario sin heredar?

cuenta la situación siguiente:

class AfricanSwallowForm(forms.ModelForm): 
    airspeed_velocity = forms.IntegerField(some_important_details_here) 
    is_migratory = forms.BooleanField(more_important_details) 

    class Meta: 
     model = AfricanBird 

class EuropeanSwallowForm(forms.Form): 
    airspeed_velocity = forms.IntegerField(some_important_details_here) 
    is_migratory = forms.BooleanField(more_important_details) 

.... hay una manera que sólo puede volver a utilizar el airspeed_velocity campos y is_migratory? Imagina que tengo un par de docenas de este tipo de formularios. El código se empapará si escribo esto una y otra vez.

(se supone, para los fines de esta pregunta, que no puede o no convertir airspeed_velocity y is_migratory en los campos del modelo AfricanBird.)

Respuesta

1

¿Qué tal un enfoque de fábrica?

def form_factory(class_name, base, field_dict): 
    always_has = { 
     'airspeed_velocity': forms.IntegerField(some_important_details_here), 
     'is_migratory': forms.BooleanField(more_important_details) 
    } 
    always_has.update(field_dict) 
    return type(class_name, (base,), always_has) 

def meta_factory(form_model): 
    class Meta: 
     model = form_model 
    return Meta 

AfricanSwallowForm = form_factory('AfricanSwallowForm', forms.ModelForm, { 
     'other' = forms.IntegerField(some_important_details_here), 
     'Meta': meta_factory(AfricanBird), 
    }) 

EuropeanSwallowForm = form_factory('EuropeanSwallowForm', forms.Form, { 
     'and_a_different' = forms.IntegerField(some_important_details_here), 
    }) 

Por lo demás, podría modificar la función de la fábrica aquí para buscar en una clase de formulario existente y seleccionar los atributos que desea, por lo que no se pierde la sintaxis declarativa ...

+0

Al principio, miré esto y lo descarté, pensando que era maravilloso pero demasiado difícil de manejar para el uso en el mundo real. Después de una segunda lectura, me doy cuenta de que este es un enfoque increíble. Funciona perfectamente, y es lo suficientemente legible con algunos comentarios adicionales. – jMyles

4

usted podría utilizar la herencia múltiple aka mixins, a factorizar los campos que se usan en Form y ModelForm.

class SwallowFormFields: 
    airspeed_velocity = forms.IntegerField(...) 
    is_migratory = forms.BooleanField(...) 

class AfricanSwallowForm(forms.ModelForm, SwallowFormFields): 
    class Meta: 
     model = AfricanBird 

class EuropeanSwallowForm(forms.Form, SwallowFormFields): 
    pass 

ACTUALIZACIÓN:

Dado que este no funciona con Django metaprogramming, que sea necesario crear un __init__ constructor personalizado que agrega los campos heredados a la lista de campos del objeto o puede agregar las referencias explícitamente dentro de la definición de clase:

class SwallowFormFields: 
    airspeed_velocity = forms.IntegerField() 
    is_migratory = forms.BooleanField() 

class AfricanSwallowForm(forms.ModelForm): 
    airspeed_velocity = SwallowFormFields.airspeed_velocity 
    is_migratory = SwallowFormFields.is_migratory 
    class Meta: 
     model = AfricanSwallow 

class EuropeanSwallowForm(forms.Form): 
    airspeed_velocity = SwallowFormFields.airspeed_velocity 
    is_migratory = SwallowFormFields.is_migratory 

ACTUALIZACIÓN:

Por supuesto que no tiene que anidan sus campos compartidos en una clase - que también podría simplemente definirlas como globales ...

airspeed_velocity = forms.IntegerField() 
is_migratory = forms.BooleanField() 

class AfricanSwallowForm(forms.ModelForm): 
    airspeed_velocity = airspeed_velocity 
    is_migratory = is_migratory 
    class Meta: 
     model = AfricanSwallow 

class EuropeanSwallowForm(forms.Form): 
    airspeed_velocity = airspeed_velocity 
    is_migratory = is_migratory 

ACTUALIZACIÓN:

bien, si Realmente quiero SECAR al máximo, tienes que ir con las metaclases.

Así que aquí es cómo puede hacerlo:

from django.forms.models import ModelForm, ModelFormMetaclass 
from django.forms.forms import get_declared_fields, DeclarativeFieldsMetaclass 
from django.utils.copycompat import deepcopy 

class MixinFormMetaclass(ModelFormMetaclass, DeclarativeFieldsMetaclass): 
    def __new__(cls, name, bases, attrs): 

     # default __init__ that calls all base classes 
     def init_all(self, *args, **kwargs): 
      for base in bases: 
       super(base, self).__init__(*args, **kwargs) 
     attrs.setdefault('__init__', init_all) 

     # collect declared fields 
     attrs['declared_fields'] = get_declared_fields(bases, attrs, False) 

     # create the class 
     new_cls = super(MixinFormMetaclass, cls).__new__(cls, name, bases, attrs) 
     return new_cls 

class MixinForm(object): 
    __metaclass__ = MixinFormMetaclass 
    def __init__(self, *args, **kwargs): 
     self.fields = deepcopy(self.declared_fields) 

Ahora se puede derivar sus colecciones de FormFields de MixinForm así:

class SwallowFormFields(MixinForm): 
    airspeed_velocity = forms.IntegerField() 
    is_migratory = forms.BooleanField() 

class MoreFormFields(MixinForm): 
    is_endangered = forms.BooleanField() 

A continuación, añadirlos a la lista de clases base como esto:

class EuropeanSwallowForm(forms.Form, SwallowFormFields, MoreFormFields): 
    pass 

class AfricanSwallowForm(forms.ModelForm, SwallowFormFields): 
    class Meta: 
     model = AfricanSwallow 

¿Qué hace?

  • La metaclase recoge todos los campos declarados en su MixinForm
  • A continuación, agrega personalizados __init__ constructores, para asegurarse de que el método de la MixinForm __init__ se llama mágicamente. (De lo contrario, tendría que llamarlo de manera explícita.)
  • MixinForm.__init__ copia los campos declarados int el campo de atributo

Tenga en cuenta que no soy ni un gurú, ni un desarrollador de Python Django, y que metaclases son peligrosos. Entonces, si te encuentras con un comportamiento extraño mejor quédate con el enfoque más detallado anterior :)

¡Buena suerte!

+0

Esto no parece funcionar para mí. – jMyles

+0

Hmm. Supongo que eso se debe a la forma en que funciona la metaprogramación de Django. – codecraft

+0

Instancias AfricanSwallowForm ahora tiene airspeed_velociy e is_migratory como campos, pero .fields no los muestra, por lo que no se iteran. ¡Tan cerca! – jMyles

0

class SwallowForm(forms.Form): 
    airspeed_velocity = forms.IntegerField() 
    is_migratory = forms.BooleanField() 

class AfricanSwallowForm(forms.ModelForm, SwallowForm): 
    class Meta: 
     model = AfricanSwallow 

class EuropeanSwallowForm(forms.Form, SwallowForm): 
    ... 

debería funcionar.

Tengo un código de larga ejecución que funciona y tiene los campos attr que se parece a esto.

languages_field = forms.ModelMultipleChoiceField(
     queryset=Language.objects.all(), 
     widget=forms.CheckboxSelectMultiple, 
     required=False 
) 

class FooLanguagesForm(forms.ModelForm): 
    languages = languages_field 

    class Meta: 
     model = Foo 
     fields = ('languages',) 

Observe que todavía utilizo los campos tuple en Meta. El campo aparece en los campos dict en la instancia del formulario ya sea que esté o no en los campos Meta.

que tienen una forma regular que utiliza este patrón, así:

genres_field = forms.ModelMultipleChoiceField(
     queryset=blah, 
     widget=forms.CheckboxSelectMultiple, 
     #required=False, 
) 

class FooGenresForm(forms.Form): 
    genres = genres_field 

veo que mis campos dict está trabajando.

In [6]: f = FooLanguagesForm() 

In [7]: f.fields 
Out[7]: {'languages': <django.forms.models.ModelMultipleChoiceField object at 0x1024be450>} 

In [8]: f2 = FooGenresForm() 

In [9]: f2.fields 
Out[9]: {'genres': <django.forms.models.ModelMultipleChoiceField object at 0x1024be3d0>} 
+2

No creo, esto crearía clases que derivan de Form y ModelForm, que crea estragos en las metaclases de Djangos – codecraft

+1

TypeError: Error al invocar las bases de la metaclase conflicto de metaclass: la metaclase de una clase derivada debe ser un (no -strict) subclase de las metaclases de todas sus bases –

0

crear una subclase de la IntegerField

class AirspeedField(forms.IntegerField): 
    def __init__(): 
     super(AirspeedField, self).__init__(some_important_details_here) 
0

acabo de hacer un fragmento que resuelve este problema de una manera SECO:

https://djangosnippets.org/snippets/10523/

Utiliza forma crujiente, pero la misma idea se puede utilizar sin formas crujientes. La idea es usar múltiples formularios bajo la misma etiqueta de formulario.

Cuestiones relacionadas