Example #1
0
    def validate_other_thread(self, data):
        request = self.context['request']
        thread = self.context['thread']
        viewmodel = self.context['viewmodel']

        other_thread_id = get_thread_id_from_url(request, data)
        if not other_thread_id:
            raise ValidationError(_("This is not a valid thread link."))
        if other_thread_id == thread.pk:
            raise ValidationError(_("You can't merge thread with itself."))

        try:
            other_thread = viewmodel(request, other_thread_id).unwrap()
            allow_merge_thread(request.user, other_thread, otherthread=True)
        except PermissionDenied as e:
            raise serializers.ValidationError(e)
        except Http404:
            raise ValidationError(
                _(
                    "The thread you have entered link to doesn't "
                    "exist or you don't have permission to see it."
                )
            )

        if not can_reply_thread(request.user, other_thread):
            raise ValidationError(_("You can't merge this thread into thread you can't reply."))

        return other_thread
Example #2
0
    def validate_thread_url(self, data):
        request = self.context['request']
        thread = self.context['thread']
        viewmodel = self.context['viewmodel']

        new_thread_id = get_thread_id_from_url(request, data)
        if not new_thread_id:
            raise serializers.ValidationError(_("This is not a valid thread link."))
        if new_thread_id == thread.pk:
            raise serializers.ValidationError(_("Thread to move posts to is same as current one."))

        try:
            new_thread = viewmodel(request, new_thread_id).unwrap()
        except Http404:
            raise serializers.ValidationError(
                _(
                    "The thread you have entered link to doesn't "
                    "exist or you don't have permission to see it."
                )
            )

        if not new_thread.acl['can_reply']:
            raise serializers.ValidationError(_("You can't move posts to threads you can't reply."))

        self.new_thread = new_thread

        return data
    def test_get_thread_id_from_invalid_urls(self):
        TEST_CASES = [
            {
                # invalid wsgi alias
                'request': MockRequest('https', 'testforum.com'),
                'url': 'http://testforum.com/discuss/t/test-thread-123/',
            },
            {
                # invalid hostname
                'request': MockRequest('http', 'misago-project.org', '/discuss/'),
                'url': 'https://testforum.com/discuss/t/test-thread-432/post/12321/',
            },
            {
                # old thread url
                'request': MockRequest('http', 'testforum.com'),
                'url': 'https://testforum.com/thread/bobboberson-123/',
            },
            {
                # dashed thread url
                'request': MockRequest('http', 'testforum.com'),
                'url': 'https://testforum.com/t/bobboberson-123/',
            },
            {
                # non-thread url
                'request': MockRequest('http', 'testforum.com'),
                'url': 'https://testforum.com/user/bobboberson-123/',
            },
            {
                # rubbish url
                'request': MockRequest('http', 'testforum.com'),
                'url': 'asdsadsasadsaSA&das8as*S(A*sa'
            },
            {
                # blank url
                'request': MockRequest('http', 'testforum.com'),
                'url': '/'
            },
            {
                # empty url
                'request': MockRequest('http', 'testforum.com'),
                'url': ''
            }
        ]

        for case in TEST_CASES:
            pk = get_thread_id_from_url(case['request'], case['url'])
            self.assertIsNone(pk, 'get_thread_id_from_url for {} should fail'.format(case['url']))
Example #4
0
def clean_thread_for_move(request, thread, viewmodel):
    new_thread_id = get_thread_id_from_url(
        request, request.data.get('thread_url', None))
    if not new_thread_id:
        raise PermissionDenied(_("This is not a valid thread link."))
    if new_thread_id == thread.pk:
        raise PermissionDenied(
            _("Thread to move posts to is same as current one."))

    try:
        new_thread = viewmodel(request, new_thread_id).unwrap()
    except Http404:
        raise PermissionDenied(
            _("The thread you have entered link to doesn't "
              "exist or you don't have permission to see it."))

    if not new_thread.acl['can_reply']:
        raise PermissionDenied(
            _("You can't move posts to threads you can't reply."))

    return new_thread
Example #5
0
def thread_merge_endpoint(request, thread, viewmodel):
    if not thread.acl['can_merge']:
        raise PermissionDenied(
            _("You don't have permission to merge this thread with others."))

    other_thread_id = get_thread_id_from_url(
        request, request.data.get('thread_url', None))
    if not other_thread_id:
        return Response({'detail': _("This is not a valid thread link.")},
                        status=400)
    if other_thread_id == thread.pk:
        return Response({'detail': _("You can't merge thread with itself.")},
                        status=400)

    try:
        other_thread = viewmodel(request,
                                 other_thread_id,
                                 select_for_update=True).unwrap()
        if not can_reply_thread(request.user, other_thread):
            raise PermissionDenied(
                _("You can't merge this thread into thread you can't reply."))
        if not other_thread.acl['can_merge']:
            raise PermissionDenied(
                _("You don't have permission to merge this thread with current one."
                  ))
    except PermissionDenied as e:
        return Response({'detail': e.args[0]}, status=400)
    except Http404:
        return Response(
            {
                'detail':
                _("The thread you have entered link to doesn't exist or you don't have permission to see it."
                  )
            },
            status=400)

    polls_handler = PollMergeHandler([thread, other_thread])
    if len(polls_handler.polls) == 1:
        poll = polls_handler.polls[0]
        poll.move(other_thread)
    elif polls_handler.is_merge_conflict():
        if 'poll' in request.data:
            polls_handler.set_resolution(request.data.get('poll'))
            if polls_handler.is_valid():
                poll = polls_handler.get_resolution()
                if poll and poll.thread_id != other_thread.id:
                    other_thread.poll.delete()
                    poll.move(other_thread)
                elif not poll:
                    other_thread.poll.delete()
            else:
                return Response({'detail': _("Invalid choice.")}, status=400)
        else:
            return Response(
                {'polls': polls_handler.get_available_resolutions()},
                status=400)

    moderation.merge_thread(request, other_thread, thread)

    other_thread.synchronize()
    other_thread.save()

    other_thread.category.synchronize()
    other_thread.category.save()

    if thread.category != other_thread.category:
        thread.category.synchronize()
        thread.category.save()

    return Response({
        'id': other_thread.pk,
        'title': other_thread.title,
        'url': other_thread.get_absolute_url()
    })
def thread_merge_endpoint(request, thread, viewmodel):
    allow_merge_thread(request.user, thread)

    other_thread_id = get_thread_id_from_url(request, request.data.get('thread_url', None))
    if not other_thread_id:
        return Response({'detail': _("This is not a valid thread link.")}, status=400)
    if other_thread_id == thread.pk:
        return Response({'detail': _("You can't merge thread with itself.")}, status=400)

    try:
        other_thread = viewmodel(request, other_thread_id).unwrap()
        allow_merge_thread(request.user, other_thread, otherthread=True)
        if not can_reply_thread(request.user, other_thread):
            raise PermissionDenied(_("You can't merge this thread into thread you can't reply."))
    except PermissionDenied as e:
        return Response({'detail': e.args[0]}, status=400)
    except Http404:
        return Response(
            {
                'detail': _(
                    "The thread you have entered link to doesn't "
                    "exist or you don't have permission to see it."
                )
            },
            status=400,
        )

    polls_handler = PollMergeHandler([thread, other_thread])
    if len(polls_handler.polls) == 1:
        poll = polls_handler.polls[0]
        poll.move(other_thread)
    elif polls_handler.is_merge_conflict():
        if 'poll' in request.data:
            polls_handler.set_resolution(request.data.get('poll'))
            if polls_handler.is_valid():
                poll = polls_handler.get_resolution()
                if poll and poll.thread_id != other_thread.id:
                    other_thread.poll.delete()
                    poll.move(other_thread)
                elif not poll:
                    other_thread.poll.delete()
            else:
                return Response({'detail': _("Invalid choice.")}, status=400)
        else:
            return Response({'polls': polls_handler.get_available_resolutions()}, status=400)

    moderation.merge_thread(request, other_thread, thread)

    other_thread.synchronize()
    other_thread.save()

    other_thread.category.synchronize()
    other_thread.category.save()

    if thread.category != other_thread.category:
        thread.category.synchronize()
        thread.category.save()

    return Response({
        'id': other_thread.pk,
        'title': other_thread.title,
        'url': other_thread.get_absolute_url(),
    })
    def test_get_thread_id_from_valid_urls(self):
        """get_thread_id_from_url extracts thread pk from valid urls"""
        TEST_CASES = [
            {
                # perfect match
                'request': MockRequest('https', 'testforum.com', '/discuss/'),
                'url': 'https://testforum.com/discuss/t/test-thread/123/',
                'pk': 123,
            },
            {
                # we don't validate scheme in case site recently moved to https
                # but user still has old url's saved somewhere
                'request': MockRequest('http', 'testforum.com', '/discuss/'),
                'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
                'pk': 432,
            },
            {
                # extract thread id from other thread urls
                'request': MockRequest('https', 'testforum.com', '/discuss/'),
                'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
                'pk': 432,
            },
            {
                # extract thread id from thread page url
                'request': MockRequest('http', 'testforum.com', '/discuss/'),
                'url': 'http://testforum.com/discuss/t/test-thread/432/123/',
                'pk': 432,
            },
            {
                # extract thread id from thread last post url with relative schema
                'request': MockRequest('http', 'testforum.com', '/discuss/'),
                'url': '//testforum.com/discuss/t/test-thread/18/last/',
                'pk': 18,
            },
            {
                # extract thread id from url that lacks scheme
                'request': MockRequest('http', 'testforum.com', ''),
                'url': 'testforum.com/t/test-thread/12/last/',
                'pk': 12,
            },
            {
                # extract thread id from schemaless thread last post url
                'request': MockRequest('http', 'testforum.com', '/discuss/'),
                'url': 'testforum.com/discuss/t/test-thread/18/last/',
                'pk': 18,
            },
            {
                # extract thread id from url that lacks scheme and hostname
                'request': MockRequest('http', 'testforum.com', ''),
                'url': '/t/test-thread/13/',
                'pk': 13,
            },
            {
                # extract thread id from url that has port name
                'request': MockRequest('http', '127.0.0.1:8000', ''),
                'url': 'https://127.0.0.1:8000/t/test-thread/13/',
                'pk': 13,
            },
            {
                # extract thread id from url that isn't trimmed
                'request': MockRequest('http', '127.0.0.1:8000', ''),
                'url': '   /t/test-thread/13/   ',
                'pk': 13,
            }
        ]

        for case in TEST_CASES:
            pk = get_thread_id_from_url(case['request'], case['url'])
            self.assertEqual(
                pk, case['pk'],
                'get_thread_id_from_url for {} should return {}'.format(case['url'], case['pk'])
            )