Come si accede all'oggetto richiesta oa qualsiasi altra variabile nel metodo clean () di un form?


99

Sto provando a request.user per il metodo pulito di un modulo, ma come posso accedere all'oggetto richiesta? Posso modificare il metodo pulito per consentire l'immissione di variabili?

Risposte:


157

La risposta di Ber - memorizzandola in threadlocals - è una pessima idea. Non c'è assolutamente alcun motivo per farlo in questo modo.

Un modo migliore è quello di ignorare del form __init__metodo per prendere un argomento in più parole chiave, request. Questo memorizza la richiesta nel modulo , dove è richiesto e da dove puoi accedervi nel tuo metodo pulito.

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

e secondo te:

myform = MyForm(request.POST, request=request)

4
Hai ragione in questo caso. Tuttavia, potrebbe non essere desiderabile modificare Moduli / Visualizzazioni in questo caso. Inoltre, ci sono casi d'uso per l'archivio locale del thread in cui è impossibile aggiungere parametri di metodo o variabili di istanza. Pensa a un argomento richiamabile per un filtro di query che necessita dell'accesso per richiedere dati. Non è possibile aggiungere un parametro alla chiamata, né esistono istanze a cui fare riferimento.
Ber

4
Non è utile quando estendi un modulo di amministrazione, perché puoi inizializzare il modulo passando la richiesta var. Qualche idea?
Mordi

13
Perché dici che usare l'archiviazione locale del thread è una pessima idea? Evita di dover eliminare il codice per passare la richiesta ovunque.
Michael Mior

9
Non passerei l'oggetto di richiesta stesso al form, ma piuttosto i campi di richiesta di cui hai bisogno (cioè utente), altrimenti leghi la logica del tuo modulo al ciclo di richiesta / risposta che rende i test più difficili.
Andrew Ingram,

2
Chris Pratt ha anche una buona soluzione, per quando si tratta di moduli in admin.ModelAdmin
radtek

34

AGGIORNATO 25/10/2011 : ora lo sto usando con una classe creata dinamicamente al posto del metodo, altrimenti Django 1.3 mostra alcune stranezze.

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

Quindi eseguire MyCustomForm.__init__l' override come segue:

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

È quindi possibile accedere all'oggetto richiesta da qualsiasi metodo ModelFormcon self.request.


1
Chris, quel "def __init __ (self, request = None, * args, ** kwargs)" è sbagliato, perché finirà con la richiesta sia nel primo arg posizionale che in kwargs. L'ho cambiato in "def __init __ (self, * args, ** kwargs)" e funziona.
slinkp

1
Ops. È stato solo un errore da parte mia. Ho trascurato di aggiornare quella parte del codice quando ho effettuato l'altro aggiornamento. Grazie per la cattura. Aggiornato.
Chris Pratt

4
È davvero una metaclasse? Penso che sia solo normale sovrascrivere, aggiungi una richiesta a __new__kwargs di s che in seguito verrà passata al __init__metodo della classe . Il nome della classe ModelFormWithRequestpenso sia molto più chiaro nel suo significato rispetto a ModelFormMetaClass.
k4ml

2
Questo NON è una metaclasse! Vedere stackoverflow.com/questions/100003/...
frnhr

32

Per quello che vale, se stai usando viste basate su classi , invece di viste basate su funzioni, sovrascrivi get_form_kwargsnella tua vista di modifica. Codice di esempio per un CreateView personalizzato :

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

Il codice di visualizzazione precedente renderà requestdisponibile come uno degli argomenti della parola chiave per la __init__funzione di costruzione del modulo . Quindi nel tuo ModelFormfare:

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)

1
Questo ha funzionato per me. Prendo nota perché stavo usando get_form_kwargs comunque a causa della complessa logica WizardForm. Nessun'altra risposta che ho visto rappresentava WizardForm.
datakid

2
Qualcuno oltre a me pensa che questo sia solo un grosso pasticcio per fare qualcosa che è piuttosto rudimentale per un framework web? Django è fantastico, ma questo mi fa venire voglia di non usare affatto CBV, mai.
trpt4him

1
Secondo me i vantaggi dei CBV superano di gran lunga gli svantaggi degli FBV, specialmente se lavori su un grande progetto con oltre 25 sviluppatori che scrivono codice che mira a una copertura del 100% di unit test. Non sono sicuro che le versioni più recenti di Django soddisfino automaticamente l' requestoggetto all'interno get_form_kwargs.
Joseph Victor Zammit

Allo stesso modo, esiste un modo per accedere all'ID dell'istanza dell'oggetto in get_form_kwargs?
Hassan Baig

1
@HassanBaig Possibilmente usando self.get_object? Il CreateViewestende il SingleObjectMixin. Ma se questo funziona o solleva un'eccezione dipende dal fatto che tu stia creando un nuovo oggetto o aggiornando uno esistente; cioè prova entrambi i casi (e ovviamente la cancellazione).
Joseph Victor Zammit

17

Il solito approccio consiste nell'archiviare l'oggetto richiesta in un riferimento locale al thread utilizzando un middleware. Quindi puoi accedervi da qualsiasi punto della tua app, incluso il metodo Form.clean ().

Cambiare la firma del metodo Form.clean () significa che hai la tua versione modificata di Django, che potrebbe non essere quella che desideri.

Grazie al conteggio middleware assomiglia a questo:

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

Registra questo middleware come descritto nella documentazione di Django


2
Nonostante i commenti sopra, questo metodo funziona mentre l'altro metodo no. L'impostazione di un attributo dell'oggetto form in init non viene trasferita in modo affidabile ai metodi puliti, mentre l'impostazione delle variabili locali del thread consente il trasferimento di questi dati.
rplevy

4
@rplevy hai effettivamente passato l'oggetto richiesta quando crei un'istanza del modulo? Nel caso in cui non l'hai notato utilizza argomenti di parole chiave **kwargs, il che significa che dovrai passare l'oggetto richiesta come MyForm(request.POST, request=request).
unode

13

Per l'amministratore di Django, in 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

1
Il metodo più votato sopra sembra in effetti aver smesso di funzionare da qualche parte tra Django 1.6 e 1.9. Questo funziona ed è molto più breve. Grazie!
Raik

9

Mi sono imbattuto in questo particolare problema durante la personalizzazione dell'amministratore. Volevo convalidare un determinato campo in base alle credenziali dell'amministratore.

Poiché non volevo modificare la vista per passare la richiesta come argomento al form, ho fatto quanto segue:

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

Grazie per quello. Errore di battitura rapido: obj=objnon obj=Nonesulla riga 11.
François Constant

Risposta davvero bella, mi piace tantissimo!
Luke Dupin

Django 1.9 offre: 'function' object has no attribute 'base_fields'. Tuttavia, la risposta più semplice (senza chiusura) di @ François funziona senza problemi.
raratiru

5

Non è sempre possibile utilizzare questo metodo (e probabilmente è una cattiva pratica), ma se si utilizza il modulo solo in una vista, è possibile esaminarlo all'interno del metodo di visualizzazione stesso.

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

Questa a volte è una soluzione davvero carina: lo faccio spesso con un get_form_classmetodo CBV , se so di dover fare molte cose con la richiesta. Potrebbe esserci un sovraccarico nella creazione ripetuta della classe, ma ciò la sposta semplicemente dal momento dell'importazione al tempo di esecuzione.
Matthew Schinckel

5

La risposta di Daniel Roseman è ancora la migliore. Tuttavia, utilizzerei il primo argomento posizionale per la richiesta invece dell'argomento parola chiave per alcuni motivi:

  1. Non corri il rischio di sovrascrivere un kwarg con lo stesso nome
  2. La richiesta è facoltativa, il che non è corretto. L'attributo di richiesta non dovrebbe mai essere Nessuno in questo contesto.
  3. Puoi passare in modo pulito args e kwargs alla classe genitore senza doverli modificare.

Infine, utilizzerei un nome più univoco per evitare di sovrascrivere una variabile esistente. Pertanto, la mia risposta modificata assomiglia a:

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


3

Ho un'altra risposta a questa domanda in base alle tue esigenze per accedere all'utente nel metodo pulito del modulo. Puoi provare questo. 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)

Ora puoi accedere a self.instance in qualsiasi metodo pulito in form.py


0

Quando vuoi accedervi attraverso le viste di classe Django "preparate" come se CreateViewci fosse un piccolo trucco da sapere (= la soluzione ufficiale non funziona immediatamente). Nel tuo CreateView dovrai aggiungere un codice come questo:

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

= in breve questa è la soluzione da passare requestal tuo form con le viste Crea / Aggiorna di Django.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.