Reputation: 8765
Take a look at this changeset for Django. I need this functionality, however this patch comes from Django's 1.7 release, which I can't use in my environment (Python 2.6 only). So for now, I've copied the admin_view
method to my code and injected it with a admin.site.admin_view = partial(admin_view, admin.site)
.
However, I would like to keep the amount of "forked" code to the minimum and wondered: is it possible to monkeypatch it, i.e. replace the self.login
function with redirect_to_login
in the execution scope of the inner
function of the decorator?
I'm aware that this would be an evil hack, however, I want to find out how far one can go with Python.
Upvotes: 2
Views: 451
Reputation: 1682
django.contrib.admin.sites.inner = yourfunctionhere
EDIT:
Wow. Kind of embarrassed I let this sit here for so long. I vaguely remember the article that I was basing the second method off of (see below comment), but I don't remember enough of the details to find it again. As such, I will just recommend subclassing AdminSite
.
EDIT 2: After some searching, I found this:
Does an equivalent of override exist for nested functions?
The 'monkey_patch_fn' function does exactly what you want and demonstrates one possible approach. It may or may not be complete.
My original plan was to modify the function in place by disassembling it, but I've been running into issues with the attributes being read-only (which I think was what my original article dealt with... but I can't find it).
EDIT 3:
Found another way using a module called byteplay
. Glad I didn't give up so soon. I like this way a lot more. It might be just as hacky under the hood, but I trust a full fledged published module to take more care than a random answer for a specific question.
Anyway. Because I don't feel like looking at the Django code right now, I will present an example which should suffice. First, the setup.
from byteplay import *
import dis
def test():
def printone():
print 1
printone()
def printtwo():
print 2
dis.dis(test)
The output here will be
2 0 LOAD_CONST 1 (<code object printone at 0x7f72097371b, file "<stdin>", line 1>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (printone)
4 9 LOAD_FAST 0 (printone)
12 CALL_FUNCTION 0
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
So, then we use byteplay
to convert it to a Code
object, edit it, then turn it back into normal bytecode.
testcode = Code.from_code(test.__code__)
print testcode.code
The output is:
[(SetLineno, 2), (LOAD_CONST, <byteplay.Code object at 0x7f72096e6a50>), (MAKE_FUNCTION, 0), (STORE_FAST, 'printone'), (SetLineno, 4), (LOAD_FAST, 'printone'), (CALL_FUNCTION, 0), (POP_TOP, None), (LOAD_CONST, None), (RETURN_VALUE, None)]
which mirrors dis
. So, we juts need to change the Code
object in the second tuple and put the new code into the original object.
testcode.code[1] = (LOAD_CONST, Code.from_code(printtwo.__code__))
test.__code__ = testcode.to_code()
dis.dis(test)
test()
The output is a little messy, but we see that:
5 0 LOAD_CONST 1 (<code object printtwo at 0x7fc668476230, file "byteplaytest.py", line 9>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (printone)
7 9 LOAD_FAST 0 (printone)
12 CALL_FUNCTION 0
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
2
So, we are loading the printtwo
function, saving it as a function under the local variable name printone
and then the final 2
is just the printtwo
function successfully being called.
This should comprehensively illustrate what you need to do. You will need to use dis
to determine which line in the bytecode you need to change, but you should only need to change the LOAD_CONST
, I think. Granted, I have no tried this with libraries or anything... but just let me know if there are any issues.
Upvotes: 2