diff --git a/README.md b/README.md index 5482c4c..0619881 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ Django users have to be created in the CRUD backend, available at https://leeway The program regularly fetches incoming mails via IMAP and starts simulations from key-value-pairs in the mail subject or text body. The sender of the mail needs to have an associated account. Allowed keys via e-mail are: `longitude`, `latitude`, `object_type`, `radius`, `duration`, `start_time`. The separator between key and value is `=`. Key-value-pairs are separated by `;` in the subject and by new lines in the text body. The date format for start date is `YYYY-MM-DD HH:MM:SS`. +# API usage + +API documentation can be found at: https://leeway.tuerantuer.org/api/docs/ + +Authentication can be provided in two ways: +1. Via your session cookie, obtained from the normal login +2. Via an authentication token, can be obtained via [/api/auth/login/](https://leeway.tuerantuer.org/api/v1/docs/#/auth/auth_login_create) + # Installation **Prerequisite:** _Python 3.8 or later is required._ diff --git a/opendrift_leeway_webgui/api/__init__.py b/opendrift_leeway_webgui/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/opendrift_leeway_webgui/api/apps.py b/opendrift_leeway_webgui/api/apps.py new file mode 100644 index 0000000..beab57a --- /dev/null +++ b/opendrift_leeway_webgui/api/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ApiConfig(AppConfig): + """ + Application settings for the `api` app, + which is the app providing the REST API. + Inherits from `AppConfig`. + """ + + name = "opendrift_leeway_webgui.api" + verbose_name = _("API") diff --git a/opendrift_leeway_webgui/api/urls.py b/opendrift_leeway_webgui/api/urls.py new file mode 100644 index 0000000..1e5bdac --- /dev/null +++ b/opendrift_leeway_webgui/api/urls.py @@ -0,0 +1,13 @@ +""" +URL patterns for the Opendrift Leeway Webgui API +""" +from django.urls import include, path + +#: The namespace for this URL config (see :attr:`django.urls.ResolverMatch.app_name`) +app_name = "api" + +#: The url patterns of this module (see :doc:`django:topics/http/urls`) +urlpatterns = [ + path("", include("opendrift_leeway_webgui.api.v1.urls", namespace="default")), + path("v1/", include("opendrift_leeway_webgui.api.v1.urls", namespace="v1")), +] diff --git a/opendrift_leeway_webgui/api/v1/__init__.py b/opendrift_leeway_webgui/api/v1/__init__.py new file mode 100644 index 0000000..78bb3e7 --- /dev/null +++ b/opendrift_leeway_webgui/api/v1/__init__.py @@ -0,0 +1,3 @@ +""" +The first version of the Opendrift Leeway Webgui API. +""" diff --git a/opendrift_leeway_webgui/api/v1/serializers.py b/opendrift_leeway_webgui/api/v1/serializers.py new file mode 100644 index 0000000..08e096f --- /dev/null +++ b/opendrift_leeway_webgui/api/v1/serializers.py @@ -0,0 +1,35 @@ +from rest_framework import serializers + +from ...leeway.models import LeewaySimulation + + +class LeewaySimulationSerializer(serializers.ModelSerializer): + """ + Serializer for the Leeway Simulations. Inherits from + `serializers.ModelSerializer`. + """ + + #: Show username instead of id + username = serializers.ReadOnlyField(source="user.username") + + class Meta: + """ + Define model and the corresponding fields + """ + + #: The model class for this serializer + model = LeewaySimulation + + #: Exclude user field because the username is shown instead + exclude = ["user"] + + #: Define fields which are shown when retrieving simulations, + #: but cannot be set when creating new ones + read_only_fields = [ + "uuid", + "img", + "netcdf", + "traceback", + "simulation_started", + "simulation_finished", + ] diff --git a/opendrift_leeway_webgui/api/v1/urls.py b/opendrift_leeway_webgui/api/v1/urls.py new file mode 100644 index 0000000..5e9ad3a --- /dev/null +++ b/opendrift_leeway_webgui/api/v1/urls.py @@ -0,0 +1,27 @@ +""" +URL patterns for the first version of the Opendrift Leeway Webgui API +""" +from django.urls import include, path +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView +from rest_framework.routers import DefaultRouter + +from . import views + +#: The namespace for this URL config (see :attr:`django.urls.ResolverMatch.app_name`) +app_name = "v1" + +#: Router for dynamic url patterns +router = DefaultRouter() +router.register("simulations", views.LeewaySimulationViewSet, "simulations") + +#: The url patterns of this module (see :doc:`django:topics/http/urls`) +urlpatterns = [ + path("", include(router.urls)), + path("auth/", include("knox.urls")), + path("schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "docs/", + SpectacularSwaggerView.as_view(url_name="api:v1:schema"), + name="swagger-ui", + ), +] diff --git a/opendrift_leeway_webgui/api/v1/views.py b/opendrift_leeway_webgui/api/v1/views.py new file mode 100644 index 0000000..0945a78 --- /dev/null +++ b/opendrift_leeway_webgui/api/v1/views.py @@ -0,0 +1,38 @@ +from rest_framework import mixins, viewsets +from rest_framework.permissions import IsAuthenticated + +from .serializers import LeewaySimulationSerializer + + +# pylint: disable=too-many-ancestors +class LeewaySimulationViewSet( + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, +): + """ + A viewset for simulations of the authenticated user, with the option to + + - Create new simulations + - List all existing simulations + - Retrieve a single simulation record + """ + + #: Only enable this viewset for authenticated users + permission_classes = (IsAuthenticated,) + + #: The serializer to use for simulations + serializer_class = LeewaySimulationSerializer + + def get_queryset(self): + """ + Only return the simulations of the current user + """ + return self.request.user.leewaysimulation_set.all() + + def perform_create(self, serializer): + """ + Automatically set the user field on creation + """ + serializer.save(user=self.request.user) diff --git a/opendrift_leeway_webgui/core/settings.py b/opendrift_leeway_webgui/core/settings.py index 82ab3fe..e61ae87 100644 --- a/opendrift_leeway_webgui/core/settings.py +++ b/opendrift_leeway_webgui/core/settings.py @@ -60,6 +60,9 @@ "django.contrib.messages", "django.contrib.staticfiles", "opendrift_leeway_webgui.leeway", + "rest_framework", + "drf_spectacular", + "knox", ] #: Activated middlewares (see :setting:`django:MIDDLEWARE`) @@ -281,6 +284,36 @@ CELERY_RESULT_BACKEND = "redis://localhost:6379/0" +############################# +# DJANGO REST API FRAMEWORK # +############################# + +#: The configuration for django-rest-framework (drf) +REST_FRAMEWORK = { + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework.authentication.SessionAuthentication", + "knox.auth.TokenAuthentication", + ), + "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning", + "ALLOWED_VERSIONS": ("v1"), + "DEFAULT_VERSION": "default", +} + +#: The configuration for the API documentation by drf-spectacular +SPECTACULAR_SETTINGS = { + "TITLE": "Opendrift Leeway Webgui API", + "DESCRIPTION": "The API documentation for the Opendrift Leeway Webgui", + "VERSION": None, + "SCHEMA_PATH_PREFIX": "/api(/v[0-9])?", + "CONTACT": {"email": "tech@integreat-app.de"}, + "LICENSE": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, +} + + ########## # EMAILS # ########## diff --git a/opendrift_leeway_webgui/core/urls.py b/opendrift_leeway_webgui/core/urls.py index 8bf620d..76593e1 100644 --- a/opendrift_leeway_webgui/core/urls.py +++ b/opendrift_leeway_webgui/core/urls.py @@ -20,4 +20,5 @@ path("", include("opendrift_leeway_webgui.leeway.urls")), path("accounts/", include("django.contrib.auth.urls")), path("admin/", admin.site.urls), + path("api/", include("opendrift_leeway_webgui.api.urls", namespace="api")), ] diff --git a/pyproject.toml b/pyproject.toml index c9f0c0f..59250fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,10 @@ classifiers = [ dependencies = [ "celery", "django>=4.1", + "djangorestframework", + "django-rest-knox", "dms2dec", + "drf-spectacular", "redis", ]