24

I am using the formfield_for_manytomany given in django documentation. But inside that function I need to get the current parent object being edited.

def formfield_for_manytomany(self, db_field, request, **kwargs):
    if db_field.name == "car":
        kwargs["queryset"] = Cars.objects.filter(owner=person)
    return super(myModel, self).formfield_for_manytomany(db_field, request, **kwargs)

How can I get the person being edited?

3 Answers 3

25

If the person cannot be easily got from request, you may need to manually pass it by overriding ModelAdmin.get_form() or InlineModelAdmin.get_formset():

from functools import partial

class MyModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
        return super(MyModelAdmin, self).get_form(request, obj, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        person = kwargs.pop('obj', None)
        formfield = super(MyModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == "car" and person:
            formfield.queryset = Cars.objects.filter(owner=person)
        return formfield 

# or its inline
class MyInlineModelAdmin(admin.StackedInline):
    def get_formset(self, request, obj=None, **kwargs):
        kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
        return super(MyInlineModelAdmin, self).get_formset(request, obj, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        person = kwargs.pop('obj', None)
        formfield = super(MyInlineModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == "car" and person:
            formfield.queryset = Cars.objects.filter(owner=person)
        return formfield 

Or

class MyModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
        return super(MyModelAdmin, self).get_form(request, obj, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name != "car":
            kwargs.pop('obj', None)
        return super(MyModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        person = kwargs.pop('obj', None)
        if db_field.name == "car" and person:
            kwargs['queryset'] = Cars.objects.filter(owner=person)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
Sign up to request clarification or add additional context in comments.

16 Comments

i don't get it. what is formfield_callback and what is partial
The callback is the behind-scene magic to invoke formfield_for_manytomany, we need to manually pass it the obj, in order to get obj from kwargs inside formfield_for_manytomany. The partial part makes a high-order function which, when being called as formfield_callback(some_more_arguments), behaves like self.formfield_for_dbfield(request, some_more_arguments), you could check the doc and the post
Everything is going above my head. i have two questiosn 1. From here i can get more info about formfield_callback . is that build in django or its custom 2. how cna i know what are the things or fields or functions avaiable inside kwargs. i how do you know that obj is in kwargs
@user2082226 1. formfield_callback is a bit hard-coded in Django w/o passing the obj, but we could customize it easily, check the code. 2. kwargs is filled by Django form to generate form field, thus it could contain possible things that the field accepts: widget and queryset for example. Here we do mix the arguments having different targets, thus remember to name it unique (obj here`).
i get this error __init__() got an unexpected keyword argument obj
|
7

I discovered a cleaner way without all of the hacking around in get_formset() / get_formsets() / formfield_for_dbfield(). Use Django's Request object (which you have access to) to retrieve the request.path_info, then retrieve the PK from the args in the resolve match. Example:

from django.contrib import admin
from django.core.urlresolvers import resolve
from app.models import YourParentModel, YourInlineModel


class YourInlineModelInline(admin.StackedInline):
    model = YourInlineModel

    def get_parent_object_from_request(self, request):
        """
        Returns the parent object from the request or None.

        Note that this only works for Inlines, because the `parent_model`
        is not available in the regular admin.ModelAdmin as an attribute.
        """
        resolved = resolve(request.path_info)
        if resolved.args:
            return self.parent_model.objects.get(pk=resolved.args[0])
        return None

    def has_add_permission(self, request):
        parent = self.get_parent_object_from_request(request)
        if parent and parent.is_active is True:
            return False
        return super(YourInlineModelInline, self).has_add_permission(request)

    def get_formset(self, request, *args, **kwargs):
        """
        Using the get_formset example from above to override the QuerySet.
        """
        def formfield_callback(field, **kwargs):
            formfield = field.formfield(**kwargs)
            if field.name == 'car':
                formfield.queryset = self.parent_model.objects.filter(
                    owner=self.get_parent_object_from_request(request)
                )
            return formfield

        if self.get_parent_object_from_request(request) is not None:
            kwargs['formfield_callback'] = formfield_callback

        return super(YourInlineModelInline, self).get_formset(*args, **kwargs)


@admin.register(YourParentModel)
class YourParentModelAdmin(admin.ModelAdmin):
    inlines = [YourInlineModelInline]

2 Comments

Not enough time for proper editing, so I'll leave here some notes to make this work under Django 2.0: from django.urls import resolve, pk=resolved.kwargs['object_id'], for ModelAdmin self.parent_model -> self.model
I feel like this is the better, less complicated solution. I did something similar to this, but didn't use the resolve() function. I just did something like object_id = request.path.split("/")[4] knowing that the ID would always be the 4th parameter in the request URL.
4

To access the model instance of the InlineModelAdmin's parent ModelAdmin, I have used this hack in the past:

class PersonAdmin(ModelAdmin):
    def get_formsets(self, request, obj=None, *args, **kwargs):
        for inline in self.inline_instances:
            inline._parent_instance = obj
            yield inline.get_formset(request, obj)

class CarInline(TabularInline):
    _parent_instance = None

    def get_formset(self, *args, **kwargs):
        def formfield_callback(field, **kwargs):
            formfield = field.formfield(**kwargs)
            if field.name == 'car':
                formfield.queryset = Cars.objects.filter(owner=self._parent_instance)
            return formfield

        if self._parent_instance is not None:
            kwargs['formfield_callback'] = formfield_callback
        return super(CarInline, self).get_formset(*args,
                                                                 **kwargs)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.