¿Cómo accedo al objeto de solicitud o cualquier otra variable en el método clean () de un formulario?

99

Estoy tratando de request.user para el método limpio de un formulario, pero ¿cómo puedo acceder al objeto de solicitud? ¿Puedo modificar el método limpio para permitir la entrada de variables?

nubela
fuente

Respuestas:

157

La respuesta de Ber, almacenarlo en threadlocals, es una muy mala idea. No hay absolutamente ninguna razón para hacerlo de esta manera.

Una mejor manera es mucho para anular de forma __init__método para tomar un argumento de palabra clave adicional, request. Esto almacena la solicitud en el formulario , donde se requiere y desde donde puede acceder a ella en su método limpio.

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

y en tu opinión:

myform = MyForm(request.POST, request=request)
Daniel Roseman
fuente
4
Tienes razón en este caso. Sin embargo, es posible que no se desee modificar formularios / vistas en este sitio. Además, hay casos de uso para la tienda local de subprocesos donde es imposible agregar parámetros de método o variables de instancia. Piense en un argumento invocable para un filtro de consulta que necesita acceso para solicitar datos. No puede agregar un parámetro a la llamada ni hay ninguna instancia a la que hacer referencia.
Ber
4
No es útil cuando está extendiendo un formulario de administrador, porque puede iniciar su formulario pasando la solicitud var. ¿Alguna idea?
Mordi
13
¿Por qué dices que usar almacenamiento local de subprocesos es una muy mala idea? Evita tener que soltar el código para pasar la solicitud a todas partes.
Michael Mior
9
No pasaría el objeto de solicitud en sí al formulario, sino a los campos de solicitud que necesita (es decir, usuario); de lo contrario, vinculará la lógica de su formulario al ciclo de solicitud / respuesta, lo que dificulta las pruebas.
Andrew Ingram
2
Chris Pratt también tiene una buena solución, para cuando se trata de formularios en admin.ModelAdmin
radtek
34

ACTUALIZADO 25/10/2011 : ahora estoy usando esto con una clase creada dinámicamente en lugar de un método, ya que Django 1.3 muestra algunas rarezas de lo contrario.

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

Luego anule de la MyCustomForm.__init__siguiente manera:

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

Luego puede acceder al objeto de solicitud desde cualquier método de ModelFormwith self.request.

Chris Pratt
fuente
1
Chris, ese "def __init __ (self, request = None, * args, ** kwargs)" es malo, porque terminará con request tanto en el primer argumento posicional como en kwargs. Lo cambié a "def __init __ (self, * args, ** kwargs)" y eso funciona.
slinkp
1
¡Ups! Eso fue solo un error de mi parte. Me olvidé de actualizar esa parte del código cuando hice la otra actualización. Gracias por la captura. Actualizado.
Chris Pratt
4
¿Es esto realmente una metaclase? Creo que es una anulación normal, agregas una solicitud a __new__los kwargs que más adelante se pasarán al __init__método de la clase . Nombrar la clase ModelFormWithRequestcreo que es mucho más claro en su significado que ModelFormMetaClass.
k4ml
2
¡Esto NO es una metaclase! Ver stackoverflow.com/questions/100003/…
viernes
32

Por lo que vale, si está utilizando vistas basadas en clases , en lugar de vistas basadas en funciones, anule get_form_kwargsen su vista de edición. Código de ejemplo para un CreateView personalizado :

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

El código de vista anterior estará requestdisponible como uno de los argumentos de palabra clave para la __init__función constructora del formulario . Por lo tanto, en tu ModelFormhacer:

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
fuente
1
Esto funcionó para mí. Tomo la nota porque de todos modos estaba usando get_form_kwargs debido a la compleja lógica de WizardForm. Ninguna otra respuesta que haya visto representó WizardForm.
datakid
2
¿Alguien además de mí piensa que esto es un gran lío para hacer algo que es bastante rudimentario para un marco web? Django es genial, pero esto me hace no querer usar CBV en absoluto, nunca.
trpt4him
1
En mi humilde opinión, los beneficios de los CBV superan con creces los inconvenientes de los FBV, especialmente si trabaja en un proyecto grande con más de 25 desarrolladores que escriben código que apunta a una cobertura de prueba unitaria del 100%. No estoy seguro de si las versiones más recientes de Django permiten tener el requestobjeto dentro get_form_kwargsautomáticamente.
Joseph Victor Zammit
De manera similar, ¿hay alguna forma de acceder al ID de la instancia del objeto en get_form_kwargs?
Hassan Baig
1
@HassanBaig ¿Posiblemente usando self.get_object? El CreateViewextiende el SingleObjectMixin. Pero si esto funciona o genera una excepción depende de si está creando un nuevo objeto o actualizando uno existente; es decir, probar ambos casos (y supresión, por supuesto).
Joseph Victor Zammit
17

El enfoque habitual es almacenar el objeto de solicitud en una referencia local de subproceso utilizando un middleware. Luego, puede acceder a esto desde cualquier lugar de su aplicación, incluido el método Form.clean ().

Cambiar la firma del método Form.clean () significa que tiene su propia versión modificada de Django, que puede no ser la que desea.

Gracias, el recuento de middleware se ve así:

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

Registre este middleware como se describe en los documentos de Django

Ber
fuente
2
A pesar de los comentarios anteriores, este método funciona mientras que el otro método no. La configuración de un atributo del objeto de formulario en init no se transfiere de manera confiable a los métodos limpios, mientras que la configuración de los locales del hilo permite que estos datos se transfieran.
rplevy
4
@rplevy, ¿realmente ha pasado el objeto de solicitud cuando crea una instancia del formulario? En caso de que no lo haya notado, utiliza argumentos de palabras clave **kwargs, lo que significa que tendrá que pasar el objeto de solicitud como MyForm(request.POST, request=request).
unode
13

Para el administrador de Django, en 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
fuente
1
El método mejor calificado más arriba parece haber dejado de funcionar en algún lugar entre Django 1.6 y 1.9. Este funciona y es mucho más corto. ¡Gracias!
Raik
9

Me encontré con este problema en particular al personalizar el administrador. Quería que un determinado campo fuera validado en función de las credenciales del administrador en particular.

Como no quería modificar la vista para pasar la solicitud como un argumento al formulario, lo siguiente es lo que hice:

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
entropía
fuente
Gracias por eso. Error rápido: obj=objno obj=Noneen la línea 11.
François Constant
Muy buena respuesta, ¡me encanta!
Luke Dupin
Django 1.9 proporciona: 'function' object has no attribute 'base_fields'. Sin embargo, la respuesta de @ François más simple (sin cierre) funciona sin problemas.
raratiru
5

No siempre puede usar este método (y probablemente sea una mala práctica), pero si solo está usando el formulario en una vista, puede ubicarlo dentro del método de vista en sí.

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
fuente
A veces, esta es una solución realmente agradable: a menudo hago esto en un get_form_classmétodo CBV , si sé que necesito hacer muchas cosas con la solicitud. Puede haber algunos gastos generales al crear repetidamente la clase, pero eso simplemente la mueve del tiempo de importación al tiempo de ejecución.
Matthew Schinckel
5

La respuesta de Daniel Roseman sigue siendo la mejor. Sin embargo, usaría el primer argumento posicional para la solicitud en lugar del argumento de palabra clave por algunas razones:

  1. No corre el riesgo de anular un kwarg con el mismo nombre
  2. La solicitud es opcional, lo cual no es correcto. El atributo de solicitud nunca debe ser Ninguno en este contexto.
  3. Puede pasar limpiamente los args y kwargs a la clase principal sin tener que modificarlos.

Por último, usaría un nombre más exclusivo para evitar anular una variable existente. Por lo tanto, mi respuesta modificada se ve así:

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 ...
Andrés Restrepo
fuente
3

Tengo otra respuesta a esta pregunta según su requisito de que desee acceder al usuario al método limpio del formulario. Puedes probar esto. View.py

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

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

Ahora puede acceder a self.instance en cualquier método limpio en form.py

Nishant Kashyap
fuente
0

Cuando desee acceder a él a través de vistas de clase de Django "preparadas" como si CreateViewhubiera un pequeño truco para saber (= la solución oficial no funciona de inmediato). En el suyo CreateView , tendrá que agregar un código como este:

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

= en resumen, esta es la solución para pasar requesta su formulario con las vistas Crear / Actualizar de Django.

Olivier Pons
fuente