Django Передача пользовательских параметров формы в Formset - PullRequest
139 голосов
/ 08 марта 2009

Это было исправлено в 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 ]

104 голосов
/ 08 марта 2009

Я бы использовал 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 (т. Е. Усложняет подкласс).

47 голосов
/ 05 марта 2016

Официальный документ Way

Джанго 2.0:

ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})

https://docs.djangoproject.com/en/2.0/topics/forms/formsets/#passing-custom-parameters-to-formset-forms

45 голосов
/ 08 марта 2009

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

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)
    ...
16 голосов
/ 18 сентября 2014

Вот что у меня сработало, Джанго 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):

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

9 голосов
/ 10 сентября 2014

На момент коммита 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 /420620/django-peredacha-polzovatelskih-parametrov-formy-v-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))

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

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

Я хотел разместить это как комментарий к ответу Карла Мейерса, но, поскольку это требует баллов, я просто разместил его здесь. Это заняло у меня 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))
9 голосов
/ 08 марта 2009

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

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

3 голосов
/ 09 февраля 2012

Решение Карла Мейера выглядит очень элегантно. Я пытался реализовать его для моделей форм. У меня сложилось впечатление, что я не могу вызывать статические методы внутри класса, но необъяснимо работает следующее:

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» распространяется на все формы-члены моего набора форм. Я доволен, но я понятия не имею, почему это работает - кажется, неправильно. Есть предложения?

1 голос
/ 27 октября 2010

Я должен был сделать подобное. Это похоже на решение 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), ... )
1 голос
/ 27 августа 2009

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...