Home

Horrible Things I Regret Doing With Python

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 InlineAdminclass by adding a page_paramproperty. The only problem was I that needed django's paginator_numbertemplate 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.

This abomination now lives on in ctxis/django-inline-admin-extensions