Ссылка на Foreignkey в Admin вызывает AttributeError, когда отладка ложна - PullRequest
4 голосов
/ 25 июня 2011

Я использовал следующий код в моем файле 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 ]

12 голосов
/ 25 августа 2011

Я наткнулся на точно такую ​​же проблему, к счастью, я ее исправил.

Исходное решение (которое вы использовали) исходит от этого вопроса , мое решение основано на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

Ну, единственное, что вам нужно, это заменить оригинальный ModelAdminWithForeignKeyLinksMetaclass на приведенный выше.

Однако это еще не конец.Самое интересное, почему оригинальное решение вызывает проблемы.Ответ на этот вопрос лежит здесь (строка 31) и здесь (строка 244).

Когда DEBUG включен, Django пытается проверить все зарегистрированные ModelAdmins (первая ссылка).Там cls - это класс SomeAdmin (т.е. экземпляр его метакласса).Когда вызывается hasattr , python пытается найти атрибут field в классе SomeAdmin или в одном из его суперклассов.Поскольку это невозможно, вызывается __getattr__ его класса (то есть метакласс SomeAdmin ), где к классу SomeAdmin добавляется новый метод.Следовательно, когда дело доходит до визуализации интерфейса, SomeAdmin уже исправлен, и Django может найти требуемое поле (вторая ссылка).

Когда DEBUG имеет значение False, Django пропускает проверку.Когда интерфейс отображается, Django пытается найти поле (опять-таки, вторая ссылка), но на этот раз SomeAdmin не исправлен, более того model_admin не является классом SomeAdmin , это его экземпляр .Таким образом, пытаясь найти атрибут name в model_admin , python не может этого сделать, а также не может найти его в своем классе ( SomeAdmin ) каккак и в любом из его суперклассов, поэтому возникает исключение.

1 голос
/ 23 февраля 2012

Я использую реализацию stepank, но мне пришлось немного изменить ее, чтобы она соответствовала моему сценарию использования.

Я просто добавил поддержку для ModelAdmin.readonly_fields и ModelAdmin.fields для поддержки link_to_-syntax.

Небольшое изменение в создании ссылки также позволило мне поддерживать ссылку на другой APP.Model, в моем случае встроенный django.contrib.auth.models.user.

Спасибо за хорошую работу @stepank и @Itai Tavor.

Надеюсь, это будет полезно для кого-то еще.


DEFAULT_LOGGER_NAME определено в моем settings.py, и я используюэто для большей части моей регистрации.Если он не определен, вы получите ошибки при использовании следующего кода.Вы можете определить свой собственный DEFAULT_LOGGER_NAME в settings.py (это просто простая строка) или просто удалить все ссылки на регистратор в приведенном ниже коде.

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