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
зарегистрированный пользователь Это работает как задумано.
Моя проблема в том, что теперь я хочу превратить эту единственную форму в набор форм. Что я не могу понять, так это как я могу передать информацию о партнере отдельным формам при создании набора форм. В соответствии с документами, чтобы сделать форму из этого, мне нужно сделать что-то вроде этого:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
И тогда мне нужно создать это так:
formset = ServiceFormSet()
Теперь, как я могу передать affiliate=request.affiliate отдельным формам таким образом?
12 ответов
Я бы использовал 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 (т. Е. Затрудняет подкласс).
Официальный путь документа
Джанго 2.0:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
Я бы динамически создал класс формы в функции, чтобы он имел доступ к филиалу через замыкание:
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):
Надеюсь, это кому-нибудь поможет, у меня ушло достаточно времени, чтобы понять это;)
Я хотел разместить это как комментарий к ответу Карла Мейерса, но так как это требует баллов, я просто разместил его здесь. Это заняло у меня 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 в четверг 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
ред или 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://stackru.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))
предостережения:
- это получило очень мало испытаний, по крайней мере, сейчас
- предоставленные параметры могут конфликтовать и перезаписывать те, которые используются любым кодом, который будет использовать объект, возвращаемый конструктором
Мне нравится решение замыкания для того, чтобы быть "чище" и более 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
Это должно дать вам общее представление. Это немного сложнее, сделать обратный вызов объектным методом, подобным этому, но дает вам немного больше гибкости, чем простой вызов функции.
Решение Карла Мейера выглядит очень элегантно. Я попытался реализовать его для моделей форм. У меня сложилось впечатление, что я не могу вызывать статические методы внутри класса, но необъяснимо работает следующее:
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" распространяется на все формы-члены моего набора форм. Я доволен, но я понятия не имею, почему это работает - кажется, неправильно. Какие-либо предложения?
Я должен был сделать подобное. Это похоже на 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), ... )
Я потратил некоторое время, пытаясь выяснить эту проблему, прежде чем увидел эту публикацию.
Решением, которое я придумал, было решение замыкания (и это решение, которое я использовал ранее с модельными формами Django).
Я попробовал метод curry(), как описано выше, но я просто не мог заставить его работать с Django 1.0, поэтому в итоге я вернулся к методу закрытия.
Метод замыкания очень аккуратен, и единственная небольшая странность заключается в том, что определение класса вложено в представление или другую функцию. Я думаю, тот факт, что это выглядит странно для меня, является следствием моего предыдущего опыта программирования, и я думаю, что кто-то с опытом работы в более динамичных языках не будет биться веко!
Я новичок здесь, поэтому я не могу добавить комментарий. Я надеюсь, что этот код тоже будет работать:
ServiceFormSet = formset_factory(ServiceForm, extra=3)
ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))
что касается добавления дополнительных параметров в форму набора 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))