some parts of product - fix custom pagination - add id to pages list

This commit is contained in:
2025-06-07 09:18:27 +03:30
parent a6cd093665
commit 627acf05a1
41 changed files with 371 additions and 4 deletions

View File

@@ -66,6 +66,7 @@ INSTALLED_APPS = [
'apps.warehouse.apps.WarehouseConfig',
'apps.search.apps.SearchConfig',
'apps.log.apps.LogConfig',
'apps.product.apps.ProductConfig',
'rest_captcha',
'captcha',
'drf_yasg'
@@ -155,8 +156,8 @@ REST_FRAMEWORK = {
'rest_framework.authentication.BasicAuthentication',
),
'EXCEPTION_HANDLER': 'apps.core.error_handler.custom_exception_handler',
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 25,
"DEFAULT_PAGINATION_CLASS": 'apps.core.pagination.CustomPageNumberPagination',
"PAGE_SIZE": 20,
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}

View File

@@ -37,5 +37,6 @@ urlpatterns = [
path('livestock/', include('apps.livestock.urls')),
path('tag/', include('apps.tag.urls')),
path('search/', include('apps.search.urls')),
path('product/', include('apps.product.urls')),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
]

View File

@@ -18,6 +18,7 @@ class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = [
'id',
'name',
'code'
]

View File

@@ -7,8 +7,9 @@ def custom_exception_handler(exc, context):
if response is not None:
response.data['status_code'] = response.status_code
response.data['message'] = response.data.get('detail', str(exc))
del response.data['detail']
if response.data.get('detail'):
response.data['message'] = response.data.get('detail', str(exc))
del response.data['detail']
else:
response = JsonResponse({'message': str(exc), 'status_code': 500})
response.status_code = 500

7
apps/core/pagination.py Normal file
View File

@@ -0,0 +1,7 @@
from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
page_size = 20 # default
page_size_query_param = 'page_size' # set from client
max_page_size = 100 # maximum items to show

0
apps/product/__init__.py Normal file
View File

3
apps/product/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/product/apps.py Normal file
View File

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

View File

View File

@@ -0,0 +1 @@
# Your custom management commands go here.

View File

@@ -0,0 +1,56 @@
# Generated by Django 5.0 on 2025-06-03 12:19
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ReferenceProduct',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('creator_info', models.CharField(max_length=100, null=True)),
('modifier_info', models.CharField(max_length=100, null=True)),
('trash', models.BooleanField(default=False)),
('name', models.CharField(default='empty', max_length=250)),
('type', models.CharField(choices=[('F', 'Free'), ('G', 'Governmental')], max_length=3)),
('img', models.CharField(default='empty', max_length=100)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('creator_info', models.CharField(max_length=100, null=True)),
('modifier_info', models.CharField(max_length=100, null=True)),
('trash', models.BooleanField(default=False)),
('name', models.CharField(default='empty', max_length=250)),
('type', models.CharField(choices=[('F', 'Free'), ('G', 'Governmental')], max_length=3)),
('img', models.CharField(default='empty', max_length=100)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
('reference', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reference_product', to='product.referenceproduct')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,54 @@
# Generated by Django 5.0 on 2025-06-03 13:14
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Attribute',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('creator_info', models.CharField(max_length=100, null=True)),
('modifier_info', models.CharField(max_length=100, null=True)),
('trash', models.BooleanField(default=False)),
('name', models.CharField(default='empty', max_length=100)),
('type', models.CharField(default='empty', help_text='type of attribute like: calculate product by kilogram', max_length=255)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
('reference_product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reference_attribute', to='product.referenceproduct')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='AttributeValue',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('creator_info', models.CharField(max_length=100, null=True)),
('modifier_info', models.CharField(max_length=100, null=True)),
('trash', models.BooleanField(default=False)),
('value', models.IntegerField(default=0)),
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attribute_value', to='product.attribute')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_attribute_value', to='product.product')),
],
options={
'abstract': False,
},
),
]

View File

View File

View File

View File

View File

94
apps/product/models.py Normal file
View File

@@ -0,0 +1,94 @@
from django.db import models
from apps.core.models import BaseModel
# Create your models here.
class ReferenceProduct(BaseModel):
""" Reference product - like: rice """
name = models.CharField(max_length=250, default='empty') # noqa
type_choices = (
('F', 'Free'), # free product
('G', 'Governmental') # government product
)
type = models.CharField(max_length=3, choices=type_choices)
img = models.CharField(max_length=100, default='empty')
def __str__(self):
return f'name: {self.name} - type: {self.type}'
def save(self, *args, **kwargs):
super(ReferenceProduct, self).save(*args, **kwargs)
class Product(BaseModel):
""" Child of reference product - like: brown rice """
name = models.CharField(max_length=250, default='empty') # noqa
type_choices = (
('F', 'Free'), # free product
('G', 'Governmental') #
)
type = models.CharField(max_length=3, choices=type_choices)
img = models.CharField(max_length=100, default='empty')
reference = models.ForeignKey(
ReferenceProduct,
on_delete=models.CASCADE,
related_name='reference_product',
null=True
)
def __str__(self):
return f'name: {self.name} - type: {self.type}'
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
class Attribute(BaseModel):
"""
every reference product have multiple attributes
"""
reference_product = models.ForeignKey(
ReferenceProduct,
on_delete=models.CASCADE,
related_name='reference_attribute',
null=True
)
name = models.CharField(max_length=100, default='empty')
type = models.CharField(
max_length=255,
default='empty',
help_text='type of attribute like: calculate product by kilogram'
)
def __str__(self):
return f'{self.reference_product.name} - {self.name}'
def save(self, *args, **kwargs):
return super(Attribute, self).save(*args, **kwargs)
class AttributeValue(BaseModel):
"""
every child product should have attribute value for
reference product attribute
"""
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='product_attribute_value',
null=True
)
attribute = models.ForeignKey(
Attribute,
on_delete=models.CASCADE,
related_name='attribute_value'
)
value = models.IntegerField(default=0)
def __str__(self):
return f'{self.product.name} - {self.attribute.name} - {self.value}'
def save(self, *args, **kwargs):
return super(AttributeValue, self).save(*args, **kwargs)

View File

View File

View File

View File

View File

View File

0
apps/product/services.py Normal file
View File

3
apps/product/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

0
apps/product/tools.py Normal file
View File

5
apps/product/urls.py Normal file
View File

@@ -0,0 +1,5 @@
from django.urls import path, include
urlpatterns = [
path('web/api/', include('apps.product.web.api.v1.urls'))
]

3
apps/product/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

View File

View File

@@ -0,0 +1,93 @@
from apps.product.web.api.v1 import serializers as product_serializers
from rest_framework.exceptions import APIException
from apps.product import models as product_models
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework import viewsets
from rest_framework import status
from django.db import transaction
def trash(queryset, pk):
""" sent object to trash """
obj = queryset.get(id=pk)
obj.trash = True
obj.save()
def delete(queryset, pk):
""" full delete object """
obj = queryset.get(id=pk)
obj.delete()
class ProductViewSet(viewsets.ModelViewSet):
queryset = product_models.Product.objects.all()
serializer_class = product_serializers.ProductSerializer
@action(
methods=['put'],
detail=True,
url_path='trash',
url_name='trash',
name='trash',
)
@transaction.atomic
def trash(self, request, pk=None):
""" Sent product to trash """
try:
trash(self.queryset, pk)
except APIException as e:
return Response(e, status.HTTP_204_NO_CONTENT)
@action(
methods=['post'],
detail=True,
url_name='delete',
url_path='delete',
name='delete'
)
@transaction.atomic
def delete(self, request, pk=None):
""" Full delete of product object """
try:
delete(self.queryset, pk)
return Response(status=status.HTTP_200_OK)
except APIException as e:
return Response(e, status=status.HTTP_204_NO_CONTENT)
class ReferenceProductViewSet(viewsets.ModelViewSet):
queryset = product_models.ReferenceProduct.objects.all()
serializer_class = product_serializers.ReferenceProductSerializer
@action(
methods=['put'],
detail=True,
url_path='trash',
url_name='trash',
name='trash',
)
@transaction.atomic
def trash(self, request, pk=None):
""" Sent product to trash """
try:
trash(self.queryset, pk)
except APIException as e:
return Response(e, status.HTTP_204_NO_CONTENT)
@action(
methods=['post'],
detail=True,
url_name='delete',
url_path='delete',
name='delete'
)
@transaction.atomic
def delete(self, request, pk=None):
""" Full delete of product object """
try:
delete(self.queryset, pk)
return Response(status=status.HTTP_200_OK)
except APIException as e:
return Response(e, status=status.HTTP_204_NO_CONTENT)

View File

@@ -0,0 +1,27 @@
from rest_framework import serializers
from apps.product import models as product_models
class ReferenceProductSerializer(serializers.ModelSerializer):
""" Serializer of reference product """
class Meta:
model = product_models.ReferenceProduct
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
""" Serializer of product """
class Meta:
model = product_models.Product
fields = '__all__'
def to_representation(self, instance):
""" Custom output of product serializer """
representation = super().to_representation(instance)
if instance.reference:
representation['reference'] = ReferenceProductSerializer(instance.reference).data
return representation

View File

@@ -0,0 +1,11 @@
from apps.product.web.api.v1 import api as api_views
from rest_framework.routers import DefaultRouter
from django.urls import path, include
router = DefaultRouter()
router.register(r'product', api_views.ProductViewSet, basename='product')
router.register(r'reference', api_views.ReferenceProductViewSet, basename='reference')
urlpatterns = [
path('v1/', include(router.urls))
]

View File

BIN
ss.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B