Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions kitsune/questions/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
from rest_framework import serializers, viewsets, permissions, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from taggit.models import Tag

from kitsune.products.api import TopicField
from kitsune.questions.models import (
Question, Answer, QuestionMetaData, AlreadyTakenException,
InvalidUserException, QuestionVote, AnswerVote)
from kitsune.sumo.api import DateTimeUTCField, OnlyCreatorEdits, GenericAPIException
from kitsune.tags.utils import add_existing_tag
from kitsune.users.api import ProfileFKSerializer


Expand Down Expand Up @@ -46,6 +48,15 @@ def restore_object(self, attrs, instance=None):
return obj


class QuestionTagSerializer(serializers.ModelSerializer):
question = serializers.PrimaryKeyRelatedField(
required=False, write_only=True)

class Meta:
model = Tag
fields = ('name', 'slug')


class QuestionSerializer(serializers.ModelSerializer):
# Use slugs for product and topic instead of ids.
product = serializers.SlugRelatedField(required=True, slug_field='slug')
Expand All @@ -57,6 +68,7 @@ class QuestionSerializer(serializers.ModelSerializer):
is_solved = serializers.Field(source='is_solved')
is_taken = serializers.Field(source='is_taken')
metadata = QuestionMetaDataSerializer(source='metadata_set', required=False)
tags = QuestionTagSerializer(source='tags', read_only=True)
num_votes = serializers.Field(source='num_votes')
solution = serializers.PrimaryKeyRelatedField(read_only=True)
taken_by = ProfileFKSerializer(source='taken_by.get_profile', read_only=True)
Expand All @@ -80,6 +92,7 @@ class Meta:
'last_answer',
'locale',
'metadata',
'tags',
'num_answers',
'num_votes_past_week',
'num_votes',
Expand Down Expand Up @@ -289,6 +302,40 @@ def take(self, request, pk=None):

return Response(status=204)

@action(methods=['POST'], permission_classes=[permissions.IsAuthenticated])
def add_tag(self, request, pk=None):
question = self.get_object()

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

tag = request.DATA['tag']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do this, it will give a 50x error if the user doesn't pass a tag. It is better to do something like in delete_metadata to check that it is the data, and if not, give a 400 bad request error. 500s are bad because the are completely opaque to users.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.


tag = Tag.objects.get(name=canonical_name)
return Response(QuestionTagSerializer(instance=tag).data)

@action(methods=['POST', 'DELETE'], permission_classes=[permissions.IsAuthenticated])
def remove_tag(self, request, pk=None):
question = self.get_object()

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

tag = request.DATA['tag']
question.tags.remove(tag)
return Response(status=status.HTTP_204_NO_CONTENT)


class AnswerSerializer(serializers.ModelSerializer):
created = DateTimeUTCField(read_only=True)
Expand Down
28 changes: 27 additions & 1 deletion kitsune/questions/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from nose.tools import eq_, ok_, raises
from rest_framework.test import APIClient
from rest_framework.exceptions import APIException
from taggit.models import Tag

from kitsune.sumo.tests import TestCase
from kitsune.questions import api
Expand All @@ -15,7 +16,7 @@
from kitsune.products.tests import product, topic
from kitsune.sumo.urlresolvers import reverse
from kitsune.users.helpers import profile_avatar
from kitsune.users.tests import profile, user
from kitsune.users.tests import profile, user, add_permission


class TestQuestionSerializerDeserialization(TestCase):
Expand Down Expand Up @@ -408,6 +409,31 @@ def test_unfollow(self):
eq_(res.status_code, 204)
eq_(Follow.objects.filter(user=u).count(), 0)

def test_add_tag(self):
q = question(save=True)
eq_(0, q.tags.count())

u = profile().user
add_permission(u, Tag, 'add_tag')
self.client.force_authenticate(user=u)

res = self.client.post(reverse('question-add-tag', args=[q.id]), data={'tag': 'test'})
eq_(res.status_code, 200)
eq_(1, q.tags.count())

def test_remove_tag(self):
q = question(save=True)
q.tags.add('test')
eq_(1, q.tags.count())

u = profile().user
add_permission(u, Tag, 'add_tag')
self.client.force_authenticate(user=u)

res = self.client.post(reverse('question-remove-tag', args=[q.id]), data={'tag': 'test'})
eq_(res.status_code, 204)
eq_(0, q.tags.count())


class TestAnswerSerializerDeserialization(TestCase):

Expand Down