Last active
February 21, 2018 21:38
-
-
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.
This file contains 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
""" | |
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