Пример #1
0
    def helpful(self, request, pk=None):
        question = self.get_object()

        if not question.editable:
            raise GenericAPIException(403, 'Question not editable')
        if question.has_voted(request):
            raise GenericAPIException(409, 'Cannot vote twice')

        QuestionVote(question=question, creator=request.user).save()
        num_votes = QuestionVote.objects.filter(question=question).count()
        return Response({'num_votes': num_votes})
Пример #2
0
    def take(self, request, pk=None):
        question = self.get_object()
        field = serializers.BooleanField()
        force = field.from_native(request.DATA.get('force', False))

        try:
            question.take(request.user, force=force)
        except InvalidUserException:
            raise GenericAPIException(400, 'Question creator cannot take a question.')
        except AlreadyTakenException:
            raise GenericAPIException(409, 'Conflict: question is already taken.')

        return Response(status=204)
Пример #3
0
    def delete_setting(self, request, user__username=None):
        profile = self.get_object()

        if 'name' not in request.DATA:
            raise GenericAPIException(400, {'name': 'This field is required'})

        try:
            meta = (Setting.objects.get(user=profile.user,
                                        name=request.DATA['name']))
            meta.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except Setting.DoesNotExist:
            raise GenericAPIException(
                404, {'detail': 'No matching user setting found.'})
Пример #4
0
    def helpful(self, request, pk=None):
        answer = self.get_object()

        if not answer.question.editable:
            raise GenericAPIException(403, 'Answer not editable')
        if answer.has_voted(request):
            raise GenericAPIException(409, 'Cannot vote twice')

        AnswerVote(answer=answer, creator=request.user, helpful=True).save()
        num_helpful_votes = AnswerVote.objects.filter(answer=answer, helpful=True).count()
        num_unhelpful_votes = AnswerVote.objects.filter(answer=answer, helpful=False).count()
        return Response({
            'num_helpful_votes': num_helpful_votes,
            'num_unhelpful_votes': num_unhelpful_votes,
        })
Пример #5
0
def suggest(request):
    text = request.body or request.GET.get('q')
    locale = request.GET.get('locale', settings.WIKI_DEFAULT_LANGUAGE)
    product = request.GET.get('product')
    max_questions = request.GET.get('max_questions', 10)
    max_documents = request.GET.get('max_documents', 10)

    errors = {}
    try:
        max_questions = int(max_questions)
    except ValueError:
        errors['max_questions'] = 'This field must be an integer.'
    try:
        max_documents = int(max_documents)
    except ValueError:
        errors['max_documents'] = 'This field must be an integer.'
    if text is None:
        errors['q'] = 'This field is required.'
    if product is not None and not Product.objects.filter(
            slug=product).exists():
        errors['product'] = 'Could not find product with slug "{0}".'.format(
            product)
    if errors:
        raise GenericAPIException(400, errors)

    searcher = (es_utils.AnalyzerS().es(urls=settings.ES_URLS).indexes(
        es_utils.read_index('default')))

    return Response({
        'questions':
        _question_suggestions(searcher, text, locale, product, max_questions),
        'documents':
        _document_suggestions(searcher, text, locale, product, max_documents),
    })
Пример #6
0
def ban(request):
    """Bans a twitter account from using the AoA tool."""
    username = json.loads(request.body).get('username')
    if not username:
        raise GenericAPIException(status.HTTP_400_BAD_REQUEST,
                                  'Username not provided.')

    username = username[1:] if username.startswith('@') else username
    account, created = TwitterAccount.objects.get_or_create(
        username=username, defaults={'banned': True})
    if not created and account.banned:
        raise GenericAPIException(status.HTTP_409_CONFLICT,
                                  'This account is already banned!')
    else:
        account.banned = True
        account.save()

    return Response({'success': 'Account banned successfully!'})
Пример #7
0
    def set_setting(self, request, user__username=None):
        data = request.DATA
        data['user'] = self.get_object().pk

        serializer = UserSettingSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            raise GenericAPIException(400, serializer.errors)
Пример #8
0
def ignore(request):
    """Ignores a twitter account from showing up in the AoA tool."""
    username = json.loads(request.body).get('username')
    if not username:
        raise GenericAPIException(status.HTTP_400_BAD_REQUEST,
                                  'Username not provided.')

    username = username[1:] if username.startswith('@') else username
    account, created = TwitterAccount.objects.get_or_create(
        username=username, defaults={'ignored': True})
    if not created and account.ignored:
        raise GenericAPIException(
            status.HTTP_409_CONFLICT,
            'This account is already in the ignore list!')
    else:
        account.ignored = True
        account.save()

    return Response({'success': 'Account is now being ignored!'})
Пример #9
0
    def generate(self, request, **kwargs):
        """
        Generate a user with a random username and password.

        The method used here has a fairly low chance of collision. In
        the case that there are 3 username parts, and each part has 4
        options, and we include random numbers between 0 and 1000 on
        usernames, there are about 60K possible usernames. Using 20
        attempts to find a username means that in order for this method
        to have a 1% chance of failing, there would need to be about 50K
        users using this method already.

        Increasing the number of items in each username part category
        would make this even more favorable fairly quickly. With 5 items
        in each category, this algorithm can support almost 90K users
        before there is a 1% chance of failure.
        """
        if not (settings.STAGE or settings.DEBUG):
            raise GenericAPIException(
                503, 'User generation temporarily only available on stage.')

        digits = ''
        # The loop counter isn't used. This is an escape hatch.
        for i in range(20):
            # Build a name consistenting of a random choice from each of the
            # name parts lists.
            name = ''.join(map(random.choice, self.username_parts)) + digits
            # Check if it is taken yet.
            if not User.objects.filter(username=name).exists():
                break
            # Names after the first start to get numbers.
            digits = str(random.randint(0, 1000))
            if digits in self.number_blacklist:
                digits = ''
        else:
            # At this point, we just have too many users.
            return Response({"error": 'Unable to generate username.'},
                            status=500)

        password = ''.join(random.choice(letters) for _ in range(10))

        u = User.objects.create(username=name)
        u.set_password(password)
        u.save()
        p = Profile.objects.create(user=u)
        token, _ = Token.objects.get_or_create(user=u)
        serializer = ProfileSerializer(instance=p)

        return Response({
            'user': serializer.data,
            'password': password,
            'token': token.key,
        })
Пример #10
0
    def delete_metadata(self, request, pk=None):
        question = self.get_object()

        if 'name' not in request.DATA:
            return Response({'name': 'This field is required.'},
                            status=status.HTTP_400_BAD_REQUEST)

        try:
            meta = (QuestionMetaData.objects
                    .get(question=question, name=request.DATA['name']))
            meta.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except QuestionMetaData.DoesNotExist:
            raise GenericAPIException(404, 'No matching metadata object found.')
Пример #11
0
def suggest(request):
    if request.DATA and request.GET:
        raise GenericAPIException(
            400,
            'Put all parameters either in the querystring or the HTTP request body.'
        )

    serializer = SuggestSerializer(data=(request.DATA or request.GET))
    if not serializer.is_valid():
        raise GenericAPIException(400, serializer.errors)

    searcher = (es_utils.AnalyzerS().es(urls=settings.ES_URLS).indexes(
        es_utils.read_index('default')))

    data = serializer.object

    return Response({
        'questions':
        _question_suggestions(searcher, data['q'], data['locale'],
                              data['product'], data['max_questions']),
        'documents':
        _document_suggestions(searcher, data['q'], data['locale'],
                              data['product'], data['max_documents']),
    })
Пример #12
0
def unban(request):
    """Unbans a twitter account from using the AoA tool."""
    usernames = json.loads(request.body).get('usernames')
    if not usernames:
        raise GenericAPIException(status.HTTP_400_BAD_REQUEST,
                                  'Usernames not provided.')

    accounts = TwitterAccount.objects.filter(username__in=usernames)
    for account in accounts:
        if account and account.banned:
            account.banned = False
            account.save()

    message = {'success': '{0} users unbanned successfully.'
               .format(len(accounts))}
    return Response(message)
Пример #13
0
    def filter_metadata(self, queryset, value):
        try:
            value = json.loads(value)
        except ValueError:
            raise GenericAPIException(400, 'metadata must be valid JSON.')

        for name, values in value.items():
            if not isinstance(values, list):
                values = [values]
            query = Q()
            for v in values:
                if v is None:
                    query = query | ~Q(metadata_set__name=name)
                else:
                    query = query | Q(metadata_set__name=name, metadata_set__value=v)
            queryset = queryset.filter(query)

        return queryset
Пример #14
0
    def get_queryset(self):
        queryset = self.queryset

        queryset = queryset.filter(category__in=settings.IA_DEFAULT_CATEGORIES,
                                   current_revision__isnull=False)

        locale = self.get_locale()
        product = self.request.QUERY_PARAMS.get('product')
        topic = self.request.QUERY_PARAMS.get('topic')
        is_template = bool(self.request.QUERY_PARAMS.get('is_template', False))
        is_archived = bool(self.request.QUERY_PARAMS.get('is_archived', False))
        is_redirect = bool(self.request.QUERY_PARAMS.get('is_redirect', False))

        if locale is not None:
            queryset = queryset.filter(locale=locale)

        if product is not None:
            if locale == settings.WIKI_DEFAULT_LANGUAGE:
                queryset = queryset.filter(products__slug=product)
            else:
                # Localized articles inherit product from the parent.
                queryset = queryset.filter(parent__products__slug=product)

            if topic is not None:
                if locale == settings.WIKI_DEFAULT_LANGUAGE:
                    queryset = queryset.filter(topics__slug=topic)
                else:
                    # Localized articles inherit topic from the parent.
                    queryset = queryset.filter(parent__topics__slug=topic)
        elif topic is not None:
            raise GenericAPIException(status.HTTP_400_BAD_REQUEST,
                                      'topic requires product')

        queryset = queryset.filter(is_template=is_template)
        queryset = queryset.filter(is_archived=is_archived)

        redirect_filter = Q(html__startswith=REDIRECT_HTML)
        if is_redirect:
            queryset = queryset.filter(redirect_filter)
        else:
            queryset = queryset.filter(~redirect_filter)

        return queryset
Пример #15
0
    def add_tags(self, request, pk=None):
        question = self.get_object()

        if 'tags' not in request.DATA:
            return Response({'tags': 'This field is required.'},
                            status=status.HTTP_400_BAD_REQUEST)

        tags = request.DATA['tags']

        for tag in tags:
            try:
                add_existing_tag(tag, question.tags)
            except Tag.DoesNotExist:
                if request.user.has_perm('taggit.add_tag'):
                    question.tags.add(tag)
                else:
                    raise GenericAPIException(403, 'You are not authorized to create new tags.')

        tags = question.tags.all()
        return Response(QuestionTagSerializer(tags).data)
Пример #16
0
    def filter_metadata(self, queryset, value):
        invalid_exc = GenericAPIException(
            400, 'metadata must be a JSON object of strings.')

        try:
            value = json.loads(value)
        except ValueError:
            raise invalid_exc

        def is_string(v):
            return isinstance(v, six.string_types)

        if not (isinstance(value, dict) and all(
                isinstance(v, six.string_types) for v in value.values())):
            raise invalid_exc

        for name, value in value.items():
            queryset = queryset.filter(metadata_set__name=name,
                                       metadata_set__value=value)

        return queryset
Пример #17
0
def suggest(request):
    text = request.body or request.GET.get('q')
    locale = request.GET.get('locale', settings.WIKI_DEFAULT_LANGUAGE)
    product = request.GET.get('product')
    max_questions = request.GET.get('max_questions', 10)
    max_documents = request.GET.get('max_documents', 10)

    errors = {}
    try:
        max_questions = int(max_questions)
    except ValueError:
        errors['max_questions'] = 'This field must be an integer.'
    try:
        max_documents = int(max_documents)
    except ValueError:
        errors['max_documents'] = 'This field must be an integer.'
    if text is None:
        errors['q'] = 'This field is required.'
    if product is not None and not Product.objects.filter(
            slug=product).exists():
        errors['product'] = 'Could not find product with slug "{0}".'.format(
            product)
    if errors:
        raise GenericAPIException(400, errors)

    wiki_f = es_utils.F(
        model='wiki_document',
        document_category__in=settings.SEARCH_DEFAULT_CATEGORIES,
        document_locale=locale,
        document_is_archived=False)

    questions_f = es_utils.F(model='questions_question',
                             question_is_archived=False,
                             question_is_locked=False,
                             question_has_helpful=True)

    if product is not None:
        wiki_f &= es_utils.F(product=product)
        questions_f &= es_utils.F(product=product)

    mapping_types = [QuestionMappingType, DocumentMappingType]
    query_fields = itertools.chain(
        *[cls.get_query_fields() for cls in mapping_types])
    query = {}
    for field in query_fields:
        for query_type in ['match', 'match_phrase']:
            key = '{0}__{1}'.format(field, query_type)
            query[key] = text

    # Transform query to be locale aware.
    query = es_utils.es_query_with_analyzer(query, locale)

    searcher = (es_utils.AnalyzerS().es(urls=settings.ES_URLS).indexes(
        es_utils.read_index('default')).doctypes(
            *[cls.get_mapping_type_name()
              for cls in mapping_types]).filter(wiki_f | questions_f).query(
                  should=True, **query))

    documents = []
    questions = []

    for result in searcher[:(max_documents + max_questions) * 2]:
        if result['model'] == 'wiki_document':
            documents.append({
                'title': result['document_title'],
                'slug': result['document_slug'],
                'summary': result['document_summary'],
            })
        elif result['model'] == 'questions_question':
            questions.append({
                'id': result['id'],
                'title': result['question_title'],
            })
        if len(documents) >= max_documents and len(questions) >= max_questions:
            break

    return Response({
        'questions': questions[:max_questions],
        'documents': documents[:max_documents],
    })
Пример #18
0
    def get_data(self, request):
        search_form = self.form_class(request.GET)
        if not search_form.is_valid():
            raise GenericAPIException(status.HTTP_400_BAD_REQUEST,
                                      _('Invalid search data.'))

        language = locale_or_default(
            request.GET.get('language', request.LANGUAGE_CODE))
        lang = language.lower()
        if settings.LANGUAGES_DICT.get(lang):
            lang_name = settings.LANGUAGES_DICT[lang]
        else:
            lang_name = ''

        page = max(smart_int(request.GET.get('page')), 1)
        offset = (page - 1) * settings.SEARCH_RESULTS_PER_PAGE

        searcher = (es_utils.AnalyzerS().es(urls=settings.ES_URLS).indexes(
            es_utils.read_index('default')))

        doctypes = self.get_doctypes()
        searcher = searcher.doctypes(*doctypes)

        filters = self.get_filters()
        searcher = searcher.filter(filters)

        # Add the simple string query.
        cleaned_q = search_form.cleaned_data.get('query')

        if cleaned_q:
            query_fields = self.get_query_fields()
            query = {}
            # Create a simple_query_search query for every field
            # we want to search.
            for field in query_fields:
                query['%s__sqs' % field] = cleaned_q

            # Transform the query to use locale aware analyzers.
            query = es_utils.es_query_with_analyzer(query, language)

            searcher = searcher.query(should=True, **query)

        try:
            num_results = min(searcher.count(), settings.SEARCH_MAX_RESULTS)

            results_per_page = settings.SEARCH_RESULTS_PER_PAGE

            # If we know there aren't any results, let's cheat and in
            # doing that, not hit ES again.
            if num_results == 0:
                searcher = []
            else:
                # TODO - Can ditch the ComposedList here, but we need
                # something that paginate can use to figure out the paging.
                documents = ComposedList()
                documents.set_count(('results', searcher), num_results)

                # Get the documents we want to show and add them to
                # docs_for_page
                documents = documents[offset:offset + results_per_page]

                if len(documents) == 0:
                    # If the user requested a page that's beyond the
                    # pagination, then documents is an empty list and
                    # there are no results to show.
                    searcher = []
                else:
                    bounds = documents[0][1]
                    searcher = searcher[bounds[0]:bounds[1]]

            results = []
            for i, doc in enumerate(searcher):
                rank = i + offset

                result = self.format_result(doc)

                result['url'] = doc['url']
                result['rank'] = rank
                result['score'] = doc.es_meta.score
                result['explanation'] = escape(
                    format_explanation(doc.es_meta.explanation))
                result['id'] = doc['id']
                results.append(result)

        except es_utils.ES_EXCEPTIONS:
            raise GenericAPIException(status.HTTP_503_SERVICE_UNAVAILABLE,
                                      _('Search Unavailable'))

        data = {
            'num_results': num_results,
            'results': results,
            'lang_name': lang_name,
        }

        if not results:
            data['message'] = _('No pages matched the search criteria')

        return data