first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:22:29 +03:00
commit ec28a2024d
208 changed files with 23836 additions and 0 deletions

0
image/__init__.py Normal file
View File

27
image/admin.py Normal file
View File

@@ -0,0 +1,27 @@
from django.contrib import admin
from .models import PostImages
@admin.register(PostImages)
class PostImagesAdmin(admin.ModelAdmin):
list_display = (
'id',
'title',
'path',
'processed_path',
'original_filename',
'format',
'width',
'height',
'size',
'quality',
'slug',
'created_at',
'updated_at',
'is_active',
'is_front',
)
list_filter = ('created_at', 'updated_at', 'is_active', 'is_front')
search_fields = ('slug',)
date_hierarchy = 'created_at'

9
image/apps.py Normal file
View File

@@ -0,0 +1,9 @@
from django.apps import AppConfig
class ImageConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'image'
def ready(self):
import image.signals

View File

@@ -0,0 +1,42 @@
# Generated by Django 5.2.9 on 2025-12-31 01:20
import autoslug.fields
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=254, verbose_name='Resim Başlığı')),
('path', models.CharField(max_length=254, verbose_name='Resim Yolu')),
('processed_path', models.CharField(max_length=254, verbose_name='Orijinal Resim Yolu')),
('original_filename', models.CharField(max_length=254, verbose_name='Orijinal Resim Adı')),
('format', models.CharField(choices=[('png', 'PNG'), ('webp', 'WebP'), ('jpg', 'JPG'), ('avif', 'AVIF')], default='webp', max_length=254, verbose_name='Resim Formati')),
('width', models.IntegerField()),
('height', models.IntegerField()),
('size', models.IntegerField()),
('quality', models.IntegerField()),
('slug', autoslug.fields.AutoSlugField(blank=True, editable=True, max_length=250, populate_from='title', unique=True)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Oluşturulma Tarihi')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')),
('is_active', models.BooleanField(choices=[(True, 'Evet'), (False, 'Hayır')], default=True, verbose_name='Yayındamı ?')),
('is_front', models.BooleanField(choices=[(True, 'Evet'), (False, 'Hayır')], default=True, verbose_name='Önde Görünsünmü ?')),
],
options={
'verbose_name': 'Post',
'verbose_name_plural': 'Posts',
'db_table': 'images',
'ordering': ['created_at'],
'unique_together': {('slug',)},
},
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.9 on 2025-12-31 01:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('image', '0001_initial'),
]
operations = [
migrations.RenameModel(
old_name='Post',
new_name='PostImages',
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.9 on 2026-01-03 21:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('image', '0002_rename_post_postimages'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='postimages',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='image_user', to=settings.AUTH_USER_MODEL, verbose_name='Kullanıcı'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.9 on 2026-01-09 07:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('image', '0003_postimages_user'),
]
operations = [
migrations.AlterModelOptions(
name='postimages',
options={'ordering': ['created_at'], 'verbose_name': 'Resim', 'verbose_name_plural': 'Resimler'},
),
]

View File

56
image/models.py Normal file
View File

@@ -0,0 +1,56 @@
from autoslug import AutoSlugField
from django.db import models
# Create your models here.
class PostImages(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
format_choose = (
('png', 'PNG'),
('webp', 'WebP'),
('jpg', 'JPG'),
('avif', 'AVIF'),
)
title = models.CharField(max_length=254, verbose_name="Resim Başlığı")
user = models.ForeignKey('accounts.CustomUser', on_delete=models.CASCADE, related_name='image_user',
verbose_name="Kullanıcı")
path = models.CharField(max_length=254, verbose_name="Resim Yolu")
processed_path = models.CharField(max_length=254, verbose_name="Orijinal Resim Yolu")
original_filename = models.CharField(max_length=254, verbose_name="Orijinal Resim Adı")
format = models.CharField(max_length=254, verbose_name="Resim Formati", choices=format_choose,default='webp')
width = models.IntegerField()
height = models.IntegerField()
size = models.IntegerField()
quality = models.IntegerField()
slug = AutoSlugField(populate_from='title', null=False, unique=True, editable=True, db_index=True, max_length=250,
blank=True)
created_at = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Oluşturulma Tarihi")
updated_at = models.DateTimeField(auto_now=True, editable=False, verbose_name="Güncelleme Tarihi")
is_active = models.BooleanField(default=True, verbose_name='Yayındamı ?', choices=aktif)
is_front = models.BooleanField(default=True, verbose_name='Önde Görünsünmü ?', choices=aktif)
class Meta:
ordering = ["created_at"]
db_table = 'images'
verbose_name_plural = "Posts"
verbose_name = "Post"
unique_together = ('slug',)
def get_slug(self):
slug = self.title.replace('ı', "i").replace('İ', 'i')
number = 1
while PostImages.objects.filter(slug=slug).exists():
slug = '{}-{}'.format(slug, number)
number += 1
return slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.get_slug()
super().save(*args, **kwargs)
def __str__(self):
return f"Resimler: {self.title}"

108
image/serializers.py Normal file
View File

@@ -0,0 +1,108 @@
import os
import uuid
from io import BytesIO
from PIL import Image, ImageOps
from django.conf import settings
from django.core.files.base import ContentFile
from rest_framework import serializers
from .models import PostImages
class PostImagesSerializer(serializers.ModelSerializer):
class Meta:
model = PostImages
fields = '__all__'
class PostImageCreateSerializer(serializers.Serializer):
"""
Serializer for uploading and processing an image.
"""
image = serializers.ImageField(write_only=True, help_text="Yüklenecek resim dosyası.")
title = serializers.CharField(max_length=254, help_text="Resim için bir başlık.")
width = serializers.IntegerField(help_text="Resmin yeni genişliği (pixel).")
height = serializers.IntegerField(help_text="Resmin yeni yüksekliği (pixel).")
quality = serializers.IntegerField(default=85, min_value=1, max_value=100, help_text="JPG/WebP için kalite (1-100).")
format = serializers.ChoiceField(
choices=['png', 'webp', 'jpg', 'avif'],
default='avif',
help_text="Çıktı resim formatı."
)
def create(self, validated_data):
image_file = validated_data.pop('image')
title = validated_data.pop('title')
new_width = validated_data.pop('width')
new_height = validated_data.pop('height')
quality = validated_data.pop('quality')
output_format = validated_data.pop('format')
# Open the image with Pillow
img = Image.open(image_file)
# Preserve original metadata for the model
original_filename = image_file.name
original_size = image_file.size
# Process the image: Resize and crop to fill target dimensions (Center Crop).
# This ensures the image fills the dimensions without distortion or padding.
# ImageOps.fit resizes and crops the image to the requested size.
img = ImageOps.fit(img, (new_width, new_height), method=Image.Resampling.LANCZOS, centering=(0.5, 0.5))
# If output format does not support transparency (like JPG), convert to RGB.
# This also ensures compatibility for formats like JPEG that don't support P or RGBA.
if output_format != 'png' and img.mode != 'RGB':
img = img.convert('RGB')
# Save the processed image to an in-memory buffer
buffer = BytesIO()
# Pillow expects 'JPEG' format identifier, not 'jpg'
save_format = output_format.upper()
if save_format == 'JPG':
save_format = 'JPEG'
save_kwargs = {'format': save_format}
if output_format in ['jpg', 'webp']:
save_kwargs['quality'] = quality
img.save(buffer, **save_kwargs)
buffer.seek(0)
# Generate a unique filename and path
new_filename = f"processed/{uuid.uuid4()}.{output_format}"
file_path = os.path.join(settings.MEDIA_ROOT, new_filename)
# Ensure the directory exists
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# Save the new file to the media directory
with open(file_path, 'wb') as f:
f.write(buffer.getvalue())
# Get the size of the new file
new_size = os.path.getsize(file_path)
# Get user from validated_data if provided (serializer.save(user=...))
user = validated_data.pop('user', None)
# Create the PostImages model instance
instance = PostImages.objects.create(
title=title,
user=user,
path=new_filename, # Store the relative path
processed_path=original_filename, # Let's use this field for original name for now
original_filename=original_filename,
format=output_format,
width=new_width, # Use the target width
height=new_height, # Use the target height
size=new_size,
quality=quality,
)
return instance
def to_representation(self, instance):
# Use the PostImagesSerializer to represent the created object
return PostImagesSerializer(instance).data

22
image/signals.py Normal file
View File

@@ -0,0 +1,22 @@
import os
from django.conf import settings
from django.db.models.signals import post_delete
from django.dispatch import receiver
from .models import PostImages
@receiver(post_delete, sender=PostImages)
def delete_image_file(sender, instance, **kwargs):
"""
Deletes the associated image file from the filesystem when a PostImages instance is deleted.
"""
# Check if the instance has a path and the path is not empty
if instance.path:
file_path = os.path.join(settings.MEDIA_ROOT, instance.path)
if os.path.exists(file_path):
try:
os.remove(file_path)
except OSError as e:
# Optionally, log the error e.g., print(f"Error deleting file {file_path}: {e}")
pass

3
image/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

8
image/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import path, include
from .views import ImageUploadView, ImageDownloadView, ImageListView # Import ImageDownloadView
urlpatterns = [
path('upload/', ImageUploadView.as_view(), name='image-upload'),
path('list/', ImageListView.as_view(), name='image-list'),
path('<slug:slug>/download/', ImageDownloadView.as_view(), name='image-download'), # New download URL
]

67
image/views.py Normal file
View File

@@ -0,0 +1,67 @@
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.permissions import IsAdminUser, AllowAny
from rest_framework import generics
import os
from django.conf import settings
from django.http import FileResponse, Http404
from .serializers import PostImageCreateSerializer, PostImagesSerializer
from .models import PostImages # Make sure to import PostImages model
class ImageUploadView(APIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = (IsAdminUser,)
def post(self, request, *args, **kwargs):
"""
Accepts an image upload and processing parameters.
Processes the image and saves it.
"""
serializer = PostImageCreateSerializer(data=request.data)
if serializer.is_valid():
instance = serializer.save(user=request.user)
# Return the details of the created object using the representation serializer
return Response(serializer.to_representation(instance), status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ImageDownloadView(APIView):
"""
API endpoint to download a processed image by its slug.
"""
def get(self, request, slug, *args, **kwargs):
try:
image_instance = PostImages.objects.get(slug=slug)
except PostImages.DoesNotExist:
raise Http404("Image not found.")
file_path = os.path.join(settings.MEDIA_ROOT, image_instance.path)
if not os.path.exists(file_path):
raise Http404("File not found on server.")
# Determine content type based on format
content_type_map = {
'png': 'image/png',
'webp': 'image/webp',
'jpg': 'image/jpeg',
'avif': 'image/avif',
}
content_type = content_type_map.get(image_instance.format.lower(), 'application/octet-stream')
# Prepare the filename for download
filename = f"{image_instance.slug}.{image_instance.format.lower()}"
response = FileResponse(open(file_path, 'rb'), content_type=content_type)
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
class ImageListView(generics.ListAPIView):
"""List all images."""
queryset = PostImages.objects.all().order_by('-created_at')
serializer_class = PostImagesSerializer
permission_classes = (AllowAny,)