diff --git a/config/conf/rest_framework.py b/config/conf/rest_framework.py index 7148bc9..b2837b3 100644 --- a/config/conf/rest_framework.py +++ b/config/conf/rest_framework.py @@ -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 } \ No newline at end of file diff --git a/config/settings/base.py b/config/settings/base.py index f20de81..94a8f90 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -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' -from config.conf import * \ No newline at end of file +AUTH_USER_MODEL = 'accounts.User' + + +from config.conf import * + +LANGUAGES = ( + ('uz', 'Uzbek'), + ('ru', 'Russian'), +) +MODELTRANSLATION_LANGUAGES = ('uz', 'ru') +MODELTRANSLATION_DEFAULT_LANGUAGE = 'uz' \ No newline at end of file diff --git a/core/apps/accounts/migrations/0001_initial.py b/core/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..1668d91 --- /dev/null +++ b/core/apps/accounts/migrations/0001_initial.py @@ -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()), + ], + ), + ] diff --git a/core/apps/accounts/migrations/0002_alter_user_created_at.py b/core/apps/accounts/migrations/0002_alter_user_created_at.py new file mode 100644 index 0000000..6bc8d10 --- /dev/null +++ b/core/apps/accounts/migrations/0002_alter_user_created_at.py @@ -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), + ), + ] diff --git a/core/apps/accounts/models/__init__.py b/core/apps/accounts/models/__init__.py index e69de29..82da278 100644 --- a/core/apps/accounts/models/__init__.py +++ b/core/apps/accounts/models/__init__.py @@ -0,0 +1 @@ +from .user import * \ No newline at end of file diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index c9dc166..ae0566c 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -1,6 +1,8 @@ from django.urls import path, include +from rest_framework_simplejwt.views import TokenObtainPairView + urlpatterns = [ - + path('login/', TokenObtainPairView.as_view()), ] \ No newline at end of file diff --git a/core/apps/products/admin/__init__.py b/core/apps/products/admin/__init__.py index e69de29..af252f9 100644 --- a/core/apps/products/admin/__init__.py +++ b/core/apps/products/admin/__init__.py @@ -0,0 +1,2 @@ +from .product import * +from .category import * \ No newline at end of file diff --git a/core/apps/products/admin/category.py b/core/apps/products/admin/category.py new file mode 100644 index 0000000..353faac --- /dev/null +++ b/core/apps/products/admin/category.py @@ -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'] + \ No newline at end of file diff --git a/core/apps/products/admin/product.py b/core/apps/products/admin/product.py new file mode 100644 index 0000000..0b77042 --- /dev/null +++ b/core/apps/products/admin/product.py @@ -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'] \ No newline at end of file diff --git a/core/apps/products/apps.py b/core/apps/products/apps.py index 146e38b..96f05db 100644 --- a/core/apps/products/apps.py +++ b/core/apps/products/apps.py @@ -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 \ No newline at end of file diff --git a/core/apps/products/migrations/0001_initial.py b/core/apps/products/migrations/0001_initial.py new file mode 100644 index 0000000..de0c748 --- /dev/null +++ b/core/apps/products/migrations/0001_initial.py @@ -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', + }, + ), + ] diff --git a/core/apps/products/migrations/0002_category_name_ru_category_name_uz_and_more.py b/core/apps/products/migrations/0002_category_name_ru_category_name_uz_and_more.py new file mode 100644 index 0000000..d5bec06 --- /dev/null +++ b/core/apps/products/migrations/0002_category_name_ru_category_name_uz_and_more.py @@ -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), + ), + ] diff --git a/core/apps/products/models/__init__.py b/core/apps/products/models/__init__.py index e69de29..e867901 100644 --- a/core/apps/products/models/__init__.py +++ b/core/apps/products/models/__init__.py @@ -0,0 +1,2 @@ +from .category import * +from .product import * \ No newline at end of file diff --git a/core/apps/products/models/category.py b/core/apps/products/models/category.py new file mode 100644 index 0000000..bac1a65 --- /dev/null +++ b/core/apps/products/models/category.py @@ -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' + + diff --git a/core/apps/products/models/product.py b/core/apps/products/models/product.py new file mode 100644 index 0000000..5206fc2 --- /dev/null +++ b/core/apps/products/models/product.py @@ -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' + diff --git a/core/apps/products/serializers/category.py b/core/apps/products/serializers/category.py new file mode 100644 index 0000000..8866e7c --- /dev/null +++ b/core/apps/products/serializers/category.py @@ -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' + ] \ No newline at end of file diff --git a/core/apps/products/serializers/product.py b/core/apps/products/serializers/product.py new file mode 100644 index 0000000..57c18c9 --- /dev/null +++ b/core/apps/products/serializers/product.py @@ -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', + ] \ No newline at end of file diff --git a/core/apps/products/translation/__init__.py b/core/apps/products/translation/__init__.py new file mode 100644 index 0000000..af252f9 --- /dev/null +++ b/core/apps/products/translation/__init__.py @@ -0,0 +1,2 @@ +from .product import * +from .category import * \ No newline at end of file diff --git a/core/apps/products/translation/category.py b/core/apps/products/translation/category.py new file mode 100644 index 0000000..a0f07ed --- /dev/null +++ b/core/apps/products/translation/category.py @@ -0,0 +1,10 @@ +from modeltranslation import translator + +from core.apps.products.models import Category + + +@translator.register(Category) +class CategoryTranslation(translator.TranslationOptions): + fields = [ + 'name' + ] \ No newline at end of file diff --git a/core/apps/products/translation/product.py b/core/apps/products/translation/product.py new file mode 100644 index 0000000..7873a1d --- /dev/null +++ b/core/apps/products/translation/product.py @@ -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' + ] \ No newline at end of file diff --git a/core/apps/products/urls.py b/core/apps/products/urls.py index c9dc166..79eda57 100644 --- a/core/apps/products/urls.py +++ b/core/apps/products/urls.py @@ -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('/list/', product_views.ProductListApiView.as_view()), + ] + )) ] \ No newline at end of file diff --git a/core/apps/products/views/category.py b/core/apps/products/views/category.py new file mode 100644 index 0000000..fa0fbc7 --- /dev/null +++ b/core/apps/products/views/category.py @@ -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) + + + \ No newline at end of file diff --git a/core/apps/products/views/product.py b/core/apps/products/views/product.py new file mode 100644 index 0000000..ac715c9 --- /dev/null +++ b/core/apps/products/views/product.py @@ -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) diff --git a/core/apps/shared/admin/__init__.py b/core/apps/shared/admin/__init__.py index e69de29..5e13493 100644 --- a/core/apps/shared/admin/__init__.py +++ b/core/apps/shared/admin/__init__.py @@ -0,0 +1 @@ +from .banner import * \ No newline at end of file diff --git a/core/apps/shared/admin/banner.py b/core/apps/shared/admin/banner.py new file mode 100644 index 0000000..7b57f4d --- /dev/null +++ b/core/apps/shared/admin/banner.py @@ -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'] + \ No newline at end of file diff --git a/core/apps/shared/apps.py b/core/apps/shared/apps.py index ca9e94d..1cd6f48 100644 --- a/core/apps/shared/apps.py +++ b/core/apps/shared/apps.py @@ -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 \ No newline at end of file diff --git a/core/apps/shared/migrations/0001_initial.py b/core/apps/shared/migrations/0001_initial.py new file mode 100644 index 0000000..3f94b76 --- /dev/null +++ b/core/apps/shared/migrations/0001_initial.py @@ -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', + }, + ), + ] diff --git a/core/apps/shared/models/__init__.py b/core/apps/shared/models/__init__.py index e69de29..80a83a1 100644 --- a/core/apps/shared/models/__init__.py +++ b/core/apps/shared/models/__init__.py @@ -0,0 +1,2 @@ +from .base import * +from .banner import * \ No newline at end of file diff --git a/core/apps/shared/models/banner.py b/core/apps/shared/models/banner.py new file mode 100644 index 0000000..eb7a313 --- /dev/null +++ b/core/apps/shared/models/banner.py @@ -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' \ No newline at end of file diff --git a/core/apps/shared/models/base.py b/core/apps/shared/models/base.py index 6317422..84246e3 100644 --- a/core/apps/shared/models/base.py +++ b/core/apps/shared/models/base.py @@ -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 diff --git a/core/apps/shared/paginations/custom.py b/core/apps/shared/paginations/custom.py new file mode 100644 index 0000000..abaa0b6 --- /dev/null +++ b/core/apps/shared/paginations/custom.py @@ -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 + }) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index cf1e5ed..ca34e9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,6 @@ djangorestframework-simplejwt drf_yasg redis django-redis -psycopg2-binary \ No newline at end of file +psycopg2-binary +pillow +django-modeltranslation \ No newline at end of file