2

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.

Nikolai Prokoschenko
  • 8,465
  • 11
  • 58
  • 97

1 Answers1

2

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.

Community
  • 1
  • 1
Logan
  • 1,614
  • 1
  • 14
  • 27
  • Your answer appeared in the low quality review queue. Please expand why this answers the question by editing your answer. – Artjom B. Dec 01 '14 at 20:20
  • *sigh* I answered it while I was on the bus. I will expand on it when I get home. – Logan Dec 01 '14 at 22:17
  • I'm interested too, since `inner` is a local nested function and thus *probably* unavailable from outside. But I don't know a lot about internals, therefore if it works, would be nice to know how and why. – Nikolai Prokoschenko Dec 02 '14 at 08:01
  • Huh. I may not have paid enough attention. I'm pretty sure it's still possible, I just don't know if I'm gunna have time to look at it in depth until Thursday. – Logan Dec 02 '14 at 21:15
  • Okay, so I figured out the theory of it and there are two ways (that I see) to do it. The first is to just Subclass AdminSite. This is how I would recommend doing it and how I imagine the Django developers intend you to do it. The second way involves disassembling AdminSite.admin_view and changing the code object of `inner` to a function of your own. I know it's possible but it would take me time to figure out. As such, when I have time, I will write up how to subclass it briefly. – Logan Dec 02 '14 at 21:40
  • 2
    I guess he is still on that bus... ;) – wim May 12 '15 at 14:52