Horrible Things I Regret Doing With Python Part One

Runtime Copy Paste With Global Variable Replacement.

"Could you make InlineAdmin in Django paginatable Joe?" Guido asked.
 I googled a while before regurgitating a Stackoverflow answer into Guido's lap.
"Oh cool, but we have multiple InlineAdmins, can you make it paginate for all of them?"
"Sure" I replied, "That'll just be a change to the admin class and some templates"

... Time passes

I had quickly changed the 'p' variable so it was dynamically determined by the model name assigned to the InlineAdmin class by adding a page_param property. The only problem was I that needed django's paginator_number template tag to take into account these new page variables as well.
from django.contrib.admin.views.main import (
    ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
)

...

@register.simple_tag
def paginator_number(cl, i):
    """
    Generates an individual page index link in a paginated list.
    """
    if i == DOT:
        return '... '
    elif i == cl.page_num:
        return format_html('<span class="this-page">{}</span> ', i + 1)
    else:
        return format_html('<a href="{}"{}>{}</a> ',
                           cl.get_query_string({PAGE_VAR: i}),
                           mark_safe(' class="end"' if i == cl.paginator.num_pages - 1 else ''),
i + 1)
The main problem being that PAGE_VAR is a global variable in paginator_number and I was trying as hard as possible to avoid copy pasting. I pondered for a while before writing out the code vomit in the next sample.

"Can you see what he's doing?" Tom called out, slightly exasperated as he looked at the hideous code I  had concocted. I cackled slightly as the other devs gathered around my desk, the faint glow of monitor lighting their faces.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import copy
import types

from django.template import Library
from django.contrib.admin.templatetags.admin_list import (
    paginator_number,
    pagination
)

register = Library()


@register.simple_tag
def parameterized_paginator_number(cl, i):
    '''This ridiculous code makes a copy of paginator_number function
    The PAGE_VAR variable is global in paginator number, this function copies
    paginator_number and replaces the global PAGE_VAR with a page_param that
    we can set.
    '''
    # copy the globals of paginator_number
    func_globals_copy = copy.copy(paginator_number.__globals__)
    func_globals_copy['PAGE_VAR'] = cl.page_param

    paginator_number_func_copy = types.FunctionType(
        code=paginator_number.__code__,
        # pass our updated globals
        globals=func_globals_copy,
        name='parameterized_paginator_number',
        argdefs=paginator_number.__defaults__,
        closure=paginator_number.__closure__,
    )
return paginator_number_func_copy(cl, i)
"It's runtime copy pasting, what the hell is wrong with you?"

Here we use copy to make a copy of django's paginator_number's global (line 21). Then, in a horrible twist, we replace the PAGE_VAR in our new dict (line 22).

In line 24-31, we use the types library to create a new function. With exactly the same code as paginator_number, but crucially in line 27, we pass in our func_globals_copy, which contains the replaced PAGE_VAR global. Then we invoke our new paginator_number function copy and all is well.

Under the hood, each python function has a reference to a dict containing all the globals that the function uses, all we are doing is badly abusing this. #sorrynotsorry as they say.

"This is your fault django, you made me do this", I blurted out in some feeble attempt at an excuse following up with "I think it's less brittle than plain copy-pasting though".

"I actually agree." Tom said doing his best Patrick Stewart facepalm.

Popular posts

Digging into python memory issues in ckan with heapy

Randomising traitor numbers in Trouble in Terrorist Town

Translating ckan extensions using the ITranslations