2009-12-01 1354 views
61

La lógica es del modelo es:limitar las opciones de clave externa en selecto en un formulario en línea en el administrador

  • Un Building tiene muchas Rooms
  • Un Room puede haber dentro de otro Room (un armario, por ejemplo, --ForeignKey en 'auto')
  • Un Room sólo puede ser dentro de otro Room en el mismo edificio (esta es la parte difícil)

Aquí está el código que tengo:

#spaces/models.py 
from django.db import models  

class Building(models.Model): 
    name=models.CharField(max_length=32) 
    def __unicode__(self): 
     return self.name 

class Room(models.Model): 
    number=models.CharField(max_length=8) 
    building=models.ForeignKey(Building) 
    inside_room=models.ForeignKey('self',blank=True,null=True) 
    def __unicode__(self): 
     return self.number 

y:

#spaces/admin.py 
from ex.spaces.models import Building, Room 
from django.contrib import admin 

class RoomAdmin(admin.ModelAdmin): 
    pass 

class RoomInline(admin.TabularInline): 
    model = Room 
    extra = 2 

class BuildingAdmin(admin.ModelAdmin): 
    inlines=[RoomInline] 

admin.site.register(Building, BuildingAdmin) 
admin.site.register(Room) 

La línea mostrará sólo las habitaciones en el edificio actual (que es lo que quiero). El problema, sin embargo, es que para el menú desplegable inside_room, muestra todas las salas de la tabla Salas (incluidas las de otros edificios).

En la línea de rooms, tengo que limitar las opciones a inside_room única rooms que están en la corriente building (el registro edificio que actualmente está alterado por la forma principal BuildingAdmin).

No puedo encontrar una manera de hacerlo con un limit_choices_to en el modelo, ni puedo averiguar cómo sobrescribir exactamente el formset en línea del administrador (creo que debería crear de alguna manera una forma personalizada en línea) , pase el building_id del formulario principal a la línea personalizada, luego limite el conjunto de preguntas para las elecciones del campo en función de eso, pero no puedo entender cómo hacerlo).

Tal vez esto es demasiado complejo para el sitio de administración, pero parece que algo que sería útil en general ...

Respuesta

2

Si Daniel, después de editar su pregunta, ha no responde - no creo que me será de mucha ayuda ... :-)

voy a sugerir que usted está tratando de forzar ajuste en el sitio administrativo cierta lógica que sería mejor implementado como su propio grupo de vistas, formularios y plantillas.

No creo que sea posible aplicar ese tipo de filtrado a InlineModelAdmin.

1

Debo admitir que no seguí exactamente lo que intenta hacer, pero creo que es lo suficientemente complejo como para considerar no basar su sitio en el administrador.

Construí un sitio una vez que comenzó con la interfaz de administración sencilla, pero finalmente se personalizó tanto que resultó muy difícil trabajar dentro de las limitaciones del administrador. Hubiera sido mejor si hubiera empezado de cero: más trabajo al principio, pero mucha más flexibilidad y menos dolor al final. Mi regla empírica sería si lo que estás tratando de hacer no está documentado (es decir, implica anular los métodos de administración, mirar el código fuente del administrador, etc.) entonces es mejor que no uses el administrador. Solo yo dos centavos. :)

4

This question and answer is very similar, and works for a regular admin form

Dentro de una línea - y ahí es donde se cae a pedazos ...Simplemente no puedo acceder a los datos del formulario principal para obtener el valor de la clave externa que necesito en mi límite (o a uno de los registros en línea para obtener el valor).

Aquí está mi admin.py. Supongo que estoy buscando la magia para reemplazar el ???? con - si me conecto a un valor codificado (por ejemplo, 1), que funciona bien y adecuadamente limita las las opciones disponibles en línea ...

#spaces/admin.py 
from demo.spaces.models import Building, Room 
from django.contrib import admin 
from django.forms import ModelForm 


class RoomInlineForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
    super(RoomInlineForm, self).__init__(*args, **kwargs) 
    self.fields['inside_room'].queryset = Room.objects.filter(
           building__exact=????)      # <------ 

class RoomInline(admin.TabularInline): 
    form = RoomInlineForm 
    model=Room 

class BuildingAdmin(admin.ModelAdmin): 
    inlines=[RoomInline] 

admin.site.register(Building, BuildingAdmin) 
admin.site.register(Room) 
4

me encontré con un fairly elegant solution que funciona bien para las formas en línea.

aplicado a mi modelo, donde estoy filtrando el campo inside_room a las habitaciones que se encuentran en el mismo edificio regresar:

#spaces/admin.py 
class RoomInlineForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
    super(RoomInlineForm, self).__init__(*args, **kwargs) #On init... 
    if 'instance' in kwargs: 
    building = kwargs['instance'].building 
    else: 
    building_id = tuple(i[0] for i in self.fields['building'].widget.choices)[1] 
    building = Building.objects.get(id=building_id) 
    self.fields['inside_room'].queryset = Room.objects.filter(building__exact=building) 

Básicamente, si una palabra clave 'ejemplo' se pasa a la forma, que es un registro existente que se muestra en línea, por lo que puedo tomar el edificio de la instancia. Si no es una instancia, es una de las filas "extra" en blanco en la línea, y por lo tanto pasa por los campos de formulario ocultos de la línea que almacena la relación implícita de vuelta a la página principal, y toma el valor de identificación de eso. Luego, toma el objeto de construcción basado en ese building_id. Finalmente, ahora que tenemos el edificio, podemos establecer el conjunto de consulta de los menús desplegables para que solo muestren los elementos relevantes.

Más elegante que mi solución original, que se colgó y quemó como en línea (pero funcionó; bueno, si no te importa guardar el formulario parcialmente para completar los formularios):

class RoomForm(forms.ModelForm): # For the individual rooms 
    class Meta: 
mode = Room 
    def __init__(self, *args, **kwargs): # Limits inside_room choices to same building only 
    super(RoomForm, self).__init__(*args, **kwargs) #On init... 
try: 
    self.fields['inside_room'].queryset = Room.objects.filter( 
    building__exact=self.instance.building) # rooms with the same building as this room 
    except:     #and hide this field (why can't I exclude?) 
    self.fields['inside_room']=forms.CharField(#Add room throws DoesNotExist error 
     widget=forms.HiddenInput, 
     required=False, 
     label='Inside Room (save room first)') 

Para no en línea, funcionó si la sala ya existía. Si no, lanzaría un error (DoesNotExist), así que lo atraparía y luego escondería el campo (ya que no había forma, desde el Administrador, de limitarlo al edificio correcto, ya que todo el registro de la sala era nuevo, ¡y aún no se había establecido ningún edificio!) ... una vez que tocas guardar, guarda el edificio y al volverlo a cargar podría limitar las opciones ...

Solo necesito encontrar una forma de conectar en cascada los filtros de la clave externa desde uno campo a otro en un nuevo registro - es decir, nuevo registro, seleccione un edificio y limita automáticamente las opciones en el cuadro de selección inside_room - antes de que se guarde el registro. Pero eso es por otro día ...

90

Se utilizó instancia de solicitud como contenedor temporal para obj. Overrided Inline method formfield_for_foreignkey para modificar queryset. Esto funciona al menos en django 1.2.3.

class RoomInline(admin.TabularInline): 

    model = Room 

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs): 

     field = super(RoomInline, self).formfield_for_foreignkey(db_field, request, **kwargs) 

     if db_field.name == 'inside_room': 
      if request._obj_ is not None: 
       field.queryset = field.queryset.filter(building__exact = request._obj_) 
      else: 
       field.queryset = field.queryset.none() 

     return field 



class BuildingAdmin(admin.ModelAdmin): 

    inlines = (RoomInline,) 

    def get_form(self, request, obj=None, **kwargs): 
     # just save obj reference for future processing in Inline 
     request._obj_ = obj 
     return super(BuildingAdmin, self).get_form(request, obj, **kwargs) 
+1

Esto me salvó muchas molestias. Necesitaba filtrar elecciones, pero por una variable de sesión. Esta respuesta me permite hacerlo con 5 líneas de código. Gracias. –

+3

¡Gracias a un millón! Una alternativa es asignar kwargs [ ''] QuerySet antes de llamar a super según docs: Esta https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey – powlo

+0

código también me ahorró TONELADAS de tiempo. Muchas gracias por publicar este – fangsterr

15

Después de leer esta publicación y experimentar mucho creo que he encontrado una respuesta bastante definitiva a esta pregunta. Como este es un patrón de diseño que se utiliza con frecuencia, he escrito Mixin for the Django admin para utilizarlo.

(dinámicamente) la limitación de la queryset para campos ForeignKey es ahora tan simple como subclases LimitedAdminMixin y la definición de un método get_filters(obj) para devolver los filtros pertinentes. Alternativamente, una propiedad filters se puede establecer en el administrador si no se requiere el filtrado dinámico.

Ejemplo de uso:

class MyInline(LimitedAdminInlineMixin, admin.TabularInline): 
    def get_filters(self, obj): 
     return (('<field_name>', dict(<filters>)),) 

Aquí, <field_name> es el nombre del campo FK a filtrar y <filters> es una lista de parámetros como lo haría normalmente especificarlos en el método de QuerySets filter().

+1

¡Gracias, funciona genial! Mucho más limpio. (Y por cierto, dejó algunas declaraciones de registro en su código que no van a ninguna parte) – Dave

8

Puede crear un par de clases personalizadas que luego pasarán una referencia a la instancia principal al formulario.

from django.forms.models import BaseInlineFormSet 
from django.forms import ModelForm 

class ParentInstInlineFormSet(BaseInlineFormSet): 
    def _construct_forms(self): 
     # instantiate all the forms and put them in self.forms 
     self.forms = [] 
     for i in xrange(self.total_form_count()): 
      self.forms.append(self._construct_form(i, parent_instance=self.instance)) 

    def _get_empty_form(self, **kwargs): 
     return super(ParentInstInlineFormSet, self)._get_empty_form(parent_instance=self.instance) 
    empty_form = property(_get_empty_form) 


class ParentInlineModelForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
     self.parent_instance = kwargs.pop('parent_instance', None) 
     super(ParentInlineModelForm, self).__init__(*args, **kwargs) 

en RoomInline clase sólo tiene que añadir:

class RoomInline(admin.TabularInline): 
     formset = ParentInstInlineFormset 
     form = RoomInlineForm #(or something) 

En su forma ahora tiene acceso en el método init para self.parent_instance! parent_instance ahora se puede utilizar para filtrar las opciones y otras cosas

algo como:

class RoomInlineForm(ParentInlineModelForm): 
    def __init__(self, *args, **kwargs): 
     super(RoomInlineForm, self).__init__(*args, **kwargs) 
     building = self.parent_instance 
     #Filtering and stuff 
+0

¡Gracias por esto! Es la primera versión que funcionó para mi aplicación y también es agradable y clara. – Justin

12

Hay limit_choices_to opción ForeignKey que permite limitar las opciones de administración disponibles para el objeto

+2

Esto no ayuda, ya que la consulta que se ejecuta en limit_choices_to no tiene ninguna referencia a la "clase principal". Es decir, si un modelo A tiene una clave externa a B, y también para C y C tiene una clave externa a B, y queremos garantizar una A sólo hace referencia a un C que se refiere a la misma B como A hace , la consulta necesita saber acerca de A-> B, que no es así. –

2

En Django 1.6:

form = SpettacoloForm(instance = spettacolo) 
form.fields['teatro'].queryset = Teatro.objects.filter(utente = request.user).order_by("nome").all() 
+1

¿Sería tan amable de adaptar la solución a los modelos existentes en la pregunta? – raratiru

Cuestiones relacionadas