diff --git a/backend/api/admin.py b/backend/api/admin.py index 4ca1660..b0c3e81 100644 --- a/backend/api/admin.py +++ b/backend/api/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from .models import User, Category, Flavor, Rating +from .models import User, Category, Flavor, Rating, Banner class CustomUserAdmin(UserAdmin): fieldsets = UserAdmin.fieldsets + ( @@ -14,3 +14,4 @@ admin.site.register(User, CustomUserAdmin) admin.site.register(Category) admin.site.register(Flavor) admin.site.register(Rating) +admin.site.register(Banner) diff --git a/backend/api/migrations/0023_banner.py b/backend/api/migrations/0023_banner.py new file mode 100644 index 0000000..1646d3b --- /dev/null +++ b/backend/api/migrations/0023_banner.py @@ -0,0 +1,26 @@ +# Generated by Django 6.0.2 on 2026-02-18 11:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0022_alter_user_theme'), + ] + + operations = [ + migrations.CreateModel( + name='Banner', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('slug', models.SlugField(unique=True)), + ('description', models.TextField(blank=True)), + ('is_active', models.BooleanField(default=False)), + ('settings', models.JSONField(blank=True, default=dict)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 37f42ab..d12353e 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -190,3 +190,21 @@ class ProfileComment(models.Model): class Meta: ordering = ['-created_at'] + +class Banner(models.Model): + name = models.CharField(max_length=100) + slug = models.SlugField(unique=True) + description = models.TextField(blank=True) + is_active = models.BooleanField(default=False) + settings = models.JSONField(default=dict, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def save(self, *args, **kwargs): + if self.is_active: + # Deactivate other banners + Banner.objects.filter(is_active=True).exclude(pk=self.pk).update(is_active=False) + super().save(*args, **kwargs) + + def __str__(self): + return self.name diff --git a/backend/api/serializers.py b/backend/api/serializers.py index ed2adb3..0c91029 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -1,11 +1,16 @@ from rest_framework import serializers -from .models import User, Flavor, Category, Rating, Reply, Notification, Ticket, TicketMessage, UserIP +from .models import User, Flavor, Category, Rating, Reply, Notification, Ticket, TicketMessage, UserIP, Banner class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ['id', 'name', 'slug'] +class BannerSerializer(serializers.ModelSerializer): + class Meta: + model = Banner + fields = ['id', 'name', 'slug', 'description', 'is_active', 'settings', 'created_at', 'updated_at'] + class ReplySerializer(serializers.ModelSerializer): user = serializers.StringRelatedField(read_only=True) diff --git a/backend/api/urls.py b/backend/api/urls.py index 057951d..333674c 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -11,6 +11,7 @@ router.register('replies', views.ReplyViewSet) router.register('notifications', views.NotificationViewSet, basename='notification') router.register('tickets', views.TicketViewSet, basename='ticket') router.register('admin-custom', views.AdminViewSet, basename='admin-custom') +router.register('banners', views.BannerViewSet, basename='banner') urlpatterns = [ path('', include(router.urls)), diff --git a/backend/api/views.py b/backend/api/views.py index bb32023..f259293 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -8,12 +8,12 @@ from django_filters.rest_framework import DjangoFilterBackend from django_ratelimit.decorators import ratelimit from django.utils.decorators import method_decorator from rest_framework.pagination import PageNumberPagination -from .models import User, Flavor, Category, Rating, Reply, Notification, Ticket, TicketMessage, UserIP, ProfileComment +from .models import User, Flavor, Category, Rating, Reply, Notification, Ticket, TicketMessage, UserIP, ProfileComment, Banner from .serializers import ( UserSerializer, FlavorSerializer, CategorySerializer, RatingSerializer, ReplySerializer, NotificationSerializer, TicketSerializer, TicketMessageSerializer, AdminUserListSerializer, AdminUserDetailSerializer, - ProfileCommentSerializer + ProfileCommentSerializer, BannerSerializer ) def log_user_ip(user, request): @@ -685,6 +685,31 @@ class TicketViewSet(viewsets.ModelViewSet): raise permissions.PermissionDenied("Only admins can delete tickets.") return super().destroy(request, *args, **kwargs) +class BannerViewSet(viewsets.ModelViewSet): + queryset = Banner.objects.all() + serializer_class = BannerSerializer + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + + def get_permissions(self): + if self.action in ['list', 'retrieve', 'active']: + return [permissions.AllowAny()] + return [permissions.IsAdminUser()] + + @action(detail=False, methods=['get']) + def active(self, request): + active_banner = Banner.objects.filter(is_active=True).first() + if active_banner: + serializer = self.get_serializer(active_banner) + return Response(serializer.data) + return Response(None) + + @action(detail=True, methods=['post']) + def activate(self, request, pk=None): + banner = self.get_object() + banner.is_active = True + banner.save() + return Response({'status': 'banner activated'}) + class AdminViewSet(viewsets.ViewSet): permission_classes = [permissions.IsAdminUser]