【问题标题】:How can I override user.groups in a custom account model in Django to implement a "virtual" group?如何在 Django 的自定义帐户模型中覆盖 user.groups 以实现“虚拟”组?
【发布时间】:2019-07-04 01:23:47
【问题描述】:

我在一个使用 PermissionsMixin 的 Django 应用程序中有一个自定义帐户类:

class Account(AbstractBaseUser, PermissionsMixin):

我们的 CMS 调用此类上的各种 .groups 方法以确定权限。

本质上,我们希望在此自定义 Account 类中覆盖从 .groups 返回的查询集,并在特定条件下注入一个额外的组。 (即用户有一个有效的订阅,然后我们希望将“成员”作为该用户的组之一返回,尽管他们实际上并不在组中。)

我们应该如何处理这个覆盖?我们需要获取原始组,以免破坏基本组功能,然后将我们的“虚拟”组注入查询集中。

【问题讨论】:

  • 我认为我可能需要使用某种类型的经理来执行此操作。非常感谢任何建议。
  • ...或者这只能通过一些可怕的猴子补丁来实现?
  • groupsManyToManyField。你不想使用ManyToManyField?或者您想将ManyToManyField 与您的virtual groups 一起使用?为什么不在Group 中添加virtual 组行

标签: django


【解决方案1】:

如果您的 CMS 直接调用 Account.groups,我会调查如何覆盖 ManyRelatedManager,因为 account.groups 实际上是 django ManyRelatedManager 对象。

使用 django Proxy model for Group 大概可以实现。

一种调查策略是:

  • 将虚拟组添加到数据库中的组中。

  • 通过更改 get_queryset()(基本查询集)覆盖 ManyRelatedManager

类似(伪代码):

def get_queryset():
   account = self.instance # Guess this exist in a RelatedManager
   if account.has_active_subscribtion():
     return self.get_all_relevant_groups_including_the_virtual_one()
   return self.get_all_relevant_groups_excluding_the_virtual_one()

这里的关键是在自定义的 RelatedManager 中访问当前实例

这可能是围绕自定义相关管理器的有用讨论。 Subclass a Django ManyRelatedManager a.k.a. ManyToManyField

我不建议使用查询集本身尝试 fiddle/monkey 补丁,因为它很容易破坏 django 管理员等...

【讨论】:

    【解决方案2】:

    更新

    在仔细阅读与 Managers 相关的文档并考虑您的要求后,我不得不说没有办法实现您想要的魔法(我需要覆盖原来的,而不是添加新的 ext_groups 集- 我需要改变调用组的第三方库行为。)不接触 Django 核心本身(猴子修补会搞乱管理员,与属性相同)。

    在我提出的解决方案中,您有必要向Group 添加一个新的管理器,也许,您应该开始考虑覆盖您正在使用的第三方库,并使其使用组的管理器你有兴趣。

    如果第三方库至少是中等质量,它会实施测试,帮助您在更改后保持它的工作。

    建议的解决方案

    好吧,好消息是你可以满足你的业务需求,坏消息是你的代码会比你预期的多一点。

    我们应该如何处理这个覆盖?

    您可以对Group 类使用代理模型,以添加返回所需查询集的自定义管理器。

    代理管理器不会为组添加额外的表,并且会保留所有 Group 功能,此外,您还可以在代理模型上设置自定义管理器,因此非常适合这种情况使用。

    class ExtendedGroupManager(models.Manager):
        def get_queryset(self):
            qs = super().get_queryset()
            # Do work with qs.
            return qs
    
    
    class ExtendedGroup(Group):
        objects = ExtendedGroupManager()
    
        class Meta:
            proxy = True
    

    那么你的Account 类应该与ExtendedGroup 有一个ManyToMany 关系,可以称为... ext_groups?

    到目前为止,您可以:

    acc = Account(...)
    acc.groups.all()      # All groups for this account (Django default).
    acc.ext_groups.all()  # Same as above, plus any modification you have done in get_queryset  method. 
    

    然后,在视图中,您可以根据自己选择的条件(例如,用户已订阅)决定是否调用一个或另一个。

    值得一提的是,您可以使用 contribute_to_class 方法将自定义管理器添加到现有模型中

    em = ExtendGroupManager()
    em.contribute_to_class(Group, 'ext_group')
    
    # Group.ext_group  is now available.
    

    【讨论】:

    • 谢谢。问题是我需要覆盖原始文件,而不是添加新的 ext_groups 集 - 我需要更改调用组的第三方库行为。
    • 啊,我明白了。我今天试一试
    【解决方案3】:

    groups 相关管理器由PermissionMixin 添加,您实际上可以删除mixin 并仅添加您需要的部分并重新定义groups

    class Account(AbstractBaseUser):
        # add the fields like is_superuser etc...
        # as defined in https://github.com/django/django/blob/master/django/contrib/auth/models.py#L200
    
        default_groups = models.ManyToManyField(Group)
    
        @property
        def groups(self):
            if self.is_subscribed:
                return Group.objects.filter(name="subscribers")
            return default_groups.all()
    

    然后您可以使用Group 模型添加您的自定义组。这种方法应该可以正常工作,只要groups 返回查询集而不是管理器的所有部分都可以(这可能大部分应该没问题,因为管理器大多提供相同的方法 - 但您可能需要自己找出答案)。

    【讨论】:

      【解决方案4】:

      覆盖get_queryset 方法ManyRelatedManagerManyRelatedManager 类的对象可以访问父实例。

      代码示例:

      def add_custom_queryset_to_many_related_manager(many_related_manage_cls):
          class ExtendedManyRelatedManager(many_related_manage_cls):
              def get_queryset(self):
                  qs = super(ExtendedManyRelatedManager, self).get_queryset()
                  # some condition based on the instance
                  if self.instance.is_staff:
                      return qs.union(Group.objects.filter(name='Gold Subscription'))
                  return qs
      
          return ExtendedManyRelatedManager
      

      ManyRelatedManager 类是从 ManyToManyDescriptor.

      class ExtendedManyToManyDescriptor(ManyToManyDescriptor):
      
          @cached_property
          def related_manager_cls(self):
              model = self.rel.related_model if self.reverse else self.rel.model
              return add_custom_queryset_to_many_related_manager(create_forward_many_to_many_manager(
                  model._default_manager.__class__,
                  self.rel,
                  reverse=self.reverse,
              ))
      

      ExtendedManyToManyDescriptorgroups 字段相关联时 Account 类已初始化。

      class ExtendedManyToManyField(ManyToManyField):
      
          def contribute_to_class(self, cls, name, **kwargs):
              super(ExtendedManyToManyField, self).contribute_to_class(cls, name, **kwargs)
              setattr(cls, self.name, ExtendedManyToManyDescriptor(self.remote_field, reverse=False))
      

      覆盖 PermissionsMixin 以将 ExtendedManyToManyField 用于 groups 字段而不是 ManyToManyField

      class ExtendedPermissionsMixin(PermissionsMixin):
          groups = ExtendedManyToManyField(
              Group,
              verbose_name=_('groups'),
              blank=True,
              help_text=_(
                  'The groups this user belongs to. A user will get all permissions '
                  'granted to each of their groups.'
              ),
              related_name="user_set",
              related_query_name="user",
          )
      
          class Meta:
              abstract = True
      

      参考: django.db.models.fields.related_descriptors.create_forward_many_to_many_manager

      测试:

      account = Account.objects.get(id=1)
      account.is_staff = True
      account.save()
      
      account.groups.all() 
      
      # output
      [<Group: Gold Subscription>]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-03
        • 1970-01-01
        • 2017-02-26
        • 2015-02-27
        • 1970-01-01
        • 2018-03-07
        • 2012-05-28
        相关资源
        最近更新 更多