Skip to content
Closed
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
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,25 @@ The matrix below shows the permissions for each role. Not all permissions are cu

(Last updated: November 14th, 2025)

| | Visitor | Annotator | Master Annotator |
| -------------------------- | ------- | --------- | ---------------- |
| Browse gold problems | Yes | Yes | Yes |
| Browse silver problems | No | Yes | Yes |
| Edit KB items | No | Yes | Yes |
| Add labels | No | Yes | Yes |
| Remove own labels | No | Yes | Yes |
| Remove other users' labels | No | No | Yes |
| Add problems | No | No | Yes |
| Copy problems | No | No | Yes |
| Update user problems | No | No | Yes |
| Delete problems | No | No | Yes |
| Edit existing problems | No | No | Yes |
| See hidden problems | No | No | Yes |
| Silver/gold problems | No | No | Yes |
| Hide/unhide problems | No | No | Yes |
| Manage users | No | No | Yes |
| | Unregistered users | Registered Users | Annotators | Master Annotators |
| ------------------------ | ------------------ | ---------------- | ---------- | ----------------- |
| Browse gold problems | Yes | Yes | Yes | Yes |
| Browse silver problems | No | Yes | Yes | Yes |
| Browse bronze problems | No | Yes | Yes | Yes |
| Add/edit/remove KB items | No | No | Yes | Yes |
| Add labels | No | No | Yes | Yes |
| Remove own labels | No | No | Yes | Yes |
| Remove others' labels | No | No | No | Yes |
| Copy problems | No | No | No | Yes |
| See hidden problems | No | No | No | Yes |
| Gold/ungold problems | No | No | No | Yes |
| Hide/unhide problems | No | No | No | Yes |
| Manage users | No | No | No | Yes |

NB:
- 'Bronze' problems are problems that are 'untouched' (i.e., non-annotated) problems. These do not have knowledge base items or labels, and have no edits made to their syntactic parses and tableaus.
- 'Silver' problems are problems that have been annotated with labels and/or knowledge base items, but have not been marked as 'gold'.
- 'Gold' problems are problems that have been marked as 'gold' by a Master Annotator.

## Licence

Expand Down
8 changes: 7 additions & 1 deletion backend/annotation/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth.models import AnonymousUser

from annotation.models import (
BaseAnnotation,
KnowledgeBaseAnnotation,
Label,
LabelAnnotation,
Expand All @@ -17,7 +18,7 @@ class AnnotationBaseSerializer(serializers.ModelSerializer):
"""

createdAt = serializers.DateTimeField(source="created_at", read_only=True)
createdBy = serializers.PrimaryKeyRelatedField(source="created_by", read_only=True)
createdBy = serializers.SerializerMethodField(method_name="get_createdBy", read_only=True)
removedAt = serializers.DateTimeField(
source="removed_at", allow_null=True, read_only=True
)
Expand Down Expand Up @@ -45,6 +46,11 @@ def get_removable(self, annotation) -> bool:
"""This should be overridden in subclasses."""
raise NotImplementedError("Subclasses must implement get_removable method.")

def get_createdBy(self, annotation) -> str | None:
"""Returns the full name of the user who created the annotation."""
return annotation.created_by.get_full_name()



class KnowledgeBaseAnnotationSerializer(AnnotationBaseSerializer):
"""
Expand Down
6 changes: 2 additions & 4 deletions backend/annotation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ def has_permission(self, request, view):
if user.is_superuser:
return True

return user.has_perm("annotation.add_labelannotation") or user.has_perm(
"annotation.change_labelannotation"
)
return user.has_perm("annotation.add_labelannotation")


class LabelAnnotationView(ModelViewSet):
Expand Down Expand Up @@ -126,7 +124,7 @@ def _update_label_annotations(
def _mark_as_removed(self, label_annotation: LabelAnnotation, user: User) -> None:
"""Mark a label annotation as removed."""

if not user.can_remove_label(label_annotation):
if not user.can_remove_label_annotation(label_annotation):
logger.warning(
f"User {user.username} attempted to remove label {label_annotation.label.pk} "
f"attached by {label_annotation.created_by.username}"
Expand Down
61 changes: 19 additions & 42 deletions backend/problem/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from problem.services import FracasData, SNLIData, SickData
from problem.models import Problem, Sentence
from user.models import User


class ProblemSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -98,32 +99,10 @@ def validate_base(self, value):
def create(self, validated_data: dict) -> Problem:
"""
Create a new Problem instance from validated input data.
Handles creation of related Sentence and KnowledgeBase objects.
"""
premise_sentences = [
Sentence.objects.get_or_create(text=premise)[0]
for premise in validated_data["premises"]
]

hypothesis_sentence = Sentence.objects.get_or_create(
text=validated_data["hypothesis"]
)[0]

problem = Problem.objects.create(
base_id=validated_data.get("base", None),
hypothesis=hypothesis_sentence,
dataset=Problem.Dataset.USER,
# TODO: Determine entailment label based on LangPro parser output.
entailment_label=Problem.EntailmentLabel.UNKNOWN,
extra_data={},
)

problem.premises.set(premise_sentences)
new_user_problem = Problem(dataset=Problem.Dataset.USER, extra_data={})

kb_items = validated_data.get("kbItems", [])
self._handle_kb_annotations(problem, kb_items)

return problem
return self._update_core_problem_fields(new_user_problem, validated_data)

def _create_update_kb_annotation(
self, kb_item: dict, problem: Problem, session: AnnotationSession
Expand Down Expand Up @@ -164,11 +143,12 @@ def _mark_kb_not_in_input_as_removed(
Marks KnowledgeBase annotations for a problem that are not included in
the provided list of kb_items as removed.
"""
kb_item_ids = {kb_item.get("id") for kb_item in kb_items if kb_item.get("id") is not None}
kb_item_ids = {
kb_item.get("id") for kb_item in kb_items if kb_item.get("id") is not None
}

annotations_to_delete = KnowledgeBaseAnnotation.objects.filter(
problem=problem,
removed_at__isnull=True
problem=problem, removed_at__isnull=True
).exclude(id__in=kb_item_ids)

current_time = timezone.now()
Expand All @@ -178,18 +158,14 @@ def _mark_kb_not_in_input_as_removed(
annotation.removed_by = session.user
annotation.save()

def _handle_kb_annotations(
self, problem: Problem, kb_items: list[dict]
def handle_kb_annotations(
self, problem: Problem, kb_items: list[dict], user: User
) -> None:
"""
Creates, updates and deletes KnowledgeBase annotations for a problem.
Creates an annotation session if it does not exist.
"""
request = self.context.get("request", None)
if not request or not request.user.is_authenticated or not request.user.can_edit_kb:
return

session = AnnotationSession.objects.create(user=request.user)
session = AnnotationSession.objects.create(user=user)

self._mark_kb_not_in_input_as_removed(problem, kb_items, session)

Expand All @@ -198,18 +174,19 @@ def _handle_kb_annotations(

def update(self, instance: Problem, validated_data: dict) -> Problem:
"""
Update an existing Problem instance from validated input data.
Handles updating of related Sentence and KnowledgeBase objects.
Updates Problem core fields from validated input data.
"""

# KB annotations can be made for all problems.
kb_items = validated_data.get("kbItems", [])
self._handle_kb_annotations(instance, kb_items)

# Other fields can only be updated for user-created problems.
# Only USER-problems can be updated.
if instance.dataset != Problem.Dataset.USER:
return instance

return self._update_core_problem_fields(instance, validated_data)

def _update_core_problem_fields(self, instance: Problem, validated_data: dict) -> Problem:
"""
Updates core Problem fields (premises, hypothesis, base) from validated
input data.
"""
instance.hypothesis = Sentence.objects.get_or_create(
text=validated_data["hypothesis"],
)[0]
Expand Down
Loading