backend merge

This commit is contained in:
Mamalizz
2024-12-13 20:46:56 +03:30
23 changed files with 526 additions and 4 deletions
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'account'
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
+2 -1
View File
@@ -16,7 +16,8 @@ urlpatterns = [
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('admin/', admin.site.urls),
path('schema/', SpectacularAPIView.as_view(), name='schema'),
path('comment/<int:pk>', views.CommentView.as_view(), name='comment-list'),
# path('comment/<int:pk>', views.CommentView.as_view(), name='comment-list'),
path('products/', include('product.urls')),
path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]
View File
+50
View File
@@ -0,0 +1,50 @@
from django.contrib import admin
from import_export.admin import ImportExportActionModelAdmin
from unfold.admin import ModelAdmin
from .models import *
@admin.register(Dare)
class Dare(ModelAdmin):
list_display = ['lang1', 'is_for_adults']
list_filter = ['is_for_adults']
@admin.register(Truth)
class Truth(ModelAdmin):
list_display = ['lang1', 'is_for_adults']
list_filter = ['is_for_adults']
@admin.register(Would_you_rather)
class Would_you_rather(ModelAdmin):
list_display = ['lang1', 'is_for_adults']
list_filter = ['is_for_adults']
@admin.register(challenge)
class Challenge(ModelAdmin):
list_display = ['type']
@admin.register(abjad)
class abjad(ModelAdmin):
list_display = ['word', 'difficulty_type', 'answer']
list_filter = ['difficulty_type']
@admin.register(MusicModel)
class MusicAdmin(ModelAdmin):
list_display = ['name', 'message_id', 'singer', 'category', 'trand']
@admin.register(MovieModel)
class MovieAdmin(ModelAdmin):
list_display = ['name', 'message_id', 'category', 'receommended']
@admin.register(MovieCategory)
class MovieCategoryAdmin(ModelAdmin):
pass
@admin.register(MusicCategory)
class MusicCategoryAdmin(ModelAdmin):
pass
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class EntertainmentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'entertainment'
verbose_name = 'بخش سرگرمی ها'
+142
View File
@@ -0,0 +1,142 @@
from django.db import models
class BaseModel(models.Model):
lang1 = models.CharField(max_length=200, verbose_name='فارسی')
is_for_adults = models.BooleanField(verbose_name='+18 سوال')
class Meta:
abstract = True
def __str__(self):
return self.lang1
async def get(self, *attrs):
if len(attrs) == 1:
return getattr(self, attrs[0], None)
else:
return tuple(getattr(self, attr, None) for attr in attrs)
class Dare(BaseModel):
class Meta:
verbose_name = 'شجاعت'
verbose_name_plural = 'شجاعت ها'
class Truth(BaseModel):
class Meta:
verbose_name = 'حقیقت'
verbose_name_plural = "حقیقت ها"
class Would_you_rather(BaseModel):
class Meta:
verbose_name = 'ترجیح میدی'
verbose_name_plural = "ترجیح میدی ها"
class challenge(models.Model):
challenge_type = (
('map', 'نقشه ی گنج'),
('prize', 'جوایز')
)
type = models.CharField(max_length=30, choices=challenge_type, verbose_name='نوع چالش', primary_key=True)
image = models.ImageField(upload_to='media/', verbose_name='عکس')
link = models.URLField(verbose_name='لینک')
text = models.TextField(verbose_name='متن توضیحات')
button_text = models.CharField(max_length=40, verbose_name='متن دکمه')
def __str__(self):
return f'{self.type} - {self.text[0:50]}'
class Meta:
verbose_name = 'چالش'
verbose_name_plural = 'چالش ها'
class abjad(models.Model):
word = models.TextField(verbose_name='صورت ی سوال')
difficulty = (
('hard', 'سخت'),
('normal', 'متوسط'),
('easy', 'اسون')
)
image = models.ImageField(upload_to='media/', verbose_name='عکس بازی افتابه', blank=True, null=True)
difficulty_type = models.CharField(max_length=13, choices=difficulty, verbose_name='سختی')
answer = models.CharField(max_length=30, verbose_name='جواب')
option2 = models.CharField(max_length=30, verbose_name='گزینه ی اشتباه', null=True, blank=True)
option3 = models.CharField(max_length=30, verbose_name='گزینه ی اشتباه', null=True, blank=True)
option4 = models.CharField(max_length=30, verbose_name='گزینه ی اشتباه', null=True, blank=True)
def __str__(self):
return f'{self.word}'
class Meta:
verbose_name = 'سوال ابجد'
verbose_name_plural = 'سوالات ابجد'
async def get(self, *attrs):
if len(attrs) == 1:
return getattr(self, attrs[0], None)
else:
return tuple(getattr(self, attr, None) for attr in attrs)
GAME_DATA = {
Dare: {
'button': 'dare',
'game_name': 'شجاعت'
},
Truth: {
'button': 'truth',
'game_name': 'حقیقت'
},
Would_you_rather: {
'button': 'wyr',
'game_name': 'ترجیح میدی'
}
}
from django.db import models
class MusicCategory(models.Model):
name = models.CharField(max_length=40, verbose_name='نام دسته بندی')
def __str__(self):
return self.name
class Meta:
verbose_name = 'دسته بندی موزیک'
verbose_name_plural = 'دسته بندی موزیک ها'
class MovieCategory(models.Model):
name = models.CharField(max_length=40, verbose_name='نام دسته بندی')
def __str__(self):
return self.name
class Meta:
verbose_name = 'دسته بندی قیلم'
verbose_name_plural = 'دسته بندی فیلم ها'
class UploadParent(models.Model):
name = models.CharField(max_length=300, verbose_name='نام')
message_id = models.CharField(max_length=40, verbose_name='ای دی پیام تلگرام')
def __str__(self):
return self.name
class MusicModel(UploadParent):
lyric = models.CharField(verbose_name='متن اهنگ', max_length=4000, blank=True, null=True)
singer = models.CharField(max_length=300, verbose_name='خواننده', blank=True, null=True)
category = models.ForeignKey(MusicCategory, on_delete=models.CASCADE, verbose_name='دسته بندی', blank=True, null=True)
trand = models.BooleanField(default=False, verbose_name='ترند')
class Meta:
verbose_name = 'مدل اهنگ'
verbose_name_plural = 'مدل اهنگ ها'
class MovieModel(UploadParent):
description = models.CharField(max_length=4000,verbose_name='توضیحات فیلم', blank=True, null=True)
category = models.ForeignKey(MovieCategory, on_delete=models.CASCADE, verbose_name='دسته بندی', blank=True, null=True)
receommended = models.BooleanField(default=False, verbose_name='پیشنهادی')
class Meta:
verbose_name = 'مدل فیلم'
verbose_name_plural = 'مدل فیلم ها'
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
@@ -0,0 +1,31 @@
# Generated by Django 5.1.2 on 2024-12-13 07:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0004_rename_product_productmodel'),
]
operations = [
migrations.CreateModel(
name='CategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='نام دسته\u200cبندی')),
('slug', models.SlugField(help_text='اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید', unique=True)),
('icon', models.CharField(blank=True, max_length=100, null=True, verbose_name='آیکون دسته\u200cبندی')),
('meta_title', models.CharField(blank=True, help_text='عنوان متا برای SEO', max_length=60, null=True, verbose_name='عنوان متا')),
('meta_description', models.TextField(blank=True, help_text='توضیحات متا برای SEO', max_length=160, null=True, verbose_name='توضیحات متا')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='product.categorymodel', verbose_name='دسته\u200cبندی والد')),
],
options={
'verbose_name': 'دسته\u200cبندی',
'verbose_name_plural': 'دسته\u200cبندی\u200cها',
'ordering': ['parent__id', 'id'],
},
),
]
+52
View File
@@ -1,6 +1,58 @@
from django.db import models
from django.utils.text import slugify
from django.contrib.auth.models import User
from django.urls import reverse
class CategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام دسته‌بندی')
slug = models.SlugField(
max_length=50,
unique=True,
help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید"
)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
null=True,
blank=True,
verbose_name='دسته‌بندی والد'
)
icon = models.CharField(max_length=100, verbose_name='آیکون دسته‌بندی', blank=True, null=True)
meta_title = models.CharField(
max_length=60,
verbose_name="عنوان متا",
help_text="عنوان متا برای SEO",
blank=True,
null=True
)
meta_description = models.TextField(
max_length=160,
verbose_name="توضیحات متا",
help_text="توضیحات متا برای SEO",
blank=True,
null=True
)
class Meta:
verbose_name = "دسته‌بندی"
verbose_name_plural = "دسته‌بندی‌ها"
ordering = ['parent__id', 'id'] # Optional: to order by hierarchy
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
def get_breadcrumb(self):
breadcrumb = []
category = self
while category:
breadcrumb.append(category)
category = category.parent
return breadcrumb[::-1]
class ProductModel(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
+20 -1
View File
@@ -1,9 +1,28 @@
from .models import *
from rest_framework import serializers
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = ProductModel
fields = "__all__"
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = CommentModel
fields = "__all__"
read_only_fields = ('show', 'product')
read_only_fields = ('show', 'product')
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = CategoryModel
fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'parent', 'children']
def get_children(self, obj):
children = obj.children.all()
if children.exists():
return CategorySerializer(children, many=True).data
return []
+10
View File
@@ -0,0 +1,10 @@
from django.urls import path
from .views import AllCategories, ProductView, AllProductsView, CommentView
urlpatterns = [
path('', AllProductsView.as_view(), name='category-products'),
path('categories', AllCategories.as_view(), name='all-categories'),
path('<int:pk>', ProductView.as_view(), name='product-detail'),
path('<int:pk>/comments', CommentView.as_view(), name='product-comments'),
path('comments/<int:pk>', CommentView.as_view(), name='comment-delete'),
]
+119 -2
View File
@@ -7,6 +7,123 @@ from rest_framework.response import Response
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from utils.pagination import StructurePagination
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
class AllCategories(APIView):
serializer_class = CategorySerializer
def get(self, request):
categories = CategoryModel.objects.all()
categories_ser = self.serializer_class(instance=categories, many=True)
return Response({"categories": categories_ser.data}, status=status.HTTP_200_OK)
class ProductView(APIView):
serializer_class = ProductModel
def get(self, request, pk):
product = get_object_or_404(ProductModel, id=pk)
product_ser = self.serializer_class(instance=product, many=False)
return Response({"product": product_ser.data}, status=status.HTTP_200_OK)
class AllProductsView(APIView):
serializer_class = ProductSerializer
pagination_class = StructurePagination
@extend_schema(
parameters=[
OpenApiParameter(
name="search",
description="Search by product name or description.",
required=False,
type=OpenApiTypes.STR,
),
OpenApiParameter(
name="category",
description="Filter by category ID.",
required=False,
type=OpenApiTypes.INT,
),
OpenApiParameter(
name="price_gte",
description="Filter products with price greater than or equal to this value.",
required=False,
type=OpenApiTypes.FLOAT,
),
OpenApiParameter(
name="price_lte",
description="Filter products with price less than or equal to this value.",
required=False,
type=OpenApiTypes.FLOAT,
),
OpenApiParameter(
name="sort",
description=(
"Sort results by one of the following fields:\n"
"`name`, `-name`, `price`, `-price`, `discount`, `-discount`."
"\nPrefix with `-` for descending order."
),
required=False,
type=OpenApiTypes.STR,
),
OpenApiParameter(
name="limit",
description="Number of results to return per page (pagination).",
required=False,
type=OpenApiTypes.INT,
),
OpenApiParameter(
name="offset",
description="The starting position of the results (pagination).",
required=False,
type=OpenApiTypes.INT,
),
],
description=(
"Retrieve products with optional filters and sorting. "
"Provide a category ID to filter products in that category and its subcategories."
),
responses={
200: ProductSerializer(many=True),
404: OpenApiTypes.OBJECT,
},
)
def get(self, request, pk=None):
try:
if pk:
category = Category.objects.get(pk=pk)
products = ProductModel.objects.filter(category__in=category.get_descendants(include_self=True))
else:
products = ProductModel.objects.all()
search_query = request.query_params.get('search', None)
if search_query:
products = products.filter(Q(name__icontains=search_query) | Q(description__icontains=search_query))
category_filter = request.query_params.get('category', None)
if category_filter:
products = products.filter(category__id=category_filter)
price_gte = request.query_params.get('price_gte', None)
price_lte = request.query_params.get('price_lte', None)
if price_gte:
products = products.filter(price__gte=price_gte)
if price_lte:
products = products.filter(price__lte=price_lte)
sort_by = request.query_params.get('sort', None)
if sort_by in ['name', '-name', 'price', '-price', 'discount', '-discount']:
products = products.order_by(sort_by)
else:
products = products.order_by('name')
paginator = self.pagination_class()
paginated_products = paginator.paginate_queryset(products, request)
serializer = self.serializer_class(paginated_products, many=True)
return paginator.get_paginated_response(serializer.data)
except Category.DoesNotExist:
return Response({"detail": "Category not found."}, status=status.HTTP_404_NOT_FOUND)
class CommentView(APIView):
@@ -22,10 +139,10 @@ class CommentView(APIView):
comment_ser = CommentSerializer(data=request.data)
product = get_object_or_404(ProductModel, id=pk)
if comment_ser.is_valid():
comment_ser.save(product=product)
#TODO comment_ser.save(product=product, user=request.user)
comment_ser.save(product=product, user=request.user)
return Response(comment_ser.data, status=status.HTTP_201_CREATED)
return Response(comment_ser.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
comment = get_object_or_404(CommentModel, pk=pk)
if comment.user == request.user:
+18
View File
@@ -0,0 +1,18 @@
from django.contrib import admin
from unfold.admin import ModelAdmin
from .models import NotifModel
from import_export.admin import ImportExportModelAdmin
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
@admin.register(NotifModel)
class NotifModelAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
compressed_fields = True
warn_unsaved_form = True
list_display = ('subject', 'priority', 'send_time', 'send_by', 'send_to_all')
search_fields = ('subject', 'description', 'send_by__email', 'send_to_branch__name')
list_filter = ('priority', 'send_time', 'send_to_all')
ordering = ('-send_time',)
filter_horizontal = ('send_to_branch', 'read_by')
+14
View File
@@ -0,0 +1,14 @@
from rest_framework import serializers
class GetNotifSerializer(serializers.ModelSerializer):
send_by = UserSerializer()
is_seen = serializers.SerializerMethodField()
read_by = UserSerializer(many=True)
class Meta:
model = NotifModel
fields = ('id', 'subject', 'priority', 'description', 'send_time', 'is_seen', 'send_by', 'read_by')
read_only_fields = ("read_by", "send_by")
def get_is_seen(self, obj):
user = self.context['user']
return user in obj.read_by.all()
+37
View File
@@ -0,0 +1,37 @@
from django.core.paginator import Paginator
from rest_framework.views import APIView
from .models import *
from .serializers import *
from rest_framework import status
from rest_framework.response import Response
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class CommentView(APIView):
serializer_class = CommentSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get(self, request, pk):
product = get_object_or_404(ProductModel, id=pk)
comments = product.comments.filter(show=True)
comments_ser = self.serializer_class(instance=comments, many=True)
return Response({'comments': comments_ser.data}, status=status.HTTP_200_OK)
def post(self, request, pk):
comment_ser = CommentSerializer(data=request.data)
product = get_object_or_404(ProductModel, id=pk)
if comment_ser.is_valid():
comment_ser.save(product=product, user=request.user)
return Response(comment_ser.data, status=status.HTTP_201_CREATED)
return Response(comment_ser.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
comment = get_object_or_404(CommentModel, pk=pk)
if comment.user == request.user:
comment.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
return Response({"detail": "شما اجازه ی پاک کردن این کامنت را ندارید"}, status=status.HTTP_403_FORBIDDEN)