当Debug为False时,Admin中的Foreignkey链接导致AttributeError

时间:2011-06-24 20:21:20

标签: python django django-admin foreign-keys

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

创建指向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

在admin.py list_display中,我已将link_to添加到我想要外键链接的每个字段的开头。这非常有效,但是当我关闭调试时,我得到一个属性错误。有什么建议吗?

2 个答案:

答案 0 :(得分:12)

我偶然发现了同样的问题,幸运的是,我已经修好了。

原始解决方案(您使用的解决方案)来自this question,我的解决方案基于它:

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

然而,这不是结束。最有趣的部分是原始解决方案导致问题的原因。这个问题的答案在于here(第31行)和here(第244行)。

当DEBUG开启时,Django会尝试验证所有已注册的ModelAdmins(第一个链接)。 cls SomeAdmin(即其元类的一个实例)。当调用 hasattr 时,python会尝试在类 SomeAdmin 或其中一个超类中查找属性 field 。由于不可能,因此调用其类的__getattr__(即 SomeAdmin 的元类),其中将新方法添加到类 SomeAdmin 。因此,在渲染界面时, SomeAdmin 已经打补丁,Django能够找到所需的字段(第二个链接)。

当DEBUG为False时,Django会跳过验证。当界面被渲染时,Django试图找到一个字段(再次,第二个链接),但这次SomeAdmin没有打补丁,而且 model_admin 不是类 SomeAdmin ,它是它的的实例即可。因此,尝试在 model_admin 中找到属性 name ,python无法执行此操作,也无法在其类中找到它( SomeAdmin )以及它的任何超类中都会引发异常。

答案 1 :(得分: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(它只是一个简单的字符串),也可以在下面的代码中删除对记录器的所有引用。

'''
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
相关问题