content, ubah theme dan topic dengan enum
This commit is contained in:
parent
11ca62eee4
commit
b50cded82f
@ -8,10 +8,10 @@ def validate_image_size(image):
|
||||
raise ValidationError("File size exceeds 1MB.")
|
||||
|
||||
def validate_image_ext(image):
|
||||
allowed_extensions = ['png']
|
||||
allowed_extensions = ['png', 'jpg', 'jpeg']
|
||||
ext = image.name.split('.')[-1].lower()
|
||||
if ext not in allowed_extensions:
|
||||
raise ValidationError("Invalid file type. Only PNG allowed.")
|
||||
raise ValidationError("Invalid file type. Only PNG or JPEG allowed.")
|
||||
|
||||
def validate_image(image):
|
||||
try:
|
||||
@ -20,57 +20,69 @@ def validate_image(image):
|
||||
except Exception:
|
||||
raise ValidationError("Invalid image file.")
|
||||
|
||||
class ContentTheme(SoftDeleteModel):
|
||||
theme = models.CharField(max_length=255, null=False)
|
||||
description = models.CharField(max_length=1000)
|
||||
|
||||
featured_image = models.ImageField(
|
||||
max_length=255,
|
||||
upload_to="uploads/content_themes/",
|
||||
validators=[validate_image_size, validate_image_ext, validate_image],
|
||||
null=False, blank=False)
|
||||
|
||||
color = models.CharField(max_length=7, null=True, blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.theme
|
||||
|
||||
class ContentTopic(SoftDeleteModel):
|
||||
topic = models.CharField(max_length=255, null=False)
|
||||
description = models.CharField(max_length=1000)
|
||||
|
||||
featured_image = models.ImageField(
|
||||
max_length=255,
|
||||
upload_to="uploads/content_topics/",
|
||||
validators=[validate_image_size, validate_image_ext, validate_image],
|
||||
null=False, blank=False)
|
||||
|
||||
theme = models.ForeignKey(ContentTheme, on_delete=models.CASCADE, null=False)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.topic
|
||||
|
||||
class Content(SoftDeleteModel):
|
||||
|
||||
CONTENT_THEME_CHOICES = [
|
||||
('furikake', 'Furikake'),
|
||||
('gizi', 'Gizi'),
|
||||
('kesehatan', 'Kesehatan'),
|
||||
('pendidikan', 'Pendidikan'),
|
||||
('keselamatan', 'Keselamatan'),
|
||||
]
|
||||
|
||||
CONTENT_TOPIC_CHOICES = {
|
||||
'furikake': [
|
||||
('sejarah-furikake', 'Sejarah Furikake'),
|
||||
('kandungan-furikake', 'Kandungan Furikake'),
|
||||
('pembuatan-furikake', 'Pembuatan Furikake'),
|
||||
('menikmati-furikake', 'Menikmati Furikake')
|
||||
],
|
||||
'gizi': [
|
||||
('gizi-seimbang', 'Gizi Seimbang'),
|
||||
('kecukupan-gizi', 'Kecukupan Gizi'),
|
||||
('kualitas-pangan', 'Kualitas Bahan Pangan'),
|
||||
('budaya-tradisi-pangan', 'Budaya dan Tradisi Pangan'),
|
||||
('ketersediaan-aksesibilitas-pangan', 'Ketersediaan dan Aksesibilitas Gizi dan Pangan')
|
||||
],
|
||||
'kesehatan': [
|
||||
('olahraga', 'Olahraga'),
|
||||
('waktu-tidur', 'Waktu Tidur'),
|
||||
('kesehatan-mental', 'Kesehatan Mental'),
|
||||
('pendidikan-seksua', 'Pendidikan Seksual'),
|
||||
('rokok-alkohol-narkoba', 'Bahaya Merokok dan Alkohol serta Pencegahan Narkoba'),
|
||||
('sistem-imun', 'Sistem Imun')
|
||||
],
|
||||
'pendidikan': [
|
||||
('berpikir-kreatif', 'Berpikir Kreatif'),
|
||||
('berpikir-kritis', 'Berpikir Kritis'),
|
||||
('pentingnya-membaca', 'Pentingnya Membaca'),
|
||||
('literasi-digital', 'Literasi Digital'),
|
||||
('kesadaran-lingkungan', 'Kesadaran Lingkungan'),
|
||||
('pancasila', 'Pancasila'),
|
||||
('biologi', 'Biologi'),
|
||||
('aritmatika', 'Aritmatika'),
|
||||
('bahasa-indonesia', 'Bahasa Indonesia'),
|
||||
],
|
||||
'keselamatan': [
|
||||
('lalu-lintas', 'Keselamatan Berlalu Lintas'),
|
||||
('pertolongan-pertama', 'Pertolongan Petrtama'),
|
||||
('bencana', 'Kesiapsiagaan Bencana')
|
||||
]
|
||||
}
|
||||
|
||||
CONTENT_FORMAT_CHOICES = [
|
||||
('Text', 'Teks'),
|
||||
('Infographic', 'Infografis'),
|
||||
('Image', 'Gambar'),
|
||||
('Video', 'Video'),
|
||||
('Branching Scenario', 'Pilihan Bercabang'),
|
||||
('Crossword', 'Teka-teki Silang'),
|
||||
('Drag And Drop', 'Pencocokan'),
|
||||
('Find the Words', 'Cari Kata'),
|
||||
('Memory Game', 'Permainan Memori'),
|
||||
('Personality Quiz', 'Permainan Tebak Sifat'),
|
||||
('Game Map', 'Peta Permainan'),
|
||||
('Multiple Choice', 'Pilihan Ganda'),
|
||||
('teks','Teks'),
|
||||
('infografis', 'Infografis'),
|
||||
('gambar', 'Gambar'),
|
||||
('video', 'Video'),
|
||||
('branching-scenario', 'Pilihan Bercabang'),
|
||||
('tts', 'Teka-teki Silang'),
|
||||
('pencocokan', 'Pencocokan'),
|
||||
('cari-kata', 'Cari Kata'),
|
||||
('memory-game', 'Permainan Memori'),
|
||||
('personality-quiz', 'Permainan Tebak Sifat'),
|
||||
('game-map', 'Peta Permainan'),
|
||||
('pilihan-ganda', 'Pilihan Ganda'),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=255, null=False)
|
||||
@ -82,8 +94,8 @@ class Content(SoftDeleteModel):
|
||||
validators=[validate_image_size, validate_image_ext, validate_image],
|
||||
null=False, blank=False)
|
||||
|
||||
theme = models.ForeignKey(ContentTheme, on_delete=models.CASCADE, null=False)
|
||||
topic = models.ForeignKey(ContentTopic, on_delete=models.CASCADE, null=False)
|
||||
theme = models.CharField(max_length=25, choices=CONTENT_THEME_CHOICES)
|
||||
topic = models.CharField(max_length=25)
|
||||
format = models.CharField(max_length=25, choices=CONTENT_FORMAT_CHOICES)
|
||||
|
||||
description = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
@ -2,27 +2,7 @@ from django.forms import ImageField
|
||||
from rest_framework import serializers
|
||||
from content import models
|
||||
|
||||
class ContentThemeSerializer(serializers.ModelSerializer):
|
||||
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||
class Meta:
|
||||
model = models.ContentTheme
|
||||
fields = ['id', 'theme', 'description', 'featured_image', 'color']
|
||||
|
||||
class ContentTopicSerializer(serializers.ModelSerializer):
|
||||
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||
|
||||
class Meta:
|
||||
model = models.ContentTopic
|
||||
fields = ['id', 'topic', 'description', 'featured_image', 'theme']
|
||||
|
||||
class ContentTopicDetailSerializer(serializers.ModelSerializer):
|
||||
theme = ContentThemeSerializer(read_only=True)
|
||||
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||
|
||||
class Meta:
|
||||
model = models.ContentTopic
|
||||
fields = ['id', 'topic', 'description', 'featured_image', 'theme']
|
||||
|
||||
class ContentSerializer(serializers.ModelSerializer):
|
||||
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||
|
||||
@ -31,10 +11,20 @@ class ContentSerializer(serializers.ModelSerializer):
|
||||
fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'description','content', 'point', 'coin', 'data', 'grades', 'color']
|
||||
|
||||
class ContentDetailSerializer(serializers.ModelSerializer):
|
||||
theme = ContentThemeSerializer(read_only=True)
|
||||
topic = ContentTopicDetailSerializer(read_only=True)
|
||||
featured_image = ImageField(max_length=255, allow_empty_file=False)
|
||||
|
||||
class Meta:
|
||||
model = models.Content
|
||||
fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'description','content', 'point', 'coin', 'data', 'grades', 'color']
|
||||
|
||||
def validate(self, data):
|
||||
theme = data.get('theme')
|
||||
topic = data.get('topic')
|
||||
|
||||
allowed_topics = dict(models.Content.CONTENT_TOPIC_CHOICES).get(theme, [])
|
||||
valid_topic_keys = [key for key, _ in allowed_topics]
|
||||
|
||||
if topic not in valid_topic_keys:
|
||||
raise serializers.ValidationError({'topic': f'Topic "{topic}" is not valid for theme "{theme}"'})
|
||||
|
||||
return data
|
||||
@ -5,11 +5,7 @@ urlpatterns = [
|
||||
path('contents/', views.ContentList.as_view()),
|
||||
path('contents/<int:pk>/', views.ContentDetail.as_view()),
|
||||
|
||||
path('themes/', views.ContentThemeList.as_view()),
|
||||
path('themes/<int:pk>/', views.ContentThemeDetail.as_view()),
|
||||
|
||||
path('topics/', views.ContentTopicList.as_view()),
|
||||
path('topics/<int:pk>/', views.ContentTopicDetail.as_view()),
|
||||
|
||||
path('themes/', views.ContentThemeChoices.as_view()),
|
||||
path('topics/<str:theme_key>/', views.ContentTopicChoices.as_view()),
|
||||
path('formats/', views.ContentFormatChoices.as_view()),
|
||||
]
|
||||
|
||||
@ -48,45 +48,33 @@ class ContentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = serializers.ContentDetailSerializer
|
||||
|
||||
return serializer_class
|
||||
|
||||
class ContentThemeList(generics.ListCreateAPIView):
|
||||
queryset = models.ContentTheme.objects.all()
|
||||
serializer_class = serializers.ContentThemeSerializer
|
||||
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
|
||||
search_fields = ['theme']
|
||||
ordering_fields = '__all__'
|
||||
|
||||
class ContentThemeDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = models.ContentTheme.objects.all()
|
||||
serializer_class = serializers.ContentThemeSerializer
|
||||
|
||||
class ContentTopicList(generics.ListCreateAPIView):
|
||||
queryset = models.ContentTopic.objects.all()
|
||||
serializer_class = serializers.ContentTopicSerializer
|
||||
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
||||
search_fields = ['topic']
|
||||
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 ContentThemeChoices(views.APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return Response({
|
||||
"count": len(models.Content.CONTENT_THEME_CHOICES),
|
||||
"results": [
|
||||
{"value": choice[0], "label": choice[1]}
|
||||
for choice in models.Content.CONTENT_THEME_CHOICES
|
||||
]
|
||||
})
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
class ContentTopicChoices(views.APIView):
|
||||
def get(self, request, theme_key):
|
||||
topics = models.Content.CONTENT_TOPIC_CHOICES.get(theme_key)
|
||||
if not topics:
|
||||
return Response({
|
||||
"count": 0,
|
||||
"results": []
|
||||
})
|
||||
|
||||
if self.request.method == 'GET':
|
||||
serializer_class = serializers.ContentTopicDetailSerializer
|
||||
|
||||
return serializer_class
|
||||
return Response({
|
||||
"count": len(topics),
|
||||
"results": [
|
||||
{"value": choice[0], "label": choice[1]}
|
||||
for choice in topics
|
||||
]
|
||||
})
|
||||
|
||||
class ContentFormatChoices(views.APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user