Django chuyển các tham số biểu mẫu tùy chỉnh để định dạng


150

Điều này đã được sửa trong Django 1.9 với form_kwargs .

Tôi có một mẫu Django trông như thế này:

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)

Tôi gọi mẫu này với một cái gì đó như thế này:

form = ServiceForm(affiliate=request.affiliate)

Người request.affiliatedùng đã đăng nhập ở đâu. Điều này hoạt động như dự định.

Vấn đề của tôi là bây giờ tôi muốn biến dạng đơn này thành một dạng. Những gì tôi không thể tìm ra là làm thế nào tôi có thể chuyển thông tin liên kết đến các biểu mẫu riêng lẻ khi tạo biểu mẫu. Theo các tài liệu để tạo ra một định dạng về điều này, tôi cần phải làm một cái gì đó như thế này:

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

Và sau đó tôi cần tạo nó như thế này:

formset = ServiceFormSet()

Bây giờ làm thế nào tôi có thể chuyển liên kết = request.affiliate cho các biểu mẫu riêng lẻ theo cách này?

Câu trả lời:


105

Tôi sẽ sử dụng funcools.partialfuncools.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)

Tôi nghĩ rằng đây là cách tiếp cận sạch nhất và không ảnh hưởng đến ServiceForm theo bất kỳ cách nào (nghĩa là bằng cách gây khó khăn cho việc phân lớp).


Nó không làm việc cho tôi. Tôi gặp lỗi: AttributionError: đối tượng '_curriedFormSet' không có thuộc tính 'get'
Paolo Bergantino

Tôi không thể lặp lại lỗi này. Đây cũng là một số lẻ vì một bộ định dạng thường không có thuộc tính 'get', vì vậy có vẻ như bạn có thể đang làm điều gì đó kỳ lạ trong mã của mình. (Ngoài ra, tôi đã cập nhật câu trả lời với một cách để loại bỏ những điều kỳ quặc như '_curriedFormSet').
Carl Meyer

Tôi đang xem lại điều này bởi vì tôi muốn giải pháp của bạn hoạt động. Tôi có thể khai báo bộ định dạng tốt, nhưng nếu tôi cố in nó thì {{formset}} là khi tôi gặp lỗi "không có thuộc tính 'get'". Nó xảy ra với một trong hai giải pháp bạn cung cấp. Nếu tôi lặp qua bộ định dạng và in các biểu mẫu dưới dạng {{form}} thì tôi lại gặp lỗi. Ví dụ: nếu tôi lặp và in dưới dạng {{form.as_table}}, tôi sẽ nhận được các bảng biểu mẫu trống, tức là. không có trường nào được in Có ý kiến ​​gì không?
Paolo Bergantino

Bạn nói đúng, tôi xin lỗi; thử nghiệm trước đây của tôi đã không đi đủ xa. Tôi đã theo dõi điều này và nó bị hỏng do một số điểm kỳ lạ trong cách thức FormSets hoạt động nội bộ. Có một cách để giải quyết vấn đề, nhưng nó bắt đầu mất đi sự thanh lịch ban đầu ...
Carl Meyer

5
Nếu chuỗi nhận xét ở đây không có ý nghĩa, thì đó là vì tôi vừa chỉnh sửa câu trả lời để sử dụng Python functools.partialthay vì Django django.utils.functional.curry. Chúng làm điều tương tự, ngoại trừ functools.partialtrả về một loại có thể gọi riêng biệt thay vì hàm Python thông thường và partialloại không liên kết như một phương thức cá thể, giải quyết gọn gàng vấn đề mà luồng nhận xét này chủ yếu dành cho gỡ lỗi.
Carl Meyer

81

Cách thức tài liệu chính thức

Django 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-parameter-to-formset-forms


8
đây là cách chính xác để làm điều đó ngay bây giờ câu trả lời được chấp nhận hoạt động tốt và là một hack
Junchao Gu

chắc chắn câu trả lời tốt nhất và cách chính xác để làm điều đó.
yaniv14


46

Tôi sẽ xây dựng lớp biểu mẫu một cách linh hoạt trong một hàm, để nó có quyền truy cập vào liên kết thông qua việc đóng:

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

Là một phần thưởng, bạn không phải viết lại bộ truy vấn trong trường tùy chọn. Nhược điểm là phân lớp là một chút sôi nổi. (Bất kỳ lớp con nào cũng phải được thực hiện theo cách tương tự.)

biên tập:

Để trả lời một bình luận, bạn có thể gọi hàm này về bất kỳ nơi nào bạn sẽ sử dụng tên lớp:

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

Cảm ơn - đã làm việc. Tôi đang giữ việc đánh dấu điều này là được chấp nhận bởi vì tôi hy vọng có một lựa chọn sạch hơn, vì làm theo cách này chắc chắn sẽ cảm thấy thú vị.
Paolo Bergantino

Đánh dấu là được chấp nhận vì rõ ràng đây là cách tốt nhất để làm điều đó. Cảm thấy kỳ lạ, nhưng thực hiện các mẹo. :) Cảm ơn bạn.
Paolo Bergantino

Carl Meyer, tôi nghĩ, cách sạch hơn mà bạn đang tìm kiếm.
Jarret Hardie

Tôi đang sử dụng phương pháp này với Django ModelForms.
Chefsmart

Tôi thích giải pháp này, nhưng tôi không chắc làm thế nào để sử dụng nó trong một khung nhìn như một bộ định dạng. Bạn có bất kỳ ví dụ tốt về cách sử dụng này trong một khung nhìn không? Bất kỳ đề xuất được đánh giá cao.
Joe J

16

Đây là những gì làm việc cho tôi, 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):

Hy vọng nó sẽ giúp được ai đó, đưa tôi đủ lâu để tìm ra nó;)


1
Bạn có thể giải thích cho tôi tại sao staticmethodcần thiết ở đây?
fpghost

9

Tôi thích giải pháp đóng cửa để "sạch" hơn và nhiều Pythonic hơn (câu trả lời +1 cho mmarshall) nhưng các mẫu Django cũng có cơ chế gọi lại mà bạn có thể sử dụng để lọc các truy vấn trong các bộ định dạng.

Nó cũng không được ghi lại, mà tôi nghĩ là một chỉ số mà các nhà phát triển Django có thể không thích nó nhiều.

Vì vậy, về cơ bản bạn tạo bộ định dạng của mình giống nhau nhưng thêm hàm gọi lại:

ServiceFormSet = forms.formsets.formset_factory(
    ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)

Điều này đang tạo ra một thể hiện của một lớp trông như thế này:

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

Điều này sẽ cung cấp cho bạn ý tưởng chung. Nó phức tạp hơn một chút làm cho phương thức gọi lại trở thành một phương thức đối tượng như thế này, nhưng cho phép bạn linh hoạt hơn một chút so với thực hiện một cuộc gọi lại chức năng đơn giản.


1
Cảm ơn bạn đã trả lời. Tôi đang sử dụng giải pháp của mmarshall ngay bây giờ và vì bạn đồng ý rằng đó là Pythonic nhiều hơn (điều mà tôi không biết vì đây là dự án Python đầu tiên của tôi) Tôi đoán rằng tôi đang gắn bó với điều đó. Mặc dù vậy, chắc chắn là tốt để biết về cuộc gọi lại. Cảm ơn một lần nữa.
Paolo Bergantino

1
Cảm ơn bạn. Cách này hoạt động tuyệt vời với modelformset_factory. Tôi không thể có được những cách khác làm việc với các mô hình đúng cách nhưng cách này rất đơn giản.
Spike

Các chức năng cà ri về cơ bản tạo ra một đóng cửa, phải không? Tại sao bạn nói rằng giải pháp của @ mmarshall là Pythonic hơn? Btw, cảm ơn câu trả lời của bạn. Tôi thích cách tiếp cận này.
Josh

9

Tôi muốn đặt nó như một bình luận cho câu trả lời của Carl Meyers, nhưng vì điều đó đòi hỏi phải có điểm nên tôi mới đặt nó ở đây. Điều này khiến tôi mất 2 giờ để tìm ra vì vậy tôi hy vọng nó sẽ giúp được ai đó.

Một lưu ý về việc sử dụng inlineformset_factory.

Tôi đã sử dụng giải pháp đó cho bản thân mình và nó đã hoạt động hoàn hảo, cho đến khi tôi thử nó với inlineformset_factory. Tôi đang chạy Django 1.0.2 và có một số ngoại lệ KeyError lạ. Tôi nâng cấp lên thân cây mới nhất và nó hoạt động trực tiếp.

Bây giờ tôi có thể sử dụng nó tương tự như thế này:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))

Điều tương tự cũng diễn ra modelformset_factory. Cảm ơn câu trả lời này!
thnee

9

Theo cam kết e091c18f50266097f648efc7cac2503968e9d217 vào ngày 14 tháng 8 23:44:46 2012 +0200 giải pháp được chấp nhận không thể hoạt động được nữa.

Phiên bản hiện tại của hàm django.forms.models.modelform_factory () sử dụng "kỹ thuật xây dựng kiểu", gọi hàm type () trên biểu mẫu đã truyền để lấy loại siêu dữ liệu, sau đó sử dụng kết quả để xây dựng một đối tượng lớp của nó gõ trên bay ::

# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)

Điều này có nghĩa là ngay cả một curryed hoặc partialđối tượng được truyền thay vì một hình thức "khiến con vịt cắn bạn" để nói: nó sẽ gọi một hàm với các tham số xây dựng của một ModelFormClassđối tượng, trả về thông báo lỗi ::

function() argument 1 must be code, not str

Để giải quyết vấn đề này, tôi đã viết một hàm tạo sử dụng hàm đóng để trả về một lớp con của bất kỳ lớp nào được chỉ định làm tham số đầu tiên, sau đó gọi super.__init__sau khi nhập updatecác kwarg với các hàm được cung cấp trong lệnh gọi của hàm tạo ::

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

Sau đó, trong mã của bạn, bạn sẽ gọi nhà máy mẫu là ::

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

hãy cẩn thận

  • điều này nhận được rất ít thử nghiệm, ít nhất là cho đến bây giờ
  • các tham số được cung cấp có thể xung đột và ghi đè lên những tham số được sử dụng bởi bất kỳ mã nào sẽ sử dụng đối tượng được trả về bởi hàm tạo

Cảm ơn, dường như hoạt động rất tốt trong Django 1.10.1 không giống như một số giải pháp khác ở đây.
fpghost

1
@fpghost hãy nhớ rằng, ít nhất là tới 1.9 (tôi vẫn không phải là 1.10 vì một số lý do) nếu tất cả những gì bạn cần làm là thay đổi Truy vấn mà biểu mẫu được tạo, bạn có thể cập nhật nó trên đã trả lại MyFormSet bằng cách thay đổi thuộc tính .queryset trước khi sử dụng. Ít linh hoạt hơn phương pháp này, nhưng đơn giản hơn nhiều để đọc / hiểu.
RobM

3

Giải pháp của Carl Meyer trông rất thanh lịch. Tôi đã thử thực hiện nó cho modelformsets. Tôi có ấn tượng rằng tôi không thể gọi tĩnh trong một lớp, nhưng các hoạt động không thể giải thích sau đây:

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

Theo quan điểm của tôi, nếu tôi làm một cái gì đó như thế này:

formset = MyFormset(request.POST,queryset=MyModel.objects.all(),request=request)

Sau đó, từ khóa "yêu cầu" được truyền đến tất cả các dạng thành viên trong tập hợp mẫu của tôi. Tôi hài lòng, nhưng tôi không biết tại sao điều này lại hiệu quả - có vẻ sai. Bất kỳ đề xuất?


Hmmm ... Bây giờ nếu tôi cố gắng truy cập thuộc tính biểu mẫu của một phiên bản MyFormset, nó sẽ trả về <function _curried> thay vì <MyForm>. Bất kỳ đề xuất về cách truy cập các hình thức thực tế, mặc dù? Tôi đã thử MyFormSet.form.Meta.model.
trubliphone

Rất tiếc ... Tôi phải gọi hàm curried để truy cập biểu mẫu. MyFormSet.form().Meta.model. Rõ ràng thực sự.
trubliphone

Tôi đã cố gắng áp dụng giải pháp của bạn cho vấn đề của mình nhưng tôi nghĩ tôi không hiểu hết câu trả lời của bạn. Bất kỳ ý tưởng nếu cách tiếp cận của bạn có thể được áp dụng cho vấn đề của tôi ở đây? stackoverflow.com/questions/14176265/ từ
finspin

1

Tôi đã dành một chút thời gian để cố gắng tìm ra vấn đề này trước khi tôi thấy bài đăng này.

Giải pháp tôi đưa ra là giải pháp đóng cửa (và đó là giải pháp tôi đã sử dụng trước đây với các mẫu mô hình Django).

Tôi đã thử phương thức curry () như được mô tả ở trên, nhưng tôi không thể làm cho nó hoạt động được với Django 1.0 nên cuối cùng tôi đã trở lại phương thức đóng.

Phương thức đóng rất gọn gàng và điều kỳ lạ duy nhất là định nghĩa lớp được lồng bên trong khung nhìn hoặc hàm khác. Tôi nghĩ rằng thực tế điều này có vẻ kỳ lạ đối với tôi là sự nôn nao từ kinh nghiệm lập trình trước đây của tôi và tôi nghĩ ai đó có nền tảng về ngôn ngữ năng động hơn sẽ không chớp mắt!


1

Tôi đã phải làm một điều tương tự. Điều này tương tự như currygiải pháp:

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

Dựa trên câu trả lời này, tôi tìm thấy giải pháp rõ ràng hơn:

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

Và chạy nó trong chế độ xem như

formset_factory(form=ServiceForm.make_service_form(affiliate))

6
Django 1.9 thực hiện bất kỳ điều gì không cần thiết, thay vào đó hãy sử dụng form_kwargs.
Paolo Bergantino

Trong công việc hiện tại của chúng tôi, chúng tôi cần sử dụng di sản django 1.7 ((
alexey_efimov

0

Tôi là người mới ở đây vì vậy tôi không thể thêm nhận xét. Tôi hy vọng mã này cũng sẽ hoạt động:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

như để thêm các tham số bổ sung BaseFormSetvào bộ định dạng thay vì biểu mẫu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.