Django Özel Form Parametrelerini Formset'e Geçiriyor


150

Bu, Django 1.9'da form_kwargs ile düzeltildi .

Şöyle bir Django formu var:

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)

Bu forma şöyle bir şey diyorum:

form = ServiceForm(affiliate=request.affiliate)

Giriş request.affiliateyapan kullanıcı nerede . Bu amaçlandığı gibi çalışır.

Benim sorunum şimdi bu tek formu bir form kümesine dönüştürmek istiyorum. Anlayamadığım şey, form seti oluştururken bağlı kuruluş bilgisini bireysel formlara nasıl aktarabileceğim. Bundan bir kalıp yapmak için belgelere göre böyle bir şey yapmam gerekiyor:

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

Ve sonra böyle yaratmalıyım:

formset = ServiceFormSet()

Şimdi nasıl affiliate = request.affiliate bireysel formlara bu şekilde geçirebilirim?

Yanıtlar:


105

Ben kullanacağı functools.partial ve 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)

Bunun en temiz yaklaşım olduğunu ve ServiceForm'u hiçbir şekilde etkilemediğini düşünüyorum (alt sınıflamayı zorlaştırarak).


Benim için çalışmıyor. Hatayı alıyorum: AttributeError: '_curriedFormSet' nesnesinin 'get' özelliği yok
Paolo Bergantino

Bu hatayı kopyalayamıyorum. Aynı zamanda tuhaf bir formdur, çünkü bir form kümesi genellikle 'get' özniteliğine sahip değildir, bu nedenle kodunuzda garip bir şey yapıyor olabilirsiniz. (Ayrıca, cevabı '_curriedFormSet' gibi tuhaflıklardan kurtulmanın bir yolu ile güncelledim).
Carl Meyer

Bunu tekrar ziyaret ediyorum çünkü çözümünüzü çalıştırmak istiyorum. Ben form seti ince ilan edebilir, ancak {{formset}} yapıyor yazdırmaya çalışırsanız "olsun 'özniteliği yok" hatası alırsınız. Sağladığınız her iki çözümde de olur. Form seti arasında dolaşıp formları {{form}} olarak yazdırırsam hatayı tekrar alıyorum. Örneğin {{form.as_table}} olarak döngü ve yazdırırsam, boş form tabloları alıyorum, yani. hiçbir alan yazdırılmaz. Herhangi bir fikir?
Paolo Bergantino

Haklısın özür dilerim; önceki testlerim yeterince ileri gitmedi. Bunu izledim ve FormSets'in dahili olarak çalışma biçimindeki bazı tuhaflıklar nedeniyle kırılıyor. Sorunu çözmek için bir yol var, ama orijinal zarafetini kaybetmeye başlıyor ...
Carl Meyer

5
Buradaki yorum dizisi mantıklı değilse, bunun nedeni functools.partial, Django yerine Python'ları kullanmak için cevabı düzenlediğim için django.utils.functional.curry. Aynı şeyi yaparlar, ancak functools.partialnormal bir Python işlevi yerine farklı bir çağrılabilir tür döndürür ve partialtür, bu yorum iş parçacığının büyük ölçüde hata ayıklamaya ayırdığı sorunu düzgün bir şekilde çözen bir örnek yöntemi olarak bağlanmaz.
Carl Meyer


46

Form sınıfını dinamik olarak bir işlevde derlerdim, böylece kapatma yoluyla bağlı kuruluşa erişimi olur:

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

Bonus olarak, sorgu kümesini seçenek alanına yeniden yazmanız gerekmez. Dezavantajı, alt sınıfların biraz korkak olmasıdır. (Herhangi bir alt sınıf benzer şekilde yapılmalıdır.)

Düzenle:

Bir yoruma yanıt olarak, sınıf adını kullanacağınız herhangi bir yer hakkında bu işlevi çağırabilirsiniz:

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

Teşekkürler - işe yaradı. Bunu kabul edilmiş olarak işaretlemeye devam ediyorum çünkü daha temiz bir seçenek olduğunu umuyorum, çünkü bu şekilde yapmak kesinlikle korkak geliyor.
Paolo Bergantino

Görünüşe göre bu kabul etmenin en iyi yolu budur. Garip geliyor ama hile yapıyor. :) Teşekkür ederim.
Paolo Bergantino

Carl Meyer, sanırım, aradığınız daha temiz bir yola sahip.
Jarret Hardie

Bu yöntemi Django ModelForms ile kullanıyorum.
chefsmart

Bu çözümü seviyorum, ancak bir form seti gibi bir görünümde nasıl kullanacağımdan emin değilim. Bunu bir görünümde nasıl kullanacağınıza dair iyi örnekleriniz var mı? Herhangi bir öneriniz takdir edilmektedir.
Joe J

16

Bu benim için işe yarayan 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):

Umarım birine yardımcı olur, anlamaya yetecek kadar uzun sürdü;)


1
Bana neden staticmethodburada ihtiyaç duyulduğunu açıklayabilir misiniz ?
fpghost

9

Ben "temiz" ve daha Pythonic (yani +1 mmarshall cevap) olmak için kapatma çözümü gibi ama Django formları da form kümeleri sorgulama filtreleme için kullanabileceğiniz bir geri arama mekanizması var.

Ayrıca belgelenmedi, bu da Django geliştiricilerinin beğenmediği bir gösterge olduğunu düşünüyorum.

Temel olarak form setinizi aynı şekilde oluşturursunuz, ancak geri aramayı eklersiniz:

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

Bu, şuna benzer bir sınıf örneği oluşturur:

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

Bu size genel bir fikir vermelidir. Geri çağrıyı böyle bir nesne yöntemi haline getirmek biraz daha karmaşıktır, ancak basit bir işlev geri çağrısı yapmanın aksine size biraz daha esneklik sağlar.


1
Cevap için teşekkür ederim. Şu anda mmarshall'ın çözümünü kullanıyorum ve daha Pythonic (bu benim ilk Python projem olduğu için bilemeyeceğim bir şey) olduğunu kabul ettiğiniz için sanırım buna bağlıyım. Yine de geri arama hakkında bilmek kesinlikle iyi. Tekrar teşekkürler.
Paolo Bergantino

1
Teşekkür ederim. Bu şekilde modelformset_factory ile harika çalışır. Modelformsets ile çalışmanın diğer yollarını düzgün bir şekilde alamadım ama bu yol çok basitti.
Spike

Köri işlevselliği esasen bir kapak oluşturur, değil mi? Neden @ mmarshall'ın çözümünün daha Pythonic olduğunu söylüyorsunuz? Btw, cevabın için teşekkürler. Bu yaklaşımı seviyorum.
Josh

9

Bunu Carl Meyers'ın cevabına bir yorum olarak yerleştirmek istedim, ancak bu puan gerektirdiğinden buraya yerleştirdim. Bu bana anlaması için 2 saat sürdü, umarım birisine yardım eder.

İnlineformset_factory kullanımı hakkında bir not.

Bu çözümü kendim kullandım ve inlineformset_factory ile deneyene kadar mükemmel çalıştı. Django 1.0.2 çalıştırıyordum ve bazı tuhaf KeyError istisnası var. En son bagaja geçtim ve doğrudan çalıştı.

Şimdi buna benzer kullanabilirsiniz:

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

Aynı şey için de geçerli modelformset_factory. Bu cevap için teşekkürler!
thnee

9

E091c18f50266097f648efc7cac2503968e9d217 Sal 14 23:44:46 2012 +0200 tarihinde kabul edilen çözüm artık çalışamaz.

Django.forms.models.modelform_factory () işlevinin geçerli sürümü, metasınıf türünü almak için iletilen formdaki type () işlevini çağıran ve ardından sonucunu bir sınıf nesnesi oluşturmak için sonucu kullanan bir "tip inşaat tekniği" kullanır anında yazın ::

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

Bu , bir form yerine geçirilen bir curryed veya partialnesne bile "ördeğin sizi ısırmasına neden olur" anlamına gelir : tabiri caizse, bir ModelFormClassnesnenin yapım parametreleriyle bir işlev çağırır ve hata mesajını döndürür:

function() argument 1 must be code, not str

Bu sorunu gidermek için o zaman çağrıları, ilk parametre olarak belirtilen herhangi bir sınıfının bir alt döndürmek için bir kapatma kullanan bir jeneratör işlevi yazdım super.__init__sonra updatejeneratör işlevin çağrısı üzerine temin olanlarla kwargs ing ::

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

Daha sonra kodunuzda form fabrikasını şöyle çağırırsınız:

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

uyarılar:

  • bu çok az test aldı, en azından şimdilik
  • Sağlanan parametreler, yapıcı tarafından döndürülen nesneyi kullanacak olan herhangi bir kod tarafından kullanılanların çakışmasına ve üzerine yazılmasına neden olabilir

Teşekkürler, buradaki diğer çözümlerin aksine Django 1.10.1'de çok iyi çalışıyor gibi görünüyor.
fpghost

1
@fpghost, en azından 1.9'a kadar (birkaç nedenden dolayı hala 1.10'da değilim) unutmayın; yapmanız gereken tek şey, formun oluşturulduğu QuerySet'i değiştirmekse, Kullanmadan önce .queryset özniteliğini değiştirerek MyFormSet öğesini döndürdü. Bu yöntemden daha az esnek, ancak okunması / anlaşılması çok daha basit.
RobM

3

Carl Meyer'ın çözümü çok zarif görünüyor. Model form setleri için uygulamayı denedim. Bir sınıf içinde statik metodları çağıramadığım izlenimi altındaydım, ancak aşağıdaki açıklanamaz şekilde çalışıyor:

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

Bence böyle bir şey yaparsam:

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

Sonra "istek" anahtar kelime benim form kümesinin tüm üye formlarına yayılır. Memnun oldum, ama bunun neden çalıştığı hakkında hiçbir fikrim yok - yanlış görünüyor. Baska öneri?


Hmmm ... Şimdi bir MyFormSet örneğinin form özniteliğine erişmeye çalışırsam, (doğru) <MyForm> yerine <işlev _curried> işlevini döndürür. Yine de, gerçek forma nasıl erişilebileceğine dair herhangi bir öneriniz var mı? Denedim MyFormSet.form.Meta.model.
trubliphone

Hata! Forma erişmek için curried fonksiyonunu çağırmalıyım . MyFormSet.form().Meta.model. Açıkçası.
trubliphone

Çözümünüzü sorunuma uygulamaya çalışıyordum ama bence tüm cevabınızı tam olarak anlamıyorum. Yaklaşımınız burada benim sorunuma uygulanabiliyor mu? stackoverflow.com/questions/14176265/…
finspin

1

Bu gönderiyi görmeden önce bu sorunu çözmeye çalışırken biraz zaman geçirdim.

Geldiğim çözüm kapatma çözümüydü (ve daha önce Django model formlarıyla kullandığım bir çözüm).

Yukarıda açıklandığı gibi curry () yöntemini denedim, ancak sonunda Django 1.0 ile çalışmak için alamadım, böylece sonunda kapatma yöntemine döndü.

Kapatma yöntemi çok düzgün ve sadece hafif gariplik, sınıf tanımının görünümün veya başka bir işlevin içinde iç içe olmasıdır. Bunun bana garip gelmesi, önceki programlama deneyimimin bir hangisi olduğunu ve daha dinamik dillerde geçmişi olan birinin göz kapağını vurmayacağını düşünüyorum!


1

Benzer bir şey yapmak zorundaydım. Bu, curryçözüme benzer :

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

bu cevaba dayanarak daha net bir çözüm buldum:

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

Ve görünüşte çalıştırın

formset_factory(form=ServiceForm.make_service_form(affiliate))

6
Django 1.9 bu gereksiz herhangi yaptı, bunun yerine form_kwargs kullanın.
Paolo Bergantino

Şu anki işimde eski django 1.7 kullanmamız gerekiyor ((
alexey_efimov

0

Burada bir acemi olduğum için yorum ekleyemiyorum. Umarım bu kod da çalışır:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

BaseFormSetform yerine form yerine ek parametreler eklemek için .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.