mission
This commit is contained in:
parent
0216751834
commit
add9caf0f2
@ -66,7 +66,7 @@ class MangaDetailSerializer(serializers.ModelSerializer):
|
|||||||
return dict(models.Manga.MANGA_GENRE_CHOICES).get(obj.genre)
|
return dict(models.Manga.MANGA_GENRE_CHOICES).get(obj.genre)
|
||||||
|
|
||||||
def get_status_display(self, obj):
|
def get_status_display(self, obj):
|
||||||
return dict(models.Manga.CONTENT_STATUS_CHOICES).get(obj.status, obj.status)
|
return dict(models.Manga.MANGA_STATUS_CHOICES).get(obj.status, obj.status)
|
||||||
|
|
||||||
class MangaChapterSerializer(serializers.ModelSerializer):
|
class MangaChapterSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ INSTALLED_APPS = [
|
|||||||
'content',
|
'content',
|
||||||
'character',
|
'character',
|
||||||
'entertainment',
|
'entertainment',
|
||||||
|
'mission',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@ -31,5 +31,6 @@ urlpatterns = [
|
|||||||
path('content/', include('content.urls')),
|
path('content/', include('content.urls')),
|
||||||
path('character/', include('character.urls')),
|
path('character/', include('character.urls')),
|
||||||
path('entertainment/', include('entertainment.urls')),
|
path('entertainment/', include('entertainment.urls')),
|
||||||
|
path('mission/', include('mission.urls')),
|
||||||
|
|
||||||
]+ debug_toolbar_urls() + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
]+ debug_toolbar_urls() + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
0
freekake_api/mission/__init__.py
Normal file
0
freekake_api/mission/__init__.py
Normal file
3
freekake_api/mission/admin.py
Normal file
3
freekake_api/mission/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
freekake_api/mission/apps.py
Normal file
6
freekake_api/mission/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MissionConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'mission'
|
||||||
99
freekake_api/mission/models.py
Normal file
99
freekake_api/mission/models.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django_softdelete.models import SoftDeleteModel
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def validate_image_size(image):
|
||||||
|
if image.size > 1 * 1024 * 1024:
|
||||||
|
raise ValidationError("File size exceeds 1MB.")
|
||||||
|
|
||||||
|
def validate_image_ext(image):
|
||||||
|
allowed_extensions = ['png', 'jpg', 'jpeg']
|
||||||
|
ext = image.name.split('.')[-1].lower()
|
||||||
|
if ext not in allowed_extensions:
|
||||||
|
raise ValidationError("Invalid file type. Only PNG or JPEG allowed.")
|
||||||
|
|
||||||
|
def validate_image(image):
|
||||||
|
try:
|
||||||
|
img = Image.open(image)
|
||||||
|
img.verify()
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError("Invalid image file.")
|
||||||
|
|
||||||
|
def validate_file_size(image):
|
||||||
|
if image.size > 1 * 1024 * 1024:
|
||||||
|
raise ValidationError("File size exceeds 1MB.")
|
||||||
|
|
||||||
|
class Mission(SoftDeleteModel):
|
||||||
|
|
||||||
|
MISSION_CATEGORY_CHOICES = [
|
||||||
|
('daily','Harian'),
|
||||||
|
('weekly', 'Mingguan'),
|
||||||
|
('monthly', 'Bulanan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
MISSION_TASK_CHOICES = [
|
||||||
|
('scan-qr','Scan QR'),
|
||||||
|
('upload-photo','Upload Foto'),
|
||||||
|
]
|
||||||
|
|
||||||
|
MISSION_STATUS_CHOICES = [
|
||||||
|
('draft', 'Draf'),
|
||||||
|
('published', 'Dipublikasikan'),
|
||||||
|
('archived', 'Diarsipkan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255, null=False)
|
||||||
|
description = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
|
|
||||||
|
featured_image = models.ImageField(
|
||||||
|
max_length=255,
|
||||||
|
upload_to="uploads/missions/",
|
||||||
|
validators=[validate_image_size, validate_image_ext, validate_image],
|
||||||
|
null=False, blank=False)
|
||||||
|
|
||||||
|
coin = models.IntegerField()
|
||||||
|
point = models.IntegerField()
|
||||||
|
|
||||||
|
category = models.CharField(max_length=50, choices=MISSION_CATEGORY_CHOICES)
|
||||||
|
task = models.CharField(max_length=50, choices=MISSION_TASK_CHOICES)
|
||||||
|
|
||||||
|
date_valid = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
|
time_from_valid = models.TimeField(null=True, blank=True)
|
||||||
|
time_to_valid = models.TimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
status = models.CharField(max_length=50, choices=MISSION_STATUS_CHOICES, default='draft')
|
||||||
|
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
posted_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
archived_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class MissionLog(SoftDeleteModel):
|
||||||
|
MISSION_LOG_STATUS_CHOICES = [
|
||||||
|
('visited', 'Baru Dikunjungi'),
|
||||||
|
('completed', 'Sudah Selesai'),
|
||||||
|
('claimed', 'Hadiah Sudah Diambil'),
|
||||||
|
]
|
||||||
|
|
||||||
|
mission = models.ForeignKey(Mission, related_name='logs', on_delete=models.CASCADE)
|
||||||
|
user_id = models.IntegerField()
|
||||||
|
|
||||||
|
status = models.CharField(max_length=50, choices=MISSION_LOG_STATUS_CHOICES)
|
||||||
|
|
||||||
|
coin = models.IntegerField()
|
||||||
|
point = models.IntegerField()
|
||||||
|
|
||||||
|
image_log = models.ImageField(
|
||||||
|
max_length=255,
|
||||||
|
upload_to="uploads/mission_logs/",
|
||||||
|
validators=[validate_image_size, validate_image_ext, validate_image],
|
||||||
|
null=False, blank=False)
|
||||||
|
|
||||||
|
completed_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
claimed_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
138
freekake_api/mission/serializers.py
Normal file
138
freekake_api/mission/serializers.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
from django.forms import ImageField, FileField
|
||||||
|
from rest_framework import serializers
|
||||||
|
from mission import models
|
||||||
|
|
||||||
|
class MissionSerializer(serializers.ModelSerializer):
|
||||||
|
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Mission
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'featured_image',
|
||||||
|
'coin',
|
||||||
|
'point',
|
||||||
|
'category',
|
||||||
|
'task',
|
||||||
|
'status',
|
||||||
|
'date_valid',
|
||||||
|
'time_from_valid',
|
||||||
|
'time_to_valid',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
request_method = self.context.get('request').method if self.context.get('request') else None
|
||||||
|
|
||||||
|
if request_method in ['PUT', 'PATCH']:
|
||||||
|
self.fields['featured_image'].required = False
|
||||||
|
|
||||||
|
class MissionDetailSerializer(serializers.ModelSerializer):
|
||||||
|
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||||
|
category_display = serializers.SerializerMethodField()
|
||||||
|
task_display = serializers.SerializerMethodField()
|
||||||
|
status_display = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Mission
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'featured_image',
|
||||||
|
'coin',
|
||||||
|
'point',
|
||||||
|
'category',
|
||||||
|
'task',
|
||||||
|
'status',
|
||||||
|
'date_valid',
|
||||||
|
'time_from_valid',
|
||||||
|
'time_to_valid',
|
||||||
|
'category_display',
|
||||||
|
'task_display',
|
||||||
|
'status_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
category = data.get('category')
|
||||||
|
task = data.get('task')
|
||||||
|
|
||||||
|
allowed_categories = dict(models.Mission.MISSION_CATEGORY_CHOICES).get(category, [])
|
||||||
|
valid_category_keys = [key for key, _ in allowed_categories]
|
||||||
|
|
||||||
|
if category not in valid_category_keys:
|
||||||
|
raise serializers.ValidationError({"category": "Invalid category."})
|
||||||
|
|
||||||
|
allowed_tasks = dict(models.Mission.MISSION_TASK_CHOICES).get(task, [])
|
||||||
|
valid_task_keys = [key for key, _ in allowed_tasks]
|
||||||
|
|
||||||
|
if task not in valid_task_keys:
|
||||||
|
raise serializers.ValidationError({"task": "Invalid task."})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_category_display(self, obj):
|
||||||
|
return obj.get_category_display()
|
||||||
|
|
||||||
|
def get_task_display(self, obj):
|
||||||
|
return obj.get_task_display()
|
||||||
|
|
||||||
|
def get_status_display(self, obj):
|
||||||
|
return obj.get_status_display()
|
||||||
|
|
||||||
|
class MissionLogSerializer(serializers.ModelSerializer):
|
||||||
|
image_log = ImageField(max_length=255, allow_empty_file=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.MissionLog
|
||||||
|
fields = [
|
||||||
|
'mission',
|
||||||
|
'user_id',
|
||||||
|
'status',
|
||||||
|
'coin',
|
||||||
|
'point',
|
||||||
|
'image_log',
|
||||||
|
'completed_at',
|
||||||
|
'claimed_at',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
request_method = self.context.get('request').method if self.context.get('request') else None
|
||||||
|
|
||||||
|
if request_method in ['PUT', 'PATCH']:
|
||||||
|
self.fields['image_log'].required = False
|
||||||
|
|
||||||
|
class MissionLogDetailSerializer(serializers.ModelSerializer):
|
||||||
|
image_log = ImageField(max_length=255, allow_empty_file=False)
|
||||||
|
status_display = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.MissionLog
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'mission',
|
||||||
|
'user_id',
|
||||||
|
'status',
|
||||||
|
'coin',
|
||||||
|
'point',
|
||||||
|
'image_log',
|
||||||
|
'completed_at',
|
||||||
|
'claimed_at',
|
||||||
|
'status_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
status = data.get('status')
|
||||||
|
|
||||||
|
allowed_statuses = dict(models.MissionLog.MISSION_LOG_STATUS_CHOICES).keys()
|
||||||
|
|
||||||
|
if status not in allowed_statuses:
|
||||||
|
raise serializers.ValidationError({"status": "Invalid status."})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_status_display(self, obj):
|
||||||
|
return obj.get_status_display()
|
||||||
3
freekake_api/mission/tests.py
Normal file
3
freekake_api/mission/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
14
freekake_api/mission/urls.py
Normal file
14
freekake_api/mission/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from mission import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('missions/', views.MissionList.as_view()),
|
||||||
|
path('missions/<int:pk>/', views.MissionDetail.as_view()),
|
||||||
|
|
||||||
|
path('categories/', views.MissionCategoryChoices.as_view()),
|
||||||
|
path('tasks/', views.MissionTaskChoices.as_view()),
|
||||||
|
path('statuses/', views.MissionStatusChoices.as_view()),
|
||||||
|
|
||||||
|
path('mission-logs/', views.MissionLogList.as_view()),
|
||||||
|
path('mission-logs/<int:pk>/', views.MissionLogDetail.as_view()),
|
||||||
|
]
|
||||||
82
freekake_api/mission/views.py
Normal file
82
freekake_api/mission/views.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import generics, filters, views
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend, FilterSet, CharFilter, ChoiceFilter
|
||||||
|
|
||||||
|
from mission import models, serializers
|
||||||
|
|
||||||
|
class MissionList(generics.ListCreateAPIView):
|
||||||
|
queryset = models.Mission.objects.all()
|
||||||
|
serializer_class = serializers.MissionSerializer
|
||||||
|
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
||||||
|
search_fields = ['name']
|
||||||
|
filterset_fields = ['category', 'task', 'status']
|
||||||
|
ordering_fields = '__all__'
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.request.method in ['GET']:
|
||||||
|
return serializers.MissionDetailSerializer
|
||||||
|
return serializers.MissionSerializer
|
||||||
|
|
||||||
|
class MissionDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
queryset = models.Mission.objects.all()
|
||||||
|
serializer_class = serializers.MissionDetailSerializer
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.request.method in ['GET']:
|
||||||
|
return serializers.MissionDetailSerializer
|
||||||
|
return serializers.MissionSerializer
|
||||||
|
|
||||||
|
class MissionCategoryChoices(views.APIView):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return Response({
|
||||||
|
"count": len(models.Mission.MISSION_CATEGORY_CHOICES),
|
||||||
|
"results": [
|
||||||
|
{"value": choice[0], "label": choice[1]}
|
||||||
|
for choice in models.Mission.MISSION_CATEGORY_CHOICES
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
class MissionTaskChoices(views.APIView):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return Response({
|
||||||
|
"count": len(models.Mission.MISSION_TASK_CHOICES),
|
||||||
|
"results": [
|
||||||
|
{"value": choice[0], "label": choice[1]}
|
||||||
|
for choice in models.Mission.MISSION_TASK_CHOICES
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
class MissionStatusChoices(views.APIView):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return Response({
|
||||||
|
"count": len(models.Mission.MISSION_STATUS_CHOICES),
|
||||||
|
"results": [
|
||||||
|
{"value": choice[0], "label": choice[1]}
|
||||||
|
for choice in models.Mission.MISSION_STATUS_CHOICES
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
class MissionLogList(generics.ListCreateAPIView):
|
||||||
|
queryset = models.MissionLog.objects.all()
|
||||||
|
serializer_class = serializers.MissionLogSerializer
|
||||||
|
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
||||||
|
search_fields = ['user_id']
|
||||||
|
filterset_fields = ['mission', 'user_id', 'status']
|
||||||
|
ordering_fields = '__all__'
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.request.method in ['GET']:
|
||||||
|
return serializers.MissionLogDetailSerializer
|
||||||
|
return serializers.MissionLogSerializer
|
||||||
|
|
||||||
|
class MissionLogDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
queryset = models.MissionLog.objects.all()
|
||||||
|
serializer_class = serializers.MissionLogSerializer
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
serializer_class = self.serializer_class
|
||||||
|
|
||||||
|
if self.request.method in ['GET']:
|
||||||
|
return serializers.MissionLogDetailSerializer
|
||||||
|
|
||||||
|
return serializer_class
|
||||||
Loading…
Reference in New Issue
Block a user