¿Cómo filtro las opciones de ForeignKey en un ModelForm de Django?
Digamos que tengo lo siguiente en mi models.py
:
class Company(models.Model):
name = ...
class Rate(models.Model):
company = models.ForeignKey(Company)
name = ...
class Client(models.Model):
name = ...
company = models.ForeignKey(Company)
base_rate = models.ForeignKey(Rate)
Es decir, hay múltiples Companies
, cada uno con un rango de Rates
y Clients
. Cada uno Client
debe tener una base Rate
elegida de su padre Company's Rates
, no de otro Company's Rates
.
Al crear un formulario para agregar un archivo Client
, me gustaría eliminar las Company
opciones (ya que ya se han seleccionado mediante el botón "Agregar cliente" en la Company
página) y limitar las Rate
opciones a eso Company
también.
¿Cómo hago esto en Django 1.0?
Mi forms.py
archivo actual es solo un texto repetitivo en este momento:
from models import *
from django.forms import ModelForm
class ClientForm(ModelForm):
class Meta:
model = Client
Y el views.py
también es básico:
from django.shortcuts import render_to_response, get_object_or_404
from models import *
from forms import *
def addclient(request, company_id):
the_company = get_object_or_404(Company, id=company_id)
if request.POST:
form = ClientForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(the_company.get_clients_url())
else:
form = ClientForm()
return render_to_response('addclient.html', {'form': form, 'the_company':the_company})
En Django 0.96 pude hackear esto haciendo algo como lo siguiente antes de renderizar la plantilla:
manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)]
ForeignKey.limit_choices_to
Parece prometedor, pero no sé cómo pasar the_company.id
y no tengo claro si funcionará fuera de la interfaz de administración de todos modos.
Gracias. (Esto parece una solicitud bastante básica, pero si debo rediseñar algo, estoy abierto a sugerencias).
ForeignKey está representada por django.forms.ModelChoiceField, que es un ChoiceField cuyas opciones son un modelo QuerySet. Consulte la referencia de ModelChoiceField .
Por lo tanto, proporcione un QuerySet al queryset
atributo del campo. Depende de cómo esté construido su formulario. Si crea un formulario explícito, tendrá campos nombrados directamente.
form.rate.queryset = Rate.objects.filter(company_id=the_company.id)
Si toma el objeto ModelForm predeterminado,form.fields["rate"].queryset = ...
Esto se hace explícitamente en la vista. No hay que piratear.
Además de la respuesta de S.Lott y como se mencionó en los comentarios, es posible agregar los filtros del conjunto de consultas anulando la ModelForm.__init__
función. (Esto podría aplicarse fácilmente a formularios normales). Puede ayudar con la reutilización y mantiene ordenada la función de visualización.
class ClientForm(forms.ModelForm):
def __init__(self,company,*args,**kwargs):
super (ClientForm,self ).__init__(*args,**kwargs) # populates the post
self.fields['rate'].queryset = Rate.objects.filter(company=company)
self.fields['client'].queryset = Client.objects.filter(company=company)
class Meta:
model = Client
def addclient(request, company_id):
the_company = get_object_or_404(Company, id=company_id)
if request.POST:
form = ClientForm(the_company,request.POST) #<-- Note the extra arg
if form.is_valid():
form.save()
return HttpResponseRedirect(the_company.get_clients_url())
else:
form = ClientForm(the_company)
return render_to_response('addclient.html',
{'form': form, 'the_company':the_company})
Esto puede ser útil para reutilizarlo, por ejemplo, si necesita filtros comunes en muchos modelos (normalmente declaro una clase de formulario abstracta). P.ej
class UberClientForm(ClientForm):
class Meta:
model = UberClient
def view(request):
...
form = UberClientForm(company)
...
#or even extend the existing custom init
class PITAClient(ClientForm):
def __init__(company, *args, **args):
super (PITAClient,self ).__init__(company,*args,**kwargs)
self.fields['support_staff'].queryset = User.objects.exclude(user='michael')
Aparte de eso, sólo estoy repitiendo el material del blog de Django, del cual hay muchos buenos por ahí.