Link To Foreignkey in Admin Causes AttributeError When Debug Is False

前端 未结 2 879
深忆病人
深忆病人 2021-02-04 21:21

I have used the following code in my models.py file:

Create hyperlink to foreignkey

class ModelAdminWithForeignKeyLinksMetaclass(MediaDefiningClass): 
         


        
相关标签:
2条回答
  • 2021-02-04 21:51

    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.

    0 讨论(0)
  • 2021-02-04 21:55

    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
    
    0 讨论(0)
提交回复
热议问题