-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/problem status #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/hidden-problems
Are you sure you want to change the base?
Changes from all commits
7adbff7
ceb32e9
dad8129
00bf33f
9cb9758
dc50912
5252e63
f281433
1c58194
01e2360
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import pytest | ||
|
|
||
| from problem.models import Problem, Sentence | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def hypothesis_sentence(db): | ||
| return Sentence.objects.create(text="Hypothesis") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def premise_sentence(db): | ||
| return Sentence.objects.create(text="Premise") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def user_problem(db, hypothesis_sentence, premise_sentence): | ||
| problem = Problem.objects.create( | ||
| dataset=Problem.Dataset.USER, | ||
| hypothesis=hypothesis_sentence, | ||
| extra_data={}, | ||
| ) | ||
| problem.premises.add(premise_sentence) | ||
| return problem | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def non_user_problem(db, hypothesis_sentence, premise_sentence): | ||
| problem = Problem.objects.create( | ||
| dataset=Problem.Dataset.SICK, | ||
| hypothesis=hypothesis_sentence, | ||
| extra_data={}, | ||
| ) | ||
| problem.premises.add(premise_sentence) | ||
| return problem | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("problem", "0009_problem_hidden"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="problem", | ||
| name="gold", | ||
| field=models.BooleanField(default=False), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import pytest | ||
|
|
||
| from annotation.models import KnowledgeBaseAnnotation, LabelAnnotation | ||
| from problem.models import Problem | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def kb_annotation(db, annotator_session, non_user_problem): | ||
| return KnowledgeBaseAnnotation.objects.create( | ||
| problem=non_user_problem, | ||
| entity1="dog", | ||
| entity2="canine", | ||
| relationship=KnowledgeBaseAnnotation.Relationship.EQUAL, | ||
|
Comment on lines
+11
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Zoology nitpick: dogs are a subset of canines. |
||
| session=annotator_session, | ||
| created_by=annotator_session.user, | ||
| ) | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_bronze_no_annotations(db, non_user_problem): | ||
| """ | ||
| A problem with no annotations and gold=False is bronze. | ||
|
|
||
| This also functions as an initial assumption check for the tests below. | ||
| """ | ||
| assert non_user_problem.status == Problem.Status.BRONZE | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_gold_without_annotation(db, non_user_problem): | ||
| """A problem with gold=True is gold without annotations.""" | ||
| non_user_problem.gold = True | ||
| non_user_problem.save() | ||
| assert non_user_problem.status == Problem.Status.GOLD | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_gold_with_annotation(db, non_user_problem, kb_annotation): | ||
| """A problem with gold=True is gold even with annotations.""" | ||
| non_user_problem.gold = True | ||
| non_user_problem.save() | ||
| assert non_user_problem.status == Problem.Status.GOLD | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_silver_with_kb_annotation(db, non_user_problem, kb_annotation): | ||
| """A problem with an active KB annotation and gold=False is silver.""" | ||
| assert non_user_problem.status == Problem.Status.SILVER | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_silver_with_label_annotation(db, non_user_problem, annotator_session, sample_label): | ||
| """A problem with an active label annotation and gold=False is silver.""" | ||
| LabelAnnotation.objects.create( | ||
| problem=non_user_problem, | ||
| label=sample_label, | ||
| session=annotator_session, | ||
| created_by=annotator_session.user, | ||
| ) | ||
| assert non_user_problem.status == Problem.Status.SILVER | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_bronze_when_all_annotations_removed(db, non_user_problem, annotator_session): | ||
| """A problem whose only annotation is removed reverts to bronze.""" | ||
| from django.utils import timezone | ||
|
|
||
| kb = KnowledgeBaseAnnotation.objects.create( | ||
| problem=non_user_problem, | ||
| entity1="dog", | ||
| entity2="canine", | ||
| relationship=KnowledgeBaseAnnotation.Relationship.EQUAL, | ||
| session=annotator_session, | ||
| created_by=annotator_session.user, | ||
| ) | ||
| kb.removed_at = timezone.now() | ||
| kb.removed_by = annotator_session.user | ||
| kb.save() | ||
|
|
||
| assert non_user_problem.status == Problem.Status.BRONZE | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_status_serialized(db, non_user_problem): | ||
| """The status field is correctly serialized.""" | ||
| from problem.serializers import ProblemSerializer | ||
|
|
||
| serializer = ProblemSerializer(non_user_problem) | ||
| assert serializer.data["status"] == Problem.Status.BRONZE | ||
| assert serializer.data["gold"] is False | ||
|
Comment on lines
+86
to
+87
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why go with |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
|
|
||
| from problem.problem_details import ( | ||
| get_filters, | ||
| apply_status_filter, | ||
| get_related_problem_ids, | ||
| ) | ||
| from problem.models import Problem | ||
|
|
@@ -45,6 +46,11 @@ def has_permission(self, request, view): | |
| return super().has_permission(request, view) and request.user.can_change_problem_visibility | ||
|
|
||
|
|
||
| class ChangeProblemStatusPermission(IsAuthenticated): | ||
| def has_permission(self, request, view): | ||
| return super().has_permission(request, view) and request.user.can_change_problem_status | ||
|
|
||
|
|
||
| class ProblemView(ModelViewSet): | ||
| queryset = Problem.objects.all() | ||
| serializer_class = ProblemSerializer | ||
|
|
@@ -56,6 +62,8 @@ def get_permissions(self): | |
| return [EditProblemPermission()] | ||
| if self.action == "set_visibility": | ||
| return [ChangeProblemVisibilityPermission()] | ||
| if self.action == "set_status": | ||
| return [ChangeProblemStatusPermission()] | ||
| return [IsAuthenticatedOrReadOnly()] | ||
|
|
||
| def list(self, request: Request) -> Response: | ||
|
|
@@ -69,9 +77,28 @@ def list(self, request: Request) -> Response: | |
| if filters is not None: | ||
| qs = qs.filter(filters) | ||
|
|
||
| qs = apply_status_filter(qs, request.query_params) | ||
|
|
||
| serializer = self.get_serializer(qs, many=True) | ||
| return Response(serializer.data, status=HTTP_200_OK) | ||
|
|
||
| @action(detail=True, methods=["post"], url_path="set-status") | ||
| def set_status(self, request: Request, pk: int) -> Response: | ||
| """ | ||
| Toggles the gold status of a Problem. | ||
| Expects a JSON body with a boolean 'gold' field. | ||
| """ | ||
| problem = get_object_or_404(Problem, id=pk) | ||
| gold = request.data.get("gold") | ||
| if not isinstance(gold, bool): | ||
| return Response( | ||
| {"detail": "'gold' must be a boolean."}, | ||
| status=HTTP_400_BAD_REQUEST, | ||
| ) | ||
|
Comment on lines
+93
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this validation come with DRF out of the box? |
||
| problem.gold = gold | ||
| problem.save(update_fields=["gold"]) | ||
| return Response({"gold": problem.gold, "status": problem.status}, status=HTTP_200_OK) | ||
|
|
||
| @action(detail=False, methods=["get"], url_path="first") | ||
| def first(self, request: Request) -> Response: | ||
| """ | ||
|
|
@@ -115,6 +142,8 @@ def _get_problem_response(self, request: Request, pk: int | None) -> Response: | |
| if filters is not None: | ||
| qs = qs.filter(filters).distinct() | ||
|
|
||
| qs = apply_status_filter(qs, request.query_params) | ||
|
|
||
| problem = None | ||
| if pk is not None: | ||
| try: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've lifted these fixtures here so they are available to more test files.