Skip to content

Commit

Permalink
Merge pull request #282 from aiarena/staging
Browse files Browse the repository at this point in the history
v1.4.1
  • Loading branch information
danielvschoor authored Jul 3, 2021
2 parents 6ab44cd + f1443e0 commit e1e6c1e
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 57 deletions.
55 changes: 9 additions & 46 deletions aiarena/api/view_filters.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,7 @@
from django_filters import rest_framework as filters
from django.db.models import Q
from rest_framework.exceptions import ValidationError

from aiarena.core.models import Match, Result, Bot, Map, User, Round, MatchParticipation, CompetitionParticipation, Competition


# Filter for items containing ALL tags in comma separated string
# If passed value contains a "|", then it will filter for items containing tags by ANY users in comma separated string on LHS of separator
class TagsFilter(filters.CharFilter):
def __init__(self, field_name2, *args, lookup_expr2='exact', **kwargs):
super().__init__(*args, **kwargs)
self.field_name2 = field_name2
self.lookup_expr2 = lookup_expr2

def filter(self, qs, value):
if not value:
return qs

# Check for pipe separator
if '|' in value:
users_str, tags_str = [s.strip() for s in value.split('|')]
else:
users_str = ""
tags_str = value

# Build query for users
user_query = Q()
if users_str:
try:
users = [int(s) for s in users_str.split(',')]
except ValueError:
raise ValidationError({"tags":["When using pipe separator (|), Expecting user_id (int) on LHS and tag_name on RHS of separator."]})
lookup = '%s__%s' % (self.field_name2, self.lookup_expr2)
for v in users:
user_query = user_query | Q(**{lookup: v})

# Build query for tags
tag_query = Q()
if tags_str:
tags = [s.strip() for s in tags_str.split(',')]
lookup = '%s__%s' % (self.field_name, self.lookup_expr)
for v in tags:
if v:
tag_query = tag_query & Q(**{lookup: v})

return self.get_method(qs)(user_query & tag_query)
from aiarena.core.d_utils import filter_tags


# Filter for the API views
Expand Down Expand Up @@ -127,8 +84,8 @@ class MatchFilter(filters.FilterSet):
requested_by = filters.NumberFilter(field_name="requested_by")
map = filters.NumberFilter(field_name="map")
bot = filters.NumberFilter(field_name="matchparticipation__bot")
tags = TagsFilter(field_name="tags__tag__name", field_name2="tags__user", lookup_expr='iexact')
tags__icontains = TagsFilter(field_name="tags__tag__name", field_name2="tags__user", lookup_expr='icontains')
tags = filters.CharFilter(method="filter_tags__exact")
tags__icontains = filters.CharFilter(method="filter_tags__icontains")

class Meta:
model = Match
Expand All @@ -137,3 +94,9 @@ class Meta:
'created',
'started'
]

def filter_tags__exact(self, qs, name, value):
return filter_tags(qs, value, "tags__tag__name", "iexact", "tags__user")

def filter_tags__icontains(self, qs, name, value):
return filter_tags(qs, value, "tags__tag__name", "icontains", "tags__user")
79 changes: 79 additions & 0 deletions aiarena/core/d_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import logging
from django.db.models.fields import CharField, TextField
from django.db.models import Q, Aggregate
from rest_framework.exceptions import ValidationError

# File for housing utils that require 'django' or would break CI if placed in utils.py
logger = logging.getLogger(__name__)


class GroupConcat(Aggregate):
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)'

def __init__(self, expression, ordering=None, separator=',', **extra):
super(GroupConcat, self).__init__(
expression,
ordering=' ORDER BY %s' % ordering if ordering is not None else '',
separator=' SEPARATOR "%s"' % separator,
output_field=TextField(),
**extra
)


def filter_tags(qs, value, tags_field_name, tags_lookup_expr="iexact", user_field_name="", exclude=False):
"""
Given a string value, filter the queryset for tags found in it.
qs: queryset
value: the string to be parsed. If it contains a "|", the LHS of the pipe is treated as users list, RHS as tags, else all are tags.
"""
if not value:
return qs

# Check for pipe separator
if '|' in value:
users_str, tags_str = [s.strip() for s in value.split('|')]
else:
users_str = ""
tags_str = value

method = lambda q: q.filter if not exclude else q.exclude

if user_field_name and users_str:
try:
users = [int(s) for s in users_str.split(',')]
except ValueError:
raise ValidationError({"tags":["When using pipe separator (|), Expecting user_id (int) on LHS and tag_name on RHS of separator."]})
else:
users = []

# Build query for users
user_query = Q()
user_lookup = '%s__%s' % (user_field_name, 'exact')
for v in users:
user_query |= Q(**{user_lookup: v})

# Build query for tags
tag_query = Q()
if tags_str:
tags = [s.strip() for s in tags_str.split(',')]
tags = [s for s in tags if s]
if tags_lookup_expr == 'icontains':
qs = method(qs)(user_query).distinct()
# Create string with all tag names and run icontains on the string
qs = qs.annotate(all_tags=GroupConcat(tags_field_name))
tags_lookup = '%s__%s' % ('all_tags', tags_lookup_expr)
for v in tags:
tag_query &= Q(**{tags_lookup: v})
return method(qs)(tag_query)
else:
user_lookup = '%s__%s' % (user_field_name, "in")
tags_lookup = '%s__%s' % (tags_field_name, tags_lookup_expr)
for v in tags:
if users:
qs = qs.filter(**{tags_lookup: v, user_lookup:users})
else:
qs = qs.filter(**{tags_lookup: v})
return qs.distinct()

return method(qs)(user_query).distinct()
22 changes: 11 additions & 11 deletions aiarena/frontend/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from aiarena.core.models import Trophy
from aiarena.core.models.relative_result import RelativeResult
from aiarena.core.utils import parse_tags
from aiarena.core.d_utils import filter_tags
from aiarena.frontend.utils import restrict_page_range
from aiarena.patreon.models import PatreonAccountBind

Expand Down Expand Up @@ -363,19 +364,18 @@ def filter_match_types(self, queryset, name, value):
return queryset.filter(match__requested_by__isnull=False)
return queryset

def filter_tags(self, queryset, name, value):
def filter_tags(self, qs, name, value):
# An unauthenticated user will have no tags
if not self.user.is_authenticated and not self.tags_by_all_value:
return queryset.none()

tag_values = parse_tags(value)
# User or All
query = Q(match__tags__user=self.user) if not self.tags_by_all_value else Q()
# Full or Partial Match
lookup = "match__tags__tag__name__" + ("icontains" if self.tags_partial_match_value else "iexact")
for v in tag_values:
query = query & Q(**{lookup:v})
return queryset.filter(query).distinct()
return qs.none()

if not self.tags_by_all_value:
value = str(self.user.id) + "|" + value

if self.tags_partial_match_value:
return filter_tags(qs, value, "match__tags__tag__name", "icontains", "match__tags__user")
else:
return filter_tags(qs, value, "match__tags__tag__name", "iexact", "match__tags__user")

def no_filter(self, queryset, name, value):
return queryset
Expand Down

0 comments on commit e1e6c1e

Please sign in to comment.