-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #554 from bounswe/dev
Merge dev into main
- Loading branch information
Showing
147 changed files
with
19,965 additions
and
12,567 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
|
@@ -18,3 +18,5 @@ venv | |
**/.env | ||
*.icloud | ||
|
||
/backend/env | ||
/backend/staticfiles |
This file contains 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 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,16 @@ | ||
# Use an official Python runtime as a parent image | ||
FROM python:3.11.6 | ||
|
||
# Set environment variables | ||
ENV PYTHONDONTWRITEBYTECODE=1 | ||
ENV PYTHONUNBUFFERED=1 | ||
|
||
# Set the working directory | ||
WORKDIR /annotations_backend | ||
|
||
# Install dependencies | ||
COPY requirements.txt /annotations_backend/ | ||
RUN pip install -r requirements.txt | ||
|
||
# Copy the project code into the container | ||
COPY . /annotations_backend/ |
Empty file.
This file contains 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,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
This file contains 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,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class AnnotationsConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "annotations" |
Empty file.
This file contains 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,53 @@ | ||
from django.db import models | ||
from django.utils.timezone import now | ||
|
||
|
||
class Body(models.Model): | ||
type = models.CharField(max_length=255, default="TextualBody") | ||
value = models.TextField() | ||
format = models.CharField(max_length=50, default="text/plain") | ||
language = models.CharField(max_length=10, default="en") | ||
|
||
def __str__(self): | ||
return self.value[:50] | ||
|
||
|
||
class Selector(models.Model): | ||
type = models.CharField(max_length=255, default="TextPositionSelector") | ||
start = models.PositiveIntegerField() | ||
end = models.PositiveIntegerField() | ||
source = models.URLField() | ||
|
||
def __str__(self): | ||
return f"{self.source} [{self.start}:{self.end}]" | ||
|
||
|
||
class Creator(models.Model): | ||
creator_id = models.URLField() | ||
type = models.CharField(max_length=50, default="Person") # 'Person', 'Agent' | ||
name = models.CharField(max_length=255) | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
|
||
class Generator(models.Model): | ||
type = models.CharField(max_length=50, default="Software") | ||
name = models.CharField(max_length=255) | ||
homepage = models.URLField(blank=True, null=True) | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
|
||
class Annotation(models.Model): | ||
type = models.CharField(max_length=255, default="Annotation") | ||
body = models.ForeignKey(Body, on_delete=models.CASCADE) | ||
target = models.ForeignKey(Selector, on_delete=models.CASCADE) | ||
creator = models.ForeignKey(Creator, on_delete=models.CASCADE) | ||
generator = models.ForeignKey(Generator, on_delete=models.SET_NULL, blank=True, null=True) | ||
created = models.DateTimeField(default=now) | ||
modified = models.DateTimeField(auto_now=True) | ||
|
||
def __str__(self): | ||
return f"Annotation on {self.target.source} by {self.creator.name}" |
This file contains 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,72 @@ | ||
from rest_framework import serializers | ||
from .models import Annotation, Body, Selector, Creator, Generator | ||
|
||
|
||
class BodySerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Body | ||
fields = '__all__' | ||
|
||
|
||
class SelectorSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Selector | ||
fields = '__all__' | ||
|
||
|
||
class CreatorSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Creator | ||
fields = '__all__' | ||
|
||
|
||
class GeneratorSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Generator | ||
fields = '__all__' | ||
|
||
|
||
class AnnotationSerializer(serializers.ModelSerializer): | ||
body = BodySerializer() | ||
target = SelectorSerializer() | ||
creator = CreatorSerializer() | ||
generator = GeneratorSerializer(required=False) | ||
|
||
class Meta: | ||
model = Annotation | ||
fields = '__all__' | ||
|
||
def create(self, validated_data): | ||
|
||
body_data = validated_data.pop('body') | ||
target_data = validated_data.pop('target') | ||
creator_data = validated_data.pop('creator') | ||
generator_data = validated_data.pop('generator', None) | ||
|
||
body = Body.objects.create(**body_data) | ||
target = Selector.objects.create(**target_data) | ||
creator = Creator.objects.create(**creator_data) | ||
|
||
generator = None | ||
if generator_data: | ||
generator = Generator.objects.create(**generator_data) | ||
|
||
annotation = Annotation.objects.create( | ||
body=body, | ||
target=target, | ||
creator=creator, | ||
generator=generator, | ||
**validated_data | ||
) | ||
return annotation | ||
|
||
def to_representation(self, instance): | ||
representation = super().to_representation(instance) | ||
|
||
representation['body'] = BodySerializer(instance.body).data | ||
representation['target'] = SelectorSerializer(instance.target).data | ||
representation['creator'] = CreatorSerializer(instance.creator).data | ||
if instance.generator: | ||
representation['generator'] = GeneratorSerializer(instance.generator).data | ||
|
||
return representation |
This file contains 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,80 @@ | ||
from django.test import TestCase | ||
from unittest.mock import patch, MagicMock | ||
from rest_framework.test import APIRequestFactory, APITestCase, force_authenticate | ||
from rest_framework import status | ||
from .models import * | ||
from .serializers import AnnotationSerializer | ||
from .views import AnnotationViewSet | ||
from django.contrib.auth.models import User | ||
from django.urls import reverse | ||
from django.conf import settings | ||
|
||
class AnnotationViewSetIntegrationTests(APITestCase): | ||
def setUp(self): | ||
self.user = User.objects.create_user(username='testuser', password='testpassword') | ||
|
||
# Create related instances for Annotation | ||
self.body = Body.objects.create( | ||
type="TextualBody", | ||
value="This info is misleading imo", | ||
format="text/plain", | ||
language="en" | ||
) | ||
self.target = Target.objects.create( | ||
type="TextPositionSelector", | ||
start=10, | ||
end=50, | ||
source="http://example.com/posts/1" | ||
) | ||
self.creator = Creator.objects.create( | ||
type="Person", | ||
name="john" | ||
) | ||
self.annotation = Annotation.objects.create( | ||
body=self.body, | ||
target=self.target, | ||
creator=self.creator | ||
) | ||
self.client.login(username='testuser', password='testpassword') | ||
|
||
def test_create_annotation(self): | ||
"""Test creating a new annotation.""" | ||
url = reverse('annotation-list') | ||
data = { | ||
"body": { | ||
"type": "TextualBody", | ||
"value": "This info is misleading imo", | ||
"format": "text/plain", | ||
"language": "en" | ||
}, | ||
"target": { | ||
"type": "TextPositionSelector", | ||
"start": 10, | ||
"end": 50, | ||
"source": "http://example.com/posts/1" | ||
}, | ||
"creator": { | ||
"type": "Person", | ||
"name": "john" | ||
} | ||
} | ||
response = self.client.post(url, data, format='json') | ||
|
||
self.assertEqual(response.status_code, status.HTTP_201_CREATED) | ||
self.assertEqual(response.data["target"]["source"], data["target"]["source"]) | ||
|
||
def test_get_annotations_by_source(self): | ||
"""Test retrieving annotations by source.""" | ||
url = reverse('annotation-get-by-posts', kwargs={"source": 1}) | ||
response = self.client.get(url) | ||
|
||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertEqual(response.data[0]["target"]["source"], self.target.source) | ||
|
||
def test_get_annotations_by_source_not_found(self): | ||
"""Test retrieving annotations by source when none are found.""" | ||
url = reverse('annotation-get-by-posts', kwargs={"source": 999}) | ||
response = self.client.get(url) | ||
|
||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) | ||
self.assertEqual(response.data['message'], "No annotations found for the specified source.") |
This file contains 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,10 @@ | ||
from django.urls import path, include | ||
from rest_framework.routers import DefaultRouter | ||
from .views import AnnotationViewSet | ||
|
||
router = DefaultRouter() | ||
router.register(r'annotations', AnnotationViewSet, basename='annotation') | ||
|
||
urlpatterns = [ | ||
path('', include(router.urls)), | ||
] |
This file contains 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,52 @@ | ||
from rest_framework import viewsets | ||
from .models import Annotation | ||
from .serializers import AnnotationSerializer | ||
from rest_framework.decorators import action | ||
from rest_framework.response import Response | ||
from django.conf import settings | ||
from rest_framework import viewsets, status, permissions, generics | ||
|
||
|
||
""" | ||
Example request: | ||
POST /api/annotations/ HTTP/1.1 | ||
Content-Type: application/json | ||
{ | ||
"body": { | ||
"type": "TextualBody", | ||
"value": "This info is misleading imo", | ||
"format": "text/plain", | ||
"language": "en" | ||
}, | ||
"target": { | ||
"type": "TextPositionSelector", | ||
"start": 10, | ||
"end": 50, | ||
"source": "http://159.223.28.163:30002/posts/1" | ||
}, | ||
"creator": { | ||
"type": "Person", | ||
"name": "john" | ||
} | ||
} | ||
""" | ||
class AnnotationViewSet(viewsets.ModelViewSet): | ||
queryset = Annotation.objects.all() | ||
serializer_class = AnnotationSerializer | ||
|
||
@action(detail=False, methods=['get'], url_path='source/(?P<source>[^/.]+)') | ||
def get_by_posts(self, request, source=None): | ||
|
||
if not source: | ||
return Response({"error": "The 'source' query parameter is required."}, status=status.HTTP_400_BAD_REQUEST) | ||
|
||
source_url = f"{settings.BACKEND_SERVICE_URL}/posts/{source}" | ||
|
||
annotations = Annotation.objects.filter(target__source=source_url) | ||
|
||
if not annotations.exists(): | ||
return Response({"message": "No annotations found for the specified source."}, status=status.HTTP_404_NOT_FOUND) | ||
|
||
serializer = AnnotationSerializer(annotations, many=True) | ||
return Response(serializer.data, status=status.HTTP_200_OK) |
Empty file.
This file contains 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,16 @@ | ||
""" | ||
ASGI config for annotations_project project. | ||
It exposes the ASGI callable as a module-level variable named ``application``. | ||
For more information on this file, see | ||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ | ||
""" | ||
|
||
import os | ||
|
||
from django.core.asgi import get_asgi_application | ||
|
||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "annotations_project.settings") | ||
|
||
application = get_asgi_application() |
Oops, something went wrong.