diff --git a/kitsune/notifications/tests/test_api.py b/kitsune/notifications/tests/test_api.py index 6970bdffba6..b01ca038b6f 100644 --- a/kitsune/notifications/tests/test_api.py +++ b/kitsune/notifications/tests/test_api.py @@ -74,10 +74,8 @@ def test_correct_fields(self): 'avatar': profile_avatar(followed.user), }) eq_(serializer.data['verb'], 'asked') - eq_(serializer.data['action_object'], { - 'type': 'question', - 'id': q.id, - }) + eq_(serializer.data['action_object']['type'], 'question') + eq_(serializer.data['action_object']['id'], q.id) eq_(serializer.data['target'], None) eq_(type(serializer.data['timestamp']), datetime) diff --git a/kitsune/questions/api.py b/kitsune/questions/api.py index 9392c757e8a..ff67cd1ed5e 100644 --- a/kitsune/questions/api.py +++ b/kitsune/questions/api.py @@ -118,6 +118,17 @@ def validate_creator(self, attrs, source): return attrs +class QuestionFKSerializer(QuestionSerializer): + + class Meta: + model = Question + fields = ( + 'creator', + 'id', + 'title', + ) + + class QuestionFilter(django_filters.FilterSet): product = django_filters.CharFilter(name='product__slug') creator = django_filters.CharFilter(name='creator__username') @@ -249,13 +260,13 @@ def helpful(self, request, pk=None): @action(methods=['POST'], permission_classes=[permissions.IsAuthenticated]) def follow(self, request, pk=None): question = self.get_object() - actstream.actions.follow(request.user, question, actor_only=False) + actstream.actions.follow(request.user, question, actor_only=False, send_action=False) return Response('', status=204) @action(methods=['POST'], permission_classes=[permissions.IsAuthenticated]) def unfollow(self, request, pk=None): question = self.get_object() - actstream.actions.unfollow(request.user, question) + actstream.actions.unfollow(request.user, question, send_action=False) return Response('', status=204) @action(methods=['POST']) @@ -367,6 +378,17 @@ def validate_creator(self, attrs, source): return attrs +class AnswerFKSerializer(AnswerSerializer): + + class Meta: + model = Answer + fields = ( + 'id', + 'question', + 'creator', + ) + + class AnswerFilter(django_filters.FilterSet): creator = django_filters.CharFilter(name='creator__username') question = django_filters.Filter(name='question__id') diff --git a/kitsune/questions/models.py b/kitsune/questions/models.py index 2a5ee12b912..d046ca95366 100755 --- a/kitsune/questions/models.py +++ b/kitsune/questions/models.py @@ -382,6 +382,12 @@ def my_tags(self): def get_mapping_type(cls): return QuestionMappingType + @classmethod + def get_generic_fk_serializer(cls): + # Avoid circular import + from kitsune.questions.api import QuestionFKSerializer + return QuestionFKSerializer + @classmethod def recent_asked_count(cls, extra_filter=None): """Returns the number of questions asked in the last 24 hours.""" @@ -478,6 +484,8 @@ def set_solution(self, answer, solver): self.add_metadata(solver_id=str(solver.id)) statsd.incr('questions.solution') QuestionSolvedEvent(answer).fire(exclude=self.creator) + actstream.action.send( + solver, verb='marked as a solution', action_object=answer, target=self) @property def related_documents(self): @@ -1113,6 +1121,12 @@ def get_images(self): def get_mapping_type(cls): return AnswerMetricsMappingType + @classmethod + def get_generic_fk_serializer(cls): + # Avoid circular import + from kitsune.questions.api import AnswerFKSerializer + return AnswerFKSerializer + def mark_as_spam(self, by_user): """Mark the answer as spam by the specified user.""" self.is_spam = True diff --git a/kitsune/questions/tests/test_models.py b/kitsune/questions/tests/test_models.py index cd2546bdba3..c6f4ba096b9 100644 --- a/kitsune/questions/tests/test_models.py +++ b/kitsune/questions/tests/test_models.py @@ -603,7 +603,8 @@ def test_add_metadata_over_1000_chars(self): eq_('a'*1000, metadata.value) -class TestSignals(TestCase): +class TestActions(TestCase): + def test_question_create_action(self): """When a question is created, an Action is created too.""" q = question(save=True) @@ -622,15 +623,26 @@ def test_answer_create_action(self): eq_(act.target, q) def test_question_change_no_action(self): - """When a question is changed, no action should be created.""" + """When a question is changed, no Action should be created.""" q = question(save=True) Action.objects.all().delete() q.save() # trigger another post_save hook eq_(Action.objects.count(), 0) def test_answer_change_no_action(self): - """When an answer is changed, no action should be created.""" - a = question(save=True) + """When an answer is changed, no Action should be created.""" + q = question(save=True) Action.objects.all().delete() - a.save() # trigger another post_save hook + q.save() # trigger another post_save hook eq_(Action.objects.count(), 0) + + def test_question_solved_makes_action(self): + """When an answer is marked as the solution to a question, an Action should be created.""" + ans = answer(save=True) + Action.objects.all().delete() + ans.question.set_solution(ans, ans.question.creator) + + act = Action.objects.action_object(ans).get() + eq_(act.actor, ans.question.creator) + eq_(act.verb, 'marked as a solution') + eq_(act.target, ans.question)