<commit_before>""".. Ignore pydocstyle D400.

================
Elastic Viewsets
================

.. autoclass:: resolwe.elastic.viewsets.ElasticSearchMixin
    :members:

"""
from __future__ import absolute_import, division, print_function, unicode_literals

from elasticsearch_dsl.query import Q

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Case, IntegerField, Value, When

from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

from .lookup import QueryBuilder
from .pagination import LimitOffsetPostPagination

__all__ = (
    'ElasticSearchMixin',
    'PaginationMixin',
    'ElasticSearchBaseViewSet',
)

ELASTICSEARCH_SIZE = 10000  # maximum number of results returned by ElasticSearch


class TooManyResults(APIException):
    """Exception when elastic query returns more than ``ELASTICSEARCH_SIZE`` results."""

    status_code = 400
    default_detail = 'Query returned too many results. Please, add more filters or use pagination.'
    default_code = 'bad_request'


class ElasticSearchMixin(object):
    """Mixin to use Django REST Framework with ElasticSearch based querysets.

    This mixin adds following methods:
      * :func:`~ElasticSearchMixin.order_search`
      * :func:`~ElasticSearchMixin.filter_search`
      * :func:`~ElasticSearchMixin.filter_permissions`

    """

    filtering_fields = []
    ordering_fields = []
    ordering_map = {}
    ordering = None

    def get_query_param(self, key, default=None):
        """Get query parameter uniformly for GET and POST requests."""
        value = self.request.query_params.get(key, None)
        if value is None:
            value = self.request.data.get(key, None)
        if value is None:
            value = default
        return value

    def get_query_params(self):
        """Get combined query parameters (GET and POST)."""
        params = self.request.query_params.copy()
        params.update(self.request.data)
        return params

    def order_search(self, search):
        """Order given search by the ordering parameter given in request.

        :param search: ElasticSearch query object

        """
        ordering = self.get_query_param('ordering', self.ordering)
        if not ordering:
            return search

        ordering_field = ordering.lstrip('-')
        if ordering_field not in self.ordering_fields:
            raise KeyError('Ordering by `{}` is not supported.'.format(ordering_field))

        ordering_field = self.ordering_map.get(ordering_field, ordering_field)
        direction = '-' if ordering[0] == '-' else ''

        return search.sort('{}{}'.format(direction, ordering_field))

    def get_always_allowed_arguments(self):
        """Return query arguments which are always allowed."""
        return [
            'ordering',
            'offset',
            'limit',
            'format',
            'fields',
        ]

    def filter_search(self, search):
        """Filter given search by the filter parameter given in request.

        :param search: ElasticSearch query object

        """
        builder = QueryBuilder(self.filtering_fields, self)
        search, unmatched = builder.build(search, self.get_query_params())

        # Ensure that no unsupported arguments were used.
        for argument in self.get_always_allowed_arguments():
            unmatched.pop(argument, None)

        if unmatched:
            raise KeyError('Unsupported filter arguments: {}'.format(', '.join(unmatched.keys())))

        return search

    def filter_permissions(self, search):
        """Filter given query based on permissions of the user in the request.

        :param search: ElasticSearch query object

        """
        user = self.request.user
        if user.is_superuser:
            return search
        if user.is_anonymous:
            user_model = get_user_model()
            user = user_model.objects.get(**{user_model.USERNAME_FIELD: settings.ANONYMOUS_USER_NAME})

        filters = [Q('match', users_with_permissions=user.pk)]
        filters.extend([
            Q('match', groups_with_permissions=group.pk) for group in user.groups.all()
        ])
        filters.append(Q('match', public_permission=True))

        # `minimum_should_match` is set to 1 by default
        return search.query('bool', should=filters)


class PaginationMixin(object):
    """Mixin for making paginated response in case pagination parameters are provided."""

    def paginate_response(self, queryset, serializers_kwargs={}):
        """Optionally return paginated response.

        If pagination parameters are provided in the request, then paginated response
        is returned, otherwise response is not paginated.

        """
        page = self.paginate_queryset(queryset)<commit_msg>Fix how anonymous user is retrieved in ES search mixin<commit_after>""".. Ignore pydocstyle D400.

================
Elastic Viewsets
================

.. autoclass:: resolwe.elastic.viewsets.ElasticSearchMixin
    :members:

"""
from __future__ import absolute_import, division, print_function, unicode_literals

from elasticsearch_dsl.query import Q

from django.db.models import Case, IntegerField, Value, When

from guardian.utils import get_anonymous_user
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

from .lookup import QueryBuilder
from .pagination import LimitOffsetPostPagination

__all__ = (
    'ElasticSearchMixin',
    'PaginationMixin',
    'ElasticSearchBaseViewSet',
)

ELASTICSEARCH_SIZE = 10000  # maximum number of results returned by ElasticSearch


class TooManyResults(APIException):
    """Exception when elastic query returns more than ``ELASTICSEARCH_SIZE`` results."""

    status_code = 400
    default_detail = 'Query returned too many results. Please, add more filters or use pagination.'
    default_code = 'bad_request'


class ElasticSearchMixin(object):
    """Mixin to use Django REST Framework with ElasticSearch based querysets.

    This mixin adds following methods:
      * :func:`~ElasticSearchMixin.order_search`
      * :func:`~ElasticSearchMixin.filter_search`
      * :func:`~ElasticSearchMixin.filter_permissions`

    """

    filtering_fields = []
    ordering_fields = []
    ordering_map = {}
    ordering = None

    def get_query_param(self, key, default=None):
        """Get query parameter uniformly for GET and POST requests."""
        value = self.request.query_params.get(key, None)
        if value is None:
            value = self.request.data.get(key, None)
        if value is None:
            value = default
        return value

    def get_query_params(self):
        """Get combined query parameters (GET and POST)."""
        params = self.request.query_params.copy()
        params.update(self.request.data)
        return params

    def order_search(self, search):
        """Order given search by the ordering parameter given in request.

        :param search: ElasticSearch query object

        """
        ordering = self.get_query_param('ordering', self.ordering)
        if not ordering:
            return search

        ordering_field = ordering.lstrip('-')
        if ordering_field not in self.ordering_fields:
            raise KeyError('Ordering by `{}` is not supported.'.format(ordering_field))

        ordering_field = self.ordering_map.get(ordering_field, ordering_field)
        direction = '-' if ordering[0] == '-' else ''

        return search.sort('{}{}'.format(direction, ordering_field))

    def get_always_allowed_arguments(self):
        """Return query arguments which are always allowed."""
        return [
            'ordering',
            'offset',
            'limit',
            'format',
            'fields',
        ]

    def filter_search(self, search):
        """Filter given search by the filter parameter given in request.

        :param search: ElasticSearch query object

        """
        builder = QueryBuilder(self.filtering_fields, self)
        search, unmatched = builder.build(search, self.get_query_params())

        # Ensure that no unsupported arguments were used.
        for argument in self.get_always_allowed_arguments():
            unmatched.pop(argument, None)

        if unmatched:
            raise KeyError('Unsupported filter arguments: {}'.format(', '.join(unmatched.keys())))

        return search

    def filter_permissions(self, search):
        """Filter given query based on permissions of the user in the request.

        :param search: ElasticSearch query object

        """
        user = self.request.user
        if user.is_superuser:
            return search
        if user.is_anonymous:
            user = get_anonymous_user()

        filters = [Q('match', users_with_permissions=user.pk)]
        filters.extend([
            Q('match', groups_with_permissions=group.pk) for group in user.groups.all()
        ])
        filters.append(Q('match', public_permission=True))

        # `minimum_should_match` is set to 1 by default
        return search.query('bool', should=filters)


class PaginationMixin(object):
    """Mixin for making paginated response in case pagination parameters are provided."""

    def paginate_response(self, queryset, serializers_kwargs={}):
        """Optionally return paginated response.

        If pagination parameters are provided in the request, then paginated response
        is returned, otherwise response is not paginated.

        """
        page = self.paginate_queryset(queryset)
{'fraction_non_alphanumeric': 0.09634825665360017, 'fraction_numerical': 0.002682071384361461, 'mean_word_length': 2.9766201804757997, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7560012', 'n_tokens_mistral': 2637, 'n_tokens_neox': 2513, 'n_words': 749}