Django 难以使用 ModelAdmin.queryset 和 ModelAdmin.list_filter 来限制显示的过滤器 itrems

Posted

技术标签:

【中文标题】Django 难以使用 ModelAdmin.queryset 和 ModelAdmin.list_filter 来限制显示的过滤器 itrems【英文标题】:Django difficulty using ModelAdmin.queryset with ModelAdmin.list_filter to limit the filter itrems displayed 【发布时间】:2013-12-05 01:32:55 【问题描述】:

在 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 后应该是自动的东西来说,这似乎是一项非常艰巨的工作!

我在这里想念一个更简单的方法吗,或者这可能是我应该买票的东西?

提前致谢,

丰富。

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

=================我的代码示例================

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)

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

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

【问题讨论】:

请给我们看一些代码! 【参考方案1】:

您是否有任何特殊原因希望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 字段中的每个可能值对 DB 进行单独的查询,因此它的效率有点低 - 如果这变成了问题,您应该改为自定义 __init__

跟进: 好的,所以正确的过滤器选择取决于请求会话。这意味着您需要在子类的__init__ 方法中建立它们。您的示例显示了一个相关字段,因此我将再次使用 RelatedFieldListFilter - 老实说,我不确定受限查询集的概念是否适用于任何其他类型的过滤器。为此,惰性方法(写得更短,效率更低)是调用超类的__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

不太懒惰的方法是从超类的__init__ 方法复制基本代码,并用上面的代码替换self.lookup_choices = field.get_choices(include_blank=False) 行。

请注意,我允许会话可能没有site - 如果是这种情况,您应该考虑您想要发生的事情。如果用户是超级用户,也许不必费心更改lookup_choices

【讨论】:

嗯,不,不是“相关领域”过滤器。对于我给出的示例,“公司”是正在汇总的数据库中的一个字段。例如,它将是当前公司部门的客户列表。问题是当部门“B”中的用户单击过滤器时,他们会得到一个列表,其中包括来自所有部门的所有公司。如果他们单击部门“A”中的公司,则会收到“找不到记录”错误 - 因为更改视图使用 ModelAdmin.queryset。是不是更清楚了? 所以这些字段与查询集的状态无关,但它们本身也受到请求的限制?这是可行的,但它让我更加困惑为什么你认为这是自动的。如果您将模型和管理代码添加到问题中,这将更容易理解。 实际上,您所描述的(“他们获得了一份包含所有部门的所有公司的列表”)不是正常行为。过滤器对管理员的查询集进行操作,因此如果您适当地限制该查询集,则无论它们是否与过滤器匹配,它们都不会显示在更改列表中。我仍然不清楚您是否担心限制过滤器中显示的选项或更改列表中显示的选项。 我可以,但这是一个复杂的例子。让我们尝试使用为 ModelAdmin.queryset 提供的更简单的示例。 docs.djangoproject.com/en/1.5/ref/contrib/admin/… code class MyModelAdmin(admin.ModelAdmin): def queryset(self, request): qs = super(MyModelAdmin, self).queryset(request) if request.user.is_superuser: return qs return qs.filter(author =request.user) code 因此,如果您实际上是在 ModelAdmin 中执行此操作,那么您只对让用户查看他保存的记录感兴趣。因此,在任何列表视图中,您都希望显示的所有过滤器选项也属于该用户。否则,您将看到属于其他用户的选择。如果你点击过滤器中的一个,你会得到一个错误——就像我的用户一样。这行得通吗?

以上是关于Django 难以使用 ModelAdmin.queryset 和 ModelAdmin.list_filter 来限制显示的过滤器 itrems的主要内容,如果未能解决你的问题,请参考以下文章

难以将 slug 添加到 Django 中的通用详细信息视图

Django(22)Django执行SQL语句

Django:使用数据表

使用 Djoser 和 Django Rest Framework 激活帐户

在 Django 中更改数据库表

Django之extra