django 重写pip模块的python类方法以全局更新其行为

lnxxn5zx  于 2023-03-24  发布在  Go
关注(0)|答案(1)|浏览(103)

使用OOP我想做一些类似的事情

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

并且期望每当admin.ModelAdminrender_change_form被调用时,它应该首先调用我的重写方法(上面),然后调用原始(父)方法。但是这没有什么区别,因为我的重写方法从来没有被调用过,而是在任何对render_change_form的调用中调用原始类admin.ModelAdmin的方法。
使用不需要的monkey修补

我能够实现我所需要的,通过添加以下代码到任何我的py文件,在我的项目/服务执行开始时由解释器读取

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

现在,在每次调用admin.ModelAdmin.render_change_form时,都会执行NavigateFormAdmin.render_change_form

但是我需要使用super()(第一段代码不工作),因为OOP意味着可重用性,所以我可以实现的并不令人满意,因为原始方法的所有50行代码都重复到只有一行用于覆盖。而且这种重复的代码导致了admin.ModelAdmin更改版本的一些意外结果

oalqel3c

oalqel3c1#

你可以像你预期的那样使用super()和monkey patch,虽然你不能只对单个方法进行monkey patch,但是你必须用你的新子类修补整个对象。因为你没有包括你遇到的错误,我假设你可能看到了一个关于superTypeError异常。
对于最小的演示,我们需要两个模块,如下所示:

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

作为示范:

>>> from io import StringIO
>>> import a
>>> print(a.A().render(StringIO()).getvalue())
calling A.render

现在,如果要导入b,则会应用monkey补丁:

>>> import b
>>> print(a.A().render(StringIO()).getvalue())
starting in B, to call super
calling A.render
ending in B

请注意,子类B可以简单地使用super()来引用其父类,即使它不再被分配给a.A-底层类层次结构被维护,B内部的super()将做正确的事情。
然而,在实际的猴子补丁示例中,您试图重新绑定一个特定的方法-这实际上不会起作用,因为在类块内重新分配函数将导致新函数成为类定义的一部分。

a.A.render = B.render

将有效地导致A类看起来像下面的定义(实际上并不是这样,细节比这个插图 * 复杂得多 *,但大致说明了您可能遇到的问题):

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

假设A没有任何子类,它不能像那样调用super(),调用A().render(...)将导致异常(具体细节取决于render方法的实际定义和分配)。
所以简而言之,你可以使用你想要做的原始代码,并对整个类进行猴子补丁,即通过执行以下操作:

admin.ModelAdmin = NavigateFormAdmin

请注意,如果您的更改与原始包及其依赖项/从属项的底层预期不兼容,猴子修补可能会有自己的陷阱。

相关问题