Skip to content

Commit

Permalink
Merge pull request #554 from bounswe/dev
Browse files Browse the repository at this point in the history
Merge dev into main
  • Loading branch information
rukiyeaslan authored Dec 16, 2024
2 parents cb6ee77 + 911d8d6 commit 240c0f0
Show file tree
Hide file tree
Showing 147 changed files with 19,965 additions and 12,567 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ venv
**/.env
*.icloud

/backend/env
/backend/staticfiles
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ The application is deployed on Digital Ocean. To access application frontend sim

Similarly to view deployed api
- API : 'http://159.223.28.163:30002/docs'

16 changes: 16 additions & 0 deletions annotations_project/Dockerfile
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.
3 changes: 3 additions & 0 deletions annotations_project/annotations/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions annotations_project/annotations/apps.py
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.
53 changes: 53 additions & 0 deletions annotations_project/annotations/models.py
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}"
72 changes: 72 additions & 0 deletions annotations_project/annotations/serializers.py
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
80 changes: 80 additions & 0 deletions annotations_project/annotations/tests.py
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.")
10 changes: 10 additions & 0 deletions annotations_project/annotations/urls.py
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)),
]
52 changes: 52 additions & 0 deletions annotations_project/annotations/views.py
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.
16 changes: 16 additions & 0 deletions annotations_project/annotations_project/asgi.py
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()
Loading

0 comments on commit 240c0f0

Please sign in to comment.