#python #django #django-rest-framework #django-orm
#python #django #django-rest-framework #django-orm
Вопрос:
У меня есть пользовательская модель профиля пользователя. Эта модель имеет can_edit
свойство, которое использует ContentType
и Permission
объекты, чтобы определить, есть ли у пользователя разрешение или нет. Я использую это свойство в serializer, и оно работает нормально, но ужасно неэффективно, поскольку для каждого пользователя 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
, поэтому каждый экземпляр пользователя добавляет около 6 ненужных запросов для ContentType
и Permission
.
Как я могу его оптимизировать?
Комментарии:
1. не уверен, что это то, что вы хотите, но проверьте cached_property docs.djangoproject.com/en/3.1/ref/utils /…
2. нет, существует несколько объектов, каждый из них будет вызывать свое собственное свойство, которое может иметь разное значение, и все они повторяют одни и те же запросы внизу
3. » , у меня есть более одного свойства с аналогичным содержимым для
can_edit
» В этомUserSerializer
классе или каком-либо другом классе? @Djent4. @ArakkalAbu — в том же классе
5. @Djent Можете ли вы добавить пример этого? (возможно, я смогу предоставить точное решение)
Ответ №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) красиво и эффективно построен
Примечание
- Я не думаю, что нам нужно узнавать строку <app_label>_<имя_модели> «программным» способом.
Комментарии:
1. Это, безусловно, «рекомендуемое» решение! Гораздо лучше кэшировать результат проверки, а не кэшировать шаги, необходимые для выполнения указанной проверки (особенно когда она встроенная). Обратите внимание , что это связано с предостережениями, например, вам нужно будет повторно заполнить кеш, если вы программно добавляете разрешение пользователю, а затем немедленно проверяете его.
Permission Caching
Для получения дополнительной информации см. Документы на **.2. Спасибо, это имеет смысл. Я имею в виду, мне все еще нужно получить имена разрешений программно, но я могу кэшировать их как атрибут класса для
CustomProfile
.3. @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))
Теперь вы можете использовать эти значения в своих методах.
Комментарии:
1. Но он все равно будет повторяться несколько раз, поскольку будут созданы экземпляры нескольких объектов профиля.
Ответ №3:
Используйте @cached_property
вместо @property
кеширования can_edit
.
https://docs.djangoproject.com/en/3.1/ref/utils/#django.utils .функциональный.cached_property
Ответ №4:
Для оптимизации запроса вы можете использовать prefetch_related
в сочетании с Case...When
условными выражениями
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]