Объединение моделей в сериализаторе Python DRF без введения внешнего ключа - PullRequest
0 голосов
/ 29 мая 2020

Так что я относительно новичок в Django и DRF. Я недавно начал проект, чтобы узнать больше о платформе.

Я также нашел руководство по передовой практике, которому я следую в данный момент. Вот Django Руководство по стилю

Строка гильдии говорит, что каждое приложение должно полностью отделяться и использовать UUID вместо внешнего ключа. И каждое бизнес-правило должно go в сервисах. py (что мне нравится, кстати.)

Вот и проблема. Я не уверен, как я могу построить две модели вместе, чтобы получить вложенный результат JSON.

Пример: у меня есть модель Post() и модель Comments(). и модель комментариев использует uuid модели Post в качестве ссылочного идентификатора.

Теперь в моем API.py я не уверен, как я могу объединить эти два вместе в Serializer.py

К вашему сведению, приведенный ниже код предназначен только для демонстрационных целей, может быть не исполняемым

Модель

class Post(models.Model):
    user_id = models.UUIDField(default=uuid.uuid4)
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=100)
    pictures = models.CharField(max_length=100)
    lat = models.CharField(max_length=16)
    long = models.CharField(max_length=16)
    vote = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now_add=True)

class Comment(models.Model):
    post_id =  models.UUIDField(default=uuid.uuid4)
    parent_id = models.ForeignKey("self", on_delete=models.CASCADE)
    text = models.CharField(max_length=1000)
    up_vote = models.IntegerField()
    down_vote = models.IntegerField()
    user_id = models.UUIDField(default=uuid.uuid4)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now_add=True)  

Сервис

def get_report(id) -> Dict:
    logger.info('Get an Report by Id')

    post= Post.objects.get(id=id)

    return {
        'post'= post
    }

def get_comments(id) -> Dict:
    logger.info('Get an Report by Id')

    comments = Comment.objects.filter(post_id=id)

    return {
        'comments' = comments 
    }

API

class ReportGetApi(APIView):    
     class OutputSerializer(serializers.ModelSerializer):
        comments = CommentsSerializer(many=True, read_only=True)
        class Meta:
            model = Post
            fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')

    class CommentsSerializer(serializers.ModelSerializer):
        class Meta:
            model = Comments
            fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')

    def get(self, request):
        post = PostService.get_post() #Only one item
        comments = PostService.get_comments() #Many Items

        serializer = self.OutputSerializer(post, many=True)

        return Response(serializer.data)

Ответы [ 2 ]

1 голос
/ 29 мая 2020

На самом деле вам не нужен файл служб, чтобы делать то, что вы просите. DRF может обрабатывать все, что вы хотите, напрямую с помощью сериализаторов и RetrieveAPIView.

Таким образом, используя ваши модели, которые у вас есть выше, вы могли бы иметь что-то вроде следующего:

serializers.py

from rest_framework import serializers

from . import models


class CommentSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField(read_only=True)

    class Meta:
        model = models.Comment
        fields = '__all__'


class PostSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField(read_only=True)
    comments = serializers.SerializerMethodField()

    class Meta:
        model = models.Post
        fields = '__all__'

    def get_comments(self, obj):
        comments = models.Comment.objects.filter(post_id=obj.id)
        return CommentSerializer(comments, many=True).data

views.py

from rest_framework import generics
from . import serializers, models


class ReportApi(generics.RetrieveAPIView):
    serializer_class = serializers.PostSerializer
    queryset = models.Post.objects.all()

Вам нужно будет указать URL-адрес для передачи первичного ключа Post объект, который вы хотите получить, например:

urls.py

from django.urls import path

from api import views

app_name = 'api'
urlpatterns = [
    path('get_post_report/<pk>/', views.ReportApi.as_view()),
]

Затем вы можете получить доступ к представлению, используя что-то вроде http://example.com/api/get_post_report/12345678/.

Note: You must configure the urls.py within your project's 
urls file to use 'api/' for including your app's urls for the
'/api/' part of the url above to be a part of the url.

Если вы не знаете, как настроить URL-адреса, обратитесь к Django Tutorial

Это даст вам что-то вроде следующего:

ответ. json

{
    "id": 1,
    "comments": [
        {
            "id": 1,
            "post_id": "00000000-0000-0000-0000-000000000001",
            "text": "Comment Text",
            "up_vote": 0,
            "down_vote": 0,
            "user_id": "00000000-0000-0000-0000-000000000001",
            "created_at": "2020-05-29T13:14:07.103072Z",
            "modified_at": "2020-05-29T13:14:07.103124Z",
            "parent_id": null
        }
    ],
    "user_id": "00000000-0000-0000-0000-000000000001",
    "title": "My Post",
    "description": "Post Description",
    "pictures": "No Pictures",
    "lat": "12",
    "long": "12",
    "vote": "12",
    "created_at": "2020-05-29T13:14:07.102316Z",
    "modified_at": "2020-05-29T13:14:07.102356Z"
}

Наконец


Я просмотрел руководство, на которое вы ссылаетесь, и не нашел ссылки на разделение модели с UUID. Хотя вы можете это сделать (а в некоторых случаях может потребоваться), я бы серьезно подумал, действительно ли вам нужна такая большая развязка.

Есть причины использовать внешние ключи вместо UUID, например, доступ к связанным моделям проще и быстрее. Разделение с использованием UUID означает, что вам нужно будет писать больше шаблонного кода каждый раз, когда вам понадобится доступ к связанной модели.

Это сводится к решению, нужно ли вам разделение или лучший опыт разработчика с (субъективно) более чистым кодом. Пожалуйста, не просто следуйте руководству и предполагайте, что это закон. В этом поможет больше опыта.

Например, если вы использовали отношения внешнего ключа, ваш PostSerializer мог бы выглядеть следующим образом:

example_serializers.py

class PostSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField(read_only=True)
    comments = CommentSerializer(many=True)

    class Meta:
        model = models.Post
        fields = '__all__'

Видите, как мы избавились от метода get_comments(self, obj)? Это был всего лишь один шаблон, от которого мы избавились, приняв дизайнерское решение использовать отношения внешнего ключа. А теперь представьте себе кодовую базу из миллионов строк кода и множества сериализаторов. Помните, что чем больше кода вы напишете, тем больше вам потребуется выполнить тестирование / отладку.

Опять же, только мое мнение, но убедитесь, что вам действительно нужно разъединить свои модели, прежде чем это делать.

Кроме того, я настоятельно рекомендую вам следовать руководству по DRF . Он проверяет все, что вам нужно для выполнения sh того, что я только что опубликовал.

Надеюсь, это поможет!

0 голосов
/ 29 мая 2020

Вы могли бы создать один сериализатор с именем OutputSerializer и включить комментарии как собственное поле? если вы измените comments = OutputCommentSerializer на comments = CommentsSerializer(many=True, read_only=True), я считаю, что это будет отображать то, что вы хотите ... (пример ниже). Вы уже определили CommentSerializer, поэтому можете его использовать.

class ReportGetApi(APIView):    
     class OutputSerializer(serializers.ModelSerializer):
        comments = CommentSerializer(many=True, read_only=True)
        class Meta:
            model = Post
            fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')

    class CommentsSerializer(serializers.ModelSerializer):
        class Meta:
            model = Comments
            fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')

    def get(self, request):
        post = PostService.get_post() #Only one item
        comments = PostService.get_comments() #Many Items

        serializer = self.OutputSerializer(post, many=True)

        return Response(serializer.data)
...