Prometheus
Prometheus

Reputation: 33605

How To Access The Request Object in Django's GenericStackedInline Admin

Using GenericStackedInline in Django 1.9 (Python 3.4) I want to access the request object before saving my model in the Django Admin.

When using MediaItemAdmin I can intercept the save function before obj.save() is run, as in this example:

admin.py

class StuffAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        # Do some stuff here like obj.user = request.user before saving.
        obj.save()

However, the same behaviour or 'hook' isn't available using a GenericStackedInline. It appears to call the model save method directly:

admin.py

class StuffAdmin(GenericStackedInline):
    model = StuffModel

    def save_model(self, request, obj, form, change):
        print("I'm never run :(")
        obj.save()

As I understand GenericStackedInline inherits from a form so I have also tried using a form and overriding that as in this example:

admin.py

class StuffAdmin(GenericStackedInline):
    model = StuffModel
    form = StuffForm

class StuffForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(StuffForm, self).__init__(*args, **kwargs)

    def save_model(self, request, obj, form, change):
        print("Still not run!(")
        obj.save()

    def save_form(self, request, obj, form, change):
        print("Work already!")
        obj.save()

I have searched stackoverflow, but most are unanswered, as seen here accessing request object within a django admin inline model or say use init to do something like self.request = kwargs.pop('request') however, request is never passed here, right?

Anyhow, any idea how I can call the request object and update my instance before the model save() is called?

Upvotes: 2

Views: 4083

Answers (2)

Antoine Pinsard
Antoine Pinsard

Reputation: 34922

The method that saves the "inlines" is part of ModelAdmin, not InlineModelAdmin.

class BarInline(GenericStackedInline):
    model = Bar

class FooModelAdmin(ModelAdmin):
    model = Foo
    inlines = [BarInline]

    def save_formset(self, request, form, formset, change):
        """
        `form` is the base Foo form
        `formset` is the ("Bar") formset to save
        `change` is True if you are editing an existing Foo,
                    False if you are creating a new Foo
        """
        if formset_matches_your_inline_or_some_requirement(formset):
            do_something_with(request)
        super().save_formset(request, form, formset, change)

If you want to check whether the formset is the BarInline's formset, you can do something like this:

class BarInline(GenericStackedInline):
    model = Bar

    def get_formset(self, *args, **kwargs):
        formset = super().get_formset(*args, **kwargs)
        formset.i_come_from_bar_inline = True
        return formset


class FooModelAdmin(ModelAdmin):
    model = Foo
    inlines = [BarInline]

    def save_formset(self, request, form, formset, change):
        if getattr(formset, 'i_come_from_bar_inline', False):
            do_something_with(request)
        super().save_formset(request, form, formset, change)

Or even better, make it generic:

class BarInline(GenericStackedInline):
    model = Bar

    def pre_save_formset(self, request, form, model_admin, change):
       """Do something here with `request`."""

class FooModelAdmin(ModelAdmin):
    model = Foo
    inlines = [BarInline]

    def save_formset(self, request, form, formset, change):
        if hasattr(formset, 'pre_save_formset'):
            formset.pre_save_formset(request, form, self, change)
        super().save_formset(request, form, formset, change)
        if hasattr(formset, 'post_save_formset'):
            formset.post_save_formset(request, form, self, change)

If you need to do something with the request before each form save rather than before each formset, you will have to use your own Form and FormSet propagate the request through the formset to the form:

from django.forms import ModelForm
from django.forms.models import BaseInlineFormSet

class BarForm(ModelForm):
    model = Bar

    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request', None)
        super().__init__(*args, **kwargs)
        self.request = request

    def save(self, commit=True):
        print(self.request)
        print(self.instance)
        obj = super().save(False)  # Get object but don't save it
        do_something_with(self.request, obj)
        if commit:
            obj.save()
            self.save_m2m()
        return obj

class BarFormSet(BaseInlineFormSet):

    @property
    def request(self):
        return self._request

    @request.setter
    def request(self, request):
        self._request = request
        for form in self.forms:
            form.request = request

class BarInline(GenericStackedInline):
    codel = Bar
    form = BarForm
    formset = BarFormSet


class FooModelAdmin(ModelAdmin):
    inlines = [BarInline]

    def _create_formsets(self, request, obj, change):
        formsets, inline_instances = super()._create_formsets(request, obj, change)
        for formset in formsets:
            formset.request = request
        return formsets, inline_instances

According to you usecase, the save method might also simply look like something like this:

class BarForm(ModelForm):
    model = Bar

    def save(self, commit=True):
        do_something_with(self.request, self.instance)
        return super().save(commit)  # Get object but don't save it

Upvotes: 3

Daniel Roseman
Daniel Roseman

Reputation: 599480

Admin classes don't inherit from forms; they include forms. And ModelForms don't have either save_model or save_form methods, they just have a save method. It's perfectly possible to override that method, but it doesn't accept request; you'd need to also override __init__ to accept that argument and pass it in from the modeladmin's get_form_kwargs method.

Upvotes: 1

Related Questions