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

16
reviews/admin.py Normal file
View File

@@ -0,0 +1,16 @@
from django.contrib import admin
from .models import Rating
@admin.register(Rating)
class RatingAdmin(admin.ModelAdmin):
list_display = ('content_object', 'user', 'score', 'created_at')
list_filter = ('score', 'created_at', 'content_type')
search_fields = ('user__username', 'user__email', 'comment')
readonly_fields = ('created_at', 'updated_at')
# GenericForeignKey alanlarını admin panelinde daha düzgün göstermek için
# content_object alanını list_display'e ekledik, bu sayede hangi nesneye oy verildiği görülebilir.
def get_queryset(self, request):
# N+1 sorununu önlemek için related alanları prefetch edelim
return super().get_queryset(request).select_related('user', 'content_type').prefetch_related('content_object')

6
reviews/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ReviewsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'reviews'

View File

@@ -0,0 +1,37 @@
# Generated by Django 6.0 on 2026-01-20 18:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Rating',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('score', models.PositiveSmallIntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], verbose_name='Puan')),
('comment', models.TextField(blank=True, null=True, verbose_name='Yorum')),
('object_id', models.PositiveIntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Değerlendirme',
'verbose_name_plural': 'Değerlendirmeler',
'ordering': ['-created_at'],
'unique_together': {('content_type', 'object_id', 'user')},
},
),
]

View File

48
reviews/models.py Normal file
View File

@@ -0,0 +1,48 @@
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Avg
class Rating(models.Model):
score = models.PositiveSmallIntegerField(choices=[(i, str(i)) for i in range(1, 6)], verbose_name="Puan")
comment = models.TextField(blank=True, null=True, verbose_name="Yorum")
# Generic Relation Fields
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ratings')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('content_type', 'object_id', 'user') # Bir kullanıcı bir şeye sadece bir kez oy verebilir
ordering = ['-created_at']
verbose_name = "Değerlendirme"
verbose_name_plural = "Değerlendirmeler"
def __str__(self):
return f"{self.score} - {self.content_object}"
# Bu Mixin'i oylanabilir olmasını istediğiniz modellere miras (inherit) verebilirsiniz.
class RateableMixin:
@property
def average_rating(self):
# İlgili nesneye ait tüm oyların ortalamasını alır
ratings = Rating.objects.filter(
content_type=ContentType.objects.get_for_model(self),
object_id=self.id
)
return ratings.aggregate(Avg('score'))['score__avg'] or 0.0
@property
def rating_count(self):
# Toplam oy sayısını döndürür
return Rating.objects.filter(
content_type=ContentType.objects.get_for_model(self),
object_id=self.id
).count()

37
reviews/serializers.py Normal file
View File

@@ -0,0 +1,37 @@
from rest_framework import serializers
from django.contrib.contenttypes.models import ContentType
from .models import Rating
class RatingSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
model_name = serializers.CharField(write_only=True) # Örn: 'product'
object_id = serializers.IntegerField()
class Meta:
model = Rating
fields = ['id', 'score', 'comment', 'user', 'created_at', 'model_name', 'object_id']
read_only_fields = ['id', 'created_at', 'user']
def create(self, validated_data):
model_name = validated_data.pop('model_name').lower()
object_id = validated_data.pop('object_id')
user = self.context['request'].user
# Model adından ContentType'ı bul
try:
# app_label='product' varsayımı yapıyoruz veya tüm app'lerde arayabiliriz.
# Daha güvenli olması için app_label'ı da parametre alabiliriz ama şimdilik basit tutalım.
# Genellikle model isimleri unique olur.
content_type = ContentType.objects.get(model=model_name)
except ContentType.DoesNotExist:
raise serializers.ValidationError(f"Model '{model_name}' bulunamadı.")
# Kullanıcının daha önce oy verip vermediğini kontrol et (unique_together constraint var ama burada da yakalayalım)
rating, created = Rating.objects.update_or_create(
content_type=content_type,
object_id=object_id,
user=user,
defaults={'score': validated_data.get('score'), 'comment': validated_data.get('comment')}
)
return rating

7
reviews/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
from .views import CreateRatingView, ListRatingsView
urlpatterns = [
path('rate/', CreateRatingView.as_view(), name='create-rating'),
path('list/', ListRatingsView.as_view(), name='list-ratings'),
]

34
reviews/views.py Normal file
View File

@@ -0,0 +1,34 @@
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.contenttypes.models import ContentType
from .models import Rating
from .serializers import RatingSerializer
class CreateRatingView(generics.CreateAPIView):
serializer_class = RatingSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer):
# Serializer'ın create metodunda user ve content_type işlemleri yapılıyor
serializer.save()
class ListRatingsView(APIView):
"""
Belirli bir model ve ID için yorumları listeler.
Örn: /api/v1/reviews/list/?model=product&id=1
"""
def get(self, request):
model_name = request.query_params.get('model')
object_id = request.query_params.get('id')
if not model_name or not object_id:
return Response({"error": "model ve id parametreleri gereklidir."}, status=status.HTTP_400_BAD_REQUEST)
try:
content_type = ContentType.objects.get(model=model_name.lower())
ratings = Rating.objects.filter(content_type=content_type, object_id=object_id)
serializer = RatingSerializer(ratings, many=True)
return Response(serializer.data)
except ContentType.DoesNotExist:
return Response({"error": "Model bulunamadı."}, status=status.HTTP_404_NOT_FOUND)