Это было исправлено в Django 1.9 с form_kwargs.
У меня есть форма Django, которая выглядит так:
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)
Я вызываю эту форму примерно так:
form = ServiceForm(affiliate=request.affiliate)
Где request.affiliate
является зарегистрированным пользователем. Это работает по назначению.
Моя проблема в том, что теперь я хочу превратить эту единую форму в набор форм. Я не могу понять, как я могу передать информацию о филиале в отдельные формы при создании набора форм. Согласно документам, чтобы сделать набор форм из этого, мне нужно сделать что-то вроде этого:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
И тогда мне нужно создать его так:
formset = ServiceFormSet()
Теперь, как я могу передать affiliate = request.affiliate отдельным формам таким образом?
Я бы использовал functools.partial и 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)
Я думаю, что это самый чистый подход и никак не влияет на ServiceForm (т.е. затрудняет подкласс).
Я бы построил класс формы динамически в функции, чтобы он имел доступ к аффилированному лицу через закрытие:
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
В качестве бонуса вам не нужно переписывать запрос в поле параметров. Недостатком является то, что подклассы немного напуганы. (Любой подкласс должен быть выполнен аналогичным образом.)
изменить
В ответ на комментарий вы можете вызвать эту функцию в любом месте, где вы бы использовали имя класса:
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.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):
Надеюсь, что это помогает кому-то, взял меня достаточно долго, чтобы понять это;)
staticmethod
?
Django 1.9:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
С момента совершения e091c18f50266097f648efc7cac2503968e9d217 в Вт Авг 14 23:44:46 2012 +0200 принятое решение больше не может работать.
Текущая версия функции django.forms.models.modelform_factory() использует "метод построения типа", вызывая функцию type() в переданной форме, чтобы получить тип метакласса, а затем используя результат для построения класса- объект его типа на лету::
# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)
Это означает, что даже объект curry
ed или partial
передан вместо формы "заставляет утку укусить вас", так сказать: она вызовет функцию со строительными параметрами объекта ModelFormClass
возвращает сообщение об ошибке::
function() argument 1 must be code, not str
Чтобы обойти это, я написал функцию-генератор, которая использует замыкание для возврата подкласса любого класса, указанного в качестве первого параметра, который затем вызывает super.__init__
после update
ввода kwargs с теми, которые входят в вызов функции генератора::
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
Затем в вашем коде вы будете называть форму factory as::
MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))
предостережений:
Я хотел разместить это в качестве комментария к ответу Карла Мейерса, но поскольку для этого нужны точки, которые я только что разместил здесь. Это заняло у меня 2 часа, чтобы понять, поэтому я надеюсь, что это поможет кому-то.
Заметка об использовании inlineformset_factory.
Я использовал это решение самостоятельно, и он работал идеально, пока я не попробовал его с inlineformset_factory. Я запускал Django 1.0.2 и получил странное исключение KeyError. Я обновился до последнего туловища, и он работал напрямую.
Теперь я могу использовать его так:
BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
modelformset_factory
. Спасибо за этот ответ!
Мне нравится решение закрытия для того, чтобы быть "более чистым" и более Pythonic (так что ответ от +1 до mmarshall), но формы Django также имеют механизм обратного вызова, который вы можете использовать для фильтрации запросов в наборах форм.
Он также не задокументирован, что я считаю индикатором, который разработчикам Django может не понравиться.
Итак, вы в основном создаете свой набор форм одинаково, но добавляете обратный вызов:
ServiceFormSet = forms.formsets.formset_factory(
ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)
Это создает экземпляр класса, который выглядит следующим образом:
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
Это должно дать вам общую идею. Это немного сложнее, делая обратный вызов объектным методом, подобным этому, но дает вам немного больше гибкости, а не выполняет простой обратный вызов функции.
Решение Carl Meyer выглядит очень элегантно. Я попытался реализовать его для modelformsets. У меня создалось впечатление, что я не могу назвать staticmethods внутри класса, но следующее необъяснимо работает:
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))
На мой взгляд, если я сделаю что-то вроде этого:
formset = MyFormset(request.POST,queryset=MyModel.objects.all(),request=request)
Затем ключевое слово "запрос" распространяется на все формы-члены моего набора форм. Я доволен, но я понятия не имею, почему это работает - кажется, неправильно. Любые предложения?
MyFormSet.form.Meta.model
.
MyFormSet.form().Meta.model
. На самом деле очевидно.
Я потратил некоторое время, пытаясь выяснить эту проблему, прежде чем я увижу это сообщение.
Решение, с которым я столкнулся, было решением закрытия (и это решение, которое я использовал ранее с формами модели Django).
Я попробовал метод curry(), как описано выше, но я просто не мог заставить его работать с Django 1.0, поэтому в конце я вернулся к методу закрытия.
Метод закрытия очень опрятен, и единственная незначительная нечетность заключается в том, что определение класса вложено внутри представления или другой функции. Я думаю, что это выглядит странно для меня - это зависание от моего предыдущего опыта в программировании, и я думаю, что кто-то с фоном в более динамичных языках не будет биться веком!
на основе этого ответа Я нашел более четкое решение:
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
И запустите его, как
formset_factory(form=ServiceForm.make_service_form(affiliate))
Я новичок здесь, поэтому я не могу добавить комментарий. Я надеюсь, что этот код тоже будет работать:
ServiceFormSet = formset_factory(ServiceForm, extra=3)
ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))
как для добавления дополнительных параметров в formet BaseFormSet
вместо формы.
Я должен был сделать подобное. Это похоже на решение 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), ... )