【问题标题】:Django: Use query result in future queries instead of repeating itDjango:在以后的查询中使用查询结果而不是重复它
【发布时间】:2021-01-20 04:07:30
【问题描述】:

我有一个自定义用户配置文件模型。该模型具有can_edit 属性,该属性使用ContentTypePermission 对象来确定用户是否具有权限。我在序列化程序中使用此属性,它工作正常,但效率极低,因为每个用户都会再次查询 ContentTypePermission

似乎prefetch_related 可以放在这里,但我不知道如何应用它,因为这些对象不是通过某些属性直接引用的,而是单独查询的。是否可以事先获取ContentTypePermission 并在进一步查询中使用结果?

这是我的模型:

class CustomProfile(models.Model):
    user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)

    @property
    def can_edit(self):
        content_type = ContentType.objects.get_for_model(Article)
        permission, _ = Permission.objects.get_or_create(
            codename="edit", name="Can edit", content_type=content_type
        )
        return self.user.has_perm(self._permission_name(permission))

    def _permission_name(self, permission):
        return f"{permission.content_type.app_label}.{permission.codename}"

我当前的查询:

User.objects.order_by("username").select_related("profile")

我的序列化器:

class UserSerializer(serializers.ModelSerializer):
    can_edit = serializers.ReadOnlyField(source="profile.can_edit")

    class Meta:
        model = User
        fields = (
            "id",
            "username",
            "first_name",
            "last_name",
            "is_active",
            "is_staff",
            "can_edit",
        )

事实上,我有不止一个与can_edit 内容相似的属性,因此每个用户实例添加了大约6 个对ContentTypePermission 的不必要查询。

如何优化它?

【问题讨论】:

  • 不确定这是否是您想要的,但请查看 cached_property docs.djangoproject.com/en/3.1/ref/utils/…
  • 不,有多个对象,每个对象都会调用自己的属性,可能具有不同的值,并且它们都在下面重复相同的查询
  • ",我有不止一个属性与can_edit" 在这个UserSerializer 类或其他类中? @Djent
  • @ArakkalAbu - 同班
  • @Djent 你能添加一个例子吗? (也许我可以提供一个准确的解决方案)

标签: python django django-rest-framework django-orm


【解决方案1】:

其实你不必重新发明轮子,Django 已经为我们做了。

在此处更改您的 can_edit(...),如下所示。

class CustomProfile(models.Model):
    user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)

    @property
    def can_edit(self):
        return self.user.has_perm("app_name_article.can_edit")

在这里,user.has_perm()--(Django Doc) 以高效的方式精美地构建

注意

  • 我认为我们不需要以 “程序化” 的方式找出 _ 字符串。

【讨论】:

  • 这肯定是“推荐”的解决方案!缓存检查结果比缓存执行所述检查所需的步骤要好得多(尤其是内置时)。请注意这有一些注意事项,例如,如果您以编程方式向用户添加权限,则需要重新填充缓存,然后立即检查它。有关更多信息,请参阅 **Permission Caching 上的文档。
  • 谢谢,有道理。我的意思是,我仍然需要以编程方式获取权限名称,但我可以将它们缓存为CustomProfile 的类属性。
  • @Djent "....我仍然需要以编程方式获取权限名称....",不幸的是,我没有看到任何这种情况给定上下文。恕我直言,使用硬编码字符串而不是编程方式(在这种情况下)
【解决方案2】:

使用 __init__ 特殊方法来初始化您需要多次使用的任何属性/变量。

例子:

class CustomProfile(models.Model):
   user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
   
   def __init__(self, *args, **kwarg):
       super().__init__(*args, **kwargs)
       self.content_type = ContentType.objects.get_for_model(Article)
       self.permission, _ = Permission.objects.get_or_create(codename="edit", name="Can edit", content_type=content_type)

    @property
    def can_edit(self):
       return self.user.has_perm(self._permission_name(self.permission))

现在您可以在方法中使用这些值。

【讨论】:

  • 但仍然会重复多次,因为多个配置文件对象将被实例化。
【解决方案3】:

使用@cached_property而不是@property来缓存can_edit
https://docs.djangoproject.com/en/3.1/ref/utils/#django.utils.functional.cached_property

【讨论】:

    【解决方案4】:

    为了优化查询,可以将prefetch_relatedCase...When结合使用Conditional Expressions

    from django.db.models import Case, When, BooleanField
    
    qs = User.objects.prefetch_related('user_permissions').annotate(
          can_edit = Case(
          When(user_permissions__codename='edit_article', then=True),
          default=False, 
          output_field=BooleanField())
         )
    
    # check the results
    >>> [(i.can_edit, i.username) for i in qs] 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-14
      • 1970-01-01
      • 2011-11-15
      • 2013-03-25
      • 1970-01-01
      • 2019-05-17
      • 1970-01-01
      相关资源
      最近更新 更多