зеркало из https://github.com/mozilla/kitsune.git
[Bug 1089748] Filter by metadata.
This commit is contained in:
Родитель
7910a256a1
Коммит
1682f2674d
|
@ -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])
|
||||
|
|
Загрузка…
Ссылка в новой задаче