Skip to content

Instantly share code, notes, and snippets.

@mjumbewu
Last active February 21, 2018 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mjumbewu/be483ba11842aefe6d6870c4140d0bf5 to your computer and use it in GitHub Desktop.
Save mjumbewu/be483ba11842aefe6d6870c4140d0bf5 to your computer and use it in GitHub Desktop.
Decorators for quickly setting up links to related model instances in the Django admin.
"""
Sometimes it can be useful to quickly navigate between objects.
This module defines two useful decorators:
* admin_link
* admin_changelist_link
Borrowed, with love, from:
https://medium.com/@hakibenita/things-you-must-know-about-django-admin-as-your-app-gets-bigger-6be0b0ee9614
"""
from django.core.urlresolvers import reverse
from django.forms.forms import pretty_name
from django.utils.html import format_html
from functools import wraps, reduce
def getattr_nested(obj, attr):
return reduce(getattr, [obj] + attr.split('__'))
def admin_change_url(obj):
app_label = obj._meta.app_label
model_name = obj._meta.model.__name__.lower()
return reverse('admin:{}_{}_change'.format(
app_label, model_name
), args=(obj.pk,))
def admin_link(attr, short_description=None, empty_description="-"):
"""Decorator used for rendering a link to a related model in
the admin detail page.
attr (str):
Name of the related field.
short_description (str):
Name if the field.
empty_description (str):
Value to display if the related field is None.
The wrapped method receives the related object and should
return the link text.
Usage:
@admin_link('credit_card', _('Credit Card'))
def credit_card_link(self, credit_card):
return credit_card.name
"""
def wrap(func):
@wraps(func)
def field_func(self, obj):
related_obj = getattr_nested(obj, attr)
if related_obj is None:
return empty_description
url = admin_change_url(related_obj)
return format_html(
'<a href="{}">{}</a>',
url,
func(self, related_obj)
)
field_func.short_description = short_description if short_description else pretty_name(attr)
field_func.allow_tags = True
return field_func
return wrap
def admin_changelist_url(model):
app_label = model._meta.app_label
model_name = model.__name__.lower()
return reverse('admin:{}_{}_changelist'.format(
app_label,
model_name)
)
def admin_changelist_link(attr, short_description=None, empty_description="-", query_string=None):
"""Decorator used for rendering a link to the list display of
a related model in the admin detail page.
attr (str):
Name of the related field.
short_description (str):
Field display name.
empty_description (str):
Value to display if the related field is None.
query_string (function):
Optional callback for adding a query string to the link.
Receives the object and should return a query string.
The wrapped method receives the related object and
should return the link text.
Usage:
@admin_changelist_link('products', _('Products'), query_string=lambda c: 'category_id={}'.format(c.pk))
def credit_card_link(self, credit_card):
return credit_card.name
"""
def wrap(func):
@wraps(func)
def field_func(self, obj):
related_obj = getattr_nested(obj, attr)
if related_obj is None:
return empty_description
url = admin_changelist_url(related_obj.model)
if query_string:
url += '?' + query_string(obj)
return format_html(
'<a href="{}">{}</a>',
url,
func(self, related_obj)
)
field_func.short_description = short_description if short_description else pretty_name(attr)
field_func.allow_tags = True
return field_func
return wrap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment