Django가 사용자 정의 양식 매개 변수를 Formset에 전달


150

이것은 Django 1.9에서 form_kwargs 로 수정되었습니다 .

다음과 같은 장고 양식이 있습니다.

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()

이제 이런 식으로 계열사 = request.affiliate를 개별 양식에 어떻게 전달할 수 있습니까?

답변:


105

내가 사용하는 것이 functools.partialfunctools.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에 영향을 미치지 않습니다 (즉, 서브 클래스 화를 어렵게 함).


그것은 나를 위해 작동하지 않습니다. 오류가 발생합니다 : AttributeError : '_curriedFormSet'개체에 'get'속성이 없습니다
Paolo Bergantino

이 오류를 복제 할 수 없습니다. 폼 세트에는 일반적으로 'get'속성이 없기 때문에 이상한 일이므로 코드에서 이상한 일을하는 것 같습니다. (또한 '_curriedFormSet'과 같은 이상한 것을 제거하는 방법으로 답변을 업데이트했습니다).
Carl Meyer

솔루션을 작동시키고 싶기 때문에 이것을 다시 방문하고 있습니다. 폼셋을 올바르게 선언 할 수 있지만 {{formset}}을 사용하여 인쇄하려고하면 "속성 'get'이 없습니다"오류가 발생합니다. 제공 한 솔루션 중 하나에서 발생합니다. 양식 세트를 반복하고 양식을 {{form}}으로 인쇄하면 오류가 다시 발생합니다. 예를 들어 {{form.as_table}}로 루프하고 인쇄하면 빈 양식 테이블이 나타납니다. 필드가 인쇄되지 않습니다. 어떤 아이디어?
Paolo Bergantino

네 말이 맞아 미안해 저의 이전 테스트는 충분히 진행되지 않았습니다. 나는 이것을 추적했으며 FormSets가 내부적으로 작동하는 방식에 약간의 이상한 점이 있기 때문에 고장납니다. 이 문제를 해결할 수있는 방법이 있지만 원래의 우아함을 잃기 시작합니다.
Carl Meyer

5
여기 주석 스레드가 의미가 없다면, functools.partialDjango ' s 대신 Python을 사용하도록 답변을 편집했기 때문 django.utils.functional.curry입니다. 그것들은 functools.partial일반 파이썬 함수 대신 별개의 호출 가능한 유형 을 반환하고 partial유형이 인스턴스 메소드로 바인드되지 않는다는 점을 제외하고는 동일한 작업을 수행합니다 .
Carl Meyer

81

공식 문서 방법

장고 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


8
이것은 지금 올바른 방법이어야합니다. 허용 된 답변이 작동하고 훌륭하지만 해킹입니다
Junchao Gu

확실히 최선의 대답과 올바른 방법입니다.
yaniv14


46

함수에서 폼 클래스를 동적으로 작성하여 클로저를 통해 가맹점에 액세스 할 수 있습니다.

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

고마워-효과가 있었다. 이 방법을 사용하면 분명히 펑키 한 느낌이 들기 때문에 더 깨끗한 옵션이 있기를 바랍니다.
Paolo Bergantino

이것이 가장 좋은 방법이므로 승인 된 것으로 표시합니다. 이상하게 느껴지지만 트릭을 수행합니다. :) 감사합니다.
Paolo Bergantino

Carl Meyer는 당신이 찾고있는 더 깨끗한 방식을 가지고 있다고 생각합니다.
Jarret Hardie

이 방법을 Django ModelForms와 함께 사용하고 있습니다.
chefsmart

이 솔루션이 마음에 들지만 폼셋과 같은 뷰에서 사용하는 방법을 잘 모르겠습니다. 뷰에서 이것을 사용하는 방법에 대한 좋은 예가 있습니까? 모든 제안을 부탁드립니다.
Joe J

16

이것이 장고 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):

그것이 누군가를 도울 수 있기를 바랍니다.


1
staticmethod여기에 필요한지 설명해 주 시겠습니까?
fpghost

9

나는 "깨끗한"그리고 더 파이썬적인 (따라서 +1 ~ mmarshall 답변) 클로저 솔루션을 좋아하지만 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

이것은 일반적인 아이디어를 제공해야합니다. 콜백을 이와 같은 객체 메소드로 만드는 것은 조금 더 복잡하지만 간단한 함수 콜백을 수행하는 것과는 달리 약간의 유연성을 제공합니다.


1
답변 감사합니다. 나는 지금 mmarshall의 솔루션을 사용하고 있으며 더 많은 Pythonic (내가 첫 번째 Python 프로젝트이므로 알 수없는 것)이라는 것에 동의하기 때문에 나는 그것을 고수하고 있다고 생각합니다. 그러나 콜백에 대해 아는 것이 좋습니다. 다시 감사합니다.
Paolo Bergantino

1
감사합니다. 이 방법은 modelformset_factory와 잘 작동합니다. 다른 방법으로 모델 형식 세트를 올바르게 사용할 수는 없었지만이 방법은 매우 간단했습니다.
스파이크

카레 기능은 본질적으로 폐쇄를 만듭니다. @mmarshall의 솔루션이 더 Pythonic이라고 말하는 이유는 무엇입니까? Btw, 답변 주셔서 감사합니다. 나는이 접근법을 좋아한다.
Josh

9

나는 이것을 Carl Meyers 답변에 대한 의견으로 배치하고 싶었지만 포인트가 필요하기 때문에 여기에 배치했습니다. 알아내는 데 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. 이 답변에 감사드립니다!
thnee

9

커밋 e091c18f50266097f648efc7cac2503968e9d217 8 월 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)

이것은 양식 대신에 curryed 나 partial객체 라도 "오리가 당신을 물게 만든다"는 말을 의미합니다. ModelFormClass객체 의 구성 매개 변수를 가진 함수를 호출하고 다음과 같은 오류 메시지를 반환합니다.

function() argument 1 must be code, not str

이 문제를 해결하기 위해 클로저를 사용하여 첫 번째 매개 변수로 지정된 클래스의 하위 클래스를 반환하는 생성기 함수를 작성했습니다. 그런 다음 생성기 함수의 호출에 제공된 클래스로 kwargs 를 호출 super.__init__한 후 호출 update합니다.

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

경고 :

  • 최소한 지금은 거의 테스트를받지 못했습니다.
  • 제공된 매개 변수는 생성자가 반환 한 개체를 사용하는 코드에서 사용하는 매개 변수를 충돌 및 덮어 쓸 수 있습니다.

감사합니다. 여기의 다른 솔루션과 달리 Django 1.10.1에서 매우 잘 작동하는 것 같습니다.
fpghost

1
@fpghost 양식을 구성하는 QuerySet을 변경하는 것만으로도 최소한 1.9까지 (여러 가지 이유로 여전히 1.10에 있지 않음) 기억해야합니다. 사용하기 전에 .queryset 속성을 변경하여 MyFormSet을 리턴했습니다. 이 방법보다 유연성은 떨어지지 만 읽기 / 이해하기가 훨씬 간단합니다.
RobM

3

Carl Meyer의 솔루션은 매우 우아해 보입니다. 모델 폼 세트에 구현하려고했습니다. 클래스 내에서 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 속성에 액세스하려고하면 <MyForm> 대신 <function _curried>를 반환합니다. 그래도 실제 양식에 액세스하는 방법에 대한 제안 사항이 있습니까? 시도했습니다 MyFormSet.form.Meta.model.
trubliphone

으악 ... 양식에 액세스하려면 카레 함수 를 호출 해야합니다 . MyFormSet.form().Meta.model. 정말이지
trubliphone

귀하의 솔루션을 내 문제에 적용하려고 노력했지만 귀하의 전체 답변을 완전히 이해하지 못한다고 생각합니다. 귀하의 접근 방식이 내 문제에 적용될 수 있다면 어떤 아이디어가 있습니까? stackoverflow.com/questions/14176265/…
finspin

1

나는이 게시물을보기 전에이 문제를 파악하려고 약간의 시간을 보냈습니다.

내가 생각해 낸 솔루션은 클로저 솔루션이었습니다 (그리고 이전에 Django 모델 양식에서 사용한 솔루션입니다).

위에서 설명한대로 curry () 메소드를 시도했지만 Django 1.0에서 작동하도록 할 수 없었으므로 결국 클로저 메소드로 돌아갔습니다.

클로저 메서드는 매우 깔끔하며 클래스 정의가 뷰 또는 다른 함수 안에 중첩되어 있다는 점이 약간 이상합니다. 나는 이것이 나에게 이상하게 보인다는 사실은 이전의 프로그래밍 경험에서 끊어진 것이라고 생각하며 더 역동적 인 언어의 배경을 가진 사람은 눈꺼풀을 치는 것이 아니라고 생각합니다!


1

나는 비슷한 일을해야했다. 이것은 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

이 답변을 바탕으로 보다 명확한 해결책을 찾았습니다.

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

6
Django 1.9는 이것을 불필요하게 만들었습니다. 대신 form_kwargs를 사용하십시오.
Paolo Bergantino

현재 작업에서 레거시 django 1.7 ((
alexey_efimov at 08:31

0

나는 여기에 초보자이므로 의견을 추가 할 수 없습니다. 이 코드가 작동하기를 바랍니다.

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

BaseFormSet폼 대신 폼 세트에 추가 매개 변수를 추가하는 것과 관련하여 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.