2009-06-04 14 views
37

Tengo un modelo para pedidos en una aplicación de tienda en línea, con una clave principal autoincrementada y una clave externa, ya que las órdenes se pueden dividir en varias órdenes, pero se debe mantener la relación con la orden original.Django: ¿accediendo a la instancia del modelo desde ModelAdmin?

class Order(models.Model): 
    ordernumber = models.AutoField(primary_key=True) 
    parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders') 
    # .. other fields not relevant here 

He registrado una clase OrderAdmin para el sitio de administración. Para la vista detallada, he incluido parent_order en el atributo fieldsets. Por supuesto, por defecto esto enumera todos los pedidos en un cuadro de selección, pero este no es el comportamiento deseado. En cambio, para pedidos que no tienen un pedido principal (es decir, no se han dividido desde otro pedido; parent_order es NULL/Ninguno), no se deben mostrar pedidos. Para los pedidos que se han dividido, esto solo debe mostrar el orden principal único.

Hay un método más nuevo de ModelAdmin disponible, formfield_for_foreignkey, que parece perfecto para esto, ya que el conjunto de consulta puede filtrarse dentro de él. Imagine que estamos viendo la vista detallada de la orden # 11234, que se ha dividido de la orden # 11208. El código está por debajo

def formfield_for_foreignkey(self, db_field, request, **kwargs): 
    if db_field.name == 'parent_order': 
     # kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234) 
     return db_field.formfield(**kwargs) 
    return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

La fila comentado funciona cuando se ejecuta en un terminal de Python, que devuelve un conjunto de consultas de un solo artículo que contiene la orden # 11208 # 11234 para todas las demás órdenes y que pueden haber sido divididas de ella.

Por supuesto, no podemos codificar el número de pedido allí. Necesitamos una referencia al campo ordernumber de la instancia de orden cuya página de detalles estamos viendo. De esta manera:

kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????) 

No he encontrado ninguna manera de trabajo para reemplazar ????? con una referencia a la instancia de pedido "actual", y he cavado bastante profundo. self dentro de formfield_for_foreignkey se refiere a la instancia de ModelAdmin, y si bien tiene un atributo model, no es la instancia del modelo de orden (es una referencia de ModelBase; self.model() devuelve una instancia, pero su número de orden es Ninguno).

Una solución podría ser extraer el número de pedido de request.path (/ admin/orders/order/11234 /), pero eso es realmente feo. Realmente deseo que haya una mejor manera.

Respuesta

54

Creo que es posible que tenga que abordar esto de una manera ligeramente diferente: modificando el ModelForm, en lugar de la clase de administración. Algo como esto:

class OrderForm(forms.ModelForm): 

    def __init__(self, *args, **kwargs): 
     super(OrderForm, self).__init__(*args, **kwargs) 
     self.fields['parent_order'].queryset = Order.objects.filter(
      child_orders__ordernumber__exact=self.instance.pk) 

class OrderAdmin(admin.ModelAdmin): 
    form = OrderForm 
+1

¡Funciona! Muchas gracias!Soy completamente nuevo en todo este negocio de ModelForm/ModelAdmin, y estaba buscando en el lugar equivocado. –

+0

Me doy cuenta de que esta publicación es antigua, pero esto también funcionó como una solución decente para get_readonly_fields en una InlineModelAdmin ya que el parámetro obj que se le pasó es actualmente el objeto principal, no el objeto de la instancia en línea. Para todos los efectos, esto hizo que mi objeto fuera solo de lectura al permitirme devolver solo un objeto a mi clave externa. – dgraves

+0

Asegúrate de llamar a super .__ init__ primero. Eso establece self.instance. – yellottyellott

5

He modelado mi clase en línea de esta manera. Es un poco feo cómo se obtiene el ID de formulario principal para filtrar los datos en línea, pero funciona. Filtra las unidades por empresa desde el formulario principal.

El concepto original se explica aquí http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 
+5

Una forma ligeramente más clara de abordar esto sería aprovechar la resolución de URL: 'object_id = resolver (request.path) .args [0]' – philipk

3

La respuesta anterior de Erwin Julio trabajó para mí, si he encontrado que los conflictos de nombres "get_object" con una función de Django para que el nombre de la función "my_get_object".

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.my_get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def my_get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 

Se me dijo que no "responder" a las respuestas de los demás, pero yo no estoy autorizado a 'responder', sin embargo, y he estado buscando esto por un tiempo así que espero que esto sea útil a los demás . ¡Tampoco puedo votar aún o lo haré totalmente!

+0

Si bien sería mejor dejar esta información como un comentario, entiendo que no tiene suficiente reputación para eso, sin embargo, saber que 'get_object' provoca una colisión es valioso. – BlackVegetable

Cuestiones relacionadas