Django:在以后的查询中使用查询结果而不是重复它
Django: Use query result in future queries instead of repeating it
我有一个自定义用户配置文件模型。该模型有 can_edit
属性 使用 ContentType
和 Permission
对象来确定用户是否具有权限。我在序列化程序中使用这个 属性,它工作正常,但效率非常低,因为每个用户再次查询 ContentType
和 Permission
。
似乎 prefetch_related
适合这里,但我不知道如何应用它,因为这些对象不是通过某些属性直接引用的,而是单独查询的。是否可以预先获取 ContentType
和 Permission
并在进一步查询中使用结果?
这是我的模型:
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
具有相似的内容,因此每个用户实例为 ContentType
和 Permission
添加了大约 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_related
和Case...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
我有一个自定义用户配置文件模型。该模型有 can_edit
属性 使用 ContentType
和 Permission
对象来确定用户是否具有权限。我在序列化程序中使用这个 属性,它工作正常,但效率非常低,因为每个用户再次查询 ContentType
和 Permission
。
似乎 prefetch_related
适合这里,但我不知道如何应用它,因为这些对象不是通过某些属性直接引用的,而是单独查询的。是否可以预先获取 ContentType
和 Permission
并在进一步查询中使用结果?
这是我的模型:
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
具有相似的内容,因此每个用户实例为 ContentType
和 Permission
添加了大约 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_related
和Case...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