some parts of product - fix custom pagination - add id to pages list
This commit is contained in:
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -18,6 +18,7 @@ class PageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'code'
|
||||
]
|
||||
|
||||
@@ -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
7
apps/core/pagination.py
Normal 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
0
apps/product/__init__.py
Normal file
3
apps/product/admin.py
Normal file
3
apps/product/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
apps/product/apps.py
Normal file
6
apps/product/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProductConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.product'
|
||||
0
apps/product/management/__init__.py
Normal file
0
apps/product/management/__init__.py
Normal file
0
apps/product/management/commands/__init__.py
Normal file
0
apps/product/management/commands/__init__.py
Normal file
1
apps/product/management/commands/command.py
Normal file
1
apps/product/management/commands/command.py
Normal file
@@ -0,0 +1 @@
|
||||
# Your custom management commands go here.
|
||||
56
apps/product/migrations/0001_initial.py
Normal file
56
apps/product/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
54
apps/product/migrations/0002_attribute_attributevalue.py
Normal file
54
apps/product/migrations/0002_attribute_attributevalue.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/product/migrations/__init__.py
Normal file
0
apps/product/migrations/__init__.py
Normal file
0
apps/product/mobile/api/__init__.py
Normal file
0
apps/product/mobile/api/__init__.py
Normal file
0
apps/product/mobile/api/v1/__init__.py
Normal file
0
apps/product/mobile/api/v1/__init__.py
Normal file
0
apps/product/mobile/api/v1/serializers.py
Normal file
0
apps/product/mobile/api/v1/serializers.py
Normal file
0
apps/product/mobile/api/v1/urls.py
Normal file
0
apps/product/mobile/api/v1/urls.py
Normal file
0
apps/product/mobile/api/v1/views.py
Normal file
0
apps/product/mobile/api/v1/views.py
Normal file
0
apps/product/mobile/tests/test_common_services.py
Normal file
0
apps/product/mobile/tests/test_common_services.py
Normal file
94
apps/product/models.py
Normal file
94
apps/product/models.py
Normal 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)
|
||||
0
apps/product/permissions.py
Normal file
0
apps/product/permissions.py
Normal file
0
apps/product/pos/api/__init__.py
Normal file
0
apps/product/pos/api/__init__.py
Normal file
0
apps/product/pos/api/v1/__init__.py
Normal file
0
apps/product/pos/api/v1/__init__.py
Normal file
0
apps/product/pos/api/v1/serializers.py
Normal file
0
apps/product/pos/api/v1/serializers.py
Normal file
0
apps/product/pos/api/v1/urls.py
Normal file
0
apps/product/pos/api/v1/urls.py
Normal file
0
apps/product/pos/api/v1/views.py
Normal file
0
apps/product/pos/api/v1/views.py
Normal file
0
apps/product/pos/tests/test_common_services.py
Normal file
0
apps/product/pos/tests/test_common_services.py
Normal file
0
apps/product/services.py
Normal file
0
apps/product/services.py
Normal file
3
apps/product/tests.py
Normal file
3
apps/product/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
0
apps/product/tools.py
Normal file
0
apps/product/tools.py
Normal file
5
apps/product/urls.py
Normal file
5
apps/product/urls.py
Normal 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
3
apps/product/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
apps/product/web/api/__init__.py
Normal file
0
apps/product/web/api/__init__.py
Normal file
0
apps/product/web/api/v1/__init__.py
Normal file
0
apps/product/web/api/v1/__init__.py
Normal file
93
apps/product/web/api/v1/api.py
Normal file
93
apps/product/web/api/v1/api.py
Normal 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)
|
||||
27
apps/product/web/api/v1/serializers.py
Normal file
27
apps/product/web/api/v1/serializers.py
Normal 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
|
||||
11
apps/product/web/api/v1/urls.py
Normal file
11
apps/product/web/api/v1/urls.py
Normal 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))
|
||||
]
|
||||
0
apps/product/web/api/v1/views.py
Normal file
0
apps/product/web/api/v1/views.py
Normal file
0
apps/product/web/tests/test_common_services.py
Normal file
0
apps/product/web/tests/test_common_services.py
Normal file
Reference in New Issue
Block a user