Jak uzyskać dostęp do obiektu żądania lub dowolnej innej zmiennej w metodzie clean () formularza?

99

Próbuję request.user dla czystej metody formularza, ale jak mogę uzyskać dostęp do obiektu żądania? Czy mogę zmodyfikować metodę czyszczenia, aby umożliwić wprowadzanie zmiennych?

nubela
źródło

Odpowiedzi:

157

Odpowiedź Bera - przechowywanie jej w „Threadlocals” - to bardzo zły pomysł. Nie ma absolutnie żadnego powodu, aby to robić w ten sposób.

Dużo lepszym sposobem jest przesłonić formie za __init__metodę do podjęcia dodatkowego argumentu słowa kluczowego request. Spowoduje to zapisanie żądania w formularzu tam, gdzie jest to wymagane i skąd możesz uzyskać do niego dostęp w swojej czystej metodzie.

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyForm, self).__init__(*args, **kwargs)


    def clean(self):
        ... access the request object via self.request ...

i Twoim zdaniem:

myform = MyForm(request.POST, request=request)
Daniel Roseman
źródło
4
Masz rację w tym przypadku. Jednak modyfikowanie formularzy / widoków w tym przypadku może nie być pożądane. Istnieją również przypadki użycia dla lokalnego magazynu wątków, w których dodanie parametrów metody lub zmiennych instancji jest niemożliwe. Pomyśl o wywoływalnym argumencie do filtru zapytań, który potrzebuje dostępu do danych. Nie możesz dodać parametru do wywołania ani nie ma żadnego wystąpienia do odniesienia.
Ber
4
Nie jest to przydatne, gdy rozszerzasz formularz administratora, ponieważ możesz zainicjować swój formularz, przekazując żądanie var. Dowolny pomysł?
Mordi
13
Dlaczego mówisz, że używanie magazynu lokalnego dla wątków jest bardzo złym pomysłem? Pozwala to uniknąć konieczności porzucania kodu do przekazywania żądania wszędzie.
Michael Mior
9
Nie przekazałbym samego obiektu żądania do formularza, ale raczej pola żądania, których potrzebujesz (np. Użytkownika), w przeciwnym razie powiązasz logikę formularza z cyklem żądanie / odpowiedź, co utrudnia testowanie.
Andrew Ingram,
2
Chris Pratt też ma dobre rozwiązanie do obsługi formularzy w admin.ModelAdmin
radtek
34

ZAKTUALIZOWANO 25.10.2011 : Teraz używam tego z dynamicznie tworzoną klasą zamiast metody, ponieważ w przeciwnym razie Django 1.3 wyświetla pewne dziwactwa.

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
        class ModelFormWithRequest(ModelForm):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return ModelForm(*args, **kwargs)
        return ModelFormWithRequest

Następnie nadpisz MyCustomForm.__init__w następujący sposób:

class MyCustomForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyCustomForm, self).__init__(*args, **kwargs)

Następnie możesz uzyskać dostęp do obiektu żądania z dowolnej metody ModelFormwith self.request.

Chris Pratt
źródło
1
Chris, że "def __init __ (self, request = None, * args, ** kwargs)" jest złe, ponieważ zakończy się żądaniem zarówno w pierwszym argumencie pozycyjnym, jak iw kwargach. Zmieniłem to na „def __init __ (self, * args, ** kwargs)” i to działa.
slinkp
1
Ups. To był tylko błąd z mojej strony. Zaniedbałem aktualizację tej części kodu, kiedy robiłem drugą aktualizację. Dzięki za połów. Zaktualizowano.
Chris Pratt,
4
Czy to naprawdę metaklasa? Myślę, że to zwykłe __new__przesłonięcie , dodajesz żądanie do kwargs, które później zostaną przekazane do metody klasy __init__. Nazwanie klasy ModelFormWithRequestwydaje mi się o wiele jaśniejsze w tym znaczeniu niż ModelFormMetaClass.
k4ml
2
To NIE jest metaklasa! Zobacz stackoverflow.com/questions/100003/…
frnhr
32

Co jest warte, jeśli używasz widoków opartych na klasach , zamiast widoków opartych na funkcjach, nadpisz get_form_kwargsw widoku do edycji. Przykładowy kod dla niestandardowego CreateView :

from braces.views import LoginRequiredMixin

class MyModelCreateView(LoginRequiredMixin, CreateView):
    template_name = 'example/create.html'
    model = MyModel
    form_class = MyModelForm
    success_message = "%(my_object)s added to your site."

    def get_form_kwargs(self):
        kw = super(MyModelCreateView, self).get_form_kwargs()
        kw['request'] = self.request # the trick!
        return kw

    def form_valid(self):
        # do something

Powyższy kod widoku będzie requestdostępny jako jeden z argumentów słów kluczowych __init__funkcji konstruktora formularza . Dlatego w twoim ModelForm:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        # important to "pop" added kwarg before call to parent's constructor
        self.request = kwargs.pop('request')
        super(MyModelForm, self).__init__(*args, **kwargs)
Joseph Victor Zammit
źródło
1
To zadziałało dla mnie. Robię notatkę, ponieważ i tak używałem get_form_kwargs ze względu na złożoną logikę WizardForm. Żadna inna odpowiedź, jaką widziałem, nie uwzględniła WizardForm.
datakid
2
Czy ktoś oprócz mnie uważa, że ​​to tylko wielki bałagan, aby zrobić coś, co jest dość podstawowe dla frameworka internetowego? Django jest świetne, ale to sprawia, że ​​w ogóle nie chcę używać CBV.
trpt4him
1
IMHO zalety CBV znacznie przewyższają wady FBV, zwłaszcza jeśli pracujesz nad dużym projektem z ponad 25 programistami piszącymi kod, który ma na celu 100% pokrycie testów jednostkowych. Nie jestem pewien, czy nowsze wersje Django zapewniają automatyczne umieszczanie requestobiektu w get_form_kwargsśrodku.
Joseph Victor Zammit,
Czy w podobny sposób można uzyskać dostęp do identyfikatora instancji obiektu w get_form_kwargs?
Hassan Baig
1
@HassanBaig Ewentualnie używasz self.get_object? CreateViewRozszerza SingleObjectMixin. Ale to, czy to zadziała, czy zgłosi wyjątek, zależy od tego, czy tworzysz nowy obiekt, czy aktualizujesz istniejący; tzn. przetestuj oba przypadki (i oczywiście usuń).
Joseph Victor Zammit
17

Typowym podejściem jest przechowywanie obiektu żądania w odwołaniu lokalnym wątku przy użyciu oprogramowania pośredniczącego. Następnie możesz uzyskać do niego dostęp z dowolnego miejsca w aplikacji, w tym z metody Form.clean ().

Zmiana sygnatury metody Form.clean () oznacza, że ​​masz własną, zmodyfikowaną wersję Django, która może nie być tym, czego chcesz.

Dziękuję, liczba oprogramowania pośredniego wygląda mniej więcej tak:

import threading
_thread_locals = threading.local()

def get_current_request():
    return getattr(_thread_locals, 'request', None)

class ThreadLocals(object):
    """
    Middleware that gets various objects from the
    request object and saves them in thread local storage.
    """
    def process_request(self, request):
        _thread_locals.request = request

Zarejestruj to oprogramowanie pośredniczące zgodnie z opisem w dokumentacji Django

Ber
źródło
2
Pomimo powyższych uwag ta metoda działa, a druga nie. Ustawienie atrybutu obiektu formularza w init nie przenosi niezawodnie na czyste metody, podczas gdy ustawienie lokalnych wątków pozwala na przeniesienie tych danych.
rplevy
4
@rplevy czy faktycznie przekazałeś obiekt żądania podczas tworzenia wystąpienia formularza? Jeśli nie zauważyłeś, używa argumentów słów kluczowych **kwargs, co oznacza, że ​​będziesz musiał przekazać obiekt żądania jako MyForm(request.POST, request=request).
unode
13

Dla administratora Django, w Django 1.8

class MyModelAdmin(admin.ModelAdmin):
    ...
    form = RedirectForm

    def get_form(self, request, obj=None, **kwargs):
        form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form
François Constant
źródło
1
Najwyżej oceniana metoda powyżej rzeczywiście wydaje się przestać działać gdzieś pomiędzy Django 1.6 a 1.9. Ten działa i jest znacznie krótszy. Dzięki!
Raik
9

Napotkałem ten konkretny problem podczas dostosowywania administratora. Chciałem, aby pewne pole zostało zweryfikowane na podstawie danych uwierzytelniających konkretnego administratora.

Ponieważ nie chciałem modyfikować widoku, aby przekazać żądanie jako argument do formularza, wykonałem następujące czynności:

class MyCustomForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def clean(self):
        # make use of self.request here

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        def form_wrapper(*args, **kwargs):
            a = ModelForm(*args, **kwargs)
            a.request = request
            return a
    return form_wrapper
entropia
źródło
Dziękuję za to. Szybka literówka: obj=objnie obj=Nonew linii 11.
François Constant
Naprawdę fajna odpowiedź, uwielbiam to!
Luke Dupin
Django 1.9 dostarcza: 'function' object has no attribute 'base_fields'. Jednak prostsza (bez zamknięcia) odpowiedź @ François działa płynnie.
raratiru
5

Nie zawsze możesz użyć tej metody (i jest to prawdopodobnie zła praktyka), ale jeśli używasz formularza tylko w jednym widoku, możesz określić zakres wewnątrz samej metody widoku.

def my_view(request):

    class ResetForm(forms.Form):
        password = forms.CharField(required=True, widget=forms.PasswordInput())

        def clean_password(self):
            data = self.cleaned_data['password']
            if not request.user.check_password(data):
                raise forms.ValidationError("The password entered does not match your account password.")
            return data

    if request.method == 'POST':
        form = ResetForm(request.POST, request.FILES)
        if form.is_valid():

            return HttpResponseRedirect("/")
    else:
        form = ResetForm()

    return render_to_response(request, "reset.html")
Chris
źródło
Czasami jest to naprawdę fajne rozwiązanie: często robię to get_form_classmetodą CBV , jeśli wiem, że muszę zrobić wiele rzeczy z żądaniem. Wielokrotne tworzenie klasy może wiązać się z pewnym narzutem, ale to po prostu przenosi ją z czasu importu do czasu wykonywania.
Matthew Schinckel
5

Odpowiedź Daniela Rosemana jest nadal najlepsza. Jednak użyłbym pierwszego argumentu pozycyjnego dla żądania zamiast argumentu słowa kluczowego z kilku powodów:

  1. Nie ryzykujesz zastąpienia kwarg o tej samej nazwie
  2. Żądanie jest opcjonalne, co nie jest właściwe. Atrybut żądania nigdy nie powinien mieć wartości Brak w tym kontekście.
  3. Możesz przejrzyście przekazać argumenty i kwargi do klasy nadrzędnej bez konieczności ich modyfikowania.

Na koniec użyłbym bardziej unikalnej nazwy, aby uniknąć zastępowania istniejącej zmiennej. Tak więc Moja zmodyfikowana odpowiedź wygląda następująco:

class MyForm(forms.Form):

  def __init__(self, request, *args, **kwargs):
      self._my_request = request
      super(MyForm, self).__init__(*args, **kwargs)


  def clean(self):
      ... access the request object via self._my_request ...
Andres Restrepo
źródło
3

Mam inną odpowiedź na to pytanie, zgodnie z twoim wymaganiem, aby uzyskać dostęp do użytkownika do czystej metody formularza. Możesz tego spróbować. View.py

person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)

forms.py

def __init__(self,*arg,**kwargs):
    self.instance=kwargs.get('instance',None)
    if kwargs['instance'] is not None:
        del kwargs['instance']
    super(Myform, self).__init__(*args, **kwargs)

Teraz możesz uzyskać dostęp do self.instance w dowolnej czystej metodzie w form.py

Nishant Kashyap
źródło
0

Kiedy chcesz uzyskać do niego dostęp przez „przygotowane” widoki klas Django, CreateViewto jest taka mała sztuczka, którą trzeba znać (= oficjalne rozwiązanie nie działa po wyjęciu z pudełka). W swoim własnym CreateView będziesz musiał dodać taki kod:

class MyCreateView(LoginRequiredMixin, CreateView):
    form_class = MyOwnForm
    template_name = 'my_sample_create.html'

    def get_form_kwargs(self):
        result = super().get_form_kwargs()
        result['request'] = self.request
        return result

= w skrócie jest to rozwiązanie, które należy przekazać requestdo formularza z widokami tworzenia / aktualizacji Django.

Olivier Pons
źródło