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
blog/__init__.py Normal file
View File

101
blog/admin.py Normal file
View File

@@ -0,0 +1,101 @@
from django.contrib import admin
from django.utils.safestring import mark_safe
from blog.models import Category, Tags, Post, Comment, CategoryView
# Register your models here.
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'post_resim', 'is_active', 'post_kategorileri', 'slug')
list_filter = ('is_active', 'categories')
search_fields = ('title', 'is_active', 'slug', 'content')
list_editable = ('is_active', 'slug',) # Removed 'price' as it is not a field
class Meta:
model = Post
def save_model(self, request, obj, form, change):
"""Admin'de kaydetme sırasında image'ı thumb'a da kopyala"""
# Model save metodu zaten bu işi yapıyor, buradaki koda gerek yok aslında
# ama form üzerinden gelen veriyi kontrol etmek için bırakılabilir.
# Ancak model save metodundaki mantık daha sağlam olduğu için burayı sadeleştiriyoruz.
super().save_model(request, obj, form, change)
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 post_tags(self, obj):
tags = '<ul>'
for tag in obj.tags.all():
tags += '<li>' + tag.tag + '</li>'
tags += '</ul>'
return mark_safe(tags)
def post_kategorileri(self, obj):
html = '<ul>'
for category in obj.categories.all():
html += '<li>' + category.title + '</li>'
html += '</ul>'
return mark_safe(html)
def post_resim(self, obj):
if obj.image:
# Uygulama adı 'blog' olduğu için URL yapısı /admin/blog/post/... olmalı
return mark_safe(
'<a href="/admin/blog/post/{}/change/"><img src="{}" width="50" height="50" style="object-fit: cover;" /></a>'.format(
obj.id, obj.image.url))
return mark_safe('Resim Yok')
post_resim.short_description = 'Kurs Resmi'
admin.site.register(Post, PostAdmin)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('title', 'parent_category', 'is_active', 'created_at', 'order') # Removed 'view_count' and 'unique_view_count'
list_filter = ('title', 'is_active', 'created_at', 'parent')
search_fields = ('title', 'is_active', 'slug')
list_editable = ('is_active', 'order')
class Meta:
model = Category
def parent_category(self, obj):
if obj.parent:
return obj.parent.title
return "Ana Kategori"
parent_category.short_description = 'Üst Kategori'
admin.site.register(Category, CategoryAdmin)
class TagsAdmin(admin.ModelAdmin):
list_display = ('tag', 'created_at',)
list_filter = ('tag',)
search_fields = ('tag',)
class Meta:
model = Tags
admin.site.register(Tags, TagsAdmin)
class CategoryViewAdmin(admin.ModelAdmin):
list_display = ('category', 'ip_address', 'created_at')
list_filter = ('created_at', 'category')
search_fields = ('ip_address', 'category__title')
readonly_fields = ('category', 'ip_address', 'user_agent', 'created_at')
class Meta:
model = CategoryView
admin.site.register(CategoryView, CategoryViewAdmin)
admin.site.register(Comment)

9
blog/apps.py Normal file
View File

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

View File

View File

View File

@@ -0,0 +1,85 @@
import os
import random
import string
import requests
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
from blog.models import Post, Category, Tags
try:
from faker import Faker
fake = Faker()
HAS_FAKER = True
except ImportError:
HAS_FAKER = False
class Command(BaseCommand):
help = 'Creates 300 fake posts with random images'
def handle(self, *args, **kwargs):
User = get_user_model()
# Prefer an existing staff user, otherwise any existing user, otherwise create a fallback user
user = User.objects.filter(is_staff=True).first() or User.objects.first()
if not user:
self.stdout.write('Hiç kullanıcı bulunamadı, `fakeuser` oluşturuluyor...')
user = User.objects.create(username='fakeuser', email='fake@example.com')
user.set_unusable_password()
user.save()
categories = list(Category.objects.all())
if not categories:
self.stdout.write(self.style.ERROR('Lütfen önce en az bir kategori oluşturun!'))
return
tags = list(Tags.objects.all())
if not tags:
self.stdout.write('Tag bulunamadı, oluşturuluyor...')
for i in range(5):
tag_name = self.get_random_string(8) if not HAS_FAKER else fake.word()
Tags.objects.create(tag=tag_name)
tags = list(Tags.objects.all())
self.stdout.write('300 adet fake post oluşturuluyor...')
for i in range(300):
if HAS_FAKER:
title = fake.sentence(nb_words=6).replace('.', '')
content = '\n\n'.join(fake.paragraphs(nb=5))
keywords = ", ".join(fake.words(nb=5))
else:
title = self.get_random_string(30)
content = self.get_random_string(500)
keywords = self.get_random_string(20)
post = Post(
user=user,
title=title,
content=content,
keywords=keywords,
video='none',
is_active=True,
is_front=True
)
post.save()
# ManyToMany ilişkileri
post.categories.add(random.choice(categories))
post.tags.add(random.choice(tags))
# Resim ekle
try:
# Picsum'dan rastgele resim (800x600)
img_url = f"https://picsum.photos/seed/{random.randint(1, 10000)}/800/600"
response = requests.get(img_url, timeout=10)
if response.status_code == 200:
file_name = f"fake_post_{i}_{random.randint(1000,9999)}.jpg"
post.image.save(file_name, ContentFile(response.content), save=True)
self.stdout.write(f'Post {i+1}/300 oluşturuldu: {title} (Resimli)')
else:
self.stdout.write(f'Post {i+1}/300 oluşturuldu: {title} (Resimsiz - İndirme hatası)')
except Exception as e:
self.stdout.write(f'Post {i+1}/300 oluşturuldu: {title} (Resimsiz - Hata: {str(e)})')
def get_random_string(self, length):
letters = string.ascii_letters + string.digits + ' '
return ''.join(random.choice(letters) for i in range(length))

View File

@@ -0,0 +1,127 @@
# Generated by Django 5.2.9 on 2026-01-05 22:33
import autoslug.fields
import core.utils
import django.db.models.deletion
import imagekit.models.fields
import tinymce.models
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='Tags',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.CharField(max_length=254, verbose_name='Post Tagları')),
('slug', autoslug.fields.AutoSlugField(blank=True, editable=True, max_length=250, populate_from='tag', 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ı')),
],
options={
'verbose_name': 'Post Tagı',
'verbose_name_plural': 'Post Tagları',
'db_table': 'tags',
'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)),
('image', imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/category'), verbose_name='Resim 630 x 653 Olmali ve Transparan PNG Olmali')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child', to='blog.category', verbose_name='Üst Kategorisi')),
],
options={
'verbose_name': 'Post Kategori',
'verbose_name_plural': 'Post Kategorilerileri',
'db_table': 'categories',
'ordering': ['order'],
'unique_together': {('slug', 'parent')},
},
),
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='Post Başlığı')),
('content', tinymce.models.HTMLField(blank=True, null=True, verbose_name='Post İçeriği')),
('keywords', models.CharField(max_length=254, verbose_name='Seo Kelimeleri Aralarına Virgül Koyunuz')),
('image', imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to=core.utils.UniquePathAndRename('uploads/post'))),
('thumb', imagekit.models.fields.ProcessedImageField(blank=True, editable=False, null=True, upload_to=core.utils.UniquePathAndRename('uploads/post/thumb'))),
('video', models.CharField(blank=True, default='none', max_length=254, null=True, verbose_name='Video')),
('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ü ?')),
('categories', models.ManyToManyField(related_name='c_categories', to='blog.category', verbose_name='Post Kategorisi')),
('parent', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child', to='blog.post', verbose_name='Konular')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='posts', to=settings.AUTH_USER_MODEL)),
('tags', models.ManyToManyField(related_name='tags', to='blog.tags', verbose_name='Post Tagları')),
],
options={
'verbose_name': 'Post',
'verbose_name_plural': 'Posts',
'db_table': 'posts',
'ordering': ['created_at'],
'unique_together': {('slug',)},
},
),
migrations.CreateModel(
name='CategoryView',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip_address', models.GenericIPAddressField(verbose_name='IP Adresi')),
('user_agent', models.TextField(blank=True, null=True, verbose_name='User Agent')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Ziyaret Tarihi')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_views', to='blog.category')),
],
options={
'verbose_name': 'Kategori Ziyareti',
'verbose_name_plural': 'Kategori Ziyaretleri',
'db_table': 'category_views',
'indexes': [models.Index(fields=['category', 'ip_address', 'created_at'], name='category_vi_categor_234334_idx')],
},
),
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='blog.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='blog.post')),
],
options={
'verbose_name': 'Post Yorum',
'verbose_name_plural': 'Post Yorumları',
'db_table': 'comments',
'ordering': ['-created_at'],
'unique_together': {('slug', 'parent')},
},
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.9 on 2026-01-09 03:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='comment',
name='product',
),
migrations.AddField(
model_name='comment',
name='post',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='_post', to='blog.post'),
preserve_default=False,
),
]

View File

240
blog/models.py Normal file
View File

@@ -0,0 +1,240 @@
import os
from autoslug import AutoSlugField
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import models
from imagekit.models import ProcessedImageField
from tinymce.models import HTMLField
from core.utils import image_optimizer
# 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')
image = ProcessedImageField(**image_optimizer('uploads/category', 300, 300, 85, 'PNG'),
verbose_name='Resim 630 x 653 Olmali ve Transparan PNG Olmali', blank=True, null=True)
class Meta:
ordering = ["order"]
db_table = 'categories'
verbose_name_plural = "Post Kategorilerileri"
verbose_name = "Post 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="Post Tagları")
slug = AutoSlugField(populate_from='tag', 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)
class Meta:
ordering = ["-created_at"]
db_table = 'tags'
verbose_name_plural = "Post Tagları"
verbose_name = "Post Tagı"
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 Post(models.Model):
aktif = (
(True, 'Evet'),
(False, 'Hayır'),
)
title = models.CharField(max_length=254, verbose_name="Post Başlığı")
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts', null=True, blank=True)
content = HTMLField(blank=True, null=True, verbose_name='Post İçeriği')
categories = models.ManyToManyField(Category, verbose_name="Post Kategorisi", related_name='c_categories')
keywords = models.CharField(max_length=254, verbose_name="Seo Kelimeleri Aralarına Virgül Koyunuz")
tags = models.ManyToManyField(Tags, verbose_name="Post Tagları", related_name='tags')
image = ProcessedImageField(**image_optimizer('uploads/post', 840, 500, 85, 'avif'), null=True, blank=True)
thumb = ProcessedImageField(**image_optimizer('uploads/post/thumb', 348, 160, 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)
parent = models.ForeignKey('self', related_name='child', on_delete=models.CASCADE, blank=True, null=True,
editable=False,
verbose_name='Konular')
class Meta:
ordering = ["created_at"]
db_table = 'posts'
verbose_name_plural = "Posts"
verbose_name = "Post"
unique_together = ('slug',)
def get_slug(self):
slug = self.title.replace('ı', "i").replace('İ', 'i')
number = 1
while Post.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.image:
# Eğer yeni bir kayıt ise veya resim değişmişse
update_thumb = False
if not self.pk:
update_thumb = True
else:
try:
old_instance = self.__class__.objects.get(pk=self.pk)
if self.image != old_instance.image:
update_thumb = True
except self.__class__.DoesNotExist:
pass
if update_thumb:
try:
if hasattr(self.image, 'closed') and self.image.closed:
self.image.open()
if hasattr(self.image, 'seek'):
self.image.seek(0)
content = self.image.read()
filename = os.path.basename(self.image.name)
# Doğrudan alana ata, super().save() işleyecek
self.thumb = ContentFile(content, name=filename)
except Exception:
pass
finally:
if hasattr(self.image, 'seek'):
self.image.seek(0)
super().save(*args, **kwargs)
def __str__(self):
return f"Postlar: {self.title}"
class CategoryView(models.Model):
"""Kategori ziyaretlerini takip etmek için model"""
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category_views')
ip_address = models.GenericIPAddressField(verbose_name='IP Adresi')
user_agent = models.TextField(blank=True, null=True, verbose_name='User Agent')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Ziyaret Tarihi')
class Meta:
db_table = 'category_views'
verbose_name = 'Kategori Ziyareti'
verbose_name_plural = 'Kategori Ziyaretleri'
# unique_together kısıtlamasını kaldırdık - artık günlük bazda kontrol edeceğiz
indexes = [
models.Index(fields=['category', 'ip_address', 'created_at']),
]
def __str__(self):
return f"{self.category.title} - {self.ip_address} - {self.created_at.strftime('%Y-%m-%d %H:%M')}"
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(Post, 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 = "Post Yorumları"
verbose_name = "Post 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])

89
blog/serializers.py Normal file
View File

@@ -0,0 +1,89 @@
from rest_framework import serializers
from blog.models import Category, Post, Tags
class CateSerializer(serializers.ModelSerializer):
parent = serializers.StringRelatedField() # ID yerine __str__ metodundaki değeri döndürür
class Meta:
model = Category
fields = ['title', 'parent', 'is_active', 'created_at', 'order', 'slug', 'image', 'keywords', 'description']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ['tag', 'slug']
class PostSerializer(serializers.ModelSerializer):
categories = CateSerializer(read_only=True, many=True)
# Tags için sadece tag ismini döndürmek daha temiz olabilir, ama mevcut yapıyı koruyalım
# Eğer sadece isim listesi istenirse: tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field='tag')
tags = TagSerializer(read_only=True, many=True)
class Meta:
model = Post
fields = ['title', 'content', 'categories', 'keywords', 'tags', 'image', 'thumb', 'video',
'slug', 'created_at', 'updated_at', 'is_active', 'is_front']
# fields = '__all__'
class PostSYalinerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['slug', ]
# fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
posts = PostSYalinerializer(source='c_categories', read_only=True, many=True)
child = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['title', 'parent', 'is_active', 'created_at', 'order', 'slug', 'image', 'keywords', 'description',
'posts', 'child']
def get_child(self, obj):
serializer = self.__class__(obj.child.all(), many=True, context=self.context)
return serializer.data
class CategoryPostSerializer(serializers.ModelSerializer):
posts = serializers.SerializerMethodField()
child = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['title', 'parent', 'is_active', 'created_at', 'order', 'slug', 'image', 'keywords', 'description',
'posts', 'child']
def get_posts(self, obj):
# Pagination context'ini al
paginator = self.context.get('paginator')
request = self.context.get('request')
posts = obj.c_categories.all()
if paginator and request:
# Pagination uygula
paginated_posts = paginator.paginate_queryset(posts, request)
serializer = PostSerializer(paginated_posts, many=True, context=self.context)
return {
'results': serializer.data,
'count': posts.count(),
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
}
else:
# Pagination yoksa normal döndür
serializer = PostSerializer(posts, many=True, context=self.context)
return serializer.data
def get_child(self, obj):
serializer = self.__class__(obj.child.all(), many=True, context=self.context)
return serializer.data

52
blog/signals.py Normal file
View File

@@ -0,0 +1,52 @@
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.core.files.base import ContentFile
from .models import Post
@receiver(pre_save, sender=Post)
def update_post_thumb(sender, instance, **kwargs):
"""
Post kaydedilmeden önce, image alanı doluysa thumb'ı da güncelle
"""
if instance.image:
# Yeni kayıt veya image güncellenmiş mi kontrol et
should_update_thumb = False
if instance.pk:
try:
old_instance = Post.objects.get(pk=instance.pk)
# Image değişmişse thumb'ı da güncelle
if str(old_instance.image) != str(instance.image):
should_update_thumb = True
except Post.DoesNotExist:
# Kayıt bulunamadı, yeni kayıt gibi davran
should_update_thumb = True
else:
# Yeni kayıt (pk yok)
should_update_thumb = True
if should_update_thumb and hasattr(instance.image, 'file'):
# Image dosyasını thumb alanına kopyala
try:
# Image dosyasının içeriğini oku
instance.image.file.seek(0)
image_content = instance.image.file.read()
instance.image.file.seek(0)
# Dosya adını al
image_name = instance.image.name.split('/')[-1]
# Thumb alanına kaydet
instance.thumb.save(
image_name,
ContentFile(image_content),
save=False
)
except Exception as e:
print(f"Thumb oluşturma hatası: {e}")
import traceback
traceback.print_exc()

40
blog/tasks.py Normal file
View File

@@ -0,0 +1,40 @@
from celery import shared_task
from django.core.mail import send_mail
from django.conf import settings
@shared_task
def send_comment_notification_email(comment_title, comment_body, post_title, user_email):
"""
Yeni bir yorum yapıldığında admin'e e-posta gönderir.
"""
subject = f'Yeni Yorum: {post_title}'
message = f"""
Merhaba Admin,
"{post_title}" başlıklı yazıya yeni bir yorum yapıldı.
Yorum Yapan: {user_email}
Başlık: {comment_title}
Yorum: {comment_body}
Kontrol etmek için admin paneline giriş yapabilirsiniz.
"""
# Admin e-posta adresini settings'den veya doğrudan buraya yazabilirsiniz
# Örnek olarak settings.DEFAULT_FROM_EMAIL kullanıldı, admin listesi de kullanılabilir
admin_email = settings.DEFAULT_FROM_EMAIL
# Eğer settings.ADMINS tanımlıysa oradaki ilk kişiye de atılabilir
if hasattr(settings, 'ADMINS') and settings.ADMINS:
recipient_list = [email for name, email in settings.ADMINS]
else:
# Fallback olarak bir email
recipient_list = ['admin@example.com']
send_mail(
subject,
message,
settings.DEFAULT_FROM_EMAIL,
recipient_list,
fail_silently=False,
)

3
blog/tests.py Normal file
View File

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

10
blog/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.urls import path
from blog.views import CategoryList, CategoryDetail, PostDetail, PostList
urlpatterns = [
path('categories/', CategoryList.as_view(), name='categories.list'),
path('categories/<slug:slug>/', CategoryDetail.as_view(), name='categories.details'),
path('post/', PostList.as_view(), name='post.list'),
path('post/<slug:slug>/', PostDetail.as_view(), name='post.details'),
]

47
blog/views.py Normal file
View File

@@ -0,0 +1,47 @@
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.pagination import PageNumberPagination
from blog.models import Post, Category
from blog.serializers import PostSerializer, CategorySerializer, CategoryPostSerializer
from core.Permission import ReadOnly
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
# Create your views here.
class CategoryList(ListAPIView):
permission_classes = [ReadOnly]
queryset = Category.objects.order_by('order').filter(is_active=True, parent__isnull=True).all()
# serializer_class = ParentSerializer
serializer_class = CategorySerializer
class CategoryDetail(RetrieveAPIView):
permission_classes = [ReadOnly]
queryset = Category.objects.order_by('order').filter(is_active=True).all()
serializer_class = CategoryPostSerializer
lookup_field = 'slug' # Slug ile arama yapılacak
def get_serializer_context(self):
context = super().get_serializer_context()
context['paginator'] = StandardResultsSetPagination()
return context
# Create your views here.
class PostList(ListAPIView):
permission_classes = [ReadOnly]
queryset = Post.objects.all()
serializer_class = PostSerializer
pagination_class = StandardResultsSetPagination
class PostDetail(RetrieveAPIView):
permission_classes = [ReadOnly]
queryset = Post.objects.all()
serializer_class = PostSerializer
lookup_field = 'slug' # Slug ile arama yapılacak