diff --git a/freekake_api/content/models.py b/freekake_api/content/models.py index a31ab32..7e6acef 100644 --- a/freekake_api/content/models.py +++ b/freekake_api/content/models.py @@ -1,5 +1,6 @@ from django.db import models from django_softdelete.models import SoftDeleteModel +from django.core.validators import MaxValueValidator, MinValueValidator class ContentTheme(SoftDeleteModel): theme = models.CharField(max_length=255, null=False) @@ -27,18 +28,22 @@ class ContentTopic(SoftDeleteModel): def __str__(self): return self.topic -class ContentFormat(SoftDeleteModel): - format = models.CharField(max_length=255, null=False) - - featured_image = models.CharField(max_length=1000) - - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - def __str__(self): - return self.format - class Content(SoftDeleteModel): + + class ContentFormat(models.TextChoices): + TEXT = 'Text', 'Teks' + INFOGRAPHIC = 'Infographic', 'Infografis' + IMAGE = 'Image', 'Gambar' + VIDEO = 'Video', 'Video' + BRANCHING_SCENARIO = 'Branching Scenario', 'Pilihan Bercabang' + CROSSWORD = 'Crossword', 'Teka-teki Silang' + DRAG_AND_DROP = 'Drag And Drop', 'Pencocokan' + FIND_THE_WORDS = 'Find the Words', 'Cari Kata' + MEMORY_GAME = 'Memory Game', 'Permainan Memori' + PERSONALITY_QUIZ = 'Personality Quiz', 'Permainan Tebak Sifat' + GAME_MAP = 'Game Map', 'Peta Permainan' + MULTIPLE_CHOICE = 'Multiple Choice', 'Pilihan Ganda' + title = models.CharField(max_length=255, null=False) slug = models.SlugField(max_length=255) @@ -46,9 +51,12 @@ class Content(SoftDeleteModel): theme = models.ForeignKey(ContentTheme, on_delete=models.CASCADE, null=False) topic = models.ForeignKey(ContentTopic, on_delete=models.CASCADE, null=False) - format = models.ForeignKey(ContentFormat, on_delete=models.CASCADE, null=False) + format = models.CharField(max_length=25, choices=ContentFormat.choices, default=ContentFormat.TEXT) - content = models.TextField() + content = models.TextField(null=True) + data = models.JSONField(null=True) + + grades = models.JSONField(null=True) point = models.IntegerField() coin = models.IntegerField() @@ -57,4 +65,4 @@ class Content(SoftDeleteModel): updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return self.title \ No newline at end of file + return self.title diff --git a/freekake_api/content/serializers.py b/freekake_api/content/serializers.py index 24a9790..68ea088 100644 --- a/freekake_api/content/serializers.py +++ b/freekake_api/content/serializers.py @@ -1,40 +1,33 @@ from rest_framework import serializers -from .models import Content, ContentTheme, ContentTopic, ContentFormat - +from content import models class ContentThemeSerializer(serializers.ModelSerializer): class Meta: - model = ContentTheme + model = models.ContentTheme fields = ['id', 'theme', 'description', 'featured_image'] class ContentTopicSerializer(serializers.ModelSerializer): class Meta: - model = ContentTopic + model = models.ContentTopic fields = ['id', 'topic', 'description', 'featured_image', 'theme'] class ContentTopicDetailSerializer(serializers.ModelSerializer): theme = ContentThemeSerializer(read_only=True) class Meta: - model = ContentTopic + model = models.ContentTopic fields = ['id', 'topic', 'description', 'featured_image', 'theme'] - -class ContentFormatSerializer(serializers.ModelSerializer): - class Meta: - model = ContentFormat - fields = ['id', 'format', 'featured_image', 'created_at', 'updated_at'] - + class ContentSerializer(serializers.ModelSerializer): class Meta: - model = Content - fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'content', 'point', 'coin'] + model = models.Content + fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'content', 'point', 'coin', 'data', 'grades'] class ContentDetailSerializer(serializers.ModelSerializer): theme = ContentThemeSerializer(read_only=True) topic = ContentTopicDetailSerializer(read_only=True) - format = ContentFormatSerializer(read_only=True) class Meta: - model = Content - fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'content', 'point', 'coin'] + model = models.Content + fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'content', 'point', 'coin', 'data', 'grades'] diff --git a/freekake_api/content/urls.py b/freekake_api/content/urls.py index 72503d1..d398181 100644 --- a/freekake_api/content/urls.py +++ b/freekake_api/content/urls.py @@ -1,16 +1,13 @@ from django.urls import path -from .views import ContentList, ContentDetail, ContentThemeList, ContentFormatList, ContentTopicList +from content import views urlpatterns = [ - path('contents/', ContentList.as_view()), - path('contents//', ContentDetail.as_view()), + path('contents/', views.ContentList.as_view()), + path('contents//', views.ContentDetail.as_view()), - path('themes/', ContentThemeList.as_view()), - path('themes//', ContentThemeList.as_view()), + path('themes/', views.ContentThemeList.as_view()), + path('themes//', views.ContentThemeDetail.as_view()), - path('formats/', ContentFormatList.as_view()), - path('formats//', ContentFormatList.as_view()), - - path('topics/', ContentTopicList.as_view()), - path('topics//', ContentTopicList.as_view()), + path('topics/', views.ContentTopicList.as_view()), + path('topics//', views.ContentTopicDetail.as_view()), ] diff --git a/freekake_api/content/views.py b/freekake_api/content/views.py index 39c0b11..c40df0d 100644 --- a/freekake_api/content/views.py +++ b/freekake_api/content/views.py @@ -1,21 +1,53 @@ -from django.shortcuts import render from rest_framework import generics, filters -from django_filters.rest_framework import DjangoFilterBackend +from django_filters.rest_framework import DjangoFilterBackend, FilterSet, CharFilter from content import models, serializers +class ContentFilter(FilterSet): + grade = CharFilter( + method='filter_grades_contains', label="Grade/Class" + ) + + def filter_grades_contains(self, queryset, name, value): + try: + value_int = int(value) + except ValueError: + return queryset.none() + return queryset.filter(grades__contains=[value_int]) + + class Meta: + model = models.Content + fields = ['format', 'theme', 'topic', 'grade'] + class ContentList(generics.ListCreateAPIView): queryset = models.Content.objects.all() serializer_class = serializers.ContentSerializer filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] search_fields = ['title'] - filterset_fields = ['format', 'theme', 'topic'] + #filterset_fields = ['format', 'theme', 'topic'] + filterset_class = ContentFilter ordering_fields = '__all__' + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.ContentDetailSerializer + + return serializer_class + class ContentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = models.Content.objects.all() serializer_class = serializers.ContentSerializer + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.ContentDetailSerializer + + return serializer_class + class ContentThemeList(generics.ListCreateAPIView): queryset = models.ContentTheme.objects.all() serializer_class = serializers.ContentThemeSerializer @@ -35,17 +67,23 @@ class ContentTopicList(generics.ListCreateAPIView): filterset_fields = ['theme'] ordering_fields = '__all__' + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.ContentTopicDetailSerializer + + return serializer_class + class ContentTopicDetail(generics.RetrieveUpdateDestroyAPIView): queryset = models.ContentTopic.objects.all() serializer_class = serializers.ContentTopicSerializer -class ContentFormatList(generics.ListCreateAPIView): - queryset = models.ContentFormat.objects.all() - serializer_class = serializers.ContentFormatSerializer - filter_backends = [filters.SearchFilter, filters.OrderingFilter] - search_fields = ['format'] - ordering_fields = '__all__' + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.ContentTopicDetailSerializer + + return serializer_class -class ContentFormatDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = models.ContentFormat.objects.all() - serializer_class = serializers.ContentFormatSerializer diff --git a/freekake_api/core/models.py b/freekake_api/core/models.py index 44c1f78..3d130a0 100644 --- a/freekake_api/core/models.py +++ b/freekake_api/core/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib.auth.models import User from django_softdelete.models import SoftDeleteModel class Province(SoftDeleteModel): @@ -33,4 +34,19 @@ class Village(models.Model): district = models.ForeignKey(District, on_delete=models.CASCADE, null=False) def __str__(self): - return self.name \ No newline at end of file + return self.name + +# Tambahan Data untuk User +class Student(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + + school = models.CharField(max_length=255, null=True) + class_grade = models.SmallIntegerField(null=True) + + province = models.ForeignKey(Province, on_delete=models.CASCADE, null=True) + regency_city = models.ForeignKey(RegencyCity, on_delete=models.CASCADE, null=True) + district = models.ForeignKey(District, on_delete=models.CASCADE, null=True) + village = models.ForeignKey(Village, on_delete=models.CASCADE, null=True) + + address = models.CharField(max_length=1000, null=True) + phone_number = models.CharField(max_length=25, null=True) \ No newline at end of file diff --git a/freekake_api/core/serializers.py b/freekake_api/core/serializers.py index 36c23c7..b1dc1e3 100644 --- a/freekake_api/core/serializers.py +++ b/freekake_api/core/serializers.py @@ -26,7 +26,6 @@ class DistrictSerializer(serializers.ModelSerializer): model = models.District fields = ['id', 'code', 'name', 'regency_city'] - class DistrictDetailSerializer(serializers.ModelSerializer): regency_city = RegencyCityDetailSerializer() @@ -39,4 +38,27 @@ class VillageSerializer(serializers.ModelSerializer): class Meta: model = models.Village - fields = ['id', 'code', 'name', 'district'] \ No newline at end of file + fields = ['id', 'code', 'name', 'district'] + +class VillageDetailSerializer(serializers.ModelSerializer): + district = DistrictDetailSerializer() + + class Meta: + model = models.Village + fields = ['id', 'code', 'name', 'district'] + +class StudentSerializer(serializers.ModelSerializer): + + class Meta: + model = models.Student + fields = ['id', 'user', 'school', 'class_grade', 'province', 'regency_city', 'district', 'village', 'address', 'phone_number'] + +class StudentDetailSerializer(serializers.ModelSerializer): + province = ProvinceSerializer(read_only=True) + regency_city = RegencyCitySerializer(read_only=True) + district = DistrictSerializer(read_only=True) + village = VillageSerializer(read_only=True) + + class Meta: + model = models.Student + fields = ['id', 'user', 'school', 'class_grade', 'province', 'regency_city', 'district', 'village', 'address', 'phone_number'] \ No newline at end of file diff --git a/freekake_api/core/urls.py b/freekake_api/core/urls.py index 4c89f3b..ab0505b 100644 --- a/freekake_api/core/urls.py +++ b/freekake_api/core/urls.py @@ -11,6 +11,12 @@ urlpatterns = [ path('districts/', views.DistrictList.as_view()), path('districts//', views.DistrictDetail.as_view()), + + path('villages/', views.VillageList.as_view()), + path('villages//', views.VillageDetail.as_view()), + + path('students/', views.StudentList.as_view()), + path('students//', views.StudentDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/freekake_api/core/views.py b/freekake_api/core/views.py index 384cd45..7756da3 100644 --- a/freekake_api/core/views.py +++ b/freekake_api/core/views.py @@ -76,4 +76,63 @@ class DistrictDetail(generics.RetrieveUpdateDestroyAPIView): if self.request.method == 'GET': serializer_class = serializers.DistrictDetailSerializer - return serializer_class \ No newline at end of file + return serializer_class + +# VILLAGE +class VillageList(generics.ListCreateAPIView): + queryset = models.Village.objects.all() + serializer_class = serializers.VillageSerializer + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['name', 'code'] + filterset_fields = ['name', 'code', 'district'] + ordering_fields = '__all__' + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.VillageDetailSerializer + + return serializer_class + +class VillageDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = models.Village.objects.all() + serializer_class = serializers.VillageSerializer + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.VillageDetailSerializer + + return serializer_class + +# STUDENT +class StudentList(generics.ListCreateAPIView): + queryset = models.Student.objects.all() + serializer_class = serializers.StudentSerializer + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['name', 'id'] + filterset_fields = ['class_grade', 'province', 'regency_city', 'district', 'village'] + ordering_fields = '__all__' + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.StudentDetailSerializer + + return serializer_class + +class StudentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = models.Student.objects.all() + serializer_class = serializers.StudentSerializer + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.StudentDetailSerializer + + return serializer_class + diff --git a/freekake_api/freekake_api/settings.py b/freekake_api/freekake_api/settings.py index 06a4c8e..baf0b7d 100644 --- a/freekake_api/freekake_api/settings.py +++ b/freekake_api/freekake_api/settings.py @@ -40,12 +40,14 @@ INSTALLED_APPS = [ 'oauth2_provider', 'rest_framework', + 'djoser', 'psycopg2', 'corsheaders', 'django_filters', 'core', 'content', + 'character', ] MIDDLEWARE = [ @@ -145,21 +147,19 @@ CORS_ALLOWED_ORIGINS = [ ] REST_FRAMEWORK = { - # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - # 'PAGE_SIZE': 10, 'DEFAULT_PAGINATION_CLASS': 'freekake_api.pagination.CustomPagination', 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend' ], - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.BasicAuthentication', - 'rest_framework.authentication.SessionAuthentication', - 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', - ), - 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.IsAuthenticated', - ) + # 'DEFAULT_AUTHENTICATION_CLASSES': ( + # 'rest_framework.authentication.BasicAuthentication', + # 'rest_framework.authentication.SessionAuthentication', + # 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + # ), + # 'DEFAULT_PERMISSION_CLASSES': ( + # 'rest_framework.permissions.IsAuthenticated', + # ) } OAUTH2_PROVIDER = { @@ -167,4 +167,12 @@ OAUTH2_PROVIDER = { 'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'} } -LOGIN_URL = '/admin/login/' \ No newline at end of file +LOGIN_URL = '/admin/login/' + +DJOSER = { + 'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}', + 'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}', + 'ACTIVATION_URL': '#/activate/{uid}/{token}', + 'SEND_ACTIVATION_EMAIL': True, + 'SERIALIZERS': {}, +} \ No newline at end of file diff --git a/freekake_api/freekake_api/urls.py b/freekake_api/freekake_api/urls.py index 75ff711..76c4a5f 100644 --- a/freekake_api/freekake_api/urls.py +++ b/freekake_api/freekake_api/urls.py @@ -22,7 +22,9 @@ urlpatterns = [ # path('admin/', admin.site.urls), path('admin/', admin.site.urls), path('oauth/', include(oauth2_urls)), + path('auth/', include('djoser.urls')), path('core/', include('core.urls')), path('content/', include('content.urls')), + path('character/', include('character.urls')), ]