Django: используйте результат запроса в будущих запросах вместо его повторения

#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 классе или каком-либо другом классе? @Djent

4. @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]