diff --git a/freekake_api/content/models.py b/freekake_api/content/models.py index 0864e98..da14741 100644 --- a/freekake_api/content/models.py +++ b/freekake_api/content/models.py @@ -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) diff --git a/freekake_api/content/serializers.py b/freekake_api/content/serializers.py index a505f1b..47d91c1 100644 --- a/freekake_api/content/serializers.py +++ b/freekake_api/content/serializers.py @@ -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 \ No newline at end of file diff --git a/freekake_api/content/urls.py b/freekake_api/content/urls.py index fc425c2..ff98120 100644 --- a/freekake_api/content/urls.py +++ b/freekake_api/content/urls.py @@ -5,11 +5,7 @@ urlpatterns = [ path('contents/', views.ContentList.as_view()), path('contents//', views.ContentDetail.as_view()), - path('themes/', views.ContentThemeList.as_view()), - path('themes//', views.ContentThemeDetail.as_view()), - - path('topics/', views.ContentTopicList.as_view()), - path('topics//', views.ContentTopicDetail.as_view()), - + path('themes/', views.ContentThemeChoices.as_view()), + path('topics//', views.ContentTopicChoices.as_view()), path('formats/', views.ContentFormatChoices.as_view()), ] diff --git a/freekake_api/content/views.py b/freekake_api/content/views.py index bd82767..9dcc142 100644 --- a/freekake_api/content/views.py +++ b/freekake_api/content/views.py @@ -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):