Django передача пользовательских параметров формы в Formset
это было исправлено в 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
является зарегистрированным пользователем. Это работает по назначению.
моя проблема в том, что теперь я хочу превратить эту единственную форму в набор форм. Чего я не могу понять, так это как я могу передать партнерскую информацию в отдельные формы при создании formset с. Согласно документам, чтобы сделать formset из этого, мне нужно сделать что-то вроде этого:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
и тогда мне нужно создать его так:
formset = ServiceFormSet()
теперь, как я могу передать affiliate=запрос.присоединяться к индивидуальным формам таким образом?
12 ответов
Я хотел бы использовать functools.частичный и functools.обертывания:
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
в качестве бонуса вам не нужно переписывать queryset в поле option. Недостатком является то, что подклассы немного фанки. (Любой подкласс должен быть сделан аналогичным образом.)
edit:
в ответ на комментарий вы можете вызвать эту функцию в любом месте, где вы будете использовать имя класса:
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)
...
Путь Официального Документа
Джанго 2.0:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
Это то, что сработало для меня, 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):
надеюсь, это поможет кому-то, мне потребовалось достаточно много времени, чтобы понять это;)
мне нравится решение закрытия для "более чистого" и более Pythonic (so +1 к ответу mmarshall), но формы Django также имеют механизм обратного вызова, который вы можете использовать для фильтрации запросов в наборах форм.
это также не документировано,что, я думаю, является показателем того, что разработчикам Django это может не понравиться.
таким образом, вы в основном создаете свой formset то же самое, но добавляете обратный вызов:
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
Это должно дать вам общее представление. Это немного сложнее сделать обратный вызов объектным методом, как это, но дает вам немного больше гибкости, в отличие от выполнения простого обратного вызова функции.
Я хотел поместить это в качестве комментария к ответу Карла Мейерса, но поскольку это требует очков, я просто разместил его здесь. Это заняло у меня 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))
по состоянию на фиксацию e091c18f50266097f648efc7cac2503968e9d217 на Вт Aug 14 23:44: 46 2012 +0200 принятое решение больше не может работать.
текущая версия django.формы.модели.функция modelform_factory() использует "метод построения типа", вызывая функцию type () в переданной форме для получения типа metaclass, а затем используя результат для построения класса-объекта его типа на лету::
# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)
это означает, что даже curry
Эд или partial
объект переданный вместо формы "заставляет утку укусить вас", так сказать: он вызовет функцию с параметрами конструкции ModelFormClass
объект, возвращающий сообщение об ошибке::
function() argument 1 must be code, not str
чтобы обойти это, я написал функцию генератора, которая использует закрытие для возврата подкласса любого класса, указанного в качестве первого параметра, который затем вызывает super.__init__
после update
ing кварги с теми, которые поставляются на вызов функции генератора::
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
тогда в вашем коде вы будете называть фабрику форм как::
MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))
предостережения:
- это получило очень небольшое тестирование, по крайней мере, на данный момент
- предоставленные параметры могут конфликтовать и перезаписывать те, которые используются любым кодом, который будет использовать объект, возвращенный конструктором
решение Карла Мейера выглядит очень элегантно. Я попытался реализовать его для 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)
затем ключевое слово "request" распространяется на все формы-члены моего набора форм. Мне приятно, но я понятия не имею, почему это работает - это кажется неправильным. Есть предложения?
Я потратил некоторое время, пытаясь выяснить эту проблему, прежде чем увидел эту публикацию.
решение, которое я придумал, было решением закрытия (и это решение, которое я использовал раньше с формами модели Django).
Я попробовал метод curry (), как описано выше, но я просто не мог заставить его работать с Django 1.0, поэтому в конце концов я вернулся к методу закрытия.
метод закрытия очень аккуратно и только небольшая странность заключается в том, что определение класса вложенные внутри представления или другой функции. Я думаю, что тот факт, что это выглядит странно для меня, - это зависание от моего предыдущего опыта программирования, и я думаю, что кто-то с фоном на более динамичных языках не моргнул бы глазом!
мне пришлось сделать подобную вещь. Это похоже на 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), ... )
Я новичок здесь, поэтому я не могу добавить комментарий. Надеюсь, этот код тоже будет работать:
ServiceFormSet = formset_factory(ServiceForm, extra=3)
ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))
Как добавить дополнительные параметры в formset с это BaseFormSet
вместо формы.
на основе ответ Я нашел более четкое решение:
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))