Using OOP I want to do something like
from django.contrib import admin
class NavigateFormAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
context['next_record_id'] = custom_function_to_calculate(context['obj'].id)
res = super().render_change_form(request, context, add, change, form_url)
return res
And expect that whenever render_change_form
of admin.ModelAdmin
is called, it should first my overridden method (above) which then should call the original (parent) method. but it makes no difference, because my overridden method never gets called rather on any call to render_change_form
the method from original class admin.ModelAdmin
is called.
Using undesired monkey patching
I am able to achieve what I need by adding following code to any of my py file that is read by interpreter at the start of my project/service execution
from django.contrib import admin
from django.template.response import TemplateResponse
# and also all the other imports used in original medthod
class NavigateFormAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
opts = self.model._meta
app_label = opts.app_label
preserved_filters = self.get_preserved_filters(request)
# and all the code of existing function has to be repeated here
context['next_record_id'] = custom_function_to_calculate(context['obj'].id)
res = TemplateResponse(request, form_template, context)
return res
admin.ModelAdmin.render_change_form = NavigateFormAdmin.render_change_form
Now on every call to admin.ModelAdmin.render_change_form
off-course NavigateFormAdmin.render_change_form
is executed
But I need to use super()
like (the first piece of code which is not working) here because OOP means re-usability, so what I could achieve is not satisfactory as all the 50 lines code of original method is repeated to only one line for overriding. Also this repeated code cause some unexpected results for changed version of admin.ModelAdmin
You can use super()
and monkey patch like you expected, though you can't just monkey patch the individual method, but you must patch out the entire object with your new subclass. Since you didn't include the errors you have encountered, I assume you may have saw a TypeError
exception about super
.
For a minimum demonstration, we need the two modules as follows:
a.py
class A:
def render(self, stream):
stream.write("calling A.render\n")
return stream
b.py
import a
class B(a.A):
def render(self, stream):
stream.write("starting in B, to call super\n")
super().render(stream)
stream.write("ending in B\n")
return stream
# monkey patching a.A with the new B
a.A = B
As a demonstration:
>>> from io import StringIO
>>> import a
>>> print(a.A().render(StringIO()).getvalue())
calling A.render
That worked as normal, now if b
were to be imported, the monkey patch will apply:
>>> import b
>>> print(a.A().render(StringIO()).getvalue())
starting in B, to call super
calling A.render
ending in B
Note how the subclass B
can simply user super()
to reference its parent class, even when it is no longer assigned to a.A
- the underlying class hierarchy is maintained, and super()
inside B
will do the right thing.
However, in your actual monkey patch example, you tried to rebind a specific method - this will in fact not work because re-assignment of a function inside a class block will result the new function being part of the class definition. Effectively, a reassignment in the form of the following
a.A.render = B.render
Will effectively result in the A
class looking something like the following definition (it's not actually, the details are significantly much more complicated than this illustration, but does roughly illustrates the problem you may have faced):
class A:
def render(self, stream):
stream.write("starting in B, to call super\n")
super().render(stream)
stream.write("ending in B\n")
return stream
Given that A
does not have subclass from anything, it can't call super()
like that and calling A().render(...)
will result in an exception (the details depends on how the render method is actually defined and then assigned to the class).
So in short, you may use the original code that you wanted to do, and monkey patch the whole class, i.e. by doing the following:
admin.ModelAdmin = NavigateFormAdmin
Do note that monkey patching can have their own pitfalls if your changes become incompatible with the underlying expectations of the original packages and their dependencies/dependents.