I have used the following code in my models.py file:
Create hyperlink to foreignkey
class ModelAdminWithForeignKeyLinksMetaclass(MediaDefiningClass):
def __getattr__(cls, name):
def foreign_key_link(instance, field):
target = getattr(instance, field)
return u'<a href="../../%s/%s/%s">%s</a>' % (
target._meta.app_label, target._meta.module_name, target.id, unicode(target))
if name[:8] == 'link_to_':
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
setattr(cls, name, method)
return getattr(cls, name)
raise AttributeError
In admin.py list_display I've added link_to to the beginning of each field I want a foreignkey link on. This works really well however when I turn debug off I get an attribute error. Any suggestions?
I stumbled on exactly the same problem, luckily, I've fixed it.
The original solution (the one you used) comes from this question, my solution is based on it:
class ForeignKeyLinksMetaclass(MediaDefiningClass):
def __new__(cls, name, bases, attrs):
new_class = super(
ForeignKeyLinksMetaclass, cls).__new__(cls, name, bases, attrs)
def foreign_key_link(instance, field):
target = getattr(instance, field)
return u'<a href="../../%s/%s/%d/">%s</a>' % (
target._meta.app_label, target._meta.module_name,
target.id, unicode(target)
)
for name in new_class.list_display:
if name[:8] == 'link_to_':
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
setattr(new_class, name, method)
return new_class
Well, the only thing you need is to replace the original ModelAdminWithForeignKeyLinksMetaclass with the one above.
However, it's not the end. The most interesting part is why the original solution causes problems. The answer to this question lies here (line 31) and here (line 244).
When DEBUG is on Django tries to validate all registered ModelAdmins (first link). There cls is a class SomeAdmin (i.e. an instance of its metaclass). When hasattr is called, python tries to find an attribute field in class SomeAdmin or in one of its super classes. Since it is impossible, __getattr__ of its class (i.e. SomeAdmin's metaclass) is called, where a new method is added to class SomeAdmin. Hence, when it comes to rendering the interface, SomeAdmin is already patched and Django is able to find the required field (second link).
When DEBUG is False, Django skips the validation. When the interface is rendered Django tries to find a field (again, second link), but this time SomeAdmin is not patched, moreover model_admin is not class SomeAdmin, it is its instance. Thus, trying to find an attribute name in model_admin, python is unable to do this, neither it is able to find it in its class (SomeAdmin) as well as in any of its super classes, so an exception is raised.
I uses stepank's implementation, but had to alter it slightly to fit my use-case.
I basically just added support for 'ModelAdmin.readonly_fields' and 'ModelAdmin.fields' to support the 'link_to_'-syntax.
A slight change to the link creation also allowed me to support a link to a different APP.Model, in my case the built-in django.contrib.auth.models.user.
Thanks for the nice work @stepank and @Itai Tavor.
I hope this is useful for somebody else.
DEFAULT_LOGGER_NAME is defined in my settings.py and I use it for most of my logging. If you don't have it defined, you will get errors when using the following code. You can either define your own DEFAULT_LOGGER_NAME in settings.py (it is just a simple string) or you just remove all references to the logger in the code below.
'''
Created on Feb 23, 2012
@author: daniel
Purpose: Provides a 'link_to_<foreignKeyModel>' function for ModelAdmin
implementations. This is based on the following work:
original: http://stackoverflow.com/a/3157065/193165
fixed original: http://stackoverflow.com/a/7192721/193165
'''
from functools import partial
from django.forms import MediaDefiningClass
import logging
from public.settings import DEFAULT_LOGGER_NAME
logger = logging.getLogger(DEFAULT_LOGGER_NAME)
class ForeignKeyLinksMetaclass(MediaDefiningClass):
def __new__(cls, name, bases, attrs):
new_class = super(
ForeignKeyLinksMetaclass, cls).__new__(cls, name, bases, attrs)
def foreign_key_link(instance, field):
target = getattr(instance, field)
ret_url = u'<a href="../../%s/%s/%d/">%s</a>' % (
target._meta.app_label, target._meta.module_name,
target.id, unicode(target)
)
#I don't know how to dynamically determine in what APP we currently
#are, so this is a bit of a hack to enable links to the
#django.contrib.auth.models.user
if "auth" in target._meta.app_label and "user" in target._meta.module_name:
ret_url = u'<a href="/admin/%s/%s/%d/">%s</a>' % (
target._meta.app_label, target._meta.module_name,
target.id, unicode(target)
)
return ret_url
def _add_method(name):
if name is None: return
if isinstance(name, basestring) and name[:8] == 'link_to_':
try:
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
#in my app the "user" field always points to django.contrib.auth.models.user
#and I want my users to see that when they edit "client" data
#"Client" is another model, that has a 1:1 relationship with
#django.contrib.auth.models.user
if "user" in name[8:]:
method.short_description = "Auth User"
setattr(new_class, name, method)
except Exception, ex:
logger.debug("_add_method(%s) failed: %s" % (name, ex))
#make this work for InlineModelAdmin classes as well, who do not have a
#.list_display attribute
if hasattr(new_class, "list_display") and not new_class.list_display is None:
for name in new_class.list_display:
_add_method(name)
#enable the 'link_to_<foreignKeyModel>' syntax for the ModelAdmin.readonly_fields
if not new_class.readonly_fields is None:
for name in new_class.readonly_fields:
_add_method(name)
#enable the 'link_to_<foreignKeyModel>' syntax for the ModelAdmin.fields
if not new_class.fields is None:
for name in new_class.fields:
_add_method(name)
return new_class
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With