Cela a été corrigé dans Django 1.9 avec form_kwargs .
J'ai un Django Form qui ressemble à ceci:
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)
J'appelle ce formulaire avec quelque chose comme ceci:
form = ServiceForm(affiliate=request.affiliate)
Où request.affiliate
est l'utilisateur connecté. Cela fonctionne comme prévu.
Mon problème est que je veux maintenant transformer ce formulaire unique en un ensemble de formulaires. Ce que je ne peux pas comprendre, c'est comment je peux transmettre les informations d'affiliation aux formulaires individuels lors de la création du jeu de formulaires. Selon les documents, pour créer un formulaire, je dois faire quelque chose comme ceci:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
Et puis je dois le créer comme ceci:
formset = ServiceFormSet()
Maintenant, comment puis-je transmettre affiliate = request.affiliate aux formulaires individuels de cette façon?
J'utiliserais functools.partial et 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)
Je pense que c'est l'approche la plus propre et n'affecte en aucun cas ServiceForm (c'est-à-dire en rendant difficile la sous-classe).
Django 2.0:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
Je construirais la classe de formulaire dynamiquement dans une fonction, afin qu'elle ait accès à l'affilié via la fermeture:
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
En prime, vous n'avez pas à réécrire l'ensemble de requêtes dans le champ d'option. L'inconvénient est que le sous-classement est un peu génial. (Toute sous-classe doit être créée de la même manière.)
modifier:
En réponse à un commentaire, vous pouvez appeler cette fonction à propos de tout endroit où vous utiliseriez le nom de classe:
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)
...
C'est ce qui a fonctionné pour moi, 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):
J'espère que ça aide quelqu'un, ça m'a pris assez de temps pour le comprendre;)
Je voulais placer cela comme un commentaire à la réponse de Carl Meyers, mais comme cela nécessite des points, je viens de le placer ici. Cela m'a pris 2 heures pour comprendre, donc j'espère que cela aidera quelqu'un.
Une note sur l'utilisation de l'inlineformset_factory.
J'ai utilisé cette solution moi-même et cela a fonctionné parfaitement, jusqu'à ce que je l'essaie avec l'inlineformset_factory. J'exécutais Django 1.0.2 et j'ai obtenu une étrange exception KeyError. J'ai mis à niveau vers le dernier tronc et cela a fonctionné directement.
Je peux maintenant l'utiliser comme ceci:
BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
J'aime la solution de fermeture pour être "plus propre" et plus Pythonique (donc +1 à mmarshall réponse) mais Django ont également un mécanisme de rappel que vous pouvez utiliser pour filtrer les ensembles de requêtes dans les ensembles de formulaires.
Ce n'est pas non plus documenté, ce qui, à mon avis, est un indicateur que les développeurs Django pourraient ne pas aimer autant.
Donc, vous créez essentiellement votre jeu de formulaires de la même manière, mais vous ajoutez le rappel:
ServiceFormSet = forms.formsets.formset_factory(
ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)
Cela crée une instance d'une classe qui ressemble à ceci:
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
Cela devrait vous donner une idée générale. C'est un peu plus complexe de faire du rappel une méthode objet comme celle-ci, mais vous donne un peu plus de flexibilité que de faire un simple rappel de fonction.
À partir de la validation e091c18f50266097f648efc7cac2503968e9d217 le mar août 14 23:44:46 2012 +0200, la solution acceptée ne peut plus fonctionner.
La version actuelle de la fonction Django.forms.models.modelform_factory () utilise une "technique de construction de type", appelant la fonction type () sur le formulaire passé pour obtenir le type de métaclasse, puis en utilisant le résultat pour construire un objet-classe de son taper à la volée ::
# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)
Cela signifie que même un objet curry
ed ou partial
passé à la place d'un formulaire "fait que le canard vous mord" pour ainsi dire: il appellera une fonction avec les paramètres de construction d'un ModelFormClass
objet, renvoyant le message d'erreur ::
function() argument 1 must be code, not str
Pour contourner ce problème, j'ai écrit une fonction de générateur qui utilise une fermeture pour renvoyer une sous-classe de toute classe spécifiée comme premier paramètre, qui appelle ensuite super.__init__
après update
ing les kwargs avec ceux fournis sur l'appel de la fonction générateur ::
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://stackoverflow.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
Ensuite, dans votre code, vous appellerez la fabrique de formulaires comme:
MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))
mises en garde:
La solution de Carl Meyer est très élégante. J'ai essayé de l'implémenter pour modelformsets. J'avais l'impression que je ne pouvais pas appeler des méthodes statiques au sein d'une classe, mais les éléments suivants fonctionnent inexplicablement:
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))
À mon avis, si je fais quelque chose comme ça:
formset = MyFormset(request.POST,queryset=MyModel.objects.all(),request=request)
Le mot clé "request" est ensuite propagé à toutes les formes membres de mon jeu de formulaires. Je suis content, mais je ne sais pas pourquoi cela fonctionne - cela semble faux. Aucune suggestion?
J'ai passé un peu de temps à essayer de comprendre ce problème avant de voir cette publication.
La solution que j'ai trouvée était la solution de fermeture (et c'est une solution que j'ai utilisée auparavant avec Django model forms).
J'ai essayé la méthode curry () comme décrit ci-dessus, mais je n'arrivais pas à la faire fonctionner avec Django 1.0 donc à la fin je suis revenu à la méthode de fermeture.
La méthode de fermeture est très soignée et la seule petite bizarrerie est que la définition de classe est imbriquée dans la vue ou une autre fonction. Je pense que le fait que cela me semble étrange est un raccroc de mon expérience de programmation précédente et je pense que quelqu'un avec une formation dans des langages plus dynamiques ne bat pas une paupière!
J'ai dû faire une chose similaire. Ceci est similaire à la solution 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), ... )
Je suis un débutant ici, donc je ne peux pas ajouter de commentaire. J'espère que ce code fonctionnera aussi:
ServiceFormSet = formset_factory(ServiceForm, extra=3)
ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))
comme pour ajouter des paramètres supplémentaires au BaseFormSet
du formset au lieu du formulaire.
basé sur cette réponse J'ai trouvé une solution plus claire:
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
Et l'exécuter en vue comme
formset_factory(form=ServiceForm.make_service_form(affiliate))