在Admin中链接Foreignkey导致Debug为False时出现AttributeError

4 投票
2 回答
1212 浏览
提问于 2025-04-16 20:14

我在我的models.py文件中使用了以下代码:

创建指向外键的超链接

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

在admin.py的list_display中,我在每个我想要外键链接的字段前面加了link_to。这效果很好,但当我关闭调试模式时,我遇到了一个属性错误。有什么建议吗?

2 个回答

1

我使用了stepank的实现,但为了适应我的需求,稍微做了一些修改。

我基本上是添加了对'ModelAdmin.readonly_fields'和'ModelAdmin.fields'的支持,以便使用'link_to_'这种语法。

对链接创建做了一点小改动,这样我就可以支持链接到不同的APP.Model,在我的情况下是内置的django.contrib.auth.models.user。

感谢@stepank和@Itai Tavor的出色工作。

希望这对其他人有帮助。


DEFAULT_LOGGER_NAME是在我的settings.py文件中定义的,我用它来处理大部分的日志记录。如果你没有定义这个,使用下面的代码时会出现错误。你可以在settings.py中定义自己的DEFAULT_LOGGER_NAME(它只是一个简单的字符串),或者直接删除下面代码中所有与logger相关的部分。

'''
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
12

我遇到了完全一样的问题,幸运的是,我解决了它。

你用的原始解决方案来自于 这个问题,而我的解决方案是基于它的:

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

其实,你只需要把原来的 ModelAdminWithForeignKeyLinksMetaclass 替换成上面的那个就可以了。

不过,这还不是全部。最有趣的部分是,为什么原来的解决方案会出问题。这个问题的答案可以在 这里(第31行)和 这里(第244行)找到。

当DEBUG模式开启时,Django会尝试验证所有注册的ModelAdmins(第一个链接)。在这里,cls 是一个 SomeAdmin(也就是它的元类的一个实例)。当调用 hasattr 时,Python会在类 SomeAdmin 或它的父类中查找一个属性 field。由于找不到,SomeAdmin 的类的 __getattr__ 被调用,在这里给 SomeAdmin 类添加了一个新方法。因此,当渲染界面时,SomeAdmin 已经被修改过了,Django能够找到所需的字段(第二个链接)。

当DEBUG模式关闭时,Django会跳过验证。当渲染界面时,Django会再次尝试查找一个字段(同样是第二个链接),但这次 SomeAdmin 并没有被修改,而且 model_admin 不是类 SomeAdmin,而是它的 实例。因此,当在 model_admin 中查找属性 name 时,Python无法找到这个属性,也无法在它的类(SomeAdmin)或任何父类中找到,所以就抛出了一个异常。

撰写回答