¿Cómo filtro las opciones de ForeignKey en un ModelForm de Django?

Resuelto Tom asked hace 16 años • 9 respuestas

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 Ratesy Clients. Cada uno Clientdebe tener una base Rateelegida 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 Companyopciones (ya que ya se han seleccionado mediante el botón "Agregar cliente" en la Companypágina) y limitar las Rateopciones a eso Companytambién.

¿Cómo hago esto en Django 1.0?

Mi forms.pyarchivo 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.pytambié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_toParece prometedor, pero no sé cómo pasar the_company.idy 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).

Tom avatar Nov 15 '08 08:11 Tom
Aceptado

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

S.Lott avatar Nov 15 '2008 01:11 S.Lott

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

michael avatar Aug 07 '2009 13:08 michael