Возврат queryset.values() из viewset.get_queryset()?

Добрый день,

У меня возникли трудности при попытке вернуть queryset.values() из набора представлений get_queryset(). Найдя 6 различных методов, якобы позволяющих справиться с этим, я все еще не уверен, как справиться с этим правильно , поскольку мое единственное работающее решение кажется халтурным.

Я использую values, чтобы получить имя UserAccount через foreign-key, а не его id. Я пробовал использовать only() (заменяя им values()), но, похоже, это ничего не дает. Конечная цель состоит в том, чтобы любой пользователь мог получать комментарии и видеть имена всех комментаторов, а не их ID.


ViewSet

class CommentViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated, )
    serializer_class = serializers.CommentSerializer
    queryset = models.Comment.objects.all()
    lookup_field='comment_id'

    # A. This works, but I don't want to define extra actions if I don't have to.
    @action(detail=False, url_path='use-user-name')
    def use_user_name(self, request, **kwargs):
        """
        Returns the user's name rather than its id.
        """
        return Response(self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # B. This doesn't work.
    def get_queryset(self):
        return self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment')

    # C. Nor does this.
    def get_queryset(self):
        queryset = self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment')
        return json.dumps(list(queryset), cls=DjangoJSONEncoder)

    # D. Nor this.
    def get_queryset(self):
        return serializers.serialize('json', list(self.queryset), fields=('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # E. Nor this.
    def get_queryset(self):
        return list(self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # F. Nor this.
    def get_queryset(self):
        return json.loads(serializers.serialize('json', queryset=self.queryset))

Модели и сериализаторы

class Comment(models.Model):
    comment_id = models.AutoField(primary_key=True, db_column='comment_id')
    operator_id = models.ForeignKey(settings.AUTH_USER_MODEL, models.DO_NOTHING, db_column='operator_id')
    dlc = models.DateTimeField()
    comment = models.CharField(max_length=100)

    class Meta:
        managed = False
        db_table = 'comment'


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Comment
        fields = ('__all__')


# What is defined in "settings.AUTH_USER_MODEL".
class UserAccount(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = UserAccountManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

Errors Raised

Варианты B и E выдают такую ошибку:

  ...
  File ".../lib/python3.8/site-packages/rest_framework/serializers.py", line 664, in <listcomp>
    self.child.to_representation(item) for item in iterable
  File ".../lib/python3.8/site-packages/rest_framework/serializers.py", line 515, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File ".../lib/python3.8/site-packages/rest_framework/relations.py", line 273, in to_representation
    return value.pk
AttributeError: 'int' object has no attribute 'pk'

Варианты C и D выдают эту ошибку:

Got AttributeError when attempting to get a value for field `dlc` on serializer `CommentSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `str` instance.
Original exception text was: 'str' object has no attribute 'dlc'.

Опция F выдает такую ошибку:

KeyError: "Got KeyError when attempting to get a value for field `dlc` on serializer `CommentSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'dlc'."

Выход

Ниже приведены примеры того, что будет возвращено из get_queryset().

# Options A and B produce this:
<QuerySet [
    {'comment_id': 1, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 48), 'comment': 'First comment.'},
    {'comment_id': 2, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 49), 'comment': 'Second comment.'}
]>

# C produces this:
[
    {"comment_id": 1, "operator_id__name": "John Smith", "dlc": "2022-04-03T20:48:48", "comment": "First comment."},
    {"comment_id": 2, "operator_id__name": "John Smith", "dlc": "2022-04-03T20:48:49", "comment": "Second comment."}
]

# Options D and F produce this (note - missing "operator_id__name"):
[
    {"model": "test.comment", "pk": 1, "fields": {"dlc": "2022-04-03T20:48:48", "comment": "First comment."}},
    {"model": "test.comment", "pk": 2, "fields": {"dlc": "2022-04-03T20:48:49", "comment": "Second comment."}}
]

# E produces this:
[
    {'comment_id': 1, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 48), 'comment': 'First comment.'},
    {'comment_id': 2, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 49), 'comment': 'Second comment.'}
]

Учитывая связь между этими двумя моделями, какой лучший способ получить поля Comment с заменой operator_id__name на operator_id?

Что я делаю не так?

Любая помощь будет оценена по достоинству, спасибо, что нашли время прочитать вопрос.

Вы можете сделать следующее:

views.py

class CommentViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated, )
    serializer_class = serializers.CommentSerializer
    queryset = models.Comment.objects.all()

serializers.py

class UserAccountSerializer(serializers.ModelSerializer):
    """UserAccount model serializer"""
    class Meta(object):
        model = UserAccount
        fields = ("id", "email", "name",)
        read_only_fields = ("id", "email", "name",)


class CommentSerializer(serializers.ModelSerializer):
    """Comment model serializer"""
    operator_id = UserAccountSerializer(read_only=True)
    class Meta(object):
        model = Comment
        fields = ("comment_id", "operator_id", "dlc",)
        read_only_fields = ("comment_id",)

или

class CommentSerializer(serializers.ModelSerializer):
    """Comment model serializer"""

    class Meta(object):
        model = Comment
        fields = ("comment_id", "operator_id", "dlc",)
        read_only_fields = ("comment_id",)

    def to_representation(self, instance):

        return {
            "comment_id": instance.id,
            "operator_id__name": instance.operator_id.name,
            "dlc" : instance.dlc
        }

Сериализаторы могут получить доступ к связанным полям через аргумент source.

name = serializers.ReadOnlyField(source='operator_id.name')

и затем, чтобы избежать проблемы n+1, в вашем наборе запросов

return super().get_queryset().select_related("operator_id")

Кроме этого, есть несколько возможных ошибок в вашем коде.

  • Есть ли причина, по которой ваши модели не управляются?
  • models.DO_NOTHING - это крайне плохо, если только у вас нет триггеров или ограничений базы данных. В этом случае вам, вероятно, следует закомментировать их в коде, чтобы следующий человек не предупредил об этом.
  • .
  • Поля отношений в Django уже добавляют _id в конец полей, так что если модель написана вручную, а не сгенерирована, вы должны просто называть поле relation = ForeignKey(), а не relation_id = ForeignKey(). Пренебрегайте этим, если db не управляется django.
  • В основном лучше избегать __all__, если только вы не знаете, что в модель не будет добавлено какое-либо другое поле, которое, возможно, не захочет быть открытым.
Вернуться на верх