views:

1109

answers:

2

I've got a model for Orders in a webshop application, with an auto-incrementing primary key and a foreign key to itself, since orders can be split into multiple orders, but the relationship to the original order must be maintained.

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

I've registered an OrderAdmin class for the admin site. For the detail view, I've included parent_order in the fieldsets attribute. Of course, by default this lists all the orders in a select box, but this is not the desired behaviour. Instead, for orders that don't have a parent order (i.e. have not been split from another order; parent_order is NULL/None), no orders should be displayed. For orders that have been split, this should only display the single parent order.

There's a rather new ModelAdmin method available, formfield_for_foreignkey, that seems perfect for this, since the queryset can be filtered inside it. Imagine we're looking at the detail view of order #11234, which has been split from order #11208. The code is below

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)

The commented row works when run in a Python shell, returning a single-item queryset containing order #11208 for #11234 and all other orders that may have been split from it.

Of course, we can't hard-code the order number there. We need a reference to the ordernumber field of the order instance whose detail page we're looking at. Like this:

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

I've found no working way to replace ????? with a reference to the "current" Order instance, and I've dug pretty deep. self inside formfield_for_foreignkey refers to the ModelAdmin instance, and while that does have a model attribute, it's not the order model instance (it's a ModelBase reference; self.model() returns an instance, but its ordernumber is None).

One solution might be to pull the order number from request.path (/admin/orders/order/11234/), but that is really ugly. I really wish there is a better way.

+3  A: 

I think you might need to approach this in a slightly different way - by modifying the ModelForm, rather than the admin class. Something like this:

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
Daniel Roseman
It works! Thank you so much! I'm completely new to all this ModelForm/ModelAdmin business, and was looking in the wrong place.
JK Laiho
A: 

I am looking for a similar solution that will work for an inline in the admin. My problem is to limit the choices of a drop down foreign key (on 'self') where the values of another foreign key are equal. (A Building has many rooms, and a room can be inside_room--like a closet inside of another room--but I want to limit the choices of what rooms any given room can be inside of to only rooms in the same building).

For a regular admin form, this works perfectly. But I'm trying to get this to work inside of an admin inline--and that's where it falls apart... I just can't get at the main form's data to get the foreign key value I need in my limit (or to one of the inline's records to grab the value).

Here's my admin.py. I guess I'm looking for the magic to replace the ???? with--if I plug in a hardcoded value (say, 1), it works fine...

#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)
mightyhal