Возврат 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__, если только вы не знаете, что в модель не будет добавлено какое-либо другое поле, которое, возможно, не захочет быть открытым.