Skip to main content
 首页 » 编程设计

python之Django 难以将 ModelAdmin.queryset 与 ModelAdmin.list_filter 一起使用来限制显示的过滤器 itrems

2024年11月01日28落叶

在 Django 1.5.1 中,我设置了一个供内部使用的 SAAS 系统,每个部门都在其中获取自己的数据集。

为此,我使用 ModelAdmin.queryset ( https://docs.djangoproject.com/en/1.5/ref/contrib/admin/#django.contrib.admin.ModelAdmin.queryset ) 将所有数据限制为仅属于当前登录用户所在部门的记录.

这非常适合主要的管理功能(汇总表等)。但是我放在 "ModelAdmin.list_filter" 上的任何内容都会显示所有值 - 显然使用的是基本查询集,而不是我在 ModelAdmin.queryset 中定义的那个。

我可以在这里看到如何定义自定义 ModelAdmin.list_filter 查询:https://docs.djangoproject.com/en/1.5/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter这让您还可以定义一个自定义过滤器管理器,其中可以包含一个自定义查询集。

但这是PER FILTER FIELD!

对于添加 ModelAdmin.queryset 后应该自动执行的操作来说,这似乎是一项非常繁重的工作!

有没有我在这里缺少的更简单的方法,或者这是我应该出票的方法?

提前致谢

丰富。

Peter,谢谢你踢我的裤子。原来我对自己问题的理解是错误的!

================= 来 self 的代码的示例 ================

class Company(models.Model): 
    """Company model.""" 
    accountid = models.CharField(_('Account Number'), max_length=20, null=False, blank=True, help_text="Link to old system and records") 
    name = models.CharField(_('name'), max_length=200, unique=True, help_text=_("Enter full company name, no abbreviations. Duplicates are not allowed.")) 
    nickname = models.CharField(_('nickname'), max_length=50, blank=True, null=True, help_text=_("Enter one or more keywords or abbreviations seperated by a space to make searching easier")) 
    slug = AutoSlugField(_('slug'), max_length=50, unique=True, blank=False, populate_from=('name', )) 
    site = models.ForeignKey(Site, default="0", blank=False, null=False, editable=False, help_text="Indicate which site this record belongs to. ") 
 
 
class CompanyAdmin(admin.ModelAdmin): 
    list_filter = ('type', 'lead_quality', ) 
 
    def queryset(self, request): 
        qs = super(PersonAdmin, self).queryset(request) 
        if request.user.is_superuser: 
            self.message_user(request, "Warning: This is the ADMINISTRATOR view!!", 'warning') 
            return qs 
        return qs.filter(site__id=request.session['site'].id) 
 
    def save_model(self, request, obj, form, change): 
        if change: 
            if obj.site.id != request.session['site'].id: 
                logger.debug("Contacts.Person.save_model: replacing site (%s) with (%s) " % (repr(obj.site), repr(request.session['site'])) ) 
        else: 
            logger.debug("Contacts.Person.save_model: setting site (%s)" % (repr(request.session['site'])) ) 
        obj.site = request.session['site'] 
        obj.save() 
 
 
class Person(models.Model): 
    """Person model.""" 
    first_name = models.CharField(_('first name'), max_length=100) 
    last_name = models.CharField(_('last name'), max_length=200) 
    slug = AutoSlugField(_('slug'), max_length=50, unique=True, blank=False, populate_from=('first_name', 'last_name')) 
    company = models.ForeignKey(Company, blank=True, null=True, help_text=_("If this person is associated with a Company, indicate which one here.") ) 
    site = models.ForeignKey(Site, default="0", blank=False, null=False, editable=False, help_text="Indicate which site this record belongs to. ") 
 
 
class PersonAdmin(admin.ModelAdmin): 
    list_filter = ('company',) 
 
    def queryset(self, request): 
        qs = super(PersonAdmin, self).queryset(request) 
        if request.user.is_superuser: 
            self.message_user(request, "Warning: This is the ADMINISTRATOR view!!", 'warning') 
            return qs 
        return qs.filter(site__id=request.session['site'].id) 
 
    def save_model(self, request, obj, form, change): 
        if change: 
            if obj.site.id != request.session['site'].id: 
                logger.debug("Contacts.Person.save_model: replacing site (%s) with (%s) " % (repr(obj.site), repr(request.session['site'])) ) 
        else: 
            logger.debug("Contacts.Person.save_model: setting site (%s)" % (repr(request.session['site'])) ) 
        obj.site = request.session['site'] 
        obj.save() 
 
    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
        if db_field.name in ('company',): 
            kwargs["queryset"] = Company.objects.get_query_set().filter(site__id=request.session['site'].id) 
        return super(PersonAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

因此,当用户查看People 的摘要屏幕并点击Company 的过滤器时,他们会看到属于其他公司部门的选项。

如果他们点击属于另一个部门的记录,他们会得到一个空列表,因为 PersonAdmin 实际上无法访问该记录。

请您参考如下方法:

您是否有任何特殊原因希望 list_filter 中的相关字段关心管理类上的查询集?此类过滤器通常不会删除不存在相关对象的值。

如果您不想要这种行为,您可以编写一个单独的过滤器类来限制选择,但是您喜欢作为 django.contrib.admin.filter.RelatedFieldListFilter 的子集并使用它,要么覆盖其 choices 方法(您在模型管理中定义的查询集将在该方法中作为 cl.root_query_set 提供)或覆盖其 __init__ 方法以不同方式创建 self.lookup_choices - 可能基于请求。我认为您没有理由必须继续重新定义类 - 一个定义应该适用于您喜欢的尽可能多的相关领域。

这是一个简单的查询,如果管理员的过滤查询集至少有一个对象作为过滤值,则应该只在过滤器中包含项目:

class RelatedFieldRestrictedListFilter(RelatedFieldListFilter): 
 
    def choices(self, cl): 
        from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 
        yield { 
            'selected': self.lookup_val is None and not self.lookup_val_isnull, 
            'query_string': cl.get_query_string({}, 
                [self.lookup_kwarg, self.lookup_kwarg_isnull]), 
            'display': _('All'), 
        } 
        for pk_val, val in self.lookup_choices: 
             if cl.root_query_set.filter(**{self.lookup_kwarg: pk_val}).exists(): 
                 yield { 
                    'selected': self.lookup_val == smart_unicode(pk_val), 
                    'query_string': cl.get_query_string({ 
                        self.lookup_kwarg: pk_val, 
                    }, [self.lookup_kwarg_isnull]), 
                    'display': val, 
                    } 
        if (isinstance(self.field, models.related.RelatedObject) 
                and self.field.field.null or hasattr(self.field, 'rel') 
                    and self.field.null): 
            yield { 
                'selected': bool(self.lookup_val_isnull), 
                'query_string': cl.get_query_string({ 
                    self.lookup_kwarg_isnull: 'True', 
                }, [self.lookup_kwarg]), 
                'display': EMPTY_CHANGELIST_VALUE, 
            } 

这会针对 list_filter 字段中的每个可能值对数据库进行单独查询,因此效率有点低 - 如果这变成一个问题,您应该自定义 __init__相反。

跟进: 好的,所以正确的过滤器选择取决于请求 session 。这意味着您需要在子类的 __init__ 方法中建立它们。您的示例显示了一个相关字段,因此我将再次使用 RelatedFieldListFilter - 老实说,我不确定受限查询集的概念对任何其他类型的过滤器是否有意义。为此,惰性方法(编写更短、效率更低)是调用父类(super class)的 __init__,然后更改 self.lookup_choices。不太懒惰的方法是完全覆盖 __init__。

惰性方法是这样的:

from django.utils.encoding import smart_unicode 
 
class RelatedFieldRestrictedListFilter(RelatedFieldListFilter): 
 
    def __init__(self, field, request, params, model, model_admin, field_path): 
        super(RelatedFieldRestrictedListFilter, self).__init__(field, request, params, model, model_admin, field_path) 
        if 'site' in request.session: 
            self.lookup_choices = [(instance.pk, smart_unicode(instance) for instance in model.objects.filter(site=request.session['site'])] 
        else: 
            # something else here 

不太懒惰的方法涉及从父类(super class)的 __init__ 方法复制基本代码,并将 self.lookup_choices = field.get_choices(include_blank=False) 行替换为以上。

请注意,我考虑到 session 可能没有站点 的可能性 - 如果是这种情况,您应该考虑您希望发生什么。如果用户是 super 用户,也许不必费心更改 lookup_choices。