Проблема с вложенными комментариями при использовании MPTT в Django и Django Rest Framework API - результат не найден

Я пытаюсь создать систему вложенных комментариев с помощью MPTT, но использую Django Rest Framework для сериализации дерева MPTT. Я добился того, что вложенные комментарии работают - и эти комментарии добавляются, редактируются и удаляются только путем вызова конечных точек API Django Rest Framework - без использования вызовов Django ORM DB вообще. К сожалению, есть ошибка, которую я не смог разгадать! Хотя комментарии добавляются, редактируются и удаляются нормально - но когда седьмой или восьмой комментарий вложен - внезапно первый комментарий или первый вложенный комментарий становится [detail: Not found.] - то есть он возвращает пустой результат или выдает неизвестную ошибку валидации, которую я не смог понять почему. Это приводит к тому, что при нажатии на редактирование или удаление ошибочных комментариев становится невозможным - но часть GET в порядке, поскольку эти ошибочные комментарии действительно отображаются в разделе комментариев (или я должен сказать, что часть списка возвращается нормально). Изображение, которое я прикреплю, покажет, что когда я ввел комментарий ggggg, комментарии aaaa и bbbb будут выдавать ошибки при попытке отредактировать или удалить их. Если я удалю комментарий gggg, комментарий hhhh также будет удален (так как был включен CASCADE) - и внезапно комментарии aaaa и bbbb снова будут работать для удаления и редактирования. enter image description here

Моя модель комментариев (models.py):

from django.db import models
from django.template.defaultfilters import truncatechars
from mptt.managers import TreeManager

from post.models import Post
from account.models import Account
from mptt.models import MPTTModel, TreeForeignKey


# Create your models here.

# With MPTT
class CommentManager(TreeManager):
    def viewable(self):
        queryset = self.get_queryset().filter(level=0)
        return queryset


class Comment(MPTTModel):
    parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='comment_children')
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comment_post')
    user = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='comment_account')
    content = models.TextField(max_length=9000)
    created_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now=True)
    status = models.BooleanField(default=True)

    objects = CommentManager()

    def __str__(self):
        return f'Comment by {str(self.pk)}-{self.user.full_name.__self__}'

    @property
    def short_content(self):
        return truncatechars(self.content, 99)

    class MPTTMeta:
        # If changing the order - MPTT needs the programmer to go into console and do Comment.objects.rebuild()
        order_insertion_by = ['-created_date']

Мой serializers.py (показывается только часть сериализатора комментариев).

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

class CommentSerializer(serializers.ModelSerializer):
    post_slug = serializers.SerializerMethodField()
    user = serializers.StringRelatedField(read_only=True)
    user_name = serializers.SerializerMethodField()
    user_id = serializers.PrimaryKeyRelatedField(read_only=True)

    comment_children = RecursiveField(many=True)

    class Meta:
        model = Comment
        fields = '__all__'

    # noinspection PyMethodMayBeStatic
    # noinspection PyBroadException
    def get_post_slug(self, instance):
        try:
            slug = instance.post.slug
            return slug
        except Exception:
            pass

    # noinspection PyMethodMayBeStatic
    # noinspection PyBroadException
    def get_user_name(self, instance):
        try:
            full_name = f'{instance.user.first_name} {instance.user.last_name}'
            return full_name
        except Exception:
            pass

    # noinspection PyMethodMayBeStatic
    def validate_content(self, value):
        if len(value) < COM_MIN_LEN:
            raise serializers.ValidationError('The comment is too short.')
        elif len(value) > COM_MAX_LEN:
            raise serializers.ValidationError('The comment is too long.')
        else:
            return value

    def get_fields(self):
        fields = super(CommentSerializer, self).get_fields()
        fields['comment_children'] = CommentSerializer(many=True, required=False)
        return fields

Представления API для комментариев будут выглядеть следующим образом:

Вызовы API будут выглядеть следующим образом в blog_post app views:

add_comment = requests.post(BLOG_BASE_URL + f'api/post-list/comments/create-comments/',
                                                headers=headers,
                                                data=user_comment)
                                                
add_reply = requests.post(BLOG_BASE_URL + f'api/post-list/comments/create-comments/',
                                              headers=headers,
                                              data=user_reply)

requests.request('PUT', BLOG_BASE_URL + f'api/post-list/comments/{pk}/',
                                 headers=headers,
                                 data=user_comment)

response = requests.request('PUT', BLOG_BASE_URL + f'api/post-list/comments/children/{pk}/',
                                            headers=headers,
                                            data=user_comment)

response = requests.request("DELETE", BLOG_BASE_URL + f'api/post-list/comments/{pk}/', headers=headers)

Эти вызовы в представлении приложения для записи блога позволят мне разрешить авторизованным пользователям создавать, редактировать и удалять комментарии.

Кто-нибудь знает, почему мое приложение получило эту ошибку? Любая помощь будет оценена по достоинству! Я где-то читал про получение ноды refresh_from_db() - но как мне это сделать в сериализации? Также, Comment.objects.rebuild() не помогает!

Оки, я догадался!

Я думаю, что при вызове одного и того же объекта в дереве MPTT для GET и PUT как-то выплевывается странная ошибка, которая не позволяет мне редактировать затронутые ответы. Итак, моим решением сейчас является просто создание конечной точки с API вида ниже:

class CommentChildrenAV(mixins.CreateModelMixin, generics.GenericAPIView):
    # This class only allows users to create comments but not list all comments.  List all comments would
    # be too taxing for the server if the website got tons of comments.
    queryset = Comment.objects.viewable().get_descendants().filter(status=True)
    serializer_class = CommentSerializer

    def get(self, request, pk):
        replies = Comment.objects.viewable().get_descendants().filter(status=True, pk=pk)
        serializer = CommentSerializer(replies, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    def perform_create(self, serializer):
        # Overriding perform_create.  Can create comment using the authenticated account.
        # Cannot pretend to be someone else to create comment on his or her behalf.
        commenter = self.request.user
        now = timezone.now()
        before_now = now - timezone.timedelta(seconds=COM_WAIT_TIME)
        # Make sure user can only create comment again after waiting for wait_time.
        this_user_comments = Comment.objects.filter(user=commenter, created_date__lt=now, created_date__gte=before_now)
        if this_user_comments:
            raise ValidationError(f'You have to wait for {COM_WAIT_TIME} seconds before you can post another comment.')
        elif Comment.objects.filter(user=commenter, level__gt=COMMENT_LEVEL_DEPTH):
            raise ValidationError(f'You cannot make another level-deep reply.')
        else:
            serializer.save(user=commenter)

    # By combining perform_create method to filter out only the owner of the comment can edit his or her own
    # comment -- and the permission_classes of IsAuthenticated -- allowing only authenticated user to create
    # comments.  When doing custome permission - such as redefinte BasePermission's has_object_permission,
    # it doesn't work with ListCreateAPIView - because has_object_permission is meant to be used on single instance
    # such as object detail.
    permission_classes = [IsAuthenticated]

Это представление API позволило бы мне передать pk ответа - получить JSON ответ следующим образом:

response = requests.request("GET", BLOG_BASE_URL + f'api/post-list/children/get-child/{pk}/', headers=headers)

Получив ответ в JSON, я могу получить исходное содержание ответа и ввести это содержание ответа в исходные данные формы ответа следующим образом:

edit_form = CommentForm(initial=original_comment_data)

Затем я получаю новое содержимое POST, которым пользователь хочет заменить содержимое оригинального ответа - суть решения, которое я сейчас использую, заключается в следующем: если пользователь аутентифицирован и если user_id оригинального JSON содержимого (имеется в виду комментатор текста ответа) совпадает с request.user.id - тогда я просто делаю:

if request.method == 'POST':
    # I can't use API endpoint here to edit reply because some weird bug won't allow me to do so.
    # Instead of calling the endpoint api for edit reply - I just update the database with
    # using ORM (Object Relational Manager) method.
    if request.user.is_authenticated:
        print(content[0]['user_id'], os.getcwd())
        if request.user.id == content[0]['user_id']:
            Comment.objects.filter(status=True, pk=pk).update(content=request.POST.get(strip_invalid_html('content')))
            post_slug = content[0]['post_slug']
            return redirect('single_post', post_slug)

Теперь это действительно решает мою проблему! Я просто надеялся, что мне не придется обманывать, идя по пути ORM для редактирования ответа. Я бы предпочел 100% вызовы API для всех действий в этом приложении. Вздох... но теперь мое приложение полностью функционирует в плане наличия системы комментариев, которая вложена с помощью пакета MPTT.

Вернуться на верх