-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/problem labels #64
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
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
e4729fb
Add Label and Labeling models and migration
XanderVertegaal 5ab479c
Refactor grey styling on ProblemDetail headers
XanderVertegaal 7b47e74
Add mockLabels (for development) -- WIP
XanderVertegaal 5d5b24e
Added ProblemLabelsComponent
XanderVertegaal 0997c43
Add ManageLabelsModal
XanderVertegaal 6c78d6f
Add and fix tests
XanderVertegaal 97fba74
Cleanup
XanderVertegaal 72b2d54
Update fixtures; create Admin interface
XanderVertegaal 4a734f0
Formatting
XanderVertegaal 973c4f5
Replace serialize methods with proper serializers
XanderVertegaal b1ef4dc
User serializers in view
XanderVertegaal 02b7eb6
Show toasts on fetching errors
XanderVertegaal 793ed9e
Query all labels
XanderVertegaal d131340
Show queried labels in frontend
XanderVertegaal d8d4b61
Expand label form
XanderVertegaal aa103ef
Fix typo
XanderVertegaal 4e11806
Save labels in backend
XanderVertegaal 9e8dac8
Handle dates
XanderVertegaal 48f9292
Merge branch 'feature/user-models' into feature/problem-labels
XanderVertegaal 16f2ff0
Restore missing imports
XanderVertegaal 8bface7
Merge branch 'feature/user-models' into feature/problem-labels
XanderVertegaal 8002cd2
Switch to ModelViewSet setup with Django permissions
XanderVertegaal 98d27c7
Write tests for labeling
XanderVertegaal 9018d5a
Merge branch 'develop' into feature/problem-labels
XanderVertegaal 2eb8545
Removed useless log
XanderVertegaal 61eed76
Wrap labels
XanderVertegaal 3bbf47a
Add loading indicator; dismiss modals correctly
XanderVertegaal 53c7140
Cleanup
XanderVertegaal 534ed83
More cleanup
XanderVertegaal 6516edc
Fix failing tests
XanderVertegaal 849741a
Merge branch 'develop' into feature/problem-labels
XanderVertegaal da15e0d
Merge branch 'develop' into feature/problem-labels
XanderVertegaal e4f8bb5
Rely on DRF built-in permission handling
XanderVertegaal d457341
Move utils to util.ts
XanderVertegaal eafc758
Fix error message
XanderVertegaal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,10 @@ | ||
| from django.contrib import admin | ||
|
|
||
| from annotation.models import Label | ||
|
|
||
|
|
||
| # Register your models here. | ||
| @admin.register(Label) | ||
| class LabelAdmin(admin.ModelAdmin): | ||
| list_display = ("text", "description") | ||
| search_fields = ("text",) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| [ | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 1, | ||
| "fields": { | ||
| "text": "Important", | ||
| "description": "This problem is particularly important." | ||
| } | ||
| }, | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 2, | ||
| "fields": { | ||
| "text": "Review needed", | ||
| "description": "This problem needs to be reviewed by a master annotator." | ||
| } | ||
| }, | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 3, | ||
| "fields": { | ||
| "text": "Ambiguous", | ||
| "description": "The problem statement is ambiguous and needs clarification." | ||
| } | ||
| }, | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 4, | ||
| "fields": { | ||
| "text": "Incomplete knowledge", | ||
| "description": "Some knowledge base items are missing." | ||
| } | ||
| }, | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 5, | ||
| "fields": { | ||
| "text": "Spelling", | ||
| "description": "The premises or hypothesis are misspelled, which may lead to wrong analyses." | ||
| } | ||
| }, | ||
| { | ||
| "model": "annotation.label", | ||
| "pk": 6, | ||
| "fields": { | ||
| "text": "Wrong entailment label", | ||
| "description": "The entailment label is incorrect." | ||
| } | ||
| } | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Generated by Django 4.2.27 on 2025-12-11 15:10 | ||
|
|
||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
| import django.db.models.deletion | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ("problem", "0007_alter_problem_options"), | ||
| ("annotation", "0001_initial"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Label", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("text", models.CharField(max_length=255)), | ||
| ( | ||
| "description", | ||
| models.TextField( | ||
| help_text="A detailed description of the label, indicating when it should be used." | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "ordering": ["text"], | ||
| }, | ||
| ), | ||
| migrations.CreateModel( | ||
| name="Labeling", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("attached_at", models.DateTimeField(auto_now_add=True)), | ||
| ( | ||
| "removed_at", | ||
| models.DateTimeField( | ||
| blank=True, | ||
| help_text="When this label was removed from the problem.", | ||
| null=True, | ||
| ), | ||
| ), | ||
| ( | ||
| "notes", | ||
| models.TextField( | ||
| blank=True, | ||
| help_text="Optional notes explaining why this label was added or removed.", | ||
| ), | ||
| ), | ||
| ( | ||
| "attached_by", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="labelings_attached", | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ( | ||
| "label", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="labelings", | ||
| to="annotation.label", | ||
| ), | ||
| ), | ||
| ( | ||
| "problem", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="labelings", | ||
| to="problem.problem", | ||
| ), | ||
| ), | ||
| ( | ||
| "removed_by", | ||
| models.ForeignKey( | ||
| blank=True, | ||
| help_text="User who removed this label.", | ||
| null=True, | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="labelings_removed", | ||
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "ordering": ["-attached_at"], | ||
| "permissions": [ | ||
| ("delete_own_labeling", "Can remove own labeling from problems"), | ||
| ("delete_any_labeling", "Can remove any labeling from problems"), | ||
| ], | ||
| "indexes": [ | ||
| models.Index( | ||
| fields=["problem", "removed_at"], | ||
| name="annotation__problem_e7ac7b_idx", | ||
| ), | ||
| models.Index( | ||
| fields=["label", "removed_at"], | ||
| name="annotation__label_i_a229d2_idx", | ||
| ), | ||
| ], | ||
| }, | ||
| ), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| from rest_framework import serializers | ||
|
|
||
| from django.contrib.auth.models import AnonymousUser | ||
|
|
||
| from annotation.models import Label, Labeling | ||
| from problem.models import Problem | ||
| from user.models import User | ||
|
|
||
|
|
||
| class LabelSerializer(serializers.ModelSerializer): | ||
| """ | ||
| Serializer for Label model. | ||
| """ | ||
|
|
||
| class Meta: | ||
| model = Label | ||
| fields = ["id", "text", "description"] | ||
|
|
||
|
|
||
| class ActiveLabelSerializer(serializers.Serializer): | ||
| """ | ||
| Serializer for active labels attached to a problem. | ||
| Includes attachedInfo and removable status based on current user. | ||
| """ | ||
|
|
||
| id = serializers.IntegerField(source="label.id") | ||
| text = serializers.CharField(source="label.text") | ||
| description = serializers.CharField(source="label.description") | ||
| attachedInfo = serializers.SerializerMethodField() | ||
| removable = serializers.SerializerMethodField() | ||
|
|
||
| def get_attachedInfo(self, labeling: Labeling) -> dict: | ||
| """Get attachment information for the label.""" | ||
| request = self.context.get("request") | ||
| user: User | AnonymousUser | None = request.user if request else None | ||
|
|
||
| if user and user.is_anonymous is False: | ||
| attached_by_current_user = labeling.attached_by.pk == user.pk | ||
| else: | ||
| attached_by_current_user = False | ||
|
|
||
| return { | ||
| "userName": labeling.attached_by.username, | ||
| "date": labeling.attached_at.isoformat(), | ||
| "attachedByCurrentUser": attached_by_current_user, | ||
| } | ||
|
|
||
| def get_removable(self, labeling: Labeling) -> bool: | ||
| """Determine if the label is removable by the current user.""" | ||
| request = self.context.get("request") | ||
| user: User | AnonymousUser | None = request.user if request else None | ||
|
|
||
| if user is None or user.is_anonymous: | ||
| return False | ||
|
|
||
| if user.is_superuser or user.has_perm("annotation.delete_any_labeling"): | ||
| return True | ||
|
|
||
| if user.has_perm("annotation.delete_own_labeling"): | ||
| return labeling.attached_by.pk == user.pk | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| class LabelingSerializer(serializers.ModelSerializer): | ||
| """ | ||
| Serializer for Labeling model, including the full label details. | ||
| """ | ||
|
|
||
| label = LabelSerializer(read_only=True) | ||
| attachedAt = serializers.DateTimeField(source="attached_at") | ||
| attachedBy = serializers.PrimaryKeyRelatedField( | ||
| source="attached_by", read_only=True | ||
| ) | ||
| removedAt = serializers.DateTimeField(source="removed_at", allow_null=True) | ||
| removedBy = serializers.PrimaryKeyRelatedField( | ||
| source="removed_by", allow_null=True, read_only=True | ||
| ) | ||
|
|
||
| class Meta: | ||
| model = Labeling | ||
| fields = [ | ||
| "id", | ||
| "label", | ||
| "attached_at", | ||
| "attached_by", | ||
| "removed_at", | ||
| "removed_by", | ||
| "notes", | ||
| ] | ||
|
|
||
|
|
||
| class SelectedLabelSerializer(serializers.Serializer): | ||
| """Serializer for a selected label in the save labels input.""" | ||
|
|
||
| id = serializers.IntegerField() | ||
|
|
||
| def validate_id(self, value): | ||
| """Validate that the label exists.""" | ||
| if not Label.objects.filter(id=value).exists(): | ||
| raise serializers.ValidationError(f"Label with ID {value} does not exist.") | ||
| return value | ||
|
|
||
|
|
||
| class SaveLabelsInputSerializer(serializers.Serializer): | ||
| """ | ||
| Serializer for validating save labels input data. | ||
| Used when saving/updating labels for a problem. | ||
| """ | ||
|
|
||
| problemId = serializers.IntegerField() | ||
| selectedLabels = SelectedLabelSerializer(many=True, allow_empty=True) | ||
| remarks = serializers.CharField(required=False, allow_blank=True, default="") | ||
|
|
||
| def validate_problemId(self, value): | ||
| """Validate that the problem exists.""" | ||
| if not Problem.objects.filter(id=value).exists(): | ||
| raise serializers.ValidationError( | ||
| f"Problem with ID {value} does not exist." | ||
| ) | ||
| return value |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Not a request for change, just a remark: another way to organize this would be to create a separate
Eventmodel for changes made by users. It would have a type (such as "deleted"), a link to the affected model, a user and a date/time. Maybe not worth worrying about for this project, but when there many models that can be edited, this could help to keep the models leaner.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.
Sounds very scalable! 👍