[Bug 1089748] Filter by metadata.

This commit is contained in:
Mike Cooper 2014-11-10 10:42:25 -08:00
Родитель 7910a256a1
Коммит 1682f2674d
3 изменённых файлов: 72 добавлений и 11 удалений

Просмотреть файл

@ -1,12 +1,14 @@
import django_filters
import json
from django.db.models import Q
from django.utils import six
from rest_framework import serializers, viewsets, permissions, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from kitsune.products.api import TopicField
from kitsune.questions.models import Question, Answer, QuestionMetaData
from kitsune.sumo.api import CORSMixin, OnlyCreatorEdits, DateTimeUTCField
from kitsune.sumo.api import DateTimeUTCField, CORSMixin, OnlyCreatorEdits, GenericAPIException
class QuestionMetaDataSerializer(serializers.ModelSerializer):
@ -96,6 +98,7 @@ class QuestionFilter(django_filters.FilterSet):
creator = django_filters.CharFilter(name='creator__username')
involved = django_filters.MethodFilter(action='filter_involved')
is_solved = django_filters.MethodFilter(action='filter_is_solved')
metadata = django_filters.MethodFilter(action='filter_metadata')
class Meta(object):
model = Question
@ -129,6 +132,27 @@ class QuestionFilter(django_filters.FilterSet):
filter = ~filter
return queryset.filter(filter)
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
class QuestionViewSet(CORSMixin, viewsets.ModelViewSet):
serializer_class = QuestionSerializer

Просмотреть файл

@ -19,8 +19,7 @@ def tags_eq(tagged_object, tag_names):
sorted(tag_names))
@with_save
def question(**kwargs):
def question(save=False, **kwargs):
defaults = dict(title=str(datetime.now()),
content='',
created=datetime.now(),
@ -29,7 +28,14 @@ def question(**kwargs):
defaults.update(kwargs)
if 'creator' not in kwargs and 'creator_id' not in kwargs:
defaults['creator'] = profile().user
return Question(**defaults)
q = Question(**defaults)
if save:
q.save()
if 'metadata' in defaults:
if not save:
raise ValueError('save must be True if metadata provided.')
q.add_metadata(**defaults['metadata'])
return q
@with_save

Просмотреть файл

@ -1,6 +1,8 @@
import json
import mock
from nose.tools import eq_, ok_
from nose.tools import eq_, ok_, raises
from rest_framework.test import APIClient
from rest_framework.exceptions import APIException
from kitsune.sumo.tests import TestCase
from kitsune.questions import api
@ -339,18 +341,21 @@ class TestAnswerViewSet(TestCase):
class TestQuestionFilter(TestCase):
def setUp(self):
self.filter = api.QuestionFilter()
self.qs = Question.objects.all()
self.filter_instance = api.QuestionFilter()
self.queryset = Question.objects.all()
def filter(self, filter_data):
return self.filter_instance.filter_metadata(self.queryset, json.dumps(filter_data))
def test_filter_involved(self):
q1 = question(save=True)
a1 = answer(question=q1, save=True)
q2 = question(creator=a1.creator, save=True)
res = self.filter.filter_involved(self.qs, q1.creator.username)
res = self.filter_instance.filter_involved(self.queryset, q1.creator.username)
eq_(list(res), [q1])
res = self.filter.filter_involved(self.qs, q2.creator.username)
res = self.filter_instance.filter_involved(self.queryset, q2.creator.username)
# The filter does not have a strong order.
res = sorted(res, key=lambda q: q.id)
eq_(res, [q1, q2])
@ -362,8 +367,34 @@ class TestQuestionFilter(TestCase):
q1.save()
q2 = question(save=True)
res = self.filter.filter_is_solved(self.qs, True)
res = self.filter_instance.filter_is_solved(self.queryset, True)
eq_(list(res), [q1])
res = self.filter.filter_is_solved(self.qs, False)
res = self.filter_instance.filter_is_solved(self.queryset, False)
eq_(list(res), [q2])
@raises(APIException)
def test_metadata_not_json(self):
self.filter_instance.filter_metadata(self.queryset, 'not json')
@raises(APIException)
def test_metadata_bad_json(self):
self.filter({'foo': []})
def test_single_filter_match(self):
q1 = question(metadata={'os': 'Linux'}, save=True)
question(metadata={'os': 'OSX'}, save=True)
res = self.filter({'os': 'Linux'})
eq_(list(res), [q1])
def test_single_filter_no_match(self):
question(metadata={'os': 'Linux'}, save=True)
question(metadata={'os': 'OSX'}, save=True)
res = self.filter({"os": "Windows 8"})
eq_(list(res), [])
def test_multi_filter_is_and(self):
q1 = question(metadata={'os': 'Linux', 'category': 'troubleshooting'}, save=True)
question(metadata={'os': 'OSX', 'category': 'troubleshooting'}, save=True)
res = self.filter({'os': 'Linux', 'category': 'troubleshooting'})
eq_(list(res), [q1])