first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:27:56 +03:00
commit d9f1ea341e
1021 changed files with 70645 additions and 0 deletions

0
product/__init__.py Normal file
View File

136
product/admin.py Normal file
View File

@@ -0,0 +1,136 @@
from django.contrib import admin
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from product.models import Product, Category, Images, Tags, ProductTree
# Register your models here.
class ProductAdmin(admin.ModelAdmin):
list_display = ('title', 'urun_resim', 'is_active', 'urun_kategorileri', 'brim', 'price', 'slug', 'urun_galeri')
list_filter = ('is_active', 'categories')
search_fields = ('title', 'is_active', 'slug', 'content')
list_editable = ('is_active', 'brim', 'price', 'slug',)
readonly_fields = ('thumb', 'image_preview', 'thumb_preview')
filter_horizontal = ('categories', 'tags', 'gallery')
fieldsets = (
('Temel Bilgiler', {
'fields': ('title', 'content', 'categories', 'tags')
}),
('Fiyat ve Birim', {
'fields': ('brim', 'price')
}),
('SEO ve Medya', {
'fields': ('slug', 'keywords', 'video')
}),
('Görseller', {
'fields': ('images', 'image_preview', 'thumb_preview', 'gallery'),
'description': 'Thumb otomatik oluşturulur, images yüklediğinizde.'
}),
('Durum', {
'fields': ('is_active', 'is_front')
}),
)
class Meta:
model = Product
def formatted_hit_count(self, obj):
return obj.current_hit_count if obj.current_hit_count > 0 else '-'
formatted_hit_count.admin_order_field = 'hit_count'
formatted_hit_count.short_description = 'Hits'
def blog_tags(self, obj):
tags = '<ul>'
for tag in obj.tags.all():
tags += '<li>' + tag.tag + '</li>'
tags += '</ul>'
return mark_safe(tags)
def urun_kategorileri(self, obj):
html = '<ul>'
for category in obj.categories.all():
html += '<li>' + category.title + '</li>'
html += '</ul>'
return mark_safe(html)
def urun_resim(self, obj):
if obj.images:
return mark_safe('<a href="/admin/product/product/{}/change/#id_images" onclick="window.location.href=\'/admin/product/product/{}/change/#id_images\'; return false;"><img src="{}" width="50" height="50" style="object-fit: cover; cursor: pointer;" title="Resmi değiştirmek için tıklayın" /></a>'.format(obj.id, obj.id, obj.images.url))
return mark_safe('<a href="/admin/product/product/{}/change/#id_images" onclick="window.location.href=\'/admin/product/product/{}/change/#id_images\'; return false;">Resim Yok</a>'.format(obj.id, obj.id))
urun_resim.short_description = 'Ürün Resmi'
def image_preview(self, obj):
if obj.images:
return format_html('<img src="{0}" width="260" height="260" style="object-fit: cover; border: 1px solid #ddd;" />', obj.images.url)
return "Resim Yok"
image_preview.short_description = 'Ana Resim Önizleme'
def thumb_preview(self, obj):
if obj.thumb:
return format_html('<img src="{0}" width="150" height="150" style="object-fit: cover; border: 1px solid #ddd;" />', obj.thumb.url)
return "Thumb Yok (Kaydet ve otomatik oluşur)"
thumb_preview.short_description = 'Thumb Önizleme'
def urun_galeri(self, obj):
html = '<ul>'
for gal in obj.gallery.all():
html += '<li>' + gal.title + '</li>'
html += '</ul>'
return mark_safe(html)
admin.site.register(Product, ProductAdmin)
class ProductTreeAdmin(admin.ModelAdmin):
list_display = ('title', 'is_active', 'price')
list_filter = ('is_active',)
search_fields = ('title', 'is_active', 'content')
list_editable = ('is_active', 'price',)
class Meta:
model = ProductTree
admin.site.register(ProductTree, ProductTreeAdmin)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('title', 'parent', 'is_active', 'created_at', 'order', 'slug')
list_filter = ('title', 'is_active', 'created_at',)
search_fields = ('title', 'is_active', 'slug')
list_editable = ('is_active', 'order', 'slug')
class Meta:
model = Category
admin.site.register(Category, CategoryAdmin)
class ImagesAdmin(admin.ModelAdmin):
list_display = ('title', 'images', 'created_at',)
list_filter = ('title',)
search_fields = ('title', 'images')
list_editable = ('images',)
class Meta:
model = Images
admin.site.register(Images, ImagesAdmin)
class TagsAdmin(admin.ModelAdmin):
list_display = ('tag', 'created_at',)
list_filter = ('tag',)
search_fields = ('tag',)
class Meta:
model = Tags
admin.site.register(Tags, TagsAdmin)

9
product/apps.py Normal file
View File

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

View File

View File

View File

@@ -0,0 +1,131 @@
import random
import string
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
from PIL import Image, ImageDraw
from product.models import Product, Category, Tags, Images
# Try to import Faker, use fallback if not available
try:
from faker import Faker
fake = Faker(['tr_TR'])
HAS_FAKER = True
except ImportError:
HAS_FAKER = False
class Command(BaseCommand):
help = 'Generates fake data for products, categories, and tags.'
def handle(self, *args, **kwargs):
self.stdout.write("Generating fake data...")
if not HAS_FAKER:
self.stdout.write(self.style.WARNING("Faker library not found. Using simple random data generator. Install with 'pip install faker' for better data."))
# 1. Generate Categories
categories = []
for i in range(10):
title = fake.word().capitalize() if HAS_FAKER else f"Kategori {i+1}"
keywords = ','.join(fake.words(nb=5)) if HAS_FAKER else "test, kategori, urun"
description = fake.sentence() if HAS_FAKER else f"Bu kategori {i+1} için açıklamadır."
cat = Category.objects.create(
title=title,
keywords=keywords,
description=description
)
categories.append(cat)
self.stdout.write(self.style.SUCCESS(f"{len(categories)} categories created."))
# 2. Generate Tags
tags = []
for i in range(20):
tag_name = fake.word() if HAS_FAKER else f"Etiket {i+1}"
tag = Tags.objects.create(tag=tag_name)
tags.append(tag)
self.stdout.write(self.style.SUCCESS(f"{len(tags)} tags created."))
# 3. Generate Gallery Images
gallery_images = []
for i in range(15):
img_name = f"gallery_{i}.jpg" # Using jpg for better compatibility
img = self._generate_random_image(img_name)
title = fake.word() if HAS_FAKER else f"Resim {i+1}"
image_instance = Images.objects.create(title=title)
image_instance.images.save(img_name, img)
gallery_images.append(image_instance)
self.stdout.write(self.style.SUCCESS(f"{len(gallery_images)} gallery images created."))
# 4. Generate Products
products = []
for i in range(50):
product_title = fake.company() if HAS_FAKER else f"Ürün {i+1} - {self._random_string(5)}"
content = fake.paragraph(nb_sentences=10) if HAS_FAKER else f"Bu ürün {i+1} için detaylııklamadır. " * 5
keywords = ','.join(fake.words(nb=5)) if HAS_FAKER else "urun, satis, online"
video_code = fake.password(length=11, special_chars=False, upper_case=True, lower_case=True, digits=True) if HAS_FAKER else self._random_string(11)
product = Product.objects.create(
title=product_title,
content=content,
keywords=keywords,
brim=random.choice(['Adet', 'Kg', 'Porsiyon', 'Dilim']),
price=round(random.uniform(10.0, 500.0), 2),
video=f"https://www.youtube.com/watch?v={video_code}"
)
# Assign categories, tags, and gallery
product.categories.set(random.sample(categories, k=random.randint(1, 3)))
product.tags.set(random.sample(tags, k=random.randint(1, 5)))
product.gallery.set(random.sample(gallery_images, k=random.randint(1, 4)))
# Generate and assign main product image
img_name = f"product_{i}.jpg"
main_image = self._generate_random_image(img_name)
product.images.save(img_name, main_image)
products.append(product)
self.stdout.write(self.style.SUCCESS(f"{len(products)} products created."))
self.stdout.write(self.style.SUCCESS("Fake data generation complete!"))
def _generate_random_image(self, name):
"""Generates a random image file in memory."""
width, height = 400, 400
img = Image.new('RGB', (width, height))
draw = ImageDraw.Draw(img)
# Random background color
bg_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.rectangle([0, 0, width, height], fill=bg_color)
# Draw some random shapes
for _ in range(random.randint(3, 7)):
shape_type = random.choice(['ellipse', 'rectangle'])
# Generate coordinates and sort them to ensure x0 <= x1 and y0 <= y1
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
xy = [
(min(x1, x2), min(y1, y2)),
(max(x1, x2), max(y1, y2))
]
fill_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
if shape_type == 'ellipse':
draw.ellipse(xy, fill=fill_color)
else:
draw.rectangle(xy, fill=fill_color)
# Save image to a byte buffer
from io import BytesIO
buffer = BytesIO()
img.save(buffer, format='JPEG') # Using JPEG as it is standard in PIL
return ContentFile(buffer.getvalue(), name=name)
def _random_string(self, length):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

View File

@@ -0,0 +1,38 @@
from django.core.management.base import BaseCommand
from product.models import Product
class Command(BaseCommand):
help = 'Tüm ürünler için eksik thumb dosyalarını oluşturur'
def handle(self, *args, **options):
products = Product.objects.filter(images__isnull=False)
total = products.count()
created = 0
skipped = 0
self.stdout.write(f'\n{total} ürün kontrol ediliyor...\n')
for product in products:
if not product.thumb:
try:
product.save() # save() metodu thumb'ı otomatik oluşturacak
created += 1
self.stdout.write(
self.style.SUCCESS(f'✓ Thumb oluşturuldu: {product.title}')
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f'✗ Hata ({product.title}): {str(e)}')
)
else:
skipped += 1
self.stdout.write(
self.style.WARNING(f'- Atlandı (zaten var): {product.title}')
)
self.stdout.write(
self.style.SUCCESS(
f'\n✓ Tamamlandı! {created} thumb oluşturuldu, {skipped} atlandı.\n'
)
)

View File

@@ -0,0 +1,123 @@
# Generated by Django 5.2.1 on 2025-06-03 04:02
import autoslug.fields
import django.db.models.deletion
import imagekit.models.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Images',
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ığı')),
('is_active', models.BooleanField(choices=[(True, 'Evet'), (False, 'Hayır')], default=True, verbose_name='Yayındamı ?')),
('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')),
('images', imagekit.models.fields.ProcessedImageField(upload_to='uploads/product/%Y')),
],
options={
'verbose_name': 'Ürün Resmi',
'verbose_name_plural': 'Ürün Resimleri',
'db_table': 'images',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=254, verbose_name='Kategori')),
('keywords', models.CharField(max_length=254, verbose_name='Seo Kelimeleri Aralarına Virgül Koyunuz')),
('description', models.CharField(max_length=254, verbose_name='ıklama')),
('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ı')),
('order', models.IntegerField(db_index=True, default=1, verbose_name='Görüntülenme Sırası')),
('slug', autoslug.fields.AutoSlugField(blank=True, editable=True, max_length=250, populate_from='title', unique=True)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child', to='product.category', verbose_name='Üst Kategorisi')),
],
options={
'verbose_name': 'Ürün Kategori',
'verbose_name_plural': 'Ürün Kategorilerileri',
'db_table': 'categories',
'ordering': ['order'],
'unique_together': {('slug', 'parent')},
},
),
migrations.CreateModel(
name='Tags',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.CharField(max_length=254, verbose_name='Ürün Tagları')),
('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ı')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='tag', unique=True)),
],
options={
'verbose_name': 'Ürün Tagı',
'verbose_name_plural': 'Ürün Tagları',
'db_table': 'tags',
'ordering': ['-created_at'],
'unique_together': {('slug',)},
},
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=254, verbose_name='Ürün Başlığı')),
('content', models.TextField(blank=True, null=True, verbose_name='Ürün İçeriği')),
('keywords', models.CharField(max_length=254, verbose_name='Seo Kelimeleri Aralarına Virgül Koyunuz')),
('price', models.FloatField(verbose_name='Fiyatı')),
('video', models.CharField(blank=True, default='none', max_length=254, null=True, verbose_name='Video')),
('slug', autoslug.fields.AutoSlugField(editable=False, 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ı ?')),
('categories', models.ManyToManyField(related_name='categories', to='product.category', verbose_name='Ürün Kategorisi')),
('images', models.ManyToManyField(related_name='img', to='product.images', verbose_name='Ürün Resimleri')),
('tags', models.ManyToManyField(related_name='tags', to='product.tags', verbose_name='Ürün Tagları')),
],
options={
'verbose_name': 'Ürün',
'verbose_name_plural': 'Ürünler',
'db_table': 'products',
'ordering': ['-created_at'],
'unique_together': {('slug',)},
},
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=254, verbose_name='Yorum Başlığı')),
('body', models.TextField(verbose_name='Yorum')),
('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ı')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child', to='product.comment')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cuser', to=settings.AUTH_USER_MODEL)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='_product', to='product.product')),
],
options={
'verbose_name': 'Ürüm Yorum',
'verbose_name_plural': 'Ürün Yorumları',
'db_table': 'comments',
'ordering': ['-created_at'],
'unique_together': {('slug', 'parent')},
},
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-06-03 04:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='product',
name='kd_price',
field=models.FloatField(blank=True, null=True, verbose_name='Kg Fiyatı'),
),
migrations.AlterField(
model_name='product',
name='price',
field=models.FloatField(verbose_name='Top Fiyatı'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-03 04:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0002_product_kd_price_alter_product_price'),
]
operations = [
migrations.RenameField(
model_name='product',
old_name='kd_price',
new_name='kg_price',
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.1 on 2025-06-03 13:21
import imagekit.models.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0003_rename_kd_price_product_kg_price'),
]
operations = [
migrations.AlterModelOptions(
name='images',
options={'ordering': ['-created_at'], 'verbose_name': 'Galeri Resmi', 'verbose_name_plural': 'Galeri Resimleri'},
),
migrations.AlterField(
model_name='images',
name='images',
field=imagekit.models.fields.ProcessedImageField(upload_to='uploads/galeri/%Y'),
),
migrations.RemoveField(
model_name='product',
name='images',
),
migrations.AlterField(
model_name='product',
name='price',
field=models.FloatField(verbose_name='Birim Fiyatı'),
),
migrations.AddField(
model_name='product',
name='images',
field=imagekit.models.fields.ProcessedImageField(default=1, upload_to='uploads/product/%Y'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.1 on 2025-06-12 01:46
import imagekit.models.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0004_alter_images_options_alter_images_images_and_more'),
]
operations = [
migrations.AddField(
model_name='product',
name='special',
field=models.BooleanField(choices=[(True, 'Evet'), (False, 'Hayır')], default=False, verbose_name='Özel Ürünmü ?'),
),
migrations.AddField(
model_name='product',
name='special_images',
field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to='uploads/product/special/%Y', verbose_name='Eğer bu Ürün Özel Ürünise Resim yükleyin !!'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-12 04:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0005_product_special_product_special_images'),
]
operations = [
migrations.AddField(
model_name='product',
name='thumbnail',
field=models.ImageField(blank=True, editable=False, null=True, upload_to='uploads/product/thumbs/%Y'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.1 on 2025-06-12 18:43
import imagekit.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0006_product_thumbnail'),
]
operations = [
migrations.AddField(
model_name='category',
name='images',
field=imagekit.models.fields.ProcessedImageField(default=1, upload_to='uploads/category/%Y'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.1 on 2025-06-13 01:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0007_category_images'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='special_images',
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.1 on 2025-06-13 01:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0008_remove_product_special_images'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='special',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-13 03:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0009_remove_product_special'),
]
operations = [
migrations.AlterField(
model_name='product',
name='price',
field=models.FloatField(default='50', verbose_name='Birim Fiyatı'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-06-13 12:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0010_alter_product_price'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='kg_price',
),
migrations.AddField(
model_name='product',
name='brim',
field=models.CharField(default=1, max_length=10, verbose_name='Birim'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-13 12:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0011_remove_product_kg_price_product_brim'),
]
operations = [
migrations.AlterField(
model_name='product',
name='brim',
field=models.CharField(choices=[('Top', 'Top'), ('Kg', 'Kg'), ('Adet', 'Adet'), ('Litre', 'Litre')], max_length=10, verbose_name='Birim'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-13 12:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0012_alter_product_brim'),
]
operations = [
migrations.AlterField(
model_name='product',
name='brim',
field=models.CharField(choices=[('Top', 'Top'), ('Kg', 'Kg'), ('Adet', 'Adet'), ('Dilim', 'Dilim'), ('Litre', 'Litre')], max_length=10, verbose_name='Birim'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.1 on 2025-06-13 13:02
import autoslug.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0013_alter_product_brim'),
]
operations = [
migrations.AlterField(
model_name='product',
name='slug',
field=autoslug.fields.AutoSlugField(blank=True, editable=True, max_length=250, populate_from='title', unique=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-14 14:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0014_alter_product_slug'),
]
operations = [
migrations.AlterField(
model_name='product',
name='brim',
field=models.CharField(choices=[('Top', 'Top'), ('Kg', 'Kg'), ('Adet', 'Adet'), ('Fincan', 'Fincan'), ('Bardak', 'Bardak'), ('Dilim', 'Dilim'), ('Litre', 'Litre')], max_length=10, verbose_name='Birim'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-14 15:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0015_alter_product_brim'),
]
operations = [
migrations.AddField(
model_name='product',
name='gallery',
field=models.ManyToManyField(related_name='gallery', to='product.images', verbose_name='Galeri'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-15 01:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0016_product_gallery'),
]
operations = [
migrations.AlterField(
model_name='product',
name='brim',
field=models.CharField(choices=[('Top', 'Top'), ('Kg', 'Kg'), ('Adet', 'Adet'), ('Porsiyon', 'Porsiyon'), ('Fincan', 'Fincan'), ('Bardak', 'Bardak'), ('Dilim', 'Dilim'), ('Litre', 'Litre')], max_length=10, verbose_name='Birim'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-15 01:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0017_alter_product_brim'),
]
operations = [
migrations.AddField(
model_name='product',
name='is_front',
field=models.BooleanField(choices=[(True, 'Evet'), (False, 'Hayır')], default=True, verbose_name='Önde Görünsünmü ?'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.1 on 2025-06-15 14:32
import imagekit.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0018_product_is_front'),
]
operations = [
migrations.AddField(
model_name='product',
name='thumb',
field=imagekit.models.fields.ProcessedImageField(default=1, upload_to='uploads/thumb/%Y'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-06-15 14:35
import imagekit.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0019_product_thumb'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='thumbnail',
),
migrations.AlterField(
model_name='product',
name='thumb',
field=imagekit.models.fields.ProcessedImageField(editable=False, upload_to='uploads/thumb/%Y'),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 6.0 on 2026-01-18 22:26
import core.utils
import imagekit.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0020_remove_product_thumbnail_alter_product_thumb'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='thumb',
),
migrations.AlterField(
model_name='category',
name='images',
field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/category')),
),
migrations.AlterField(
model_name='images',
name='images',
field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/images')),
),
migrations.AlterField(
model_name='product',
name='images',
field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/post')),
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 6.0 on 2026-01-18 22:37
import core.utils
import imagekit.models.fields
import tinymce.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0021_remove_product_thumb_alter_category_images_and_more'),
]
operations = [
migrations.AlterField(
model_name='product',
name='content',
field=tinymce.models.HTMLField(blank=True, null=True, verbose_name='Ürün İçeriği'),
),
migrations.AlterField(
model_name='product',
name='images',
field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/products')),
),
]

View File

@@ -0,0 +1,48 @@
# Generated by Django 6.0 on 2026-01-19 19:04
import core.utils
import imagekit.models.fields
import tinymce.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0022_alter_product_content_alter_product_images'),
]
operations = [
migrations.AlterField(
model_name='product',
name='brim',
field=models.CharField(choices=[('Kg', 'Kg'), ('Adet', 'Adet')], max_length=10, verbose_name='Birim'),
),
migrations.AlterField(
model_name='product',
name='categories',
field=models.ManyToManyField(related_name='products', to='product.category', verbose_name='Ürün Kategorisi'),
),
migrations.CreateModel(
name='ProductTree',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=254, verbose_name='Text')),
('button', models.CharField(max_length=254, verbose_name='Button Text')),
('content', tinymce.models.HTMLField(blank=True, null=True, verbose_name='Ürün İçeriği')),
('price', models.FloatField(default='50', verbose_name='Birim Fiyatı')),
('images', imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/products'))),
('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ü ?')),
('categories', models.ManyToManyField(related_name='product_trees', to='product.category', verbose_name='Ürün Kategorisi')),
],
options={
'verbose_name': 'Ürün',
'verbose_name_plural': 'Ürünler',
'db_table': 'products_tree',
'ordering': ['-created_at'],
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 6.0 on 2026-01-19 19:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0023_alter_product_brim_alter_product_categories_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='producttree',
options={'ordering': ['-created_at'], 'verbose_name': 'Ürün Tree', 'verbose_name_plural': 'Ürünler Tree'},
),
migrations.RemoveField(
model_name='producttree',
name='categories',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0 on 2026-01-19 19:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0024_alter_producttree_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='producttree',
name='content',
field=models.TextField(blank=True, null=True, verbose_name='Ürün İçeriği'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0 on 2026-01-20 18:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0025_alter_producttree_content'),
]
operations = [
migrations.AddField(
model_name='producttree',
name='categories',
field=models.ManyToManyField(related_name='product_trees', to='product.category', verbose_name='Ürün Kategorisi'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 6.0 on 2026-01-21 01:07
import core.utils
import imagekit.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0026_producttree_categories'),
]
operations = [
migrations.AddField(
model_name='product',
name='thumb',
field=imagekit.models.fields.ProcessedImageField(blank=True, editable=False, null=True, upload_to=core.utils.UniquePathAndRename('uploads/products/thumb')),
),
]

View File

275
product/models.py Normal file
View File

@@ -0,0 +1,275 @@
import os
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import models
from imagekit.models import ProcessedImageField
from autoslug import AutoSlugField
from tinymce.models import HTMLField
from core.utils import image_optimizer
from reviews.models import RateableMixin # RateableMixin import edildi
# Create your models here.
class Category(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
title = models.CharField(max_length=254, verbose_name="Kategori")
keywords = models.CharField(max_length=254, verbose_name="Seo Kelimeleri Aralarına Virgül Koyunuz")
description = models.CharField(max_length=254, verbose_name="ıklama")
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)
order = models.IntegerField(verbose_name='Görüntülenme Sırası', default=1, db_index=True)
slug = AutoSlugField(populate_from='title', null=False, unique=True, editable=True, db_index=True, max_length=250,
blank=True)
parent = models.ForeignKey('self', related_name='child', on_delete=models.CASCADE, blank=True, null=True,
verbose_name='Üst Kategorisi')
images = ProcessedImageField(**image_optimizer('uploads/category', 400, 400, 90, 'avif'), null=True, blank=True)
class Meta:
ordering = ["order"]
db_table = 'categories'
verbose_name_plural = "Ürün Kategorilerileri"
verbose_name = "Ürün Kategori"
unique_together = ('slug', 'parent',)
def get_slug(self):
slug = self.title.replace('ı', "i").replace('İ', 'i')
number = 1
while Category.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):
full_path = [self.title]
k = self.parent
while k is not None:
full_path.append(k.title)
k = k.parent
return ' -> '.join(full_path[::-1])
class Tags(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
tag = models.CharField(max_length=254, verbose_name="Ürün Tagları")
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)
slug = AutoSlugField(populate_from='tag', null=False, unique=True, editable=False, db_index=True)
class Meta:
ordering = ["-created_at"]
db_table = 'tags'
verbose_name_plural = "Ürün Tagları"
verbose_name = "Ürün Tagı"
unique_together = ('slug',)
def get_slug(self):
slug = self.tag.replace('ı', "i").replace('İ', 'i')
number = 1
while Tags.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 self.tag
class Images(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
title = models.CharField(max_length=254, verbose_name="Resim Başlığı")
is_active = models.BooleanField(default=True, verbose_name='Yayındamı ?', choices=aktif)
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")
images = ProcessedImageField(**image_optimizer('uploads/images', 1500, 1500, 90, 'avif'), null=True, blank=True)
class Meta:
ordering = ["-created_at"]
db_table = 'images'
verbose_name_plural = "Galeri Resimleri"
verbose_name = "Galeri Resmi"
def __str__(self):
return self.title
class Product(RateableMixin, models.Model): # RateableMixin eklendi
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
birim = (
('Kg', 'Kg'),
('Adet', 'Adet'),
)
title = models.CharField(max_length=254, verbose_name="Ürün Başlığı")
content = HTMLField(blank=True, null=True, verbose_name='Ürün İçeriği')
categories = models.ManyToManyField(Category, verbose_name="Ürün Kategorisi", related_name='products')
keywords = models.CharField(max_length=254, verbose_name="Seo Kelimeleri Aralarına Virgül Koyunuz")
brim = models.CharField(max_length=10, verbose_name="Birim",choices=birim)
price = models.FloatField(verbose_name='Birim Fiyatı',default='50')
tags = models.ManyToManyField(Tags, verbose_name="Ürün Tagları", related_name='tags')
gallery = models.ManyToManyField(Images, verbose_name="Galeri", related_name='gallery')
images = ProcessedImageField(**image_optimizer('uploads/products', 260, 260, 90, 'avif'), null=True, blank=True)
thumb = ProcessedImageField(**image_optimizer('uploads/products/thumb', 150, 150, 85, 'avif'), null=True, blank=True, editable=False)
video = models.CharField(verbose_name="Video", null=True, blank=True, max_length=254, default='none')
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 = 'products'
verbose_name_plural = "Ürünler"
verbose_name = "Ürün"
unique_together = ('slug',)
def get_slug(self):
slug = self.title.replace('ı', "i").replace('İ', 'i')
number = 1
while Product.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()
if self.images:
# Eğer yeni bir kayıt ise veya resim değişmişse veya thumb yoksa
update_thumb = False
if not self.pk:
update_thumb = True
elif not self.thumb:
# Thumb yoksa oluştur
update_thumb = True
else:
try:
old_instance = self.__class__.objects.get(pk=self.pk)
if self.images != old_instance.images:
update_thumb = True
except self.__class__.DoesNotExist:
pass
if update_thumb:
try:
if hasattr(self.images, 'closed') and self.images.closed:
self.images.open()
if hasattr(self.images, 'seek'):
self.images.seek(0)
content = self.images.read()
filename = os.path.basename(self.images.name)
# Doğrudan alana ata, super().save() işleyecek
self.thumb = ContentFile(content, name=filename)
except Exception:
pass
finally:
if hasattr(self.images, 'seek'):
self.images.seek(0)
super().save(*args, **kwargs)
def __str__(self):
return f"Ürünler: {self.title}"
class ProductTree(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
birim = (
('Kg', 'Kg'),
('Adet', 'Adet'),
)
title = models.CharField(max_length=254, verbose_name="Text")
button = models.CharField(max_length=254, verbose_name="Button Text")
content = models.TextField(blank=True, null=True, verbose_name='Ürün İçeriği')
categories = models.ManyToManyField(Category, verbose_name="Ürün Kategorisi", related_name='product_trees')
price = models.FloatField(verbose_name='Birim Fiyatı',default='50')
images = ProcessedImageField(**image_optimizer('uploads/products', 740, 1100, 90, 'avif'), null=True, 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 = 'products_tree'
verbose_name_plural = "Ürünler Tree"
verbose_name = "Ürün Tree"
def __str__(self):
return f"Ürünler Tree: {self.title}"
class Comment(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='cuser')
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='_product')
title = models.CharField(max_length=254, verbose_name="Yorum Başlığı")
body = models.TextField(verbose_name='Yorum')
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)
slug = AutoSlugField(populate_from='title', null=False, unique=True, editable=False, db_index=True)
parent = models.ForeignKey('self', related_name='child', on_delete=models.CASCADE, blank=True, null=True)
class Meta:
ordering = ["-created_at"]
db_table = 'comments'
verbose_name_plural = "Ürün Yorumları"
verbose_name = "Ürüm Yorum"
unique_together = ('slug', 'parent',)
def get_slug(self):
slug = self.title.replace('ı', "i").replace('İ', 'i')
number = 1
while Comment.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):
full_path = [self.title]
k = self.parent
while k is not None:
full_path.append(k.title)
k = k.parent
return ' -> '.join(full_path[::-1])

100
product/serializers.py Normal file
View File

@@ -0,0 +1,100 @@
from rest_framework import serializers
from product.models import Category, Product, Images, Tags, ProductTree
class CateSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['title', 'parent', 'is_active', 'created_at', 'order', 'slug', 'images', 'keywords', 'description']
def get_images(self, obj):
if obj.images:
return obj.images.url
return None
class GalSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
class Meta:
model = Images
fields = ['title', 'images']
def get_images(self, obj):
if obj.images:
return obj.images.url
return None
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ['tag', 'slug']
class ProductTreeSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
categories = CateSerializer(read_only=True, many=True)
class Meta:
model = ProductTree
fields = ['id', 'title', 'button', 'content', 'categories', 'price', 'images', 'created_at', 'updated_at', 'is_active', 'is_front']
def get_images(self, obj):
if obj.images:
return obj.images.url
return None
class ProductSerializer(serializers.ModelSerializer):
categories = CateSerializer(read_only=True, many=True)
gallery = GalSerializer(read_only=True, many=True)
tags = TagSerializer(read_only=True, many=True)
images = serializers.SerializerMethodField()
thumb = serializers.SerializerMethodField()
# RateableMixin'den gelen property'leri buraya ekliyoruz
average_rating = serializers.FloatField(read_only=True)
rating_count = serializers.IntegerField(read_only=True)
class Meta:
model = Product
fields = ['id', 'title', 'content', 'categories', 'keywords', 'brim', 'tags', 'gallery', 'images', 'thumb', 'video',
'slug', 'created_at', 'updated_at', 'is_active', 'is_front', 'price', 'average_rating', 'rating_count']
def get_images(self, obj):
if obj.images:
return obj.images.url
return None
def get_thumb(self, obj):
try:
if obj.thumb:
return obj.thumb.url
except Exception:
# Thumbnail oluşturulamadıysa veya bir hata olursa ana resmi dene
if obj.images:
return obj.images.url
return None
class CategorySerializer(serializers.ModelSerializer):
categories = ProductSerializer(read_only=True, many=True)
child = serializers.SerializerMethodField()
images = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['title', 'parent', 'is_active', 'created_at', 'order', 'slug', 'images', 'keywords', 'description',
'categories', 'child']
def get_images(self, obj):
if obj.images:
return obj.images.url
return None
def get_child(self, obj):
serializer = self.__class__(obj.child.all(), many=True, context=self.context)
return serializer.data

35
product/signals.py Normal file
View File

@@ -0,0 +1,35 @@
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .models import Product, Category, ProductTree
@receiver([post_save, post_delete], sender=Product)
def clear_product_cache(sender, instance, **kwargs):
"""
Product modeli güncellendiğinde, silindiğinde veya yeni bir ürün eklendiğinde
ilgili cache'leri temizler.
"""
cache.delete('product:products:list')
cache.delete('product:products:featured')
cache.delete(f'product:product:{instance.slug}')
@receiver([post_save, post_delete], sender=Category)
def clear_category_cache(sender, instance, **kwargs):
"""
Category modeli güncellendiğinde, silindiğinde veya yeni bir kategori eklendiğinde
ilgili cache'leri temizler.
"""
cache.delete('product:categories:list')
cache.delete(f'product:category:{instance.slug}')
# Eğer alt kategoriler varsa, üst kategorinin de cache'ini temizlemek gerekebilir.
if instance.parent:
cache.delete(f'product:category:{instance.parent.slug}')
@receiver([post_save, post_delete], sender=ProductTree)
def clear_product_tree_cache(sender, instance, **kwargs):
"""
ProductTree modeli güncellendiğinde, silindiğinde veya yeni bir nesne eklendiğinde
ilgili cache'i temizler.
"""
cache.delete('product:product_trees:list')

3
product/tests.py Normal file
View File

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

13
product/urls.py Normal file
View File

@@ -0,0 +1,13 @@
from django.urls import path
from product.views import CategoryListView, CategoryRetrieveAPIView, ProductListAPIView, ProductRetrieveAPIView, \
ProductListTreeAPIView, FeaturedProductListAPIView
urlpatterns = [ # Success/Error pages
path('categories/', CategoryListView.as_view(), name='categories.list'),
path('categories/<slug:slug>/', CategoryRetrieveAPIView.as_view(), name='categories.details'),
path('products-tree/', ProductListTreeAPIView.as_view(), name='products.tree'),
path('products-featured/', FeaturedProductListAPIView.as_view(), name='products.featured'),
path('products/', ProductListAPIView.as_view(), name='products.list'),
path('products/<slug:slug>/', ProductRetrieveAPIView.as_view(), name='products.details'),
]

137
product/views.py Normal file
View File

@@ -0,0 +1,137 @@
from django.core.cache import cache
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from core.Permission import ReadOnly
from product.models import Category, Product, ProductTree
from product.serializers import CategorySerializer, ProductSerializer, ProductTreeSerializer
CACHE_TTL = 60 * 5 # 5 dakika
class StandardResultsSetPagination(PageNumberPagination):
page_size = 12
page_size_query_param = 'page_size'
max_page_size = 100
class CategoryListView(generics.ListAPIView):
permission_classes = [ReadOnly]
serializer_class = CategorySerializer
queryset = Category.objects.order_by('order').filter(is_active=True, parent__isnull=True).all()
def get(self, request, *args, **kwargs):
cache_key = 'product:categories:list'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
cache.set(cache_key, serializer.data, timeout=CACHE_TTL)
return Response(serializer.data)
class CategoryRetrieveAPIView(generics.RetrieveAPIView):
permission_classes = [ReadOnly]
queryset = Category.objects.order_by('order').filter(is_active=True).all()
serializer_class = CategorySerializer
lookup_field = 'slug'
def get(self, request, *args, **kwargs):
slug = self.kwargs.get('slug')
cache_key = f'product:category:{slug}'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
instance = self.get_object()
serializer = self.get_serializer(instance)
cache.set(cache_key, serializer.data, timeout=CACHE_TTL)
return Response(serializer.data)
class ProductListAPIView(generics.ListAPIView):
permission_classes = [ReadOnly]
serializer_class = ProductSerializer
queryset = Product.objects.filter(is_active=True).all()
pagination_class = StandardResultsSetPagination
def get(self, request, *args, **kwargs):
# Sayfa numarasını al
page = request.query_params.get('page', 1)
cache_key = f'product:products:list:page:{page}'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
queryset = self.get_queryset()
# Sayfalama işlemini uygula
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# Sayfalanmış yanıtı al (count, next, previous, results yapısı)
response = self.get_paginated_response(serializer.data)
# Yanıtın verisini (data) cache'e kaydet
cache.set(cache_key, response.data, timeout=CACHE_TTL)
return response
# Sayfalama yoksa normal döndür
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class FeaturedProductListAPIView(generics.ListAPIView):
permission_classes = [ReadOnly]
serializer_class = ProductSerializer
queryset = Product.objects.filter(is_active=True, is_front=True).all()
def get(self, request, *args, **kwargs):
cache_key = 'product:products:featured'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
cache.set(cache_key, serializer.data, timeout=CACHE_TTL)
return Response(serializer.data)
class ProductListTreeAPIView(generics.ListAPIView):
permission_classes = [ReadOnly]
serializer_class = ProductTreeSerializer
queryset = ProductTree.objects.filter(is_active=True).all()[:3]
def get(self, request, *args, **kwargs):
cache_key = 'product:product_trees:list'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
cache.set(cache_key, serializer.data, timeout=CACHE_TTL)
return Response(serializer.data)
class ProductRetrieveAPIView(generics.RetrieveAPIView):
permission_classes = [ReadOnly]
queryset = Product.objects.filter(is_active=True).all()
serializer_class = ProductSerializer
lookup_field = 'slug'
def get(self, request, *args, **kwargs):
slug = self.kwargs.get('slug')
cache_key = f'product:product:{slug}'
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
instance = self.get_object()
serializer = self.get_serializer(instance)
cache.set(cache_key, serializer.data, timeout=CACHE_TTL)
return Response(serializer.data)