diff --git a/frog_api/serializers/model_serializers.py b/frog_api/serializers/model_serializers.py index 75d3d15..ae1b499 100644 --- a/frog_api/serializers/model_serializers.py +++ b/frog_api/serializers/model_serializers.py @@ -10,7 +10,7 @@ class VersionSerializer(serializers.HyperlinkedModelSerializer): class ProjectSerializer(serializers.HyperlinkedModelSerializer): - versions = VersionSerializer(many=True) + versions = VersionSerializer(many=True, read_only=True) class Meta: model = Project diff --git a/frog_api/serializers/request_serializers.py b/frog_api/serializers/request_serializers.py index 02f6726..3d1fb60 100644 --- a/frog_api/serializers/request_serializers.py +++ b/frog_api/serializers/request_serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from frog_api.models import AUTH_KEY_LEN +from frog_api.serializers.model_serializers import ProjectSerializer, VersionSerializer class ApiKeySerializer(serializers.CharField): @@ -7,6 +8,16 @@ class ApiKeySerializer(serializers.CharField): max_length = AUTH_KEY_LEN +class CreateProjectSerializer(serializers.Serializer): # type:ignore + api_key = ApiKeySerializer() + project = ProjectSerializer() + + +class CreateVersionSerializer(serializers.Serializer): # type:ignore + api_key = ApiKeySerializer() + version = VersionSerializer() + + # Classes for valdating requests to create new entries class CreateEntrySerializer(serializers.Serializer): # type:ignore timestamp = serializers.IntegerField() diff --git a/frog_api/tests.py b/frog_api/tests.py index ace65e1..5832271 100644 --- a/frog_api/tests.py +++ b/frog_api/tests.py @@ -27,7 +27,7 @@ class CreateCategoriesTests(APITestCase): version.save() response = self.client.post( - reverse("category-structure", args=[project.slug, version.slug]), + reverse("version-structure", args=[project.slug, version.slug]), create_json, format="json", ) diff --git a/frog_api/urls.py b/frog_api/urls.py index 8ea5d70..d9fdb7f 100644 --- a/frog_api/urls.py +++ b/frog_api/urls.py @@ -3,19 +3,26 @@ from frog_api.views import data, structure urlpatterns = [ # structure (/project) - path( - "projects/", - structure.ProjectStructureView.as_view(), - ), re_path( "projects/(?P.+)/(?P.+)/$", - structure.CategoryStructureView.as_view(), - name="category-structure", + structure.VersionStructureView.as_view(), + name="version-structure", + ), + re_path( + "projects/(?P.+)/$", + structure.ProjectStructureView.as_view(), + name="project-structure", + ), + path( + "projects/", + structure.RootStructureView.as_view(), + name="root-structure", ), # data (/data) re_path( "data/(?P.+)/(?P.+)/(?P.+)/$", data.CategoryDataView.as_view(), + name="category-data", ), re_path( "data/(?P.+)/(?P.+)/$", @@ -25,9 +32,11 @@ urlpatterns = [ re_path( "data/(?P.+)/$", data.ProjectDataView.as_view(), + name="project-data", ), path( "data/", data.RootDataView.as_view(), + name="root-data", ), ] diff --git a/frog_api/views/common.py b/frog_api/views/common.py index 3a0bedd..e7c7125 100644 --- a/frog_api/views/common.py +++ b/frog_api/views/common.py @@ -29,6 +29,13 @@ def get_category(slug: str, version: Version) -> Category: return ret +def validate_ultimate_api_key(key: str) -> bool: + if key == ULTIMATE_API_KEY: + return True + else: + raise InvalidAPIKeyException() + + def validate_api_key(key: str, project: Project) -> bool: if key == ULTIMATE_API_KEY or key == project.auth_key: return True diff --git a/frog_api/views/structure.py b/frog_api/views/structure.py index ba5942c..6a2edda 100644 --- a/frog_api/views/structure.py +++ b/frog_api/views/structure.py @@ -2,31 +2,93 @@ from typing import Any from django.db import models from frog_api.exceptions import AlreadyExistsException -from frog_api.models import Category, Project +from frog_api.models import Category, Project, Version from frog_api.serializers.model_serializers import ProjectSerializer -from frog_api.serializers.request_serializers import CreateCategoriesSerializer -from frog_api.views.common import get_project, get_version, validate_api_key +from frog_api.serializers.request_serializers import ( + CreateCategoriesSerializer, + CreateProjectSerializer, + CreateVersionSerializer, +) +from frog_api.views.common import ( + get_project, + get_version, + validate_api_key, + validate_ultimate_api_key, +) from rest_framework import status from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from frog_api.views.data import DEFAULT_CATEGORY_NAME, DEFAULT_CATEGORY_SLUG -class ProjectStructureView(APIView): + +class RootStructureView(APIView): """ - API endpoint that allows projects to be viewed. + API endpoint that allows the structure of the database to be viewed or edited. """ def get(self, request: Request) -> Response: """ - Return a list of all projects. + Return a digest of the database structure. """ projects = Project.objects.all() serializer = ProjectSerializer(projects, many=True) return Response(serializer.data) + def post(self, request: Request) -> Response: + """ + Create a new project. + """ + request_ser = CreateProjectSerializer(data=request.data) -class CategoryStructureView(APIView): + validate_ultimate_api_key(request_ser.data["api_key"]) + + if request_ser.is_valid(): + request_ser.project.save() + return Response(request_ser.project.data, status=status.HTTP_201_CREATED) + return Response(request_ser.errors, status=status.HTTP_400_BAD_REQUEST) + + +class ProjectStructureView(APIView): + """ + API endpoint for adding a new version + """ + + def post(self, request: Request, project_slug: str) -> Response: + request_ser = CreateVersionSerializer(data=request.data) + + project = get_project(project_slug) + + validate_api_key(request_ser.data["api_key"], project) + + if request_ser.is_valid(): + if Version.objects.filter( + slug=request_ser.data["version"]["slug"], project=project + ).exists(): + raise AlreadyExistsException( + f"Version with slug {request_ser.data['version']['slug']} already exists" + ) + + version = Version( + project=project, + slug=request_ser.data["version"]["slug"], + name=request_ser.data["version"]["name"], + ) + version.save() + + # Create the default category + default_cat = Category( + version=version, + slug=DEFAULT_CATEGORY_SLUG, + name=DEFAULT_CATEGORY_NAME, + ) + default_cat.save() + return Response(request_ser.version.data, status=status.HTTP_201_CREATED) + return Response(request_ser.errors, status=status.HTTP_400_BAD_REQUEST) + + +class VersionStructureView(APIView): """ API endpoint for adding new categories """ @@ -51,7 +113,7 @@ class CategoryStructureView(APIView): for cat, name in categories.items(): if Category.objects.filter(slug=cat, version=version).exists(): raise AlreadyExistsException( - f"Category {cat} already exists for project '{project_slug}', version '{version_slug}'" + f"Category '{cat}' already exists for project '{project_slug}', version '{version_slug}'" ) to_save.append(Category(version=version, slug=cat, name=name)) @@ -61,7 +123,7 @@ class CategoryStructureView(APIView): return len(to_save) def post(self, request: Request, project_slug: str, version_slug: str) -> Response: - result = CategoryStructureView.create_categories( + result = VersionStructureView.create_categories( request.data, project_slug, version_slug )