получить список рецептов из базы и вернуть ответ в...
Tạo vào: 13 tháng 2, 2025
Tạo vào: 13 tháng 2, 2025
получить список рецептов из базы и вернуть ответ вида: (serializer.data,
status=status.HTTP_200_OK)
const responseSchema = {
"type": "object",
"required": ["count", "next", "previous", "results"],
"additionalProperties": false,
"properties": {
"count": {"type": "number"},
"next": {"type": ["string", "null"]},
"previous": {"type": ["string", "null"]},
"results": {
"type": "array",
"items": {
"type": "object",
"properties":{
"id": {"type": "number"},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "number"},
"name": {"type": "string"},
"slug": {"type": "string"},
},
"required": ["id", "name", "slug"],
"additionalProperties": false
}
},
"author": {
"type": "object",
"properties": {
"id": {"type": "number"},
"username": {"type": "string"},
"first_name": {"type": "string"},
"last_name": {"type": "string"},
"email": {"type": "string"},
"is_subscribed": {"type": "boolean"},
"avatar": {"type": ["string", "null"]}
},
"required": ["id", "username", "first_name", "last_name", "email", "is_subscribed", "avatar"],
"additionalProperties": false
},
"ingredients": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "number"},
"name": {"type": "string"},
"measurement_unit": {"type": "string"},
"amount": {"type": "number"}
},
"required": ["id", "name", "measurement_unit", "amount"],
"additionalProperties": false
}
},
"is_favorited": {"type": "boolean"},
"is_in_shopping_cart": {"type": "boolean"},
"name": {"type": "string"},
"image": {"type": "string"},
"text": {"type": "string"},
"cooking_time": {"type": "number"}
},
"required": [
"id", "tags", "author", "ingredients", "is_favorited", "is_in_shopping_cart",
"name", "image", "text", "cooking_time"
],
"additionalProperties": false
}
}
}
};
pm.test("Статус-код ответа должен быть 200", function () {
pm.expect(
pm.response.status,
"Запрос незарегистрированного пользователя должен вернуть ответ со статус-кодом 200"
).to.be.eql("OK");
});
pm.test('Структура ответа должна соответствовать ожидаемой', function () {
pm.expect(
pm.response,
"Убедитесь, что для запрошенного эндпоинта корректно настроена пагинация"
).to.have.jsonSchema({"type": "object"});
pm.response.to.have.jsonSchema(responseSchema);
});
Ниже приведён пример, как в Django REST Framework можно отдать список рецептов с пагинацией и структурой ответа в формате:
json{ "count": <число>, "next": "<url или null>", "previous": "<url или null>", "results": [ { "id": ..., "tags": [...], "author": {...}, "ingredients": [...], "is_favorited": true/false, "is_in_shopping_cart": true/false, "name": "...", "image": "...", "text": "...", "cooking_time": ... } ] }
Сериализатор (пример)
Предположим, у вас уже есть модели Recipe
, Tag
, Ingredient
, а также модель пользователя User
. Для соответствия схеме нужно описать сериализаторы. Ниже пример «упрощённого» варианта, где предполагается, что:
tags
— Many-to-many к модели Tag
.ingredients
— Many-to-many или связанно через промежуточную модель, в которой хранится amount
.author
) ссылается на стандартную (или кастомную) модель пользователя.pythonfrom rest_framework import serializers from .models import Recipe, Tag, Ingredient, User class TagSerializer(serializers.ModelSerializer): class Meta: model = Tag fields = ('id', 'name', 'slug') # должно совпадать с ожидаемыми полями class UserSerializer(serializers.ModelSerializer): # Поле is_subscribed и avatar могут быть вашими кастомными полями; # в стандартном User их нет, но предположим, что у вас так. class Meta: model = User fields = ( 'id', 'username', 'first_name', 'last_name', 'email', 'is_subscribed', 'avatar', ) class IngredientInRecipeSerializer(serializers.ModelSerializer): """ Если модель Ingredient напрямую не хранит в себе "amount", то обычно делается либо вложенный сериализатор, либо дополнительное поле (source), обращающееся к промежуточной модели. Для простоты допустим, что amount хранится напрямую или выносится в отдельную логику. """ amount = serializers.IntegerField() class Meta: model = Ingredient fields = ('id', 'name', 'measurement_unit', 'amount') class RecipeSerializer(serializers.ModelSerializer): tags = TagSerializer(many=True) author = UserSerializer() ingredients = IngredientInRecipeSerializer(many=True) is_favorited = serializers.BooleanField() is_in_shopping_cart = serializers.BooleanField() class Meta: model = Recipe fields = ( 'id', 'tags', 'author', 'ingredients', 'is_favorited', 'is_in_shopping_cart', 'name', 'image', 'text', 'cooking_time', )
Настройки пагинации
Чтобы вернуть ключи count
, next
, previous
, results
, необходимо использовать встроенные механизмы пагинации DRF.
В файле settings.py
добавьте, например:
pythonREST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, # Количество объектов на одной странице }
Либо укажите пагинацию прямо во вьюхе (см. пример ниже).
Вьюха (пример на базе ListAPIView
)
pythonfrom rest_framework import generics, status from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response from .models import Recipe from .serializers import RecipeSerializer class RecipePagination(PageNumberPagination): page_size = 10 # Можно настроить как нужно class RecipeListView(generics.ListAPIView): queryset = Recipe.objects.all() serializer_class = RecipeSerializer pagination_class = RecipePagination # Переопределять метод list() часто необязательно, но если хотите # явно управлять логикой, можно так: def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) # Вернёт структуру: {count, next, previous, results: [...]} return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK)
RecipeListView
вы получите структуру, которую ожидает Postman-тест, а именно:
json{ "count": <общее кол-во рецептов>, "next": "<ссылка на след. страницу или null>", "previous": "<ссылка на пред. страницу или null>", "results": [ ... ] }
Пример простой view-функции (если не хотите использовать generic-классы):
pythonfrom rest_framework.decorators import api_view from rest_framework import status from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination @api_view(['GET']) def recipe_list(request): queryset = Recipe.objects.all() paginator = PageNumberPagination() paginator.page_size = 10 # Разбиваем queryset на «страницу» page = paginator.paginate_queryset(queryset, request) serializer = RecipeSerializer(page, many=True) # Формируем стандартный ответ с {count, next, previous, results} return paginator.get_paginated_response(serializer.data)
Здесь DRF также автоматически подставит count
, next
, previous
, results
.
Postman-тест
Судя по вашему скрипту на Postman, вы проверяете:
count
, next
, previous
и results
.При условии, что вьюха и сериализатор настроены как указано выше, тесты должны проходить успешно.
Таким образом, чтобы «вернуть список рецептов из базы и ответить (serializer.data, status=200)» именно в формате, который требует ваш Postman-тест, необходимо:
tags
, ingredients
, полям пользователя и пр.).count
, next
, previous
, results
.ListAPIView
) или функцию с ручной пагинацией (PageNumberPagination
) и возвращать get_paginated_response(serializer.data)
.После этого Postman-тест с предоставленной JSON-схемой должен проходить без ошибок.