python - modelchoicefield - ¿Cómo puedo filtrar las opciones de ForeignKey en Django ModelForm?
modelchoicefield django example (7)
Además de la respuesta de S.Lott y como se convirtió en gurú mencionado en los comentarios, es posible agregar los filtros queryset anulando la función ModelForm.__init__
. (Esto podría aplicarse fácilmente a los formularios regulares) 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 su reutilización, por ejemplo, si tiene filtros comunes necesarios en muchos modelos (normalmente declaro una clase de formulario abstracto). 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, simplemente estoy replanteando el material del blog de Django del que hay muchos buenos por ahí.
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 varias Companies
, cada una con un rango de Rates
y Clients
. Cada Client
debe tener una Rate
base que se elija entre Company''s Rates
matriz, no Company''s Rates
otra Company''s Rates
.
Al crear un formulario para agregar un Client
, me gustaría eliminar las opciones de la Company
(ya que eso ya se seleccionó a través del botón "Agregar Cliente" en la página de la Company
) y limitar las opciones de Rate
a esa Company
también.
¿Cómo hago para esto en Django 1.0?
Mi archivo de forms.py
actual es solo boilerplate 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 mostrar 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 en the_company.id
y no estoy 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).
Entonces, realmente he tratado de entender esto, pero parece que Django todavía no lo hace muy sencillo. No soy tan tonto, pero simplemente no puedo ver ninguna solución (algo) simple.
Por lo general, me parece muy feo tener que anular las vistas de administrador para este tipo de cosas, y cada ejemplo que encuentro nunca se aplica completamente a las vistas de administrador.
Esta es una circunstancia tan común con los modelos que hago que me parece terrible que no haya una solución obvia para esto ...
Tengo estas clases:
# models.py
class Company(models.Model):
# ...
class Contract(models.Model):
company = models.ForeignKey(Company)
locations = models.ManyToManyField(''Location'')
class Location(models.Model):
company = models.ForeignKey(Company)
Esto crea un problema al configurar el Administrador para la Compañía, porque tiene líneas tanto para el Contrato como para la Ubicación, y las opciones de m2m del Contrato para la Ubicación no se filtran correctamente de acuerdo con la Compañía que está editando actualmente.
En resumen, necesitaría alguna opción de administrador para hacer algo como esto:
# admin.py
class LocationInline(admin.TabularInline):
model = Location
class ContractInline(admin.TabularInline):
model = Contract
class CompanyAdmin(admin.ModelAdmin):
inlines = (ContractInline, LocationInline)
inline_filter = dict(Location__company=''self'')
En última instancia, no me importaría si el proceso de filtrado se colocó en el CompanyAdmin base, o si se colocó en el ContractInline. (Colocarlo en la línea tiene más sentido, pero hace que sea difícil hacer referencia al Contrato base como ''yo'').
¿Hay alguien por ahí que sepa de algo tan sencillo como este atajo tan necesario? Antes, cuando hice administradores de PHP para este tipo de cosas, ¡esto se consideraba una funcionalidad básica! De hecho, siempre fue automático, y tenía que ser deshabilitado si realmente no lo quería.
Esto es simple, y funciona con Django 1.4:
class ClientAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ClientAdminForm, self).__init__(*args, **kwargs)
# access object through self.instance...
self.fields[''base_rate''].queryset = Rate.objects.filter(company=self.instance.company)
class ClientAdmin(admin.ModelAdmin):
form = ClientAdminForm
....
No necesita especificar esto en una clase de formulario, pero puede hacerlo directamente en ModelAdmin, ya que Django ya incluye este método incorporado en ModelAdmin (de los documentos):
ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶
''''''The formfield_for_foreignkey method on a ModelAdmin allows you to
override the default formfield for a foreign keys field. For example,
to return a subset of objects for this foreign key field based on the
user:''''''
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Una forma aún más inteligente de hacerlo (por ejemplo, al crear una interfaz de administración de front-end a la que los usuarios pueden acceder) es crear una subclase de ModelAdmin y luego modificar los métodos a continuación. El resultado neto es una interfaz de usuario que SOLAMENTE les muestra contenido relacionado con ellos, mientras que le permite a usted (un superusuario) verlo todo.
He anulado cuatro métodos, los dos primeros hacen imposible que un usuario elimine algo y también elimina los botones de eliminación del sitio de administración.
La tercera anulación filtra cualquier consulta que contenga una referencia a (en el ejemplo "usuario" o "puercoespín" (solo como una ilustración).
La última anulación filtra cualquier campo de clave foránea en el modelo para filtrar las opciones disponibles de la misma forma que el conjunto de consulta básico.
De esta manera, puede presentar un sitio de administración frontal fácil de administrar que permite a los usuarios jugar con sus propios objetos, y no tiene que recordar escribir los filtros específicos de ModelAdmin de los que hemos hablado anteriormente.
class FrontEndAdmin(models.ModelAdmin):
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
self.admin_site = admin_site
super(FrontEndAdmin, self).__init__(model, admin_site)
eliminar los botones ''borrar'':
def get_actions(self, request):
actions = super(FrontEndAdmin, self).get_actions(request)
if ''delete_selected'' in actions:
del actions[''delete_selected'']
return actions
impide el permiso de eliminación
def has_delete_permission(self, request, obj=None):
return False
Filtra los objetos que se pueden ver en el sitio de administración:
def get_queryset(self, request):
if request.user.is_superuser:
try:
qs = self.model.objects.all()
except AttributeError:
qs = self.model._default_manager.get_queryset()
return qs
else:
try:
qs = self.model.objects.all()
except AttributeError:
qs = self.model._default_manager.get_queryset()
if hasattr(self.model, ‘user’):
return qs.filter(user=request.user)
if hasattr(self.model, ‘porcupine’):
return qs.filter(porcupine=request.user.porcupine)
else:
return qs
Filtra las opciones para todos los campos de llave extranjera en el sitio de administración:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if request.employee.is_superuser:
return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
else:
if hasattr(db_field.rel.to, ''user''):
kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user)
if hasattr(db_field.rel.to, ''porcupine''):
kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine)
return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs)
ForeignKey está representada por django.forms.ModelChoiceField, que es un ChoiceField cuyas opciones son un QuerySet modelo. Vea la referencia para ModelChoiceField .
Por lo tanto, proporcione un QuerySet al atributo queryset
del campo. Depende de cómo se construye tu formulario. Si creas un formulario explícito, tendrás 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 hackear alrededor.
Para hacer esto con una vista genérica, como CreateView ...
class AddPhotoToProject(CreateView):
"""
a view where a user can associate a photo with a project
"""
model = Connection
form_class = CreateConnectionForm
def get_context_data(self, **kwargs):
context = super(AddPhotoToProject, self).get_context_data(**kwargs)
context[''photo''] = self.kwargs[''pk'']
context[''form''].fields[''project''].queryset = Project.objects.for_user(self.request.user)
return context
def form_valid(self, form):
pobj = Photo.objects.get(pk=self.kwargs[''pk''])
obj = form.save(commit=False)
obj.photo = pobj
obj.save()
return_json = {''success'': True}
if self.request.is_ajax():
final_response = json.dumps(return_json)
return HttpResponse(final_response)
else:
messages.success(self.request, ''photo was added to project!'')
return HttpResponseRedirect(reverse(''MyPhotos''))
la parte más importante de eso ...
context[''form''].fields[''project''].queryset = Project.objects.for_user(self.request.user)
Si no ha creado el formulario y desea cambiar el queryset, puede hacerlo:
formmodel.base_fields[''myfield''].queryset = MyModel.objects.filter(...)
¡Esto es bastante útil cuando estás usando vistas genéricas!
Una forma más pública es llamando a get_form en las clases de administrador. También funciona para campos que no son de base de datos también. Por ejemplo, aquí tengo un campo llamado ''_terminal_list'' en el formulario que se puede usar en casos especiales para elegir varios elementos terminales de get_list (solicitud), y luego filtrar según la solicitud.usuario:
class ChangeKeyValueForm(forms.ModelForm):
_terminal_list = forms.ModelMultipleChoiceField(
queryset=Terminal.objects.all() )
class Meta:
model = ChangeKeyValue
fields = [''_terminal_list'', ''param_path'', ''param_value'', ''scheduled_time'', ]
class ChangeKeyValueAdmin(admin.ModelAdmin):
form = ChangeKeyValueForm
list_display = (''terminal'',''task_list'', ''plugin'',''last_update_time'')
list_per_page =16
def get_form(self, request, obj = None, **kwargs):
form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs)
qs, filterargs = Terminal.get_list(request)
form.base_fields[''_terminal_list''].queryset = qs
return form