2009-03-08 2 views
128

This was fixed in Django 1.9 with form_kwargs .Джанго Передача пользовательской формы Параметры для Formset

У меня есть форма 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 из этого мне нужно сделать что-то вроде этого:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3) 

А потом мне нужно создать его так:

formset = ServiceFormSet() 

Теперь, как я могу передать филиалу = request.affiliate для отдельных форм таким образом?

ответ

102

Я хотел бы использовать 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 каким-либо образом (т.е., затрудняя к подклассу).

+0

Это не работает для меня. Я получаю ошибку: AttributeError: объект «_curriedFormSet» не имеет атрибута «get» –

+0

Я не могу дублировать эту ошибку. Это также странно, потому что у набора форм обычно нет атрибута get, поэтому, похоже, вы можете делать что-то странное в своем коде. (Кроме того, я обновил ответ, чтобы избавиться от странностей, таких как «_curriedFormSet»). –

+0

Я пересматриваю это, потому что хочу, чтобы ваше решение работало. Я могу объявить набор форм штрафом, но если я попытаюсь напечатать его, то {{formset}} будет, когда я получу «не имеет атрибута« получить »ошибку. Это происходит с любым решением, которое вы предоставили. Если я прокручу форму и напечатаю формы как {{form}}, я снова получу ошибку. Если я, например, петлю и печатаю как {{form.as_table}}, я получаю пустые таблицы форм, т. Е. никакие поля не печатаются. Есть идеи? –

43

я хотел бы построить класс формы динамически, в функции, так что он имеет доступ к партнерскому через закрытие:

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 в поле опций , Недостатком является то, что подклассы немного напуганы. (Любой подкласс должен быть сделан таким же образом.)

редактировать:

В ответ на комментарий, вы можете вызвать эту функцию о любом месте вы будете использовать имя класса:

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) 
    ... 
+0

Спасибо - что работал. Я держусь, отмечая это как принятое, потому что я вроде надеюсь, что есть более чистый вариант, так как это так определенно кажется фанки. –

+0

Маркировка считается принятой, поскольку, по-видимому, это лучший способ сделать это. Чувствует себя странно, но делает трюк. :) Спасибо. –

+0

У Карла Мейера, как мне кажется, самый чистый путь, который вы искали. –

9

Мне нравится решение для закрытия «чистое» и более Pythonic (так что ответ от +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 

Это должно дать вам общее идея. Это немного сложнее, делая обратный вызов объектным методом, подобным этому, но дает вам немного больше гибкости, а не просто выполняет обратный вызов функции.

+1

Спасибо за ответ. Я использую решение mmarshall прямо сейчас, и, поскольку вы согласны, что это более Pythonic (что-то, что я не знаю, так как это мой первый проект Python), я думаю, что я придерживаюсь этого. Однако определенно хорошо знать о обратном вызове. Еще раз спасибо. –

+1

Спасибо. Этот способ отлично работает с modelformset_factory. Я не мог правильно использовать другие способы работы с modelformsets, но этот способ был очень прост. – Spike

+0

Функциональность карри по существу создает закрытие, не так ли? Почему вы говорите, что решение @ mmarshall более Pythonic? Кстати, спасибо за ваш ответ. Мне нравится этот подход. – Josh

9

Я хотел разместить это как комментарий к Карлу Мейерсу, но поскольку для этого нужны точки, которые я только что разместил здесь. Это заняло у меня 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)) 
+0

То же самое касается 'modelformset_factory'. Спасибо за этот ответ! – thnee

1

Я провел некоторое время, пытаясь выяснить эту проблему, прежде чем я увидел объявление.

Решение, с которым я столкнулся, было решением закрытия (и это решение, которое я использовал ранее с формами модели Django).

Я пробовал метод curry(), как описано выше, но я просто не мог заставить его работать с Django 1.0, поэтому в конце я вернулся к методу закрытия.

Метод закрытия очень аккуратный, и только небольшая нечетность заключается в том, что определение класса вложено внутри представления или другой функции. Я думаю, что это выглядит странно для меня - это зависание от моего предыдущего опыта в программировании, и я думаю, что кто-то с фоном на более динамичных языках не будет биться веком!

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), ...) 
3

Карла Мейера выглядит очень элегантно. Я попытался реализовать его для 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) 

Тогда «запрос «ключевое слово распространяется на все формы-члены моего набора форм. Я доволен, но я понятия не имею, почему это работает - кажется, неправильно. Какие-либо предложения?

+0

Хммм ...Теперь, если я попытаюсь получить доступ к атрибуту формы экземпляра MyFormSet, он (правильно) возвращает вместо . Любые предложения о том, как получить доступ к фактической форме? Я пробовал 'MyFormSet.form.Meta.model'. – trubliphone

+0

Упс ... Я должен _call_ использовать функцию curries для доступа к форме. 'MyFormSet.form(). Meta.model'. На самом деле очевидно. – trubliphone

+0

Я пытаюсь применить ваше решение к своей проблеме, но я думаю, что не полностью понимаю ваш ответ. Любые идеи, если ваш подход может быть применен к моей проблеме здесь? http://stackoverflow.com/questions/14176265/limit-values-in-the-modelformset-field – finspin

0

Я новичок здесь, поэтому я не могу добавить комментарий. Я надеюсь, что этот код будет работать также:

ServiceFormSet = formset_factory(ServiceForm, extra=3) 

ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate)) 

, как для добавления дополнительных параметров к formset-х BaseFormSet вместо формы.

9

С момента совершения e091c18f50266097f648efc7cac2503968e9d217 в Вт Авг 14 23:44:46 2012 +0200 принятое решение больше не может работать.

Текущая версия django.forms.models.В функции modelform_factory() используется «метод построения типа», вызывающий функцию типа() в переданной форме для получения типа метакласса, а затем используя результат для построения объекта класса его типа на лету ::

# 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 

Тогда в вашем коде вы будете называть форму завода, как: :

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user)) 

предостережений:

  • это получил очень мало испытаний, по крайней мере сейчас
  • поставляемых параметры могут конфликтовать и перезаписывать те, которые используются любым кодом будет использовать объект, возвращаемый конструктором
+0

Спасибо, кажется, отлично работает в Django 1.10.1, в отличие от некоторых других решений здесь. – fpghost

+0

@fpghost помните, что, по крайней мере, до 1,9 (я по-прежнему не включен в 1.10 по ряду причин), если вам нужно всего лишь изменить QuerySet, на котором построена форма, вы можете обновить ее возвращаемый MyFormSet, изменив его атрибут .queryset перед его использованием. Менее гибкий, чем этот метод, но гораздо проще читать/понимать. – RobM

16

Это то, что работает для меня, 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): 

Надеется, что это поможет кому-то, взяли меня достаточно долго, чтобы понять это;)

+0

nvm ... У меня была опечатка (формы вместо формы ....) – gabn88

+1

Не могли бы вы объяснить мне, почему здесь нужен «staticmethod»? – fpghost

0

на основе this answer я нашел более четкое решение:

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)) 
+5

Django 1.9 сделал все это ненужным, вместо этого используйте form_kwargs. –

+0

В моей текущей работе нам нужно использовать устаревшее django 1.7 (( –

Смежные вопросы