Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add redoc api #12

Merged
merged 7 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deployment/docker/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ django-celery-beat==2.5.0
redis==4.3.4

psycopg2-binary==2.9.9
# drf yasg
drf-yasg==1.21.7
49 changes: 49 additions & 0 deletions django_project/core/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: Core factories.
"""

from typing import Generic, TypeVar

import factory
from django.contrib.auth import get_user_model

T = TypeVar('T')
User = get_user_model()


class BaseMetaFactory(Generic[T], factory.base.FactoryMetaClass):
"""Base meta factory class."""

def __call__(cls, *args, **kwargs) -> T:
"""Override the default Factory() syntax to call the default strategy.

Returns an instance of the associated class.
"""
return super().__call__(*args, **kwargs)


class BaseFactory(Generic[T], factory.django.DjangoModelFactory):
"""Base factory class to make the factory return correct class typing."""

@classmethod
def create(cls, **kwargs) -> T:
"""Create an instance of the model, and save it to the database."""
return super().create(**kwargs)


class UserF(
BaseFactory[User], metaclass=BaseMetaFactory[User]
):
"""Factory class for User."""

class Meta: # noqa
model = User

username = factory.Sequence(
lambda n: u'username %s' % n
)
first_name = 'John'
last_name = 'Doe'
1 change: 1 addition & 0 deletions django_project/core/settings/contrib.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'django_cleanup.apps.CleanupConfig',
'django_celery_beat',
'django_celery_results',
'drf_yasg',
)
WEBPACK_LOADER = {
'DEFAULT': {
Expand Down
3 changes: 2 additions & 1 deletion django_project/core/settings/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
INSTALLED_APPS = INSTALLED_APPS + (
'core',
'frontend',
'gap'
'gap',
'gap_api'
)

TEMPLATES[0]['DIRS'] += [
Expand Down
Empty file.
32 changes: 32 additions & 0 deletions django_project/core/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: Common class for unit tests.
"""

from django.test import TestCase
from rest_framework.test import APIRequestFactory
from core.factories import UserF


class BaseAPIViewTest(TestCase):
"""Base class for API test."""

def setUp(self):
"""Init test class."""
self.factory = APIRequestFactory()
self.superuser = UserF.create(
is_staff=True,
is_superuser=True,
is_active=True
)
self.user_1 = UserF.create(
is_active=True
)


class FakeResolverMatchV1:
"""Fake class to mock versioning."""

namespace = 'v1'
7 changes: 5 additions & 2 deletions django_project/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from django.urls import path, include, re_path

urlpatterns = [
path('', include('frontend.urls')),
re_path(
r'^api/', include(('gap_api.urls', 'api'), namespace='api')
),
path('admin/', admin.site.urls),
path('', include('frontend.urls')),
]

if settings.DEBUG:
Expand Down
29 changes: 21 additions & 8 deletions django_project/gap/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
.. note:: Factory classes for Models
"""
import factory
from factory.django import DjangoModelFactory
from django.contrib.gis.geos import Point, MultiPolygon, Polygon

from core.factories import BaseMetaFactory, BaseFactory
from gap.models import (
Provider,
Attribute,
Expand All @@ -14,10 +16,11 @@
Measurement,
ObservationType
)
from django.contrib.gis.geos import Point, MultiPolygon, Polygon


class ProviderFactory(DjangoModelFactory):
class ProviderFactory(
BaseFactory[Provider], metaclass=BaseMetaFactory[Provider]
):
"""Factory class for Provider model."""

class Meta: # noqa
Expand All @@ -27,7 +30,9 @@ class Meta: # noqa
description = factory.Faker('text')


class AttributeFactory(DjangoModelFactory):
class AttributeFactory(
BaseFactory[Attribute], metaclass=BaseMetaFactory[Attribute]
):
"""Factory class for Attribute model."""

class Meta: # noqa
Expand All @@ -39,7 +44,9 @@ class Meta: # noqa
description = factory.Faker('text')


class ObservationTypeFactory(DjangoModelFactory):
class ObservationTypeFactory(
BaseFactory[ObservationType], metaclass=BaseMetaFactory[ObservationType]
):
"""Factory class for ObservationType model."""

class Meta: # noqa
Expand All @@ -51,7 +58,9 @@ class Meta: # noqa
description = factory.Faker('text')


class CountryFactory(DjangoModelFactory):
class CountryFactory(
BaseFactory[Country], metaclass=BaseMetaFactory[Country]
):
"""Factory class for Country model."""

class Meta: # noqa
Expand All @@ -67,7 +76,9 @@ class Meta: # noqa
description = factory.Faker('text')


class StationFactory(DjangoModelFactory):
class StationFactory(
BaseFactory[Station], metaclass=BaseMetaFactory[Station]
):
"""Factory class for Station model."""

class Meta: # noqa
Expand All @@ -83,7 +94,9 @@ class Meta: # noqa
observation_type = factory.SubFactory(ObservationTypeFactory)


class MeasurementFactory(DjangoModelFactory):
class MeasurementFactory(
BaseFactory[Measurement], metaclass=BaseMetaFactory[Measurement]
):
"""Factory class for Measurement model."""

class Meta: # noqa
Expand Down
2 changes: 1 addition & 1 deletion django_project/gap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from django.contrib.gis.db import models

from core.models.general import Definition
from core.models.common import Definition


class Provider(Definition):
Expand Down
Empty file.
Empty file.
38 changes: 38 additions & 0 deletions django_project/gap_api/api_views/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: User APIs
"""

from drf_yasg.utils import swagger_auto_schema
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from gap_api.serializers.common import APIErrorSerializer
from gap_api.serializers.user import UserInfoSerializer
from gap_api.utils.helper import ApiTag


class UserInfo(APIView):
"""API to return user info."""

permission_classes = [IsAuthenticated]

@swagger_auto_schema(
operation_id='user-info',
tags=[ApiTag.USER],
responses={
200: UserInfoSerializer,
400: APIErrorSerializer
}
)
def get(self, request, *args, **kwargs):
"""Login user info.

Return current login user information.
"""
return Response(
status=200, data=UserInfoSerializer(request.user).data
)
15 changes: 15 additions & 0 deletions django_project/gap_api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: App Config for GAP API
"""

from django.apps import AppConfig


class GapApiConfig(AppConfig):
"""App Config for GAP API."""

default_auto_field = 'django.db.models.BigAutoField'
name = 'gap_api'
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions django_project/gap_api/serializers/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: Common serializer class.
"""

from rest_framework import serializers


class APIErrorSerializer(serializers.Serializer):
"""Serializer for error in the API."""

detail = serializers.CharField()


class NoContentSerializer(serializers.Serializer):
"""Empty serializer for API that returns 204 No Content."""

pass
28 changes: 28 additions & 0 deletions django_project/gap_api/serializers/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: User serializer class.
"""

from django.contrib.auth import get_user_model
from rest_framework import serializers

User = get_user_model()


class UserInfoSerializer(serializers.ModelSerializer):
"""Serializer for User Info."""

class Meta: # noqa
model = User
fields = ['username', 'email', 'first_name', 'last_name']
swagger_schema_fields = {
'title': 'User Info',
'example': {
'username': 'jane.doe@example.com',
'email': 'jane.doe@example.com',
'first_name': 'Jane',
'last_name': 'Doe'
}
}
Empty file.
41 changes: 41 additions & 0 deletions django_project/gap_api/tests/test_user_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: Unit tests for User API.
"""

from django.urls import reverse

from core.tests.common import FakeResolverMatchV1, BaseAPIViewTest
from gap_api.api_views.user import UserInfo


class UserInfoAPITest(BaseAPIViewTest):
"""User info api test case."""

def test_get_user_info_without_auth(self):
"""Test get user info without authentication."""
view = UserInfo.as_view()
request = self.factory.get(
reverse('api:v1:user-info')
)
request.resolver_match = FakeResolverMatchV1
response = view(request)
self.assertEqual(response.status_code, 401)

def test_get_user_info(self):
"""Test get user info with superuser."""
view = UserInfo.as_view()
request = self.factory.get(
reverse('api:v1:user-info')
)
request.user = self.superuser
request.resolver_match = FakeResolverMatchV1
response = view(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['username'], self.superuser.username)
self.assertEqual(
response.data['first_name'], self.superuser.first_name
)
self.assertEqual(response.data['last_name'], self.superuser.last_name)
14 changes: 14 additions & 0 deletions django_project/gap_api/urls/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# coding=utf-8
"""
Tomorrow Now GAP.

.. note:: GAP API urls.
"""

from django.urls import include, re_path

urlpatterns = [
re_path(
r'^v1/', include(('gap_api.urls.v1', 'v1'), namespace='v1')
)
]
Loading
Loading