El campo de formulario ModelChoiceField sirve para permitir la selección de un elemento entre los objetos resultantes de una QuerySet. La QuerySet inicial puede definirse en el propio campo del formulario. Un ejemplo de uso en nuestro forms.py sería:
from django import forms
from django.contrib.auth.models import User
class MiFormulario(forms.Form):
usuario = forms.ModelChoiceField(queryset=User.objects.all())
En este ejemplo nuestro formulario tiene un campo usuario que permite seleccionar un usuario entre todos los registrados. Sin embargo a veces necesitamos que la QuerySet sea distinta de la predefinida en el formulario. Vamos a ver cómo definir la QuerySet dinámicamente al crear un objeto de formulario.
Para definirla dinámicamente primero cambiamos la QuerySet inicial por una QuerySet vacía. El campo ModelChoiceField espera que le pasemos una QuerySet pero aún no sabemos qué QuerySet queremos ejecutar (la generaremos dinámicamente al crear cada objeto de formulario) por lo que primero usamos el método none() que devuelve una lista vacía:
usuario = forms.ModelChoiceField(queryset=User.objects.none())
A continuación sobreescribimos el método __init__ del formulario para poder definir la QuerySet dinámicamente cuando se crea un formulario. Imaginemos que queremos crear una vista que contenga el formulario y que permita seleccionar cualquier usuario que no sea staff. Sin embargo si un superusuario accede a la vista queremos permitirle seleccionar cualquier usuario (incluyendo los que forman parte del staff). Éste es un ejemplo donde necesitamos definir la QuerySet en el momento de crear el formulario:
class MiFormulario(forms.Form):
usuario = forms.ModelChoiceField(queryset=User.objects.none())
def __init__(self, user, *args, **kwargs):
super(MiFormulario, self).__init__(*args, **kwargs)
if user.is_superuser:
self.fields['usuario'].queryset = User.objects.all()
else:
self.fields['usuario'].queryset = User.objects.filter(is_staff=False)
Como podemos ver, el método __init__ recibe un parámetro user. Si el usuario que recibe es superusuario el campo ModelChoiceField del formulario se construirá con la lista de todos los usuarios. De lo contrario se construirá con la lista de usuarios que no forman parte del staff del sitio web. Al crear el formulario en nuestra vista pasaremos request.user al formulario para que se construya con la QuerySet adecuada en función del usuario activo:
from forms import MiFormulario
def mi_vista(request):
mi_form = MiFormulario(user=request.user)
# ...
¡Listo! La QuerySet del campo usuario será una u otra en función de si el usuario que accede a la vista es superusuario o no. Hemos construído la QuerySet de un ModelChoiceField dinámicamente :)
Publicado por Antonio Melé el Friday 7 de August de 2009 | 9 comentarios | Categorías: fields, forms, trucos
Actualización/Update: Zack translated this post into english and it is available here.
A veces tenemos que modificar información relativa a dos o más modelos distintos desde un solo formulario HTML. Es algo común por ejemplo cuando definimos un perfil de usuario y necesitamos que se pueda modificar la información del modelo User del sistema de autenticación de Django y la información de nuestro modelo de perfil simultáneamente. Vamos a ver cómo puede hacerse. Para seguir este ejemplo necesitaremos tener django.contrib.auth entre las aplicaciones instaladas de nuestro proyecto (setting INSTALLED_APPS).
Suponemos un modelo Perfil como el siguiente definido en nuestro models.py:
from django.db import models
class Perfil(models.Model):
user = models.OneToOneField(User, unique=True, related_name='perfil')
telefono = models.PositiveIntegerField()
direccion = models.TextField()
A continuación creamos un ModelForm para este modelo Perfil y otro para el modelo User del sistema de autenticación de Django. Más adelante usaremos conjuntamente ambos ModelForms en un único formulario HTML. Guardamos el siguiente código en forms.py:
from django import forms
from models import Perfil
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class PerfilForm(forms.ModelForm):
class Meta:
model = Perfil
Como podemos ver sólo queremos editar los campos username, first_name, last_name y email del modelo User.
Ahora creamos la vista que generará el formulario para editar el perfil de un usuario. Para ello necesitaremos crear una instancia de los dos ModelForms que hemos definido anteriormente. Nuestro views.py debe tener la siguiente pinta:
from django.shortcuts import render_to_response
from django.template import RequestContext, loader
from django.contrib.auth.decorators import login_required
from forms import PerfilForm, UserForm
@login_required
def editar_perfil(request):
if request.method == 'POST':
# formulario enviado
user_form = UserForm(request.POST, instance=request.user)
perfil_form = PerfilForm(request.POST, instance=request.user.perfil)
if user_form.is_valid() and perfil_form.is_valid():
# formulario validado correctamente
user_form.save()
perfil_form.save()
return HttpResponseRedirect('/formulario-guardado/')
else:
# formulario inicial
user_form = UserForm(instance=request.user)
perfil_form = PerfilForm(instance=request.user.perfil)
return render_to_response('editar_perfil.html', { 'user_form': user_form, 'perfil_form': perfil_form }, context_instance=RequestContext(request))
Utilizamos el decorator @login_required ya que nuestros usuarios deberán estar autenticados para poder modificar su perfil. Si la petición llega por el método POST significa que se ha enviado el formulario y creamos dos Forms, uno para el modelo de usuario y otro para el del perfil, a partir de los datos recibidos. Validamos ambos formularios para guardarlos al igual que hacemos normalmente con un sólo formulario. Utilizamos una plantilla llamada editar_perfil.html a la que le pasamos ambos formularios a través de las variables user_form y perfil_form. El formulario de nuestra plantilla HTML será así de sencillo:
<form action="" method="post">
{{ user_form }}
{{ perfil_form }}
<input type="submit" value="Guardar cambios">
</form>
Con esto generaremos un formulario HTML que tiene primero los campos del modelo User seguidos por los campos del modelo Perfil asociado y que guardará correctamente cualquier cambio realizado en cualquiera de los dos objetos.
Publicado por Antonio Melé el Monday 6 de April de 2009 | 3 comentarios | Categorías: auth, forms, trucos, tutorial
Hoy me he topado con un artículo sobre un error muy común al utilizar ImageField ó FileField. Al usar cualquiera de estos dos tipos de campo podemos especificar la ruta relativa a nuestro setting MEDIA_ROOT en la que queremos que se guarde el archivo subido mediante el argumento upload_to.
Si utilizamos una barra / al comienzo de la ruta en upload_to el archivo ya no se sube a la ruta especificada de forma relativa a MEDIA_ROOT sino que se trata de guardar en dicha ruta a partir del directorio raíz.
Por ejemplo, con MEDIA_ROOT='/home/usuario/archivos/' y upload_to='imagenes' un supuesto archivo abc.jpg se guardaría en el directorio /home/usuario/archivos/imagenes/abc.jpg. Sin embargo con upload_to='/imagenes' el archivo se trataría de guardar como /imagenes/abc.jpg.
Publicado por Antonio Melé el Friday 27 de February de 2009 | 0 comentarios | Categorías: fields, forms, trucos
¿Quieres evitar el spam en tus formularios sin tener que utilizar captchas ni tener que recurrir a servicios de terceros como Askimet? Entonces magicforms probablemente sea lo que estés buscando. Creado por fíam (también creador de byNotes) estas dos clases que heredan de django.forms.Form y django.forms.ModelForm respectivamente te ayudan a engañar a los spambots y tener unos formularios limpos de spam.
¿Cómo se consigue? Utilizando un campo con estilo display:none para que no sea mostrado en el navegador (que hace de honeypot). Este campo no aparece en el navegador pero los spambots sí lo ven y tratarán de rellenarlo. El campo permite una longitud máxima de cero, por lo que el formulario no validará correctamente si este campo contiene alguna información. De esta forma se evita que se acepte un formulario rellenado por un spambot.
El otro método adicional añade un campo hidden que contiene un token generado a partir de la IP del visitante y el timestamp actual encriptados utilzando el setting SECTRET_KEY de tu proyecto como llave del cifrado. De esta forma al validar el formulario se descifra el valor del campo y se compara la IP del visitante con la almacenada anteriormente para verificar que son las mismas. Tras esto se compara el timestamp actual con el almacenado para que el formulario no valide correctamente si se ha enviado en menos de 5 segundos desde el timestamp inicial (demasiado rápido para ser un humano quien esté enviándolo) o más de 1 hora después por si el spambot reutiliza el token en el futuro.
Utilizarlo es realmente sencillo: En lugar de heredar de Form y ModelForm tus formularios deberán heredar de MagicForm y MagicModelForm. A la hora de utilizarlos en tus vistas al crear un objeto formulario deberás pasarle como argumentos request.META['REMOTE_ADDR'] (la IP del visitante) y un identificador único (por ejemplo el id del modelo asociado al formulario) además de los argumentos que quieras para el formulario. Suponiendo que tuvieramos un modelo Comentario como el siguiente:
from django.db import models
class Entrada(models.Model):
# ...
class Comentario(models.Model):
entrada = models.ForeignKey(Entrada)
texto = models.TextField()
El código para nuestro formuario sería:
from models import Comentario
from magicforms import MagicModelForm
class ComentarioForm(MagicModelForm):
class Meta:
model = Comentario
Y nuestra vista para añadir un comentario, suponiendo que le pasamos el id de la entrada por la URL sería:
from django.shortcuts import get_object_or_404
def add_comentario(request, entrada_id):
entrada = get_object_or_404(Entrada, id=entrada_id)
formulario = ComentarioForm(request.META['REMOTE_ADDR'], entrada.id)
if request.method == 'POST':
formulario = ComentarioForm(request.META['REMOTE_ADDR'], entrada.id, request.POST)
# ...
Descarga magicforms.py para Django. Puedes leer la entrada original en inglés sobre magicforms en el blog de fíam.
Publicado por Antonio Melé el Sunday 14 de December de 2008 | 4 comentarios | Categorías: forms, spam
Suscríbete a nuestro feed RSS y al feed de la comunidad para estar al tanto de todo lo que ocurre entorno a Django.
Tú también puedes escribir en éste blog. Para hacerlo basta con que nos digas sobre qué quieres escribir un artículo relacionado con Django.
Utilizar un formulario para modificar 2 modelos
Descubriendo objetos similares por sus etiquetas