From 3e2375582c8b216a4f9206c829e304cab53a042b Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sun, 8 Jun 2025 14:36:57 +0330 Subject: [PATCH] change bug of permissions list --- apps/authorization/api/v1/api.py | 21 +++ apps/authorization/api/v1/serializers.py | 5 +- ...alter_attributevalue_attribute_and_more.py | 47 ++++++ ...name_product_saleunit_reference_product.py | 18 ++ .../0006_alter_saleunit_unit_and_more.py | 23 +++ apps/product/models.py | 59 ++++++- apps/product/web/api/v1/api.py | 156 +++++++++++++++++- apps/product/web/api/v1/serializers.py | 75 +++++++++ apps/product/web/api/v1/urls.py | 4 + 9 files changed, 398 insertions(+), 10 deletions(-) create mode 100644 apps/product/migrations/0004_alter_attribute_type_alter_attributevalue_attribute_and_more.py create mode 100644 apps/product/migrations/0005_rename_product_saleunit_reference_product.py create mode 100644 apps/product/migrations/0006_alter_saleunit_unit_and_more.py diff --git a/apps/authorization/api/v1/api.py b/apps/authorization/api/v1/api.py index a1d7197..225a1f1 100644 --- a/apps/authorization/api/v1/api.py +++ b/apps/authorization/api/v1/api.py @@ -1,5 +1,6 @@ from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.exceptions import APIException from apps.authorization.api.v1.serializers import ( RoleSerializer, PermissionSerializer, @@ -17,6 +18,7 @@ from apps.authorization.models import ( from rest_framework import viewsets from django.db import transaction from rest_framework import filters +from rest_framework import status class RoleViewSet(viewsets.ModelViewSet): @@ -34,6 +36,25 @@ class PageViewSet(viewsets.ModelViewSet): filter_backends = [filters.SearchFilter] search_fields = ['name', 'code'] + @action( + methods=['delete'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of page & permissions of page object """ + try: + page = self.queryset.get(id=pk) + permissions = Permissions.objects.filter(page=page) + permissions.delete() + page.delete() + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT) + class PermissionViewSet(viewsets.ModelViewSet): """ Crud Operations for Permissions """ diff --git a/apps/authorization/api/v1/serializers.py b/apps/authorization/api/v1/serializers.py index c6207b6..5afbb0c 100644 --- a/apps/authorization/api/v1/serializers.py +++ b/apps/authorization/api/v1/serializers.py @@ -54,8 +54,9 @@ class PermissionSerializer(serializers.ModelSerializer): return representation @classmethod - def permissions_structure_output(cls, permissions: list) -> typing.Any: + def permissions_structure_output(cls, permissions) -> typing.Any: """ set a structure for permissions """ + structure = [] pages_list = [] for counter, permission in enumerate(permissions): @@ -64,7 +65,7 @@ class PermissionSerializer(serializers.ModelSerializer): structure.append({ 'page_name': permission.page.name, 'page_access': itertools.chain(*list( - permission.page.permission_page.all().values_list('name'))) + permissions.filter(page=permission.page).values_list('name'))) }) return structure diff --git a/apps/product/migrations/0004_alter_attribute_type_alter_attributevalue_attribute_and_more.py b/apps/product/migrations/0004_alter_attribute_type_alter_attributevalue_attribute_and_more.py new file mode 100644 index 0000000..11da6ab --- /dev/null +++ b/apps/product/migrations/0004_alter_attribute_type_alter_attributevalue_attribute_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 5.0 on 2025-06-08 07:32 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authorization', '0016_alter_permissions_name'), + ('product', '0003_saleunit'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='attribute', + name='type', + field=models.CharField(choices=[('K', 'Per Kilo'), ('', '')], default='empty', help_text='type of attribute like: calculate product by kilogram', max_length=10), + ), + migrations.AlterField( + model_name='attributevalue', + name='attribute', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attribute_value', to='product.attribute'), + ), + migrations.CreateModel( + name='Broker', + 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)), + ('calculation_strategy', models.CharField(choices=[('K', 'Per Kilo'), ('', '')], default='empty', max_length=3)), + ('required', models.BooleanField(default=False)), + ('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)), + ('organization_relations', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_organization', to='authorization.userrelations')), + ('reference_product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_broker', to='product.referenceproduct')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/product/migrations/0005_rename_product_saleunit_reference_product.py b/apps/product/migrations/0005_rename_product_saleunit_reference_product.py new file mode 100644 index 0000000..4fadd13 --- /dev/null +++ b/apps/product/migrations/0005_rename_product_saleunit_reference_product.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-06-08 08:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0004_alter_attribute_type_alter_attributevalue_attribute_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='saleunit', + old_name='product', + new_name='reference_product', + ), + ] diff --git a/apps/product/migrations/0006_alter_saleunit_unit_and_more.py b/apps/product/migrations/0006_alter_saleunit_unit_and_more.py new file mode 100644 index 0000000..494a31c --- /dev/null +++ b/apps/product/migrations/0006_alter_saleunit_unit_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0 on 2025-06-08 08:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0005_rename_product_saleunit_reference_product'), + ] + + operations = [ + migrations.AlterField( + model_name='saleunit', + name='unit', + field=models.CharField(choices=[('10P', '10KG Package'), ('50P', '50KG Package'), ('', '')], max_length=10, null=True), + ), + migrations.AlterField( + model_name='saleunit', + name='variation_coefficient', + field=models.IntegerField(default=0), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index 372fdce..1f40982 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,5 +1,6 @@ from django.db import models from apps.core.models import BaseModel +from apps.authorization.models import UserRelations # Create your models here. @@ -56,8 +57,13 @@ class Attribute(BaseModel): null=True ) name = models.CharField(max_length=100, default='empty') + type_choices = ( + ('K', 'Per Kilo'), + ('', ''), + ) type = models.CharField( - max_length=255, + max_length=10, + choices=type_choices, default='empty', help_text='type of attribute like: calculate product by kilogram' ) @@ -83,7 +89,8 @@ class AttributeValue(BaseModel): attribute = models.ForeignKey( Attribute, on_delete=models.CASCADE, - related_name='attribute_value' + related_name='attribute_value', + null=True ) value = models.IntegerField(default=0) @@ -94,16 +101,56 @@ class AttributeValue(BaseModel): return super(AttributeValue, self).save(*args, **kwargs) +class Broker(BaseModel): + """ Broker for product """ + + reference_product = models.ForeignKey( + ReferenceProduct, + on_delete=models.CASCADE, + related_name='product_broker', + null=True + ) + organization_relations = models.ForeignKey( + UserRelations, + on_delete=models.CASCADE, + related_name='product_organization', + null=True + ) + calculation_choices = ( + ('K', 'Per Kilo'), + ('', ''), + ) + calculation_strategy = models.CharField( + max_length=3, + choices=calculation_choices, + default='empty' + ) + required = models.BooleanField(default=False) + + def __str__(self): + return f'{self.organization_relations.organization.name} - {self.reference_product.name}' + + def save(self, *args, **kwargs): + return super(Broker, self).save(*args, **kwargs) + + class SaleUnit(BaseModel): - product = models.ForeignKey( + """ Units of product for sale """ + + reference_product = models.ForeignKey( ReferenceProduct, on_delete=models.CASCADE, related_name='sale_unit', null=True ) - unit = models.CharField(max_length=255, null=True) - variation_coefficient = models.CharField(max_length=255, null=True) + unit_choices = ( + ('10P', '10KG Package'), + ('50P', '50KG Package'), + ('', ''), + ) + unit = models.CharField(max_length=10, choices=unit_choices, null=True) + variation_coefficient = models.IntegerField(default=0) required = models.BooleanField(default=False) def __str__(self): - return f'{self.product} - {self.unit} - {self.variation_coefficient}' + return f'{self.reference_product} - {self.unit} - {self.variation_coefficient}' diff --git a/apps/product/web/api/v1/api.py b/apps/product/web/api/v1/api.py index 5df632c..3e1aebd 100644 --- a/apps/product/web/api/v1/api.py +++ b/apps/product/web/api/v1/api.py @@ -8,7 +8,7 @@ from rest_framework import status from django.db import transaction -def trash(queryset, pk): +def trash(queryset, pk): # noqa """ sent object to trash """ obj = queryset.get(id=pk) obj.trash = True @@ -90,4 +90,156 @@ class ReferenceProductViewSet(viewsets.ModelViewSet): delete(self.queryset, pk) return Response(status=status.HTTP_200_OK) except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) \ No newline at end of file + return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class AttributeViewSet(viewsets.ModelViewSet): + """ attributes of reference product """ + + queryset = product_models.Attribute.objects.all() + serializer_class = product_serializers.AttributeSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent attribute 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 attribute 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 AttributeValueViewSet(viewsets.ModelViewSet): + """ apis for attribute values of child products """ # noqa + + queryset = product_models.AttributeValue.objects.all() + serializer_class = product_serializers.AttributeValueSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent attribute value 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 attribute value 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 BrokerViewSet(viewsets.ModelViewSet): + """ apis of product brokers """ # noqa + + queryset = product_models.Broker.objects.all() + serializer_class = product_serializers.BrokerSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent broker 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 broker 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 SaleUnitViewSet(viewsets.ModelViewSet): + """ apis of unit of sale for products """ # noqa + + queryset = product_models.SaleUnit.objects.all() + serializer_class = product_serializers.SaleUnitSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent unit sale 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 unit sale 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) diff --git a/apps/product/web/api/v1/serializers.py b/apps/product/web/api/v1/serializers.py index 5695bdb..6dc91a0 100644 --- a/apps/product/web/api/v1/serializers.py +++ b/apps/product/web/api/v1/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from apps.product import models as product_models +from apps.authorization.api.v1 import serializers as authorize_serializers class ReferenceProductSerializer(serializers.ModelSerializer): @@ -25,3 +26,77 @@ class ProductSerializer(serializers.ModelSerializer): representation['reference'] = ReferenceProductSerializer(instance.reference).data return representation + + +class AttributeSerializer(serializers.ModelSerializer): + """ serialize attributes of reference product """ + + class Meta: + model = product_models.Attribute + fields = '__all__' + + def to_representation(self, instance): + representation = super().to_representation(instance) + if instance.reference_product: + representation['reference_product'] = ReferenceProductSerializer( + instance.reference_product + ).data + + return representation + + +class AttributeValueSerializer(serializers.ModelSerializer): + """ serialize attribute values for child products """ + + class Meta: + model = product_models.AttributeValue + fields = '__all__' + + def to_representation(self, instance): + """ Custom output """ + + representation = super().to_representation(instance) + if instance.product: + representation['product'] = ProductSerializer(instance.product).data + if instance.attribute: + representation['attribute'] = AttributeSerializer(instance.attribute).data + + return representation + + +class BrokerSerializer(serializers.ModelSerializer): + """ serialize product broker """ + + class Meta: + model = product_models.Broker + fields = '__all__' + + def to_representation(self, instance): + representation = super().to_representation(instance) + if instance.reference_product: + representation['reference_product'] = ReferenceProductSerializer( + instance.reference_product + ).data + if instance.organization_relations: + representation['organization_relations'] = authorize_serializers.UserRelationSerializer( + instance.organization_relations + ).data + + return representation + + +class SaleUnitSerializer(serializers.ModelSerializer): + """ serialize unit of products for sale """ + + class Meta: + model = product_models.SaleUnit + fields = '__all__' + + def to_representation(self, instance): + representation = super().to_representation(instance) + if instance.reference_product: + representation['reference_product'] = ReferenceProductSerializer( + instance.reference_product + ).data + + return representation diff --git a/apps/product/web/api/v1/urls.py b/apps/product/web/api/v1/urls.py index 9cba8b9..3f85369 100644 --- a/apps/product/web/api/v1/urls.py +++ b/apps/product/web/api/v1/urls.py @@ -5,6 +5,10 @@ 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') +router.register(r'attribute', api_views.AttributeViewSet, basename='attribute') +router.register(r'attribute_value', api_views.AttributeValueViewSet, basename='attribute_value') +router.register(r'broker', api_views.BrokerViewSet, basename='broker') +router.register(r'sale_unit', api_views.SaleUnitViewSet, basename='sale_unit') urlpatterns = [ path('v1/', include(router.urls))