diff --git a/freekake_api/content/models.py b/freekake_api/content/models.py index baea7e1..9b0ccd5 100644 --- a/freekake_api/content/models.py +++ b/freekake_api/content/models.py @@ -1,12 +1,34 @@ from django.db import models +from django.core.exceptions import ValidationError from django_softdelete.models import SoftDeleteModel -from django.core.validators import MaxValueValidator, MinValueValidator +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'] + ext = image.name.split('.')[-1].lower() + if ext not in allowed_extensions: + raise ValidationError("Invalid file type. Only PNG allowed.") + +def validate_image(image): + try: + img = Image.open(image) + img.verify() + 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.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) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -18,7 +40,11 @@ class ContentTopic(SoftDeleteModel): topic = models.CharField(max_length=255, null=False) description = models.CharField(max_length=1000) - featured_image = 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) @@ -48,7 +74,11 @@ class Content(SoftDeleteModel): title = models.CharField(max_length=255, null=False) slug = models.SlugField(max_length=255) - featured_image = models.CharField(max_length=1000) + featured_image = models.ImageField( + max_length=255, + upload_to="uploads/contents/", + 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) diff --git a/freekake_api/content/serializers.py b/freekake_api/content/serializers.py index 68ea088..65fa280 100644 --- a/freekake_api/content/serializers.py +++ b/freekake_api/content/serializers.py @@ -1,12 +1,15 @@ +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'] class ContentTopicSerializer(serializers.ModelSerializer): + featured_image = ImageField(max_length=255, allow_empty_file=False) class Meta: model = models.ContentTopic @@ -14,12 +17,15 @@ class ContentTopicSerializer(serializers.ModelSerializer): 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) + class Meta: model = models.Content fields = ['id', 'title', 'slug', 'featured_image', 'theme', 'topic', 'format', 'content', 'point', 'coin', 'data', 'grades'] @@ -27,6 +33,7 @@ class ContentSerializer(serializers.ModelSerializer): 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 diff --git a/freekake_api/content/views.py b/freekake_api/content/views.py index 0992463..f7e509b 100644 --- a/freekake_api/content/views.py +++ b/freekake_api/content/views.py @@ -1,4 +1,4 @@ -from rest_framework import generics, filters +from rest_framework import generics, filters, parsers from django_filters.rest_framework import DjangoFilterBackend, FilterSet, CharFilter, ChoiceFilter from content import models, serializers diff --git a/freekake_api/freekake_api/settings.py b/freekake_api/freekake_api/settings.py index 833aec0..ffef796 100644 --- a/freekake_api/freekake_api/settings.py +++ b/freekake_api/freekake_api/settings.py @@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ from pathlib import Path from decouple import config +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -183,4 +184,9 @@ DJOSER = { INTERNAL_IPS = [ '127.0.0.1', -] \ No newline at end of file +] + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' + +DATA_UPLOAD_MAX_MEMORY_SIZE = 2 * 1024 * 1024 # 5MB diff --git a/freekake_api/freekake_api/urls.py b/freekake_api/freekake_api/urls.py index 3a5670d..429f8b8 100644 --- a/freekake_api/freekake_api/urls.py +++ b/freekake_api/freekake_api/urls.py @@ -18,6 +18,8 @@ from django.contrib import admin from django.urls import path, include from oauth2_provider import urls as oauth2_urls from debug_toolbar.toolbar import debug_toolbar_urls +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ # path('admin/', admin.site.urls), @@ -28,4 +30,4 @@ urlpatterns = [ path('core/', include('core.urls')), path('content/', include('content.urls')), path('character/', include('character.urls')), -]+ debug_toolbar_urls() +]+ debug_toolbar_urls() + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)