[Fixed]-How do I get the actual object id in a Django admin page (inside formfield_for_foreignkey)?

11👍

As far as i know it is not possible to access the current instance through the formfield_for_...-methods, because they will only be called for a single field instance!

A better point to hook into this logic where you can access the whole instance/form would be get_form. You can also overwrite a form field’s queryset there!

27👍

After some digging around, we were able to grab the arguments that get passed to the admin view (after being parsed by django admin’s urls.py) and use that (self_pub_id) to grab the object:

class PublicationAdmin(admin.ModelAdmin):

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name == "authors":
            #this line below got the proper primary key for our object of interest
            self_pub_id = request.resolver_match.args[0]

            #then we did some stuff you don't care about
            pub = Publication.objects.get(id=self_pub_id)
            kwargs["queryset"] = pub.authors.all()
        return super(PublicationAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

A more elegant solution is to use the accepted answers recomendation and leverage the get_form ModelAdmin member function. Like so:

class ProfileAdmin(admin.ModelAdmin):
    my_id_for_formfield = None
    def get_form(self, request, obj=None, **kwargs):
        if obj:
            self.my_id_for_formfield = obj.id
        return super(ProfileAdmin, self).get_form(request, obj, **kwargs)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "person":
            kwargs["queryset"] = Person.objects.filter(profile=self.my_id_for_formfield)
        return super(ProfileAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

18👍

The following code snippet will give you the object id:

request.resolver_match.kwargs['object_id']

Sample usage: (I’m filtering the phone numbers shown, to only show customer’s phone numbers)

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == 'preferred_contact_number':
        kwargs['queryset'] = CustomerPhone.objects.filter(customer__pk=request.resolver_match.kwargs['object_id'])
    return super().formfield_for_foreignkey(db_field, request, **kwargs)

P.S: Found it by debugging and walking through accessible variables.

4👍

I made it work by rewrite change_view()

class CartAdmin(admin.ModelAdmin):

def change_view(self, request, object_id, form_url='', extra_context=None):
    self.object_id = object_id
    return self.changeform_view(request, object_id, form_url, extra_context)


def formfield_for_foreignkey(self, db_field, request, **kwargs):
    print self.object_id
    return super(CartAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

then you can call self.object_id inside formfield_for_foreignkey()

1👍

Short answer

If you really need the object id inside formfield_for_foreignkey(), you could use request.resolver_match.kwargs.get('object_id') (docs), as proposed in @mazyar-mk’s answer.

But, if the goal is e.g. to filter a field queryset based on the object being edited, it is probably better to either

See examples below.

Long answer (with example)

The accepted answer by @BernhardVallant suggests extending ModelAdmin.get_form(), and in the comments it is suggested to modify the base_fields attribute. (Note that get_form() returns a form class, not a bound form.)

This is tempting, and you can probably get away with something like this:

def get_form(self, request, obj=None, change=False, **kwargs):
    form_class = super().get_form(request, obj, change, **kwargs)
    if obj:
        form_class.base_fields['my_field'].queryset = my_queryset.filter(
            my_lookup=obj
        )
    return form_class

Note that Django’s documentation for the Forms API warns against modifying base_fields (also see e.g. this answer):

Beware not to alter the base_fields attribute because this modification will influence all subsequent ContactForm instances within the same Python process: …

However, I do not think this restriction applies here, because the admin form is generated dynamically, using a modelform_factory.

The answer by @fizxmike provides an alternative example using get_form() without modifying base_fields, but it still needs formfield_for_foreignkey() as well.

Solution from documentation

The documentation for formfield_for_foreignkey() suggests another approach altogether (also see this ticket and fields docs):

For more complex filters, you can use [the] ModelForm.__init__() method to filter based on an instance of your model …

Once the form is initialized, you can access the fields attribute to modify the queryset. Moreover, you get access to the actual object (as self.instance), not just the object id.

For example:

class MyModelAdminForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk is not None:
            self.fields['my_field'].queryset = my_queryset.filter(
                my_lookup=self.instance
            )


class MyModelAdmin(admin.ModelAdmin):
    form = MyModelAdminForm
    ...

Here we check self.instance.pk to see if the object exists in the database (add vs change view).

This approach also works with inlines.

👤djvg

0👍

A more general approach could be writing an helper method to obtain the model instance (if any), much as you normally do with a (bounded) ModelForm, and from that retrieve the id or any other property:

from django.contrib import admin

class MyModelAdmin(admin.ModelAdmin):

    def get_instance(self, request):
        try:
            object_id = request.resolver_match.kwargs['object_id']
            obj = self.get_object(request, object_id)
        except:
            obj = None
        return obj

-2👍

I was dealing with a similar situation, and realized that the id that that I needed from the request, I could from from the model it self since it was a foreign key to that model. So it would be something like:

cpu = CompanyUser.objects.filter(company__id=self.company_id)

or what ever the structure of your model dictates.

-2👍

I made it work by creating a property() in model.py that returns the ID

models.py:

class MyModel(models.Model):
    myfield = models.CharField(max_length=75)
    ...
    def get_id(self):
        return str(self.id)
    getid = property(get_id)

admin.py:

from myapp.models import MyModel

class MyModelAdmin(admin.ModelAdmin):
    list_display = ['mylink',]
    def mylink(self, object):
        return '<a href="http://mywebsite/'+object.getid+'/">Edit</a>'
    mylink.allow_tags = True

admin.site.register(MyModel, MyModelAdmin)

Leave a comment