Hi All,
Within the Django Admin, I'm trying to pass the current object from the ModelAdmin to the InlineModelAdmin. While I've come up with a few solutions, I'm finding them all to be extremely hacky and hopefully a better solution is possible, perhaps via the addition of a feature. I apologize if this is more relevant to Django Users.
Solution 1 (doesn't work in all cases)
The
cleanest solution seems to override the __init__ method of the InlineModelAdmin's ModelForm class which allows access to the current inline instance. Because the current inline instance must by definition have a foreign key relationship to the parent, one is able to access the page's current object. However, this does not work for new inline forms.
class MyInlineModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['group'].queryset = Car.objects.filter(some_filtering_here=self.instance.parent)
Solution 2
Another
solution, is to override the formfield_callback within the ModelAdmin as we still have access to the obj parameter at this point. We pass the obj parameter through to the formfield_for_dbfield function allowing a particular field to change its queryset depending on the page's current obj (i.e filtering a select box).
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().get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
obj = kwargs.pop('obj', None)
formfield = super().formfield_for_dbfield(db_field, **kwargs)
if db_field.name == "group" and person:
formfield.queryset = Car.objects.filter(some_filtering_here=obj)
return formfield
Solution 3
Yet
another solution is to run a regex on the url and utilize the formfield_for_foreignkey to filter a select box using the current obj id. This is definitely the hackiest and only allows for filtering a select box, not setting initial parameters on the inline ModelForm based its the parent object.
class MyInlineModelAdmin(admin.StackedInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "group":
try:
parent_obj_id = request.resolver_match.args[0]
kwargs["queryset"] = Car.objects.filter(some_filtering_here=parent_obj_id)
except IndexError:
pass
return super(
).formfield_for_foreignkey(db_field, request, **kwargs)
Expansion on Solution 2 and Summary
The root of the issue is the ModelForm has no idea of its current context as an inline form within the parent ModelAdmin.
I've found Solution 2 be the cleanest at this point, but it still has its issues.
For example, my child object has a foreign key field (myfield) which needs to be filled out based on a relationship to the parent object. I would ideally like to just override the save method of the model form and add the myfield parameter but I can't since I don't have access to the parent object. Instead, I need to pass it in via a hidden input field to the form.
def formfield_for_dbfield(self, db_field, **kwargs):
obj = kwargs.pop('obj', None)
formfield = super().formfield_for_dbfield(db_field, **kwargs)
if db_field.name == "myfield" and obj:
formfield = forms.CharField(initial=obj.some_relationship.id, widget=forms.HiddenInput())
return formfield
Ideally I could just add the HiddenInput widget and set the initial attribute on the formfield but apparently that doesn't work with the ModelChoiceField. Instead, I have to completely override the formfield with a CharField.
However, because its now a CharField I need to "clean" the parameter in the ModelForm to convert it back into a model. (hacky!)
def clean_myfield(self):
data = self.cleaned_data['myfield']
return MyModel.objects.get(id=int(data))
There must be an easier way to do all of this! What am I missing? Any help would be appreciated.
Thanks,
Al