Utilisation d'un mixin avec une classe de forme Django
je pense à créer une classe de forme mixin pour que je puisse ajouter un ensemble commun de champs à une variété de formes par ailleurs très différentes. L'utiliser comme une classe de base ne marchera pas parce que je veux pouvoir utiliser d'autres formes comme des classes de base comme ça:
class NoteFormMixin(object):
note = forms.CharField()
class MainForm(forms.Form):
name = forms.CharField()
age = forms.IntegerField()
class SpecialForm(MainForm, NoteFormMixin):
favorite_color = forms.CharField()
Ma seule question est: comment cela fonctionne? Jusqu'à présent, c'est comme si j'utilise un mixin, alors il ne reconnaît pas le champs de définir à partir de ce mixin:
>>> ff1 = SpecialForm()
>>> ff1.fields
{'name': <django.forms.fields.CharField object at 0x178d3110>, 'age': <django.forms.fields.IntegerField object at 0x178d3190>, 'favorite_color': <django.forms.fields.CharField object at 0x178d3210>}
<!-Est-ce que c'est juste quelque chose qui ne peut pas être fait?
3 réponses
le problème est que votre NoteFormMixin dérive de l'objet plutôt que des formes.Forme. Vous avez besoin de le changer comme suit:
class NoteFormMixin(forms.Form):
note = forms.CharField()
LA solution de Patrick Altman n'est vraie qu'avec formulaires - si vous essayez cela avec ModelForm vous serez coincé soit avec une métaclasse ou des conflits avec certains des champs manquants.
LA solution la plus simple et la plus courte que j'ai trouvée était dans un attachement à Django' ticket #7018 - je vous remercie, bear330: o)
Vous aurez besoin de:
from django.forms.forms import get_declared_fields
. . .
class ParentsIncludedModelFormMetaclass(ModelFormMetaclass):
"""
Thanks to bear330 - taken from https://code.djangoproject.com/attachment/ticket/7018/metaforms.py
"""
def __new__(cls, name, bases, attrs):
# We store attrs as ModelFormMetaclass.__new__ clears all fields from it
attrs_copy = attrs.copy()
new_class = super(ParentsIncludedModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
# All declared fields + model fields from parent classes
fields_without_current_model = get_declared_fields(bases, attrs_copy, True)
new_class.base_fields.update(fields_without_current_model)
return new_class
def get_next_in_mro(current_class, class_to_find):
"""
Small util - used to call get the next class in the MRO chain of the class
You'll need this in your Mixins if you want to override a standard ModelForm method
"""
mro = current_class.__mro__
try:
class_index = mro.index(class_to_find)
return mro[class_index+1]
except ValueError:
raise TypeError('Could not find class %s in MRO of class %s' % (class_to_find.__name__, current_class.__name__))
puis vous définissez votre mixin comme une forme de modèle habituelle, mais sans déclarant Meta:
from django import forms
class ModelFormMixin(forms.ModelForm):
field_in_mixin = forms.CharField(required=True, max_length=100, label=u"Field in mixin")
. . .
# if you need special logic in your __init__ override as usual, but make sure to
# use get_next_in_mro() instead of super()
def __init__(self, *args, **kwargs):
#
result = get_next_in_mro(self.__class__, ModelFormMixin).__init__(self, *args, **kwargs)
# do your specific initializations - you have access to self.fields and all the usual stuff
print "ModelFormMixin.__init__"
return result
def clean(self):
result = get_next_in_mro(self.__class__, ModelFormMixin).clean(self)
# do your specific cleaning
print "ModelFormMixin.clean"
return result
et, enfin, - la forme finale du modèle, en réutilisant les caractéristiques du ModelFormMixin. Tu devrais définir Meta et tous les trucs habituels. Dans les formes finales, vous pouvez simplement appeler super(...) lorsque vous supprimez des méthodes (voir ci-dessous).
NOTE: La forme finale doit avoir ParentsIncludedModelFormMetaclass définir comme une métaclasse
NOTE: L'ordre des classes est important - mettre le mixin premier, puis le modèle de.
class FinalModelForm(ModelFormMixin, forms.ModelForm):
"""
The concrete form.
"""
__metaclass__ = ParentsIncludedModelFormMetaclass
class Meta:
model = SomeModel
field_in_final_form = forms.CharField(required=True, max_length=100, label=u"Field in final form")
def clean(self):
result = super(FinalModelForm, self).clean()
# do your specific cleaning
print "FinalModelForm.clean"
return result
gardez à l'esprit que cela ne fonctionne que si les deux classes sont des Modelformes. Si vous essayez de mélanger et de faire correspondre Form et ModelFrom avec cette technique, il ne va pas être joli du tout :o)
class TextFormMixin(object):
def __init__(self, *args, **kwargs):
super(TextFormMixin, self).__init__(*args, **kwargs)
self.fields['text'] = forms.CharField(widget=forms.Textarea, required=True)
def clean_text(self):
if not ('{{EMAIL}}' in self.cleaned_data.get('text', '')):
raise ValidationError("You have to put {{EMAIL}} in message body.")
return self.cleaned_data.get('text', '')
def get_text(self):
return self.cleaned_dat['text'].replace('{{EMAIL}}', self.case.get_email())
class NewCaseForm(TextFormMixin, forms.ModelForm):
pass
class ReplyForm(TextFormMixin, forms.Form):
to = forms.CharField(max_length=50)
subject = forms.CharField(max_length=50)