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
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']))
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
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']) )