Django przekazuje własne parametry formularza do Formset

Zostało to naprawione w Django 1.9 z form_wargs .

Mam formę Django, która wygląda tak:

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)

Nazwę tę formę czymś takim:

form = ServiceForm(affiliate=request.affiliate)

Gdzie request.affiliate jest zalogowanym użytkownikiem. Działa to zgodnie z przeznaczeniem.

Mój problem polega na tym, że chcę teraz przekształcić ten pojedynczy formularz w zestaw formularzy. Nie mogę dowiedzieć się, w jaki sposób mogę przekazać informacje partnerskie do poszczególnych formularzy podczas tworzenia zestawu formularzy. Według dokumentów do zrób z tego formset muszę zrobić coś takiego:

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

A potem muszę go stworzyć w ten sposób:

formset = ServiceFormSet()

Teraz Jak mogę przekazać affiliate = request.afiliacja poszczególnych form w ten sposób?

Author: Paolo Bergantino, 2009-03-08

12 answers

Użyłbym functools.częściowe i 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)

Myślę, że jest to najczystsze podejście i w żaden sposób nie wpływa na ServiceForm (tj. utrudniając podklasę).

 101
Author: Carl Meyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-07 16:49:11

Zbudowałbym klasę form dynamicznie w funkcję, tak aby miała dostęp do afiliatora poprzez zamknięcie:

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

Jako bonus, nie musisz przepisywać queryset w polu opcji. Minusem jest to, że podklasowanie jest trochę funky. (Każda podklasa musi być wykonana w podobny sposób.)

Edit:

W odpowiedzi na komentarz, możesz wywołać tę funkcję w dowolnym miejscu, w którym użyjesz nazwy klasy:

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)
    ...
 44
Author: Matthew Marshall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 01:11:21

Oficjalny Sposób Dokumentu

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-parameters-to-formset-forms

 33
Author: roach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-29 04:50:58

To mi się udało, 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):

Mam nadzieję, że komuś pomoże, Zajęło mi to wystarczająco dużo czasu, aby to rozgryźć;)

 16
Author: rix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 14:29:59

Podoba mi się rozwiązanie zamknięcia za to, że jest "czystsze" i bardziej Pythoniczne (więc +1 do odpowiedzi mmarshall), ale formularze Django mają również mechanizm callback, którego możesz użyć do filtrowania zestawów zapytań w zestawach formularzy.

Nie jest to również udokumentowane, co moim zdaniem jest wskaźnikiem, że deweloperzy Django mogą nie lubić tego tak bardzo.

Więc zasadniczo tworzysz swój zestaw formularzy tak samo, ale dodajesz wywołanie zwrotne:

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

Tworzymy instancję klasy, która wygląda tak:

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

To powinno dam ci ogólny pomysł. Jest to trochę bardziej skomplikowane, dzięki czemu wywołanie zwrotne jest metodą obiektową, ale daje trochę więcej elastyczności, w przeciwieństwie do wykonywania prostej funkcji wywołania zwrotnego.

 9
Author: Van Gale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-08 07:08:41

Chciałem umieścić to jako komentarz do odpowiedzi Carla Meyersa, ale ponieważ wymaga to punktów, umieściłem go tutaj. Zajęło mi to 2 godziny, aby dowiedzieć się, więc mam nadzieję, że to pomoże komuś.

Uwaga o używaniu inlineformset_factory.

Używałem tego rozwiązania sam i działało idealnie, dopóki nie wypróbowałem go z inlineformset_factory. Używałem Django 1.0.2 i dostałem jakiś dziwny wyjątek KeyError. Uaktualniłem do najnowszego bagażnika i działał bezpośrednio.

I can now use to podobne do tego:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
 9
Author: Johan Berg Nilsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-11 12:36:03

Od zatwierdzenia e091c18f50266097f648efc7cac2503968e9d217 dnia Tue Aug 14 23: 44: 46 2012 +0200 zaakceptowane rozwiązanie nie może już działać.

Aktualna wersja django.formularze.modelki.funkcja modelform_factory() używa "techniki budowy typu", wywołując funkcję type () w przekazywanym formularzu, aby uzyskać Typ metaclass, a następnie używając wyniku do zbudowania obiektu klasy tego typu w locie:: {]}

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

Oznacza to nawet curry ed lub partial obiekt przekazany zamiast formularza "powoduje, że kaczka cię ugryzie", że tak powiem: wywoła funkcję o parametrach konstrukcyjnych obiektu ModelFormClass, zwracając komunikat o błędzie::

function() argument 1 must be code, not str

Aby obejść to, napisałem funkcję generatora, która używa zamknięcia, aby zwrócić podklasę dowolnej klasy określonej jako pierwszy parametr, która następnie wywołuje super.__init__ po update ing kwargs z tymi dostarczonymi w wywołaniu funkcji generatora::

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

Następnie w Twoim kodzie zadzwonisz do fabryki formularzy as::

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

Zastrzeżenia:

    To było bardzo mało testowane, przynajmniej na razie.]}
  • podane parametry mogą kolidować i nadpisywać te używane przez dowolny kod wykorzystujący obiekt zwracany przez konstruktor
 9
Author: RobM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-10 13:18:04

Rozwiązanie Carla Meyera wygląda bardzo elegancko. Próbowałem zaimplementować go dla zestawów modelowych. Byłem pod wrażeniem, że nie mogę nazwać staticmethods w klasie, ale następujące niewytłumaczalnie działa: {]}

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

Moim zdaniem, jeśli zrobię coś takiego:

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

Następnie słowo kluczowe "request" zostanie propagowane do wszystkich formularzy członkowskich mojego zestawu formularzy. Jestem zadowolony, ale nie mam pojęcia, dlaczego to działa-wydaje się to złe. Jakieś sugestie?

 3
Author: trubliphone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-09 07:16:01

Spędziłem trochę czasu próbując rozwiązać ten problem, zanim zobaczyłem ten post.

Rozwiązaniem, które wymyśliłem, było rozwiązanie zamknięcia (i jest to rozwiązanie, którego używałem wcześniej z formami modeli Django).

Próbowałem metody curry() opisanej powyżej, ale po prostu nie mogłem jej uruchomić z Django 1.0, więc w końcu wróciłem do metody closure.

Metoda closure jest bardzo zgrabna, a jedyną niewielką osobliwością jest to, że definicja klasy jest zagnieżdżona wewnątrz widoku lub inną funkcję. Myślę, że fakt, że to wygląda dziwnie dla mnie jest zawieszeniem z mojego poprzedniego doświadczenia w programowaniu i myślę, że ktoś z tłem w bardziej dynamicznych językach Nie mrugnie powieką!

 1
Author: Nick Craig-Wood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-11 12:34:46

Musiałem zrobić coś podobnego. Jest to podobne do rozwiązania 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
Author: Rory,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-27 12:12:55

Jestem tu nowicjuszem, więc nie mogę dodać komentarza. Mam nadzieję, że ten kod też zadziała:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

Co do dodawania dodatkowych parametrów do zestawu formularzy BaseFormSet zamiast formularza.

 0
Author: Philamer Sune,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-29 16:39:49

Na podstawie tej odpowiedzi znalazłem bardziej jasne rozwiązanie:

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

I uruchom go w widoku jak

formset_factory(form=ServiceForm.make_service_form(affiliate))
 0
Author: alexey_efimov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:32