Django:在以后的查询中使用查询结果而不是重复它

Django: Use query result in future queries instead of repeating it

我有一个自定义用户配置文件模型。该模型有 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 具有相似的内容,因此每个用户实例为 ContentTypePermission 添加了大约 6 个不必要的查询。

如何优化它?

使用 __init__ 特殊方法初始化您需要多次使用的任何 propertys/variables。

示例:

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))

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

其实不用重新发明轮子,Django已经帮我们搞定了。

在这里更改你的can_edit(...)如下。

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

    <b>@property
    def can_edit(self):
        return self.user.has_perm("app_name_article.can_edit")</b>

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

备注

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

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

from django.db.models import Case, When, BooleanField

qs = User.objects.<b>prefetch_related('user_permissions')</b>.annotate(
      can_edit = <b>Case(
      When(user_permissions__codename='edit_article', then=True),
      default=False, 
      output_field=BooleanField())</b>
     )

# check the results
>>> [(i.can_edit, i.username) for i in qs] 

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