【问题标题】:Django Admin: How to access the request object in admin.py, for list_display methods?Django Admin:对于 list_display 方法,如何访问 admin.py 中的请求对象?
【发布时间】:2009-04-07 23:02:57
【问题描述】:

我在模型的 admin.py 类中添加了一个方法 highlight_link

class RadioGridAdmin(admin.ModelAdmin):
    
    list_display = ('start_time', highlight_link)
    
    def highlight_link(self):
        return ('some custom link')
    
    
admin.site.register(RadioGrid, RadioGridAdmin)

它为更改列表中返回的每条记录返回一个自定义链接(为简洁起见,我省略了highlight_link.short_description)。这是伟大的。但我想检查当前的查询字符串并根据它更改自定义链接。有没有办法访问highlight_link中的请求对象?

【问题讨论】:

标签: django django-admin request


【解决方案1】:

我通过这种方式解决了我的问题。

class MyClassAdmin(admin.ModelAdmin):

    def queryset(self, request):
        qs = super(MyClassAdmin, self).queryset(request)
        self.request = request
        return qs

现在我可以在任何地方使用self.request

更新

Changed in Django 1.6: The get_queryset method was previously named queryset.

class MyClassAdmin(admin.ModelAdmin):

    def get_queryset(self, request):
        qs = super(MyClassAdmin, self).get_queryset(request)
        self.request = request
        return qs

【讨论】:

  • 似乎仍然有效,但现在必须将 queryset 替换为 get_queryset
  • 在 1.9 中仍然有效,但现在必须将 queryset 替换为 get_queryset
  • 我似乎没有人真正尝试过这个解决方案,它似乎“NameError: name 'self' is not defined”
  • @fsalazar_sch 名称错误闻起来就像您忘记了方法的“self”第一个参数。
  • 有知识的人可以详细说明为什么这个解决方案不是线程安全的吗?
【解决方案2】:
class RadioGridAdmin(admin.ModelAdmin):

    def highlight_link(self, obj):
        return (self.param)

   def changelist_view(self, request, extra_context=None):
        self.param = request.GET['param']
        return super(RadioGridAdmin,self).changelist_view(request, extra_context=extra_context)

【讨论】:

  • 覆盖 ModelAdmin 的方法是一种非常简洁的方法,但代码有一些错误:“def highlight_link(self):”应该是“def highlight_link(self, obj):”和“super(RadioGridAdmin, self).changelist_view(...)" 前面应该有一个“return”。
  • @CollinAnderson 为什么它不是线程安全的?你能给我解释一下吗?谢谢!
  • 因为 RadioGridAdmin 实例(自身)在请求之间共享。
  • @CollinAnderson 谢谢!我还从源代码中找到了这个:github.com/django/django/blob/…
【解决方案3】:

描述了如何refactor this into a mixin,并根据@taha-jahangir 的回答添加了线程安全位。这是混合:

import threading

class ModelAdminRequestMixin(object):
    def __init__(self, *args, **kwargs):
        # let's define this so there's no chance of AttributeErrors
        self._request_local = threading.local()
        self._request_local.request = None
        super(ModelAdminRequestMixin, self).__init__(*args, **kwargs)

    def get_request(self):
        return self._request_local.request

    def set_request(self, request):
        self._request_local.request = request

    def changeform_view(self, request, *args, **kwargs):
        # stash the request
        self.set_request(request)

        # call the parent view method with all the original args
        return super(ModelAdminRequestMixin, self).changeform_view(request, *args, **kwargs)

    def add_view(self, request, *args, **kwargs):
        self.set_request(request)
        return super(ModelAdminRequestMixin, self).add_view(request, *args, **kwargs)

    def change_view(self, request, *args, **kwargs):
        self.set_request(request)
        return super(ModelAdminRequestMixin, self).change_view(request, *args, **kwargs)

    def changelist_view(self, request, *args, **kwargs):
        self.set_request(request)
        return super(ModelAdminRequestMixin, self).changelist_view(request, *args, **kwargs)

    def delete_view(self, request, *args, **kwargs):
        self.set_request(request)
        return super(ModelAdminRequestMixin, self).delete_view(request, *args, **kwargs)

    def history_view(self, request, *args, **kwargs):
        self.set_request(request)
        return super(ModelAdminRequestMixin, self).history_view(request, *args, **kwargs)

使用 mixin 子类 ModelAdmin:

class PollAdmin(ModelAdminRequestMixin, admin.ModelAdmin):
    pass

...您可以通过任何方法调用self.get_request()

【讨论】:

    【解决方案4】:

    没有直接的方法可以做到这一点。我看到了 2 个可能的解决方案。

    • 使用线程本地存储到同一个请求对象

      from django.utils._threading_local import locals
      
      globals = locals()
      
      class RadioGridAdmin(admin.ModelAdmin):
        def __call__(self, request, *args, **kwargs):
            globals['radio_grid_admin_request'] = request
            return super(RadioGridAdmin, self).__call__(request, *args, **kwargs)
      
        def highlight_link(self):
            request = globals['radio_grid_admin_request']
            # request.GET processing
            return ('some custom link')
      
    • 如果您使用简单的非线程 Django 安装,可以将请求对象保存为属性:

      class RadioGridAdmin(admin.ModelAdmin):
        def __call__(self, request, *args, **kwargs):
            self.request = request
            return super(RadioGridAdmin, self).__call__(request, *args, **kwargs)
      
        def highlight_link(self):
            # self.request.GET processing
            return ('some custom link')
      

    【讨论】:

    • 使用这两种方案中的一种会有什么影响?
    • @Gelbander 如果有并发,只有第一个是安全的。第二个更容易,但你可能会得到错误的请求对象。
    • 何时使用ModelAdmin 实例上的request 参数调用__call__ 方法?我在 Django 的代码中没有看到它。
    • 从 Django 1.11 开始,没有 django.utils._threading_local
    【解决方案5】:

    Diego Puente 答案(python 3.6)的小代码说明:

    class MyClassAdmin(admin.ModelAdmin):
        def __init__(self, model, admin_site): 
            self.request = None
            super().__init__(model, admin_site)
    
        def get_queryset(self, request):
            self.request = request      
            return super().get_queryset(request)
    

    所以你可以从MyClassAdmin的任何其他方法获得self.request

    如果在get_queryset 方法中定义self.request(没有在__init__ 中声明)PyCharm 将生成警告Instance attribute attribute_name defined outside __init__

    【讨论】:

      【解决方案6】:

      这是@user27478 答案的编辑版本,它使用线程本地变量:

      class RadioGridAdmin(admin.ModelAdmin):
          def __init__(self, model, admin_site):
              super().__init__(model, admin_site)
              self._request_local = threading.local()
      
          def changelist_view(self, request, extra_context=None):
              self._request_local.request = request
              return super().changelist_view(request, extra_context)
      
          @property
          def _request(self):
              return self._request_local.request
      
          def example_highlight_link(self, obj):
              changelist = self.get_changelist_instance(self._request)
              url = changelist.get_query_string(new_params={'key1': 1})
      

      【讨论】:

        【解决方案7】:

        我尝试了此处留下的其他答案,但遇到了对我来说变得越来越复杂的问题。我玩了def __call__() 并想出了以下内容。这可能不是正确的方法,但它确实有效......

        在此处获取 GET 变量(所有在 RadioGridAdmin 类中,如我最初的帖子中所述):

        def __call__(self, request, url):
             global start_date
             start_date = request.GET['param']
        
             return super(RadioGridAdmin, self).__call__(request, url)
        

        由于它是全球性的,您现在可以在这里访问它:

        def highlight_link(self):
            # access start_date here
        

        【讨论】:

        • 全局变量在多线程环境的上下文中是危险的,例如在 apache 服务器上的 mod_phyton 中运行。
        【解决方案8】:
        import threading
        
        _thread_local = threading.local()
        
        def get_thread_local_request():
            return getattr(_thread_local, "request", None)
        
        class RadioGridAdmin(admin.ModelAdmin):
            list_display = ('display_field', ...)
        
            def display_field(self, obj):
                # ...
                request = get_thread_local_request()
                # ... 
        

        【讨论】:

        • 你好,使用 django 1.9.6 from django.core import get_thread_local_request 提供 ImportError: cannot import name 'get_thread_local_request'。有什么帮助吗?
        【解决方案9】:

        admin_mixins.py

        ​​>
        from functools import partial, update_wrapper, lru_cache
        
        # Django admin call 2 times get_list_display.
        # We need to return the same function to make the method sortable using 'admin_order_field'
        # https://github.com/django/django/blob/2161db0792f2e4d3deef3e09cd72f7a08340cafe/django/contrib/admin/templatetags/admin_list.py#L84
        @lru_cache(maxsize=100)
        def cache_display_wrap(f, request):
            wf = partial(f, request)
            nf = update_wrapper(wf, f)
            return nf
        
          
        class ModelAdminMixin(admin.ModelAdmin):
          
            def get_list_display(self, request):
                def check_needs_request(display):
                    f = getattr(self, display, None) if not callable(display) else display 
                    if f and getattr(f, 'needs_request', False):
                      return cache_display_wrap(f, request)
                    return display
                return [check_needs_request(display) for display in super().get_list_display(request)]
        

        any_app/admin.py

        ​​>
        from django.contrib import admin
        from core.admin_mixins import ModelAdminMixin
        
        
        @admin.register(AnyModel)
        class AnyModelAdmin(ModelAdminMixin, admin.ModelAdminMixin):
        
            list_display = ['id', 'especial_display_with_request']
        
            def especial_display_with_request(self, request, obj):
                # Make something special with the request
                return obj.any_field
            especial_display_with_request.needs_request = True  # Similar to short_description or any other django admin attr.
        

        来源:https://gist.github.com/pricco/24826bae3d5102d963eb13ecc0493f33

        【讨论】:

          【解决方案10】:

          这是怎么回事:

          def highlight_link(self, request):
              # access start_date here
          

          【讨论】:

          • 我不相信 highlight_link 会将请求对象传递给它。
          • 当一个方法被列为一个字段时,它会被自动调用以生成该字段的HTML,并使用一组参数调用它,request不是其中之一。只是将它添加到方法签名中不会改变它的调用方式。
          猜你喜欢
          • 2016-11-08
          • 1970-01-01
          • 2017-05-28
          • 1970-01-01
          • 2014-01-06
          • 2013-04-28
          • 1970-01-01
          • 1970-01-01
          • 2012-01-28
          相关资源
          最近更新 更多