From 614a22d45a541599eb66b14ae684d4cd6c65e0e8 Mon Sep 17 00:00:00 2001 From: Thomas Uher Date: Fri, 28 Oct 2022 12:54:06 +0200 Subject: [PATCH] add api tests, implement client_id --- docker/localcosmos_private/settings.py | 4 +- localcosmos_server/api/serializers.py | 112 +------ localcosmos_server/api/tests/test_views.py | 303 ++++++++++++++++++ localcosmos_server/api/urls.py | 6 + localcosmos_server/api/views.py | 125 ++++++-- .../locale/de/LC_MESSAGES/django.mo | Bin 25465 -> 25372 bytes .../locale/de/LC_MESSAGES/django.po | 130 ++++---- localcosmos_server/requirements.txt | 4 +- localcosmos_server/settings.py | 4 +- localcosmos_server/urls.py | 9 +- setup.py | 2 + 11 files changed, 481 insertions(+), 218 deletions(-) create mode 100644 localcosmos_server/api/tests/test_views.py diff --git a/docker/localcosmos_private/settings.py b/docker/localcosmos_private/settings.py index 6e0bd4a..8f3d65f 100644 --- a/docker/localcosmos_private/settings.py +++ b/docker/localcosmos_private/settings.py @@ -20,7 +20,7 @@ # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '-l4*f++k5$u!cr(o#-hio-d9hl)9b&nb37%_6v3l^w#20(rr!*' +SECRET_KEY = 'YOUR SECRET KEY' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -59,7 +59,7 @@ 'django_countries', 'corsheaders', 'rest_framework', - "drf_spectacular", + 'drf_spectacular', 'rest_framework_simplejwt', 'rest_framework_simplejwt.token_blacklist', diff --git a/localcosmos_server/api/serializers.py b/localcosmos_server/api/serializers.py index 432ad26..89cbd63 100644 --- a/localcosmos_server/api/serializers.py +++ b/localcosmos_server/api/serializers.py @@ -1,115 +1,18 @@ from rest_framework import serializers -from rest_framework.authtoken.models import Token from django.utils.translation import gettext_lazy as _ -from django.contrib.auth import authenticate, get_user_model - -from localcosmos_server.datasets.models import Dataset -from localcosmos_server.models import UserClients +from django.contrib.auth import get_user_model User = get_user_model() -import uuid - +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer -''' - AuthTokenSerializer - - [POST] requires username or email - - [POST] requires password, client_id and platform - - returns a token if auth was successful - - permissions if a user may perform an action on the tenant schema has to be done elsewhere. - Otherwise, one could login on his on project and authenticate with this token on any other project -''' -from rest_framework.authtoken.serializers import AuthTokenSerializer -class LCAuthTokenSerializer(AuthTokenSerializer): +class TokenObtainPairSerializerWithClientID(TokenObtainPairSerializer): - # optional for linking client_ids with users + # required for linking client_ids with users client_id = serializers.CharField() platform = serializers.CharField() - - def update_datasets(self, user, client): - # client is present now - # update all Datasets with the user - # this is necessary: if the user has done anonymous uploads - client_datasets = Dataset.objects.filter(client_id=client.client_id, user__isnull=True) - - for dataset in client_datasets: - dataset.update(user=user) - - - # this uses email or username - # todo: subclass this into the simplejwt: - # see: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/customizing_token_claims.html - def validate(self, attrs): - username = attrs.get('username') - password = attrs.get('password') - - client_id = attrs.get('client_id') - platform = attrs.get('platform') - - if username and password and client_id and platform: - - # determine if the user exists, username can be the username or the email address - unauthorized_user = User.objects.filter(username=username).first() - - if not unauthorized_user: - unauthorized_user = User.objects.filter(email=username).first() - - if not unauthorized_user: - raise serializers.ValidationError(_('No user found for that username or email address.')) - - # set the correct username - username = unauthorized_user.username - - - # AUTHENTICATE THE USER - user = authenticate(request=self.context.get('request'), username=username, password=password) - - # The authenticate call simply returns None for is_active=False - # users. (Assuming the default ModelBackend authentication - # backend.) - if not user: - msg = _('Unable to log in with provided credentials.') - raise serializers.ValidationError(msg, code='authorization') - - # user is authenticated now - # one client can be used by multiple users - # but only one client_id per browser - # if a browser client_id exists, the user will receive it from the server - if platform == 'browser': - client = UserClients.objects.filter(user=user, platform='browser').first() - - if not client: - # create a new browser client uuid for this user - client_id = uuid.uuid4() - - else: - # check if the non-browser client is linked to user - client = UserClients.objects.filter(user=user, client_id=client_id).first() - - - # if no client link is present, create one - if not client: - client, created = UserClients.objects.get_or_create( - user = user, - client_id = client_id, - platform = platform, - ) - - # update datasets - self.update_datasets(user, client) - - - else: - msg = _('Must include "username", "password", "client_id" and "platform".') - raise serializers.ValidationError(msg, code='authorization') - - - attrs['user'] = user - return attrs - - ''' private user serializer: only accessible for the account owner - details JSONField is still missing @@ -175,13 +78,6 @@ def create(self, validated_data): user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'], **extra_fields) - user_client = UserClients( - user=user, - platform=validated_data['platform'], - client_id=validated_data['client_id'], - ) - - user_client.save() return user diff --git a/localcosmos_server/api/tests/test_views.py b/localcosmos_server/api/tests/test_views.py new file mode 100644 index 0000000..6159184 --- /dev/null +++ b/localcosmos_server/api/tests/test_views.py @@ -0,0 +1,303 @@ +from django.test import TestCase + +from localcosmos_server.tests.common import (test_settings, test_settings_commercial, + TEST_DATASET_DATA_WITH_ALL_REFERENCE_FIELDS, TEST_CLIENT_ID) + +from localcosmos_server.tests.mixins import WithUser, WithDataset, WithApp + +from rest_framework.test import APIRequestFactory, APIClient + +from localcosmos_server.api.views import APIHome, RegisterAccount, TokenObtainPairViewWithClientID + +from localcosmos_server.datasets.models import Dataset + +from localcosmos_server.models import UserClients +from django.contrib.auth import get_user_model + +User = get_user_model() + +import uuid + +class TestAPIHome(TestCase): + + @test_settings + def test_get(self): + + factory = APIRequestFactory() + request = factory.get('/api/') + response = APIHome.as_view()(request) + self.assertEqual(response.status_code, 200) + + json_request = factory.get('/api/?format=json') + json_response = APIHome.as_view()(json_request) + self.assertEqual(json_response.data, {'success':True}) + self.assertEqual(json_response.status_code, 200) + + +class TestRegisterAccount(WithApp, WithDataset, TestCase): + + test_client_id = TEST_CLIENT_ID + test_password = 'dfbvrthGF%/()' + test_email = 'test@test.it' + + + def get_post_data(self): + + post_data = { + 'username' : 'TestUser', + 'password' : self.test_password, + 'password2' : self.test_password, + 'first_name' : 'Test first name', + 'last_name' : 'Test last name', + 'email' : self.test_email, + 'email2' : self.test_email, + 'client_id' : self.test_client_id, + 'platform' : 'browser', + 'app_uuid' : str(uuid.uuid4()), + } + + return post_data + + + @test_settings + def test_post(self): + + post_data = self.get_post_data() + + factory = APIRequestFactory() + request = factory.post('/api/user/register/', post_data, format='json') + + json_response = RegisterAccount.as_view()(request) + self.assertEqual(json_response.status_code, 200) + + self.assertEqual(json_response.data['success'], True) + + for field, value in json_response.data['user'].items(): + if field in post_data: + self.assertEqual(value, post_data[field]) + + # check if UserClients entry has been made + user = User.objects.get(pk=json_response.data['user']['id']) + client = UserClients.objects.get(user=user) + self.assertEqual(client.client_id, post_data['client_id']) + self.assertEqual(client.platform, post_data['platform']) + + + @test_settings + def test_post_with_existing_anonymous_dataset(self): + + dataset = self.create_dataset() + + self.assertEqual(dataset.user, None) + + post_data = self.get_post_data() + + factory = APIRequestFactory() + request = factory.post('/api/user/register/', post_data, format='json') + + json_response = RegisterAccount.as_view()(request) + self.assertEqual(json_response.status_code, 200) + + self.assertEqual(json_response.data['success'], True) + + user = User.objects.get(pk=json_response.data['user']['id']) + + dataset.refresh_from_db() + + self.assertEqual(dataset.user, user) + + +class TestTokenObtainPairViewWithClientID(WithApp, WithDataset, WithUser, TestCase): + + test_client_id = TEST_CLIENT_ID + + def get_user_client(self, user): + client = UserClients.objects.filter(user=user).first() + return client + + def get_post_data(self): + + post_data = { + 'client_id': TEST_CLIENT_ID, + 'platform': 'browser', + 'username': self.test_username, + 'password': self.test_password, + } + + return post_data + + @test_settings + def test_post(self): + + user = self.create_user() + + client = self.get_user_client(user) + self.assertEqual(client, None) + + post_data = self.get_post_data() + + factory = APIRequestFactory() + request = factory.post('/api/token/', post_data, format='json') + + response = TokenObtainPairViewWithClientID.as_view()(request) + self.assertEqual(response.status_code, 200) + + client = self.get_user_client(user) + self.assertEqual(client.platform, post_data['platform']) + self.assertEqual(client.user, user) + self.assertEqual(client.client_id, post_data['client_id']) + + @test_settings + def test_with_anonymous_dataset(self): + + dataset = self.create_dataset() + self.assertEqual(dataset.user, None) + + user = self.create_user() + + client = self.get_user_client(user) + self.assertEqual(client, None) + + post_data = self.get_post_data() + + factory = APIRequestFactory() + request = factory.post('/api/token/', post_data, format='json') + + response = TokenObtainPairViewWithClientID.as_view()(request) + self.assertEqual(response.status_code, 200) + + dataset.refresh_from_db() + + self.assertEqual(dataset.user, user) + + +class GetJWTokenMixin: + + def get_jw_token(self, username, password): + + post_data = { + 'client_id': TEST_CLIENT_ID, + 'platform': 'browser', + 'username': username, + 'password': password, + } + + factory = APIRequestFactory() + request = factory.post('/api/token/', post_data, format='json') + + response = TokenObtainPairViewWithClientID.as_view()(request) + + return response.data + + +class TestManageAccount(GetJWTokenMixin, WithUser, WithApp, TestCase): + + def get_authenticated_client(self, username, password): + + token_pair = self.get_jw_token(username, password) + + access_token = token_pair['access'] + + auth_header_value = 'Bearer {0}'.format(access_token) + + authed_client = APIClient() + authed_client.credentials(HTTP_AUTHORIZATION=auth_header_value) + + return authed_client + + + + @test_settings + def test_get(self): + + user = self.create_user() + superuser = self.create_superuser() + + client = APIClient() + response = client.get('/api/user/manage/?format=json') + + # raises 401 unauthorized + self.assertEqual(response.status_code, 401) + + authed_client = self.get_authenticated_client(user.username, self.test_password) + + authed_response = authed_client.get('/api/user/manage/?format=json') + + self.assertEqual(authed_response.status_code, 200) + + for key, value in authed_response.data['user'].items(): + + user_value = getattr(user, key) + + if key == 'uuid': + user_value = str(user_value) + self.assertEqual(value, user_value) + + + @test_settings + def test_put(self): + + user = self.create_user() + superuser = self.create_superuser() + + put_data = { + 'username' : 'new_username', + 'first_name' : 'new first name', + 'last_name' : 'new last name', + 'email' : 'new@mail.email', + } + + client = APIClient() + response = client.put('/api/user/manage/', put_data, format='json') + + # raises 401 unauthorized + self.assertEqual(response.status_code, 401) + + authed_client = self.get_authenticated_client(user.username, self.test_password) + + authed_response = authed_client.put('/api/user/manage/', put_data, format='json') + + #print(authed_response.data) + + self.assertEqual(authed_response.status_code, 200) + self.assertTrue(authed_response.data['success']) + + + for key, value in authed_response.data['user'].items(): + + if key in put_data: + self.assertEqual(value, put_data[key]) + + @test_settings + def test_put_400(self): + + user = self.create_user() + superuser = self.create_superuser() + + put_data = { + 'username' : 'new username', + } + + + authed_client = self.get_authenticated_client(user.username, self.test_password) + + authed_response = authed_client.put('/api/user/manage/', put_data, format='json') + + #print(authed_response.data) + + self.assertEqual(authed_response.status_code, 400) + + self.assertFalse(authed_response.data['success']) + self.assertIn('errors', authed_response.data) + + + +class TestPasswordResetRequest(TestCase): + + @test_settings + def test_get(self): + pass + + @test_settings + def test_post(self): + pass \ No newline at end of file diff --git a/localcosmos_server/api/urls.py b/localcosmos_server/api/urls.py index b34feb7..10ae2df 100644 --- a/localcosmos_server/api/urls.py +++ b/localcosmos_server/api/urls.py @@ -1,6 +1,8 @@ from django.urls import include, path from rest_framework.urlpatterns import format_suffix_patterns +from rest_framework_simplejwt.views import TokenRefreshView, TokenBlacklistView + from . import views urlpatterns = [ @@ -10,6 +12,10 @@ path('user/delete/', views.DeleteAccount.as_view()), path('user/register/', views.RegisterAccount.as_view()), path('password/reset/', views.PasswordResetRequest.as_view()), + # JSON WebToken + path('token/', views.TokenObtainPairViewWithClientID.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('token/blacklist/', TokenBlacklistView.as_view(), name='token_blacklist'), # app specific path('app//', views.AppAPIHome.as_view(), name='app_api_home'), ] diff --git a/localcosmos_server/api/views.py b/localcosmos_server/api/views.py index 03f5a37..870c0b8 100644 --- a/localcosmos_server/api/views.py +++ b/localcosmos_server/api/views.py @@ -8,7 +8,6 @@ # ################################################################################################################### from django.contrib.auth import logout -from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.views import APIView @@ -16,16 +15,22 @@ from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer from drf_spectacular.utils import inline_serializer, extend_schema from rest_framework_simplejwt.authentication import JWTAuthentication +from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from rest_framework import status from localcosmos_server.models import App from django_road.permissions import IsAuthenticatedOnly, OwnerOnly -from .serializers import AccountSerializer, RegistrationSerializer, PasswordResetSerializer +from .serializers import (AccountSerializer, RegistrationSerializer, PasswordResetSerializer, + TokenObtainPairSerializerWithClientID) + from localcosmos_server.mails import send_registration_confirmation_email -import os, json +from localcosmos_server.datasets.models import Dataset +from localcosmos_server.models import UserClients + +import uuid ################################################################################################################## @@ -52,7 +57,43 @@ class APIDocumentation(APIView): pass -class RegisterAccount(APIView): +class ManageUserClient: + + def update_datasets(self, user, client): + # update datasets if the user has done anonymous uploads and then registers + # assign datasets with no user and the given client_id to the now known user + # this is only valid for android and iOS installations, not browser views + + client_datasets = Dataset.objects.filter(client_id=client.client_id, user__isnull=True) + + for dataset in client_datasets: + dataset.user = user + dataset.save() + + + def get_client(self, user, platform, client_id): + + if platform == 'browser': + # only one client_id per user and browser + client = UserClients.objects.filter(user=user, platform='browser').first() + + else: + # check if the non-browser client is linked to user + client = UserClients.objects.filter(user=user, client_id=client_id).first() + + + # if no client link is present, create one + if not client: + client, created = UserClients.objects.get_or_create( + user = user, + client_id = client_id, + platform = platform, + ) + + return client + + +class RegisterAccount(ManageUserClient, APIView): """ User Account Registration, App specific """ @@ -61,33 +102,27 @@ class RegisterAccount(APIView): renderer_classes = (JSONRenderer,) serializer_class = RegistrationSerializer - - def get(self, request, *args, **kwargs): - serializer = self.serializer_class() - serializer.lc_initial = { - 'client_id': request.GET['client_id'], - 'platform' : request.GET['platform'], - 'app_uuid' : request.GET['app_uuid'], - } - serializer_context = { - 'serializer': serializer, - 'request':request, - } - return Response(serializer_context) - - - # this is for creating only def post(self, request, *args, **kwargs): serializer_context = { 'request': request } serializer = self.serializer_class(data=request.data, context=serializer_context) - context = { 'success' : False, } + context = { + 'success' : False, + } if serializer.is_valid(): app_uuid = serializer.validated_data['app_uuid'] user = serializer.save() + + # create the client + platform = serializer.validated_data['platform'] + client_id = serializer.validated_data['client_id'] + client = self.get_client(user, platform, client_id) + # update datasets + self.update_datasets(user, client) + request.user = user context['user'] = AccountSerializer(user).data context['success'] = True @@ -113,8 +148,8 @@ class ManageAccount(APIView): Manage Account - authenticated users only - owner only - - [GET] delivers the form html to the client - - [POST] validates and saves - and returns html + - [GET] delivers the account as json to the client + - [POST] validates and saves - and returns json ''' permission_classes = (IsAuthenticatedOnly, OwnerOnly) @@ -139,21 +174,18 @@ def put(self, request, *args, **kwargs): serializer_context = { 'request': request } serializer = self.serializer_class(data=request.data, instance=request.user, context=serializer_context) - context = { - 'user': request.user, + context = { 'success' : False, - 'request' : request, } if serializer.is_valid(): serializer.save() - context['user'] = request.user + context['success'] = True + context['user'] = serializer.data else: - context['success'] = False - context['serializer'] = serializer + context['errors'] = serializer.errors return Response(context, status=status.HTTP_400_BAD_REQUEST) - context['serializer'] = serializer return Response(context) @@ -249,8 +281,37 @@ def post(self, request, *args, **kwargs): return Response(context, status=status.HTTP_400_BAD_REQUEST) context['serializer'] = serializer - return Response(context) - + return Response(context) + + +from rest_framework_simplejwt.views import TokenObtainPairView +class TokenObtainPairViewWithClientID(ManageUserClient, TokenObtainPairView): + + serializer_class = TokenObtainPairSerializerWithClientID + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + + try: + serializer.is_valid(raise_exception=True) + except TokenError as e: + raise InvalidToken(e.args[0]) + + # serializer.user is available + # user is authenticated now, and serializer.user is available + # client_ids make sense for android and iOS, but not for browser + # if a browser client_id exists, use the existing browser client_id, otherwise create one + # only one browser client_id per user + platform = request.data['platform'] + client_id = request.data['client_id'] + + client = self.get_client(serializer.user, platform, client_id) + + # update datasets + self.update_datasets(serializer.user, client) + + return Response(serializer.validated_data, status=status.HTTP_200_OK) + ################################################################################################################## # diff --git a/localcosmos_server/locale/de/LC_MESSAGES/django.mo b/localcosmos_server/locale/de/LC_MESSAGES/django.mo index d983d2a9dc79378f26506efaa1c010163b1aed4c..9d6ca73f77a2419387d2f6ed5f0158677e416828 100644 GIT binary patch delta 3711 zcmZwHe{2-T6~OU1^V1kGF>#2Ejn`m+#`f0dHQe5D zEef0pjRI8?Rcxh1ge0Xhs#1bdaw(~zq)BlEqADV7RY;LiOI4$!Zd0Wak&>!y`hB-+ z<4V>2@tK*Moq6xg?0$94ckY_+r)x5k0i~3)QK>Ba9OvP`un^~LQYsIZ<3qR=AI1RA z$2b<_X)M8uI2}L0ZFmO*Xlzz$HXg%l{2orhvzt{?&86`Y1DSZq^G%cueu-P~_gICw zHEtjcC;_+QP3%Q^ehrglqZ*V%cHnXh;4D0a@|=T@;mcc+N-d-@#=v5nUh76yj?F$c z#hHvBuX8K$6iNo?F$XWBZ1_G(fWN^C{2I%!qF$*L*owt?!gCmx(!Z9Zv4h5EI0x4^ zC{>P+qC|WcC6gymO8bo0{{c!M7f_DqXDCN=3uV1eQ7XH}? zRF`SwGH?|izz>nrP-A!;D;`y<2wy=7^ajdV-^D@vv)A9ljgyQ6xBv&xz>`>l!&rzn zJU_uCKQ(Q;o6(afC4U;H;d3aZe9;^K3ue;4hjMxTgA&+wI+B2iQo$rj#m;!+KSnwG zcd!gcaShI7@jUidn`v-dstx4~UqD&t4a~ypD3SjTrG)=Lc^&8ObT3gUN`EKH9qK_T z`C(j+!zh*cz;hI5(f>OpCDX5HOvkTLHlDsqDGT%P4LpT2adVTKX(LKT0hGXwqa4jS zlmIW`4Ez-Bo!clIXYF$X-H5B{AHy@4yi8*i4gY@kj|5iHzlgHH zACRxBn%m~~ccCnJ0wu%OaS{F;SKu9-i_;%>*ULq@gxhgG#!>!1jU=8_&(n~IQYdHt z2|k2h-~yaYSsum`?8MC|6?*|?-W8M*-^7b}8|5;eXjiHOPvcJf*z2!iP(D=8qF>&B zheiagUb}DO68sLz zTXX?s{56z$Z(;$yhsoJAKB6I+jiH=rcF-+RE=pkeC}+0`=V3GEVi4tWCQ#1wEXsPX zploynCE$0l6mOwy`~}LqoRI2GM_3ecBQ3{;{IS||H|EhF@cK`pWPBbM;kzhj{YTG_ zQ8N7-=Hoq-jj~gjIV0u1aO&2^morv|<3?DUma#`4F?GTShU}hxvuD7L8TL>l64Nx8 zazsb%cu&8!DxDW=8l6vT3TO8QBN5xswh=a?shlmxeZH{c)RtuW$87foX&f81GEcXge~lj&tk%UC!xWb1A#IH=2QBVk4dB4$s&5gEBcAlllZ z+Qw%)x%EqCPIT((i|=;^>X%iF|6`O+T1H#3ULCcxWlUb^7xnqo|984n!j6%csiXh< zOe(V>*Ow6tJInmvpJRrMxD{i9%y(}1mo1gMchFPP>QqjGEe z1`0XA6WY*KgF!Q5=xG1Qa3p5ODviOo?UuwQdx{;?k%+7l4%$>W?!3RXq{WNGu%zQ)wSuY z+_tV*=ITMCO^0k9CIr)`L)ux{ zROW4yK?I4W!xN{^{tjLylboWo0@VN{-8B5Q8UhnOtevV=#UZ8Ry=kzmTK7Z zmM`Vo`>bz1F?8#phPE?bB6boOp)7sbKg&V>5`?6w$06Oo;gtE7Vw<|1MgPg@33FoB_{|tZJ2vJFAtfOY4 zy)uwj?2YsG_31$!3G0|*$bF66az_Ho<%x8dKMYh&v~LHNtn$VUi+sEbM$Wcz|1NVt zO!5jZ@l|&&k_?(VmwDIoSZ7h?_!UeScH%tJ$KlpbTj_PuLHxRN!$c6-T`MNF7dV|= z57dkY#cq_Bw_@b&NX+EjFzPuJxwurus5eR0mOIm@6C=a@5s6d+e66b}M{X(a2=B#U z`pz?ax2v!^EEiwPMU|5c#(Sk}b9E=>>%Ix@QpMe+lRio<-DU0(J<*-lNs!}3mc72I zxg$ojdwHP+iY*1SL!i=MP<~jbrTnOr{^0iZwD+vHr*hA| zR2k!nF-GPP@x;stW@c_~Q@|~EgGq?fT_x;~j>S0COt!$XWiFX+Cc`low(oP!DJ|2a z>F0gld(Qj5&+|U-IetFv`uVgUS7s$Tl~PWXQrY+^=Hd-3!YTJCm5+;YCa%X>*oo6| z2v4Y%Vv7)E)&iic&P29!Wruna>u5s#p}_Z;4fFV!TJDxmQX2Ik?UT5o4daa)>F zl{kU%Lv>yzo<@n_1WvHX~AW95DPJhlCj?) z>n7A?8Z#L97^mX6_1>A!!~^tq;2iud%0~Z*a@Lttn2ot8{YOzEeiHNWdDQqbEWt}y zgrEE7Z{?UO>sr zWz4~kP$s;Fve9YH-o^@0GPDw9e7CQOl7VP5`IjHQ?hibMlG>ww|A#0=nZ$YcHhu;F zg5?<4qEsC=qGaMQ%7^O}l+6ADW&XEu3H};oqc`vloSkUlAB_gwiMvsX<1ESopP(#U zxYhfl*5HfuAIF7QzRi1X8_N3!u?l~Om6*5PTX+*njqOJX;0=5l6W3_mN#oEC?l_*q z#rSuW2?DK3t;Ve={i7(6oJWc98V2w`Sb+uidqs9H%6vOfitsQ_$JhMdhmpV&>O&gx z;AbdjUq%>m1S>HQn{gI~F^C6IGWJK5=Wd{+ct)F2r!at0%)`i^dY6A(0JUTn*~R@R zAF9jPAou?&4b4DlySKxQxRQPcN~*qv62UM^FH~1dsY81N~)j1DR>Ms@D(h=S5Y!BjB@QR zVKM$5b|yzg)rW{^Ubuv_E2o!g_uw>vRv=Mwb;FFc#T5k~+xZJV2>M#GimKp(~NFEs<5;G)?<+>+m z+j3IQ^9?1HvIm|vW4gzPy5}cP>!{IZ+Oa6{#B?~wGZlJkC>)G?oBz0BrF(c~>&o5g z*tPCR+HCLiy|w&BOmfNYoPV!hUPN@pfNnLqZ6nIj>Ha~h*X)bS64b+%4Hw7d+{}k$ zV&Ufc4B4b^%3mhyt9mmQGj!k0hk|jUXmvvi8)nSzi>A(5l3jWG)aJsH(MR`@%7I|S zEndrrQ-#gsU}!KJbE-F&jVzPFGRdl@J?ZHi!^u<4^V2Gs$uD-b!!)D@*9NWr(G2Ri zMKp%4d*A`h%kkJgX2@(go932%@{O%erp=Hu3J-?DeMZDRdpDa-aC^yk?4FBAk>@UC9WD-FpLrBUpaM!D|wE}LoTx=?74>e9Wb zb5SWp+o^5q&e%wXUTQ0yD0>@L#bbJj_Tn+UAGHUT$&(|+O1`~hQXr|*y}evS zx$IJ*-NxQQuRfi>?k;e0JF-VEg6^0zc}%g(={I)dk6ER^W7U}6m5w|o)sc70Na>hf zZfAj8tG2E;3D4HlS)VbK`d&B^t5A};^__Ea9L#{dnF4fROZRydE z!pJ3Z-+!xf{w<%pPdiK96A5(XyPw#RT)G$Wt43^JOb0DD*W3a&FjA7Tm)@?x47|B6 dpRpco1w&!y*{&tCzVOT5LHBwjuXI&q{0|<7Ii&yq diff --git a/localcosmos_server/locale/de/LC_MESSAGES/django.po b/localcosmos_server/locale/de/LC_MESSAGES/django.po index 90d2236..41024ef 100644 --- a/localcosmos_server/locale/de/LC_MESSAGES/django.po +++ b/localcosmos_server/locale/de/LC_MESSAGES/django.po @@ -189,7 +189,7 @@ msgstr "Noch keine Nutzer." #: datasets/forms.py:52 msgid "This step already exists in your Validation Routine" -msgstr "Dieser Schritt existiert bereits in ihrer Validierungsroutine" +msgstr "Dieser Schritt existiert bereits in deiner Validierungsroutine" #: datasets/models.py:347 datasets/tests/test_models.py:428 msgid "Unidentified" @@ -235,9 +235,9 @@ msgid "" "and have him check the datasets photo." msgstr "" "Das Erstellen einer Validierungsroutine für Beobachtungen kann dabei helfen, " -"die Qualität ihrer gesammelten Daten zu verbessern. Sie können " -"Validierungsschritte optional auf bestimmte Taxa becshränken. Zum Beispiel " -"möchten Sie vielleicht, dass ein Experte sich Beobachtungen seltener Arten " +"die Qualität der gesammelten Daten zu verbessern. Du kannst " +"Validierungsschritte optional auf bestimmte Taxa beschränken. Zum Beispiel " +"möchtest Du vielleicht, dass ein Experte sich Beobachtungen seltener Arten " "anschaut und das Foto des Datensatzes prüft." #: datasets/templates/datasets/dataset_validation_routine.html:18 @@ -349,7 +349,7 @@ msgstr "Beobachtung würde gelöscht." #: datasets/templates/datasets/validation/ajax/delete_dataset_image.html:6 #: datasets/views.py:299 msgid "Do you really want to delete this image?" -msgstr "Wollen Sie dieses Bild wirklich löschen?" +msgstr "Willst du dieses Bild wirklich löschen?" #: datasets/templates/datasets/validation/ajax/large_modal_image.html:4 msgid "Image" @@ -366,15 +366,15 @@ msgstr "Expertenreview" #: datasets/templates/datasets/validation/expert_review.html:16 msgid "Thank you for your review" -msgstr "Vielen Dank für ihr Review" +msgstr "Vielen Dank für dein Review" #: datasets/templates/datasets/validation/expert_review.html:18 msgid "You marked this observation as valid." -msgstr "Sie haben diese Beobachtung als valide markiert." +msgstr "Du hast diese Beobachtung als valide markiert." #: datasets/templates/datasets/validation/expert_review.html:20 msgid "You marked this observation as invalid." -msgstr "Sie haben diese Beobachtung als nicht valide markiert." +msgstr "Du hast diese Beobachtung als nicht valide markiert." #: datasets/templates/datasets/validation/expert_review.html:24 msgid "Back to overview" @@ -382,7 +382,7 @@ msgstr "zurück zur Übersicht" #: datasets/templates/datasets/validation/expert_review.html:47 msgid "Your review" -msgstr "Ihr Review" +msgstr "Dein Review" #: datasets/templates/datasets/validation/expert_review.html:51 msgid "" @@ -390,9 +390,9 @@ msgid "" "contains errors. If you have been able to correct the data, mark the " "observation as valid." msgstr "" -"Bevor Sie ihr Review abschicken sollten Sie versuchen die Beobachtung zu " -"korrigieren, falls diese Fehler enthält. Markieren Sie die Beobachtung als " -"valide, alls es ihnen möglich war, die Daten zu korrigieren." +"Bevor du dein Review abschickst solltest du versuchen, die Beobachtung zu " +"korrigieren, falls diese Fehler enthält. Markiere die Beobachtung als " +"valide, falls es dir möglich war, die Daten zu korrigieren." #: datasets/templates/datasets/validation/expert_review.html:61 msgid "Submit review" @@ -400,11 +400,11 @@ msgstr "Review abschicken" #: datasets/templates/datasets/widgets/picture_widget.html:15 msgid "In your app this button opens the camera." -msgstr "In ihrer App öffnet dieser Button die Kamera." +msgstr "In deiner App öffnet dieser Button die Kamera." #: datasets/templates/datasets/widgets/picture_widget.html:18 msgid "In your app this button opens the photo album." -msgstr "In ihrer App öffnet dieser Button das Fotoalbum." +msgstr "In deiner App öffnet dieser Button das Fotoalbum." #: datasets/templates/datasets/widgets/select_datetime_widget.html:13 msgid "Select timestamp" @@ -486,11 +486,11 @@ msgstr "" #: datasets/views.py:278 #, python-brace-format msgid "Do you really want to remove {0}?" -msgstr "Wollen Sie wirklich {0} entfernen?" +msgstr "Willst du wirklich {0} entfernen?" #: datasets/views.py:289 msgid "Do you really want to delete this obsersavtion?" -msgstr "Wollen Sie wirklich diese Beobachtung löschen?" +msgstr "Willst du wirklich diese Beobachtung löschen?" #: forms.py:40 msgid "Username or email address" @@ -502,7 +502,7 @@ msgstr "Nutername oder e-mail ungültig" #: forms.py:158 msgid "You selected an invalid area of the image." -msgstr "Sie haben einen ungültigen Bereich ihres Bildes ausgewählt." +msgstr "Du hast einen ungültigen Bereich des Bildes ausgewählt." #: forms.py:179 msgid "The image upload was not successful. Please try again." @@ -638,7 +638,7 @@ msgstr "Veröffentlichung von %(template_content)s erfolgreich aufgehoben" #, python-format msgid "Do you really want to unpublish %(template_content)s?" msgstr "" -"Wollen Sie die Veröffentlichung von %(template_content)s wirklich aufheben?" +"Willst du die Veröffentlichung von %(template_content)s wirklich aufheben?" #: online_content/templates/online_content/ajax/unpublish_template_content.html:21 #: online_content/templates/online_content/template_content_list_entry.html:83 @@ -660,7 +660,7 @@ msgstr "Inhalt löschen" #: online_content/templates/online_content/delete_microcontent.html:29 msgid "Do you really want to delete this content?" -msgstr "Wollen Sie diesen Inhalt wirklich löschen?" +msgstr "Willst du diesen Inhalt wirklich löschen?" #: online_content/templates/online_content/filecontent_field.html:16 #: templates/localcosmos_server/widgets/app_theme_image_file_input.html:15 @@ -697,11 +697,11 @@ msgstr "nicht für die Übersetzung bereit" #: online_content/templates/online_content/manage_template_content.html:40 msgid "You marked the content in this language as ready for translation." -msgstr "Sie haben diesen Inhalt als für die Übersetzung bereit markiert." +msgstr "Du hast diesen Inhalt als für die Übersetzung bereit markiert." #: online_content/templates/online_content/manage_template_content.html:51 msgid "Successfully saved your content as draft." -msgstr "Ihr Inhalt wurde erfolgreich als Entwurf gespeichert." +msgstr "Der Inhalt wurde erfolgreich als Entwurf gespeichert." #: online_content/templates/online_content/manage_template_content.html:63 #: online_content/templates/online_content/template_content_list_entry.html:53 @@ -762,7 +762,7 @@ msgstr "Seiten" #: online_content/templates/online_content/online_content_base.html:36 msgid "You do not have any online pages for this app yet." -msgstr "Sie haben noch keine Seiten für diese App." +msgstr "Du hast noch keine Seiten für diese App." #: online_content/templates/online_content/online_content_base.html:41 msgid "Add new page" @@ -774,7 +774,7 @@ msgstr "Features" #: online_content/templates/online_content/online_content_base.html:55 msgid "You do not have any online feature content for this app yet." -msgstr "Sie haben noch keinen Feature Content für diese App." +msgstr "Du hast noch keinen Feature Content für diese App." #: online_content/templates/online_content/online_content_base.html:60 msgid "Add new feature" @@ -858,7 +858,7 @@ msgid "" "Absolute URL where your app will be served according to your web server " "configuration." msgstr "" -"Absolute URL unter der die App gemäß der Konfiguration ihres Webservers " +"Absolute URL unter der die App gemäß der Konfiguration des Webservers " "bereitgestellt wird." #: server_control_panel/forms.py:13 @@ -908,7 +908,7 @@ msgid "" "Use this url in the app kit on localcosmos.org to connect your app to this " "server." msgstr "" -"Benutzen Sie diese url im App Kit auf localcosmos.org um ihre App mit diesem " +"Benutze diese url im App Kit auf localcosmos.org um deine App mit diesem " "Server zu verbinden." #: server_control_panel/templates/server_control_panel/home.html:24 @@ -968,7 +968,7 @@ msgstr "" #: server_control_panel/templates/server_control_panel/home.html:107 msgid "You have not installed any apps yet." -msgstr "Sie haben noch keine Apps installiert." +msgstr "Du hast noch keine Apps installiert." #: server_control_panel/templates/server_control_panel/home.html:114 #: server_control_panel/templates/server_control_panel/install_app.html:8 @@ -985,7 +985,7 @@ msgid "" "Your app has been installed successfully. You can check its status in the " "overview." msgstr "" -"Ihre App wurde erfolgreich installiert. Sie können deren Status in der " +"Deine App wurde erfolgreich installiert. Du kannst deren Status in der " "Übersicht sehen." #: server_control_panel/templates/server_control_panel/install_app.html:29 @@ -995,16 +995,16 @@ msgstr "Wichtige Information" #: server_control_panel/templates/server_control_panel/install_app.html:35 #, python-format msgid "Your web app will be installed in %(localcosmos_apps_root)sAPP_UID/" -msgstr "Ihre WebApp wird nach %(localcosmos_apps_root)sAPP_UID/ installiert" +msgstr "Deine WebApp wird in %(localcosmos_apps_root)sAPP_UID/ installiert" #: server_control_panel/templates/server_control_panel/install_app.html:36 msgid "You can look up your APP_UID on localcosmos.org" -msgstr "Sie können ihre APP_UID auf localcosmos.org nachschlagen" +msgstr "Du kannst deine APP_UID auf localcosmos.org nachschlagen" #: server_control_panel/templates/server_control_panel/install_app.html:37 msgid "Your webserver (nginx or apache) will serve your web app, not django" msgstr "" -"Ihre WebApp wird von ihrem Webserver (nginx oder apache) bereitgestellt, " +"Deine WebApp wird von deinem Webserver (nginx oder apache) bereitgestellt, " "nicht von django" #: server_control_panel/templates/server_control_panel/install_app.html:38 @@ -1013,16 +1013,16 @@ msgid "" "Make sure to set an alias to %(localcosmos_apps_root)sAPP_UID/www/ in your " "web server" msgstr "" -"Stellen Sie sicher, dass is einen Alias für %(localcosmos_apps_root)sAPP_UID/" -"www/ in ihrem Webserver konfigurieren" +"Stelle sicher, dass ein Alias für %(localcosmos_apps_root)sAPP_UID/" +"www/ in deinem Webserver konfiguriert ist" #: server_control_panel/templates/server_control_panel/install_app.html:39 msgid "" "Your Android and iOS Apps will be able to connect to your server after you " "installed the web app" msgstr "" -"Ihre Android und iOS Apps können erst mit ihrem Server verbinden, nachdem " -"Sie die WebApp dort installiert haben." +"Deine Android und iOS Apps können sich erst mit deinem Server verbinden, nachdem " +"du die WebApp auf diesem installiert hast." #: server_control_panel/templates/server_control_panel/install_app.html:57 msgid "The following errors occured during app import:" @@ -1036,18 +1036,18 @@ msgstr "installieren" #, python-brace-format msgid "Error importing the app. Please upload a valid app .zip file: {0}" msgstr "" -"Fehler beim import der App. Bitte löaden Sie eine valide App .zip Datei " +"Fehler beim import der App. Bitte lade eine valide App .zip Datei " "hoch: {0}" #: server_control_panel/views.py:146 #, python-brace-format msgid "The zip file you uploaded does not contain the app {0}" -msgstr "Die zip Datei, die Sie hochgeladen haben enthielt nicht die App {0}" +msgstr "Die zip Datei, die du hochgeladen hast enthielt nicht die App {0}" #: server_control_panel/views.py:230 #, python-format msgid "Do you really want to uninstall %s?" -msgstr "Wollen Sie wirklich %s deinstallieren ?" +msgstr "Willst du %s wirklich deinstallieren ?" #: setup_forms.py:17 msgid "The two password fields didn't match." @@ -1076,7 +1076,7 @@ msgstr "Taxon" #: taxonomy/views.py:144 #, python-format msgid "Do you really want to remove %s?" -msgstr "Wollen Sie wirklich %s entfernen ?" +msgstr "Willst du %s wirklich entfernen ?" #: templates/400.html:10 msgid "Bad request" @@ -1084,7 +1084,7 @@ msgstr "Bad request" #: templates/403.html:10 msgid "You are not allowed to perform the operation you requested." -msgstr "Sie sind nicht berechtigt, die angeforderte Operation durchzuführen." +msgstr "Du bist nicht berechtigt, die angeforderte Operation durchzuführen." #: templates/404.html:10 msgid "The requested page could not be found." @@ -1122,7 +1122,7 @@ msgstr "Vielen Dank für die Registrierung auf %(url)s." #: templates/email/registration_confirmation.txt:13 #, python-format msgid "Your username is: %(username)s" -msgstr "Ihr Nutzername ist: %(username)s" +msgstr "Dein Nutzername ist: %(username)s" #: templates/email/registration_confirmation.html:336 msgid "Questions?" @@ -1151,8 +1151,8 @@ msgid "" "We've emailed you instructions for setting your password, if an account " "exists with the email you entered. You should receive them shortly." msgstr "" -"Wir haben ihnen Anweisungen geschickt, wie Sie ein neues Passwort setzen " -"können, falls ein Account für dies e-mail Addresse existiert. Die e-mail " +"Wir haben dir Anweisungen geschickt, wie du ein neues Passwort setzen " +"kannst, falls ein Account für diese e-mail Addresse existiert. Die e-mail " "sollte in Kürze ankommen." #: templates/localcosmos_server/api/password_reset_form.html:10 @@ -1161,9 +1161,9 @@ msgid "" "If you don't receive an email, please make sure you've entered the address " "you registered with, and check your spam folder." msgstr "" -"Falls Sie keine e-mail erhalten, müssen Sie sicherstellen, dass Sie auch " -"diejenige e-mail Addresse eingeben, mit der Sie sich registriert haben. " -"Schauen Sie auch im Spam-Ordner nach." +"Falls du keine e-mail erhältst, musst du sicherstellen, dass du auch " +"diejenige e-mail Addresse eingibst, mit der du dich registriert hast. " +"Schaue auch im Spam-Ordner nach." #: templates/localcosmos_server/api/password_reset_form.html:17 #: templates/localcosmos_server/registration/password_reset_form.html:18 @@ -1171,13 +1171,13 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" -"Passwort vergessen? Geben Sie unten ihre e-mail Adresse ein, und wir " -"schicken ihnen Anweisungen, wie sie ein neues Passwort setzen können." +"Passwort vergessen? Gib unten deine e-mail Adresse ein, und wir " +"schicken dir Anweisungen, wie du ein neues Passwort setzen kannst." #: templates/localcosmos_server/api/password_reset_form.html:27 #: templates/localcosmos_server/api/register_account.html:6 msgid "There have been errors in the data you submitted." -msgstr "Die von ihnen übermittelten Daten enthielten Fehler." +msgstr "Die von dir übermittelten Daten enthielten Fehler." #: templates/localcosmos_server/api/register_account.html:12 #, python-format @@ -1185,8 +1185,8 @@ msgid "" "Welcome %(username)s. Your account has been created successfully and you " "have been logged in." msgstr "" -"Willkommen %(username)s. Ihr Account wurde erfolgreich angelegt und Sie " -"wurden angemeldet." +"Willkommen %(username)s. Dein Account wurde erfolgreich angelegt und du " +"wurdest angemeldet." #: templates/localcosmos_server/bootstrap_field.html:21 msgid "no choices found" @@ -1200,7 +1200,7 @@ msgstr "%(verbose_name)s löschen" #: templates/localcosmos_server/generic/delete_object.html:30 #, python-format msgid "Do you really want to delete %(verbose_name)s?" -msgstr "Wollen Sie wirklich %(verbose_name)s löschen?" +msgstr "Willst du %(verbose_name)s wirklich löschen?" #: templates/localcosmos_server/legal/legal_notice.html:17 msgid "Operator and contact person" @@ -1222,11 +1222,11 @@ msgstr "abmelden" #: templates/localcosmos_server/registration/login.html:19 #, python-format msgid "You are signed in as %(user)s" -msgstr "Sie sind als %(user)s angemeldet" +msgstr "Du bist als %(user)s angemeldet" #: templates/localcosmos_server/registration/loggedout.html:27 msgid "You are signed out" -msgstr "Sie sind abgemeldet" +msgstr "Du bist abgemeldet" #: templates/localcosmos_server/registration/login.html:10 #: templates/localcosmos_server/registration/login.html:28 @@ -1248,16 +1248,16 @@ msgstr "registrieren" #: templates/localcosmos_server/registration/password_change_done.html:17 msgid "You have to use your new password the next time you log in." -msgstr "Bei der nächsten Anmeldung müssen Sie ihr neues Paswort benutzen." +msgstr "Bei der nächsten Anmeldung musst du dein neues Paswort benutzen." #: templates/localcosmos_server/registration/password_change_form.html:20 msgid "" "Please enter your old password, for security's sake, and then enter your new " "password twice so we can verify you typed it in correctly." msgstr "" -"Bitte geben Sie aus SWicherheitsgründen ihr altes Passwort ein. Danach geben " -"Sie zweimal ihr neues Passwort ein, damit wir verifizieren können, dass Sie " -"es korrekt eingegeben haben." +"Bitte gib aus Sicherheitsgründen dein altes Passwort ein. Danach musst du " +"zweimal dein neues Passwort eingeben, damit wir verifizieren können, dass du " +"es korrekt eingegeben hast." #: templates/localcosmos_server/registration/password_change_form.html:25 #: templates/localcosmos_server/registration/password_reset_confirm.html:25 @@ -1266,15 +1266,15 @@ msgstr "Passwort ändern" #: templates/localcosmos_server/registration/password_reset_complete.html:19 msgid "Your password has been set. You may go ahead and sign in now." -msgstr "Ihr Passwort wurde gesetzt. Sie können sich jetzt anmelden." +msgstr "Dein Passwort wurde gesetzt. Du kannst dich jetzt anmelden." #: templates/localcosmos_server/registration/password_reset_confirm.html:20 msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." msgstr "" -"Bitte geben Sie ihr neues Passwort zweimal ein, damit wir verifizieren " -"können, dass Sie es korrekt eingegeben haben." +"Bitte gib dein neues Passwort zweimal ein, damit wir verifizieren " +"können, dass du es korrekt eingegeben hast." #: templates/localcosmos_server/registration/password_reset_confirm.html:31 msgid "" @@ -1282,7 +1282,7 @@ msgid "" "used. Please request a new password reset." msgstr "" "Der Link für das Zurücksetzen des Passwortes war ungültig. Vermutlich, weil " -"er bereits benutzt wurde. Bitte fordern Sie das Zurücksetzen des Passwortes " +"er bereits benutzt wurde. Bitte fordere das Zurücksetzen des Passwortes " "neu an." #: templates/localcosmos_server/registration/password_reset_email.html:2 @@ -1302,7 +1302,7 @@ msgstr "" #: templates/localcosmos_server/registration/password_reset_email.html:10 msgid "Thanks for using our site!" -msgstr "Vielen Dank, dass Sie unsere App benutzen!" +msgstr "Vielen Dank, dass du unsere App benutzt!" #: templates/localcosmos_server/registration/password_reset_email.html:12 #, python-format @@ -1329,8 +1329,8 @@ msgstr "Taxonomische Restriktionen verwalten" msgid "" "You have to publish your app before being able to set taxonomic restrictions" msgstr "" -"Sie müssen ihre App veröffentlichen, bevor Sie Taxonomische Restriktionen " -"festlegen können." +"Du musst deine App veröffentlichen, bevor du Taxonomische Restriktionen " +"festlegen kannst." #: templates/localcosmos_server/taxonomy/taxonomic_restrictions_form.html:5 #, python-format @@ -1388,7 +1388,7 @@ msgstr "Ungültige SVG Datei: svg nicht im xml tree der Datei gefunden." #, python-format msgid "Wrong image ratio. Upload a file with the dimensions w:h = %(ratio)s" msgstr "" -"Falsche Bildabmessungen. Laden Sie eine Datei mit den Abmessungen Breite:" +"Falsche Bildabmessungen. Lade eine Datei mit den Abmessungen Breite:" "Höhe = %(ratio)s hoch." #: validators.py:96 validators.py:189 diff --git a/localcosmos_server/requirements.txt b/localcosmos_server/requirements.txt index 49eebdb..9a68442 100644 --- a/localcosmos_server/requirements.txt +++ b/localcosmos_server/requirements.txt @@ -10,7 +10,7 @@ django-el-pagination==3.3.0 django-octicons==1.0.2 django-countries==6.1.3 django-cors-headers==3.5.0 -drf-spectacular -djangorestframework-simplejwt +drf-spectacular==0.24.* +djangorestframework-simplejwt==5.2.* Pillow matplotlib diff --git a/localcosmos_server/settings.py b/localcosmos_server/settings.py index 7769c81..e359ada 100644 --- a/localcosmos_server/settings.py +++ b/localcosmos_server/settings.py @@ -13,8 +13,8 @@ LAZY_TAXONOMY_SOURCES = [] AUTHENTICATION_BACKENDS = ( - 'rules.permissions.ObjectPermissionBackend', - 'django.contrib.auth.backends.ModelBackend', + 'rules.permissions.ObjectPermissionBackend', + 'django.contrib.auth.backends.ModelBackend', ) LOGOUT_REDIRECT_URL = '/server/loggedout/' diff --git a/localcosmos_server/urls.py b/localcosmos_server/urls.py index 1b8d1fd..8e1f6af 100644 --- a/localcosmos_server/urls.py +++ b/localcosmos_server/urls.py @@ -2,7 +2,6 @@ from django.contrib import admin from django.urls import include, path from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView -from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenBlacklistView from . import views @@ -23,12 +22,8 @@ path('api/', include('localcosmos_server.online_content.api.urls')), path('api/anycluster/', include('localcosmos_server.anycluster_schema_urls')), - path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('api/token/blacklist/', TokenBlacklistView.as_view(), name='token_blacklist'), - - path("schema/", SpectacularAPIView.as_view(), name="schema"), - path("docs/", SpectacularSwaggerView.as_view(template_name="swagger-ui.html", url_name="schema"), name="swagger-ui"), + path('schema/', SpectacularAPIView.as_view(), name='schema'), + path('docs/', SpectacularSwaggerView.as_view(template_name='swagger-ui.html', url_name='schema'), name='swagger-ui'), ] if getattr(settings, 'LOCALCOSMOS_ENABLE_GOOGLE_CLOUD_API', False) == True: diff --git a/setup.py b/setup.py index be17f03..20d9ef0 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,8 @@ install_requires = [ 'django==3.1.*', 'djangorestframework==3.11.2', + 'drf-spectacular==0.24.*', + 'djangorestframework-simplejwt==5.2.*', 'django-imagekit==4.0.2', 'django-road', 'content-licencing',