add: add product and category models

This commit is contained in:
behruz-dev
2025-08-28 16:43:33 +05:00
parent 53042ed25e
commit 8e4f79d856
32 changed files with 418 additions and 7 deletions

View File

@@ -4,6 +4,6 @@ REST_FRAMEWORK = {
'rest_framework.authentication.BasicAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
# 'DEFAULT_PAGINATION_CLASS': 'core.apps.shared.paginations.custom.CustomPageNumberPagination',
# 'PAGE_SIZE': 10
'DEFAULT_PAGINATION_CLASS': 'core.apps.shared.paginations.custom.CustomPageNumberPagination',
'PAGE_SIZE': 10
}

View File

@@ -12,6 +12,7 @@ ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
INSTALLED_APPS = [
'modeltranslation',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -110,4 +111,14 @@ MEDIA_ROOT = BASE_DIR / 'resources/media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.User'
from config.conf import *
LANGUAGES = (
('uz', 'Uzbek'),
('ru', 'Russian'),
)
MODELTRANSLATION_LANGUAGES = ('uz', 'ru')
MODELTRANSLATION_DEFAULT_LANGUAGE = 'uz'

View File

@@ -0,0 +1,46 @@
# Generated by Django 5.2 on 2025-08-28 16:06
import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('created_at', models.DateTimeField(auto_created=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2 on 2025-08-28 16:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='created_at',
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@@ -0,0 +1 @@
from .user import *

View File

@@ -1,6 +1,8 @@
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView
urlpatterns = [
path('login/', TokenObtainPairView.as_view()),
]

View File

@@ -0,0 +1,2 @@
from .product import *
from .category import *

View File

@@ -0,0 +1,11 @@
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from core.apps.products.models import Category
@admin.register(Category)
class CategoryAdmin(TranslationAdmin):
list_display = ['id', 'name']

View File

@@ -0,0 +1,11 @@
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from core.apps.products.models import Product
@admin.register(Product)
class ProductAdmin(TranslationAdmin):
list_display = ['id', 'name', 'price', 'category']
list_filter = ['category']

View File

@@ -4,3 +4,6 @@ from django.apps import AppConfig
class ProductsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core.apps.products'
def ready(self):
from . import admin, translation

View File

@@ -0,0 +1,45 @@
# Generated by Django 5.2 on 2025-08-28 16:17
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('image', models.ImageField(upload_to='products/category/')),
('name', models.CharField(max_length=200)),
],
options={
'verbose_name': 'Kategoriya',
'verbose_name_plural': 'Kategoriyalar',
},
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('name', models.CharField(max_length=200)),
('image', models.ImageField(upload_to='products/product/')),
('price', models.PositiveBigIntegerField()),
('description', models.TextField(blank=True, null=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='products.category')),
],
options={
'verbose_name': 'Mahsulot',
'verbose_name_plural': 'Mahsulotlar',
},
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2 on 2025-08-28 16:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='category',
name='name_ru',
field=models.CharField(max_length=200, null=True),
),
migrations.AddField(
model_name='category',
name='name_uz',
field=models.CharField(max_length=200, null=True),
),
migrations.AddField(
model_name='product',
name='description_ru',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='product',
name='description_uz',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='product',
name='name_ru',
field=models.CharField(max_length=200, null=True),
),
migrations.AddField(
model_name='product',
name='name_uz',
field=models.CharField(max_length=200, null=True),
),
]

View File

@@ -0,0 +1,2 @@
from .category import *
from .product import *

View File

@@ -0,0 +1,18 @@
from django.db import models
from core.apps.shared.models.base import BaseModel
class Category(BaseModel):
image = models.ImageField(upload_to='products/category/')
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Kategoriya'
verbose_name_plural = 'Kategoriyalar'

View File

@@ -0,0 +1,20 @@
from django.db import models
from core.apps.shared.models.base import BaseModel
from core.apps.products.models import Category
class Product(BaseModel):
name = models.CharField(max_length=200)
image = models.ImageField(upload_to='products/product/')
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
price = models.PositiveBigIntegerField()
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Mahsulot'
verbose_name_plural = 'Mahsulotlar'

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.products.models import Category
class CategoryListSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'id', 'name', 'image'
]

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.products.models import Product
class ProductListSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
'id', 'name', 'image', 'price', 'description',
]

View File

@@ -0,0 +1,2 @@
from .product import *
from .category import *

View File

@@ -0,0 +1,10 @@
from modeltranslation import translator
from core.apps.products.models import Category
@translator.register(Category)
class CategoryTranslation(translator.TranslationOptions):
fields = [
'name'
]

View File

@@ -0,0 +1,10 @@
from modeltranslation import translator
from core.apps.products.models import Product
@translator.register(Product)
class ProductTranslation(translator.TranslationOptions):
fields = [
'name', 'description'
]

View File

@@ -1,6 +1,17 @@
from django.urls import path, include
from core.apps.products.views import category as category_veiws
from core.apps.products.views import product as product_views
urlpatterns = [
path('category/', include(
[
path('list/', category_veiws.CategoryListApiView.as_view()),
]
)),
path('product/', include(
[
path('<uuid:category_id>/list/', product_views.ProductListApiView.as_view()),
]
))
]

View File

@@ -0,0 +1,23 @@
from rest_framework import generics, permissions
from rest_framework.response import Response
from core.apps.products.models import Category
from core.apps.products.serializers import category as serializers
class CategoryListApiView(generics.GenericAPIView):
serializer_class = serializers.CategoryListSerializer
queryset = Category.objects.all()
permission_classes = []
def get(self, request):
category = self.get_queryset()
page = self.paginate_queryset(category)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.serializer_class(category, many=True)
return Response(serializer.data, status=200)

View File

@@ -0,0 +1,23 @@
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response
from core.apps.products.models import Product, Category
from core.apps.products.serializers import category as serializers
class ProductListApiView(generics.GenericAPIView):
serializer_class = serializers.CategoryListSerializer
queryset = Product.objects.all()
permission_classes = []
def get(self, request, category_id):
category = get_object_or_404(Category, id=category_id)
products = Product.objects.filter(category=category)
page = self.paginate_queryset(products)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.serializer_class(products, many=True)
return Response(serializer.data, status=200)

View File

@@ -0,0 +1 @@
from .banner import *

View File

@@ -0,0 +1,9 @@
from django.contrib import admin
from core.apps.shared.models import Banner
@admin.register(Banner)
class BannerAdmin(admin.ModelAdmin):
list_display = ['id', 'banner']

View File

@@ -4,3 +4,6 @@ from django.apps import AppConfig
class SharedConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core.apps.shared'
def ready(self):
from . import admin

View File

@@ -0,0 +1,27 @@
# Generated by Django 5.2 on 2025-08-28 16:17
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Banner',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('banner', models.ImageField(upload_to='shared/banners/')),
],
options={
'verbose_name': 'banner',
'verbose_name_plural': 'bannerlar',
},
),
]

View File

@@ -0,0 +1,2 @@
from .base import *
from .banner import *

View File

@@ -0,0 +1,11 @@
from django.db import models
from core.apps.shared.models import BaseModel
class Banner(BaseModel):
banner = models.ImageField(upload_to='shared/banners/')
class Meta:
verbose_name = 'banner'
verbose_name_plural = 'bannerlar'

View File

@@ -6,7 +6,7 @@ from django.db import models
class BaseModel(models.Model):
id = models.UUIDField(primary_key=True, editable=False, unique=True, default=uuid.uuid4)
created_at = models.DateTimeField(auto_created=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True

View File

@@ -0,0 +1,22 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class CustomPageNumberPagination(PageNumberPagination):
page_size = 10
page_query_param = 'page'
page_size_query_param = 'page_size'
max_page_size = 100
def get_paginated_response(self, data):
return Response({
'total': self.page.paginator.count,
'page': self.page.number,
'page_size': self.get_page_size(self.request),
'total_pages': self.page.paginator.num_pages,
'has_next': self.page.has_next(),
'has_previous': self.page.has_previous(),
'results': data
})

View File

@@ -9,3 +9,5 @@ drf_yasg
redis
django-redis
psycopg2-binary
pillow
django-modeltranslation