Created
April 24, 2019 20:35
-
-
Save fish2000/e0309cfd453c0ea57f097e6c79cbf425 to your computer and use it in GitHub Desktop.
Decorator to simplify setting display-related attributes in Django ModelAdmin subclasses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
class display(object): | |
""" A decorator to make short work of using custom display/format methods | |
in Django ModelAdmin subclasses -- they'd have you do shit like this: | |
class DoggAdmin(ModelAdmin): | |
list_display = ['id', 'with_name'] | |
def with_name(self, obj): | |
return "Yo dogg: <b>{0}</b>".format( | |
obj.name.capitalize()) | |
with_name.short_description = "The guy's fucking name" | |
with_name.allow_tags = True | |
... which, what the fuck? Nobody ever does that sort of thing ever. | |
Instead of assigning post-hoc values to an unbound method definition, | |
use @display() -- do it like this instead: | |
class DoggAdmin(ModelAdmin): | |
list_display = ['id', 'with_name'] | |
@display(desc="The guy's fucking name") | |
def with_name(self, obj): | |
return "Yo dogg: <b>{0}</b>".format( | |
obj.name.capitalize()) | |
... because doing it like that second example is clearly: | |
a) shorter, | |
b) less retarded-looking, | |
c) generally more legible now, | |
d) generally more legible in six months, which is probably | |
the next time you'll edit a Django ModelAdmin def, | |
e) decorator-based (which is like Python's syntactic equivalent | |
of Tweeting your method definitions), | |
f) default-able -- I nearly always do a ModelAdmin display | |
method in order to spice up the admin list view HTML, so | |
with the time I saved by not having to manually and awkwardly | |
type out `display_thingy.allow_tags = True`, I have that much | |
more time to enjoy my passtimes such as e.g. writing gratuitously | |
circumlocutious docstrings (like for example this one)... and | |
g) pythonic -- or at least more pythonic than the idiotic syntax | |
the off-the-shelf mechanism foists on you. | |
""" | |
# The params we set on the decorat-ee are based on these defaults: | |
_kw = { | |
'desc': ('short_description', ""), | |
'tags': ('allow_tags', True), | |
'boolean': ('boolean', False), | |
'admin_order_field': None, | |
} | |
def __init__(self, *args, **kwargs): | |
# an instance-local dict for the values in question: | |
self._values = {} | |
# This enumerate nonsense deals with positional args, | |
# however unlikely their use-case may be here. | |
# We fudge it by assuming their keyword based on our | |
# _kw struct and then jam them into the kwargs dict | |
# with the fudged keyword. | |
for idx, value in enumerate(args): | |
try: | |
update_key = self._kw.keys()[idx-1] | |
except (KeyError, ValueError, IndexError): | |
continue | |
else: | |
if value is not None: | |
kwargs.update({ update_key: value, }) | |
# This next slightly less assumptive bit scans the kwargs | |
# with which the decorator was invoked, according to the | |
# _kw struct, and fills the _values dict with either the | |
# _kw structs' default or (if present) the passed value. | |
for call_key, default in self._kw.items(): | |
if type(default) in (tuple, list): | |
real_key, default_value = (default[0], default[1]) | |
else: | |
real_key, default_value = (call_key, default) | |
self._values[real_key] = kwargs.get( | |
call_key, default_value) | |
def __call__(self, f): | |
""" Set attributes on the decorated function, per kwargs """ | |
# This _values dict has been, at this point, filled with either | |
# kwargs from the @display() invocation, or (failing that) whatever | |
# internal defaults we found in _kw during the __init__() stuff. | |
for real_key, value in self._values.items(): | |
if value is not None: | |
setattr(f, real_key, value) | |
return f |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment