diff --git a/ecommerce/baskets/migrations/0001_initial.py b/ecommerce/baskets/migrations/0001_initial.py index 79a58d7..ca5b3f4 100644 --- a/ecommerce/baskets/migrations/0001_initial.py +++ b/ecommerce/baskets/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 3.2.9 on 2021-12-03 17:37 +# Generated by Django 3.2.9 on 2021-12-14 19:34 -from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -10,8 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('products', '0003_auto_20211203_1919'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('products', '0001_initial'), ] operations = [ @@ -21,8 +19,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('status', models.CharField(choices=[('open', 'Open'), ('submitted', 'Submitted'), ('merged', 'Merged')], max_length=10, verbose_name='Basket Status')), - ('customer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Customer')), + ('status', models.CharField(choices=[('open', 'Open'), ('submitted', 'Submitted'), ('merged', 'Merged')], default='open', max_length=10, verbose_name='Basket Status')), ], options={ 'verbose_name': 'Basket', @@ -37,8 +34,8 @@ class Migration(migrations.Migration): ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), ('quantity', models.PositiveIntegerField(verbose_name='Quantity')), ('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')), - ('basket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='baskets.basket', verbose_name='Basket')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Product')), + ('basket', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='baskets.basket', verbose_name='Basket')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product')), ], options={ 'verbose_name': 'Basket item', diff --git a/ecommerce/baskets/migrations/0002_auto_20211205_0953.py b/ecommerce/baskets/migrations/0002_auto_20211205_0953.py deleted file mode 100644 index 17bcf3b..0000000 --- a/ecommerce/baskets/migrations/0002_auto_20211205_0953.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-05 06:53 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('products', '0004_alter_product_categories'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('baskets', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='basket', - name='customer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Customer'), - ), - migrations.AlterField( - model_name='basket', - name='status', - field=models.CharField(choices=[('open', 'Open'), ('submitted', 'Submitted'), ('merged', 'Merged')], default='open', max_length=10, verbose_name='Basket Status'), - ), - migrations.AlterField( - model_name='basketitem', - name='basket', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='baskets.basket', verbose_name='Basket'), - ), - migrations.AlterField( - model_name='basketitem', - name='product', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product'), - ), - ] diff --git a/ecommerce/baskets/migrations/0002_basket_customer.py b/ecommerce/baskets/migrations/0002_basket_customer.py new file mode 100644 index 0000000..8d541da --- /dev/null +++ b/ecommerce/baskets/migrations/0002_basket_customer.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.9 on 2021-12-14 19:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('baskets', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='basket', + name='customer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Customer'), + ), + ] diff --git a/ecommerce/baskets/models.py b/ecommerce/baskets/models.py index 4f109d5..2d3b7bf 100644 --- a/ecommerce/baskets/models.py +++ b/ecommerce/baskets/models.py @@ -12,7 +12,7 @@ class Basket(BaseAbstractModel): Basket model """ customer = models.ForeignKey(Customer, verbose_name=_("Customer"), - on_delete=models.PROTECT, null=True, blank=True) + on_delete=models.PROTECT, null=True, blank=True,related_name='cart') status = models.CharField(choices=enums.BasketStatus.choices, max_length=10, verbose_name=_("Basket Status"), default=enums.BasketStatus.OPEN) @@ -28,8 +28,8 @@ class BasketItem(BaseAbstractModel): """ Basket item model """ - basket = models.ForeignKey(Basket, verbose_name=_("Basket"), on_delete=models.PROTECT) - product = models.ForeignKey(Product, verbose_name=_("Product"), on_delete=models.PROTECT) + basket = models.ForeignKey(Basket,related_name='items', verbose_name=_("Basket"), on_delete=models.PROTECT) + product = models.ForeignKey(Product,related_name='items', verbose_name=_("Product"), on_delete=models.PROTECT) quantity = models.PositiveIntegerField(verbose_name=_("Quantity")) price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Price")) diff --git a/ecommerce/baskets/serializers.py b/ecommerce/baskets/serializers.py index 9dc4c30..1118be3 100644 --- a/ecommerce/baskets/serializers.py +++ b/ecommerce/baskets/serializers.py @@ -26,3 +26,10 @@ class BasketItemDetailedSerializer(BasketItemSerializer): class BasketDetailedSerializer(BasketSerializer): customer = CustomerSerializer() + +class BasketPostSerializer(serializers.ModelSerializer): + customer = CustomerSerializer() + items = BasketItemSerializer(many=True) + class Meta: + model = Basket + fields = ('id', 'customer', 'items') \ No newline at end of file diff --git a/ecommerce/baskets/views.py b/ecommerce/baskets/views.py index ee32571..865417c 100644 --- a/ecommerce/baskets/views.py +++ b/ecommerce/baskets/views.py @@ -1,9 +1,12 @@ from rest_framework import viewsets - +from rest_framework.decorators import action +from rest_framework.response import Response from baskets.filters import BasketItemFilter, BasketFilter from baskets.models import BasketItem, Basket -from baskets.serializers import BasketItemSerializer, BasketSerializer, BasketItemDetailedSerializer, BasketDetailedSerializer +from baskets.serializers import BasketItemSerializer, BasketSerializer, BasketItemDetailedSerializer, \ + BasketDetailedSerializer,BasketPostSerializer from core.mixins import DetailedViewSetMixin +from products.models import Product class BasketItemViewSet(DetailedViewSetMixin, viewsets.ModelViewSet): @@ -22,5 +25,88 @@ class BasketViewSet(DetailedViewSetMixin, viewsets.ModelViewSet): filterset_class = BasketFilter serializer_action_classes = { "detailed_list": BasketDetailedSerializer, - "detailed": BasketDetailedSerializer, + "detailed": BasketPostSerializer, + "add_to_basket": BasketPostSerializer, + "remove_from_basket": BasketPostSerializer } + + def get_queryset(self): + queryset = super().get_queryset() + user = self.request.user + return queryset.filter(customer=user) + + @action(detail=True, methods=['post', 'put']) + def add_to_basket(self, request, pk=None): + """Add an item to a user's basket. + Adding to basket is disallowed if there is not enough inventory for the + product available. If there is, the quantity is increased on an existing + basket item or a new basket item is created with that quantity and added + to the basket. + """ + basket = self.get_object() + + try: + product = Product.objects.get( + pk=request.data['items'][0]['product'] + ) + quantity = int(request.data['items'][0]['quantity']) + price = request.data['items'][0]['price'] + except Exception as ex: + print(ex) + return Response('Required fields must be filled') + """ + before adding the item to the basket, product's stock quantity is checked + """ + + if product.stock.quantity <= 0 or product.stock.quantity - quantity < 0: + print("There is no more product available") + return Response('There is no more product available') + existing_basket_item = BasketItem.objects.filter(basket=basket, product=product).first() + """ + before creating a new basket item check if it is in the basket already + and if it is increase the quantity of that item + """ + if existing_basket_item: + existing_basket_item.quantity += quantity + existing_basket_item.save() + else: + new_basket_item = BasketItem(basket=basket, product=product, quantity=quantity, price=price) + new_basket_item.save() + serializer = BasketPostSerializer(basket) + return Response(serializer.data) + + @action(detail=True, methods=['post', 'put']) + def remove_from_basket(self, request, pk=None): + """Remove an item from a user's basket. + Removing from the basket can be done with editing quantity for item. + Edited quantity is decreased from the existing quantity. + But after removing if items quantity is equal to zero item is deleted. + """ + basket = self.get_object() + try: + product = Product.objects.get( + pk=request.data['items'][0]['product'] + ) + quantity = int(request.data['items'][0]['quantity']) + price = request.data['items'][0]['price'] + except Exception as ex: + print(ex) + return Response('Required fields must be filled') + + try: + basket_item = BasketItem.objects.get(basket=basket, product=product) + except Exception as ex: + print(ex) + return Response({'status': 'fail'}) + + # if removing an item where the quantity of the item will become zero after process, remove the basket item + # completely otherwise decrease the quantity of the basket item + if basket_item.quantity - quantity <= 0: + basket_item.delete() + else: + basket_item.quantity -= quantity + basket_item.save() + + # return the updated basket to indicate success + serializer = BasketPostSerializer(basket) + return Response(serializer.data) diff --git a/ecommerce/customers/migrations/0001_initial.py b/ecommerce/customers/migrations/0001_initial.py index a691fbb..eeea00c 100644 --- a/ecommerce/customers/migrations/0001_initial.py +++ b/ecommerce/customers/migrations/0001_initial.py @@ -1,8 +1,11 @@ -# Generated by Django 3.2.9 on 2021-11-28 09:32 +# Generated by Django 3.2.9 on 2021-12-14 19:34 +import core.utils import customers.managers +from django.conf import settings import django.contrib.auth.validators from django.db import migrations, models +import django.db.models.deletion import django.utils.timezone @@ -11,111 +14,81 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( - name="Customer", + name='Customer', fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("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", - ), - ), - ( - "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( - max_length=254, - unique=True, - validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() - ], - 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" - ), - ), - ( - "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", - ), - ), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('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(max_length=254, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], 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')), + ('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": "customer", - "verbose_name_plural": "customers", + 'verbose_name': 'customer', + 'verbose_name_plural': 'customers', }, managers=[ - ("objects", customers.managers.CustomerManager()), + ('objects', customers.managers.CustomerManager()), ], ), + migrations.CreateModel( + name='Country', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('name', models.CharField(max_length=255, verbose_name='Country')), + ], + options={ + 'verbose_name': 'country', + 'verbose_name_plural': 'countries', + }, + ), + migrations.CreateModel( + name='City', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('name', models.CharField(max_length=255)), + ('country', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='customers.country', verbose_name='Country')), + ], + options={ + 'verbose_name': 'city', + 'verbose_name_plural': 'cities', + }, + ), + migrations.CreateModel( + name='Address', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('full_name', models.CharField(max_length=255, verbose_name='Full Name')), + ('line_1', models.CharField(max_length=255, verbose_name='Address Line 1')), + ('line_2', models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2')), + ('phone', models.CharField(help_text='Phone number must be entered in the format: +901234567890. ', max_length=20, validators=[core.utils.PhoneNumberValidator()], verbose_name='Phone Number')), + ('district', models.CharField(max_length=255, verbose_name='District')), + ('zipcode', models.CharField(max_length=20, verbose_name='Zip Code')), + ('is_default', models.BooleanField(default=False)), + ('city', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='customers.city', verbose_name='City')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='Customer')), + ], + options={ + 'verbose_name': 'address', + 'verbose_name_plural': 'addresses', + }, + ), ] diff --git a/ecommerce/customers/migrations/0002_address_city_country.py b/ecommerce/customers/migrations/0002_address_city_country.py deleted file mode 100644 index 1f4de99..0000000 --- a/ecommerce/customers/migrations/0002_address_city_country.py +++ /dev/null @@ -1,65 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-03 16:34 - -import customers.models -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('customers', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Country', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('name', models.CharField(max_length=255, verbose_name='Country')), - ], - options={ - 'verbose_name': 'country', - 'verbose_name_plural': 'countries', - }, - ), - migrations.CreateModel( - name='City', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('name', models.CharField(max_length=255)), - ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='customers.country', verbose_name='Country')), - ], - options={ - 'verbose_name': 'city', - 'verbose_name_plural': 'cities', - }, - ), - migrations.CreateModel( - name='Address', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Name')), - ('line_1', models.CharField(max_length=255, verbose_name='Address Line 1')), - ('line_2', models.CharField(blank=True, max_length=255, verbose_name='Address Line 2')), - ('phone', models.CharField(max_length=20, validators=[customers.models.PhoneNumberValidator()], verbose_name='Phone Number')), - ('district', models.CharField(blank=True, max_length=255, verbose_name='District')), - ('zipcode', models.CharField(blank=True, max_length=20, verbose_name='Zip Code')), - ('is_default', models.BooleanField(default=False)), - ('city', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='customers.city', verbose_name='City')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Customer')), - ], - options={ - 'verbose_name': 'address', - 'verbose_name_plural': 'addresses', - }, - ), - ] diff --git a/ecommerce/customers/migrations/0003_auto_20211203_2031.py b/ecommerce/customers/migrations/0003_auto_20211203_2031.py deleted file mode 100644 index d7e465e..0000000 --- a/ecommerce/customers/migrations/0003_auto_20211203_2031.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-03 17:31 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('customers', '0002_address_city_country'), - ] - - operations = [ - migrations.AlterField( - model_name='address', - name='city', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='customers.city', verbose_name='City'), - ), - migrations.AlterField( - model_name='city', - name='country', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='customers.country', verbose_name='Country'), - ), - ] diff --git a/ecommerce/customers/migrations/0004_alter_address_district.py b/ecommerce/customers/migrations/0004_alter_address_district.py deleted file mode 100644 index 1b80f6c..0000000 --- a/ecommerce/customers/migrations/0004_alter_address_district.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-04 00:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('customers', '0003_auto_20211203_2031'), - ] - - operations = [ - migrations.AlterField( - model_name='address', - name='district', - field=models.CharField(max_length=255, verbose_name='District'), - ), - ] diff --git a/ecommerce/customers/migrations/0005_auto_20211205_0953.py b/ecommerce/customers/migrations/0005_auto_20211205_0953.py deleted file mode 100644 index 431b355..0000000 --- a/ecommerce/customers/migrations/0005_auto_20211205_0953.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-05 06:53 - -import core.utils -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('customers', '0004_alter_address_district'), - ] - - operations = [ - migrations.AlterField( - model_name='address', - name='full_name', - field=models.CharField(max_length=255, verbose_name='Full Name'), - ), - migrations.AlterField( - model_name='address', - name='line_2', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2'), - ), - migrations.AlterField( - model_name='address', - name='phone', - field=models.CharField(help_text='Phone number must be entered in the format: +901234567890. ', max_length=20, validators=[core.utils.PhoneNumberValidator()], verbose_name='Phone Number'), - ), - migrations.AlterField( - model_name='address', - name='zipcode', - field=models.CharField(max_length=20, verbose_name='Zip Code'), - ), - ] diff --git a/ecommerce/customers/migrations/0006_alter_address_customer.py b/ecommerce/customers/migrations/0006_alter_address_customer.py deleted file mode 100644 index cd05d1c..0000000 --- a/ecommerce/customers/migrations/0006_alter_address_customer.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-12 10:34 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('customers', '0005_auto_20211205_0953'), - ] - - operations = [ - migrations.AlterField( - model_name='address', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='Customer'), - ), - ] diff --git a/ecommerce/customers/serializers.py b/ecommerce/customers/serializers.py index ffad735..af99cc2 100644 --- a/ecommerce/customers/serializers.py +++ b/ecommerce/customers/serializers.py @@ -7,14 +7,12 @@ class CustomerSerializer(serializers.ModelSerializer): - class Meta: model = Customer fields = ("id", "first_name", "last_name", "email", "is_staff", "is_active", "date_joined") class ProfileSerializer(serializers.ModelSerializer): - class Meta: model = Customer fields = ("first_name", "last_name", "email") @@ -27,7 +25,6 @@ class Meta: class CitySerializer(serializers.ModelSerializer): - class Meta: model = City fields = ("id", "name", "country") @@ -68,3 +65,23 @@ class AddressDetailedSerializer(AddressSerializer): class CityDetailedSerializer(CitySerializer): country = CountrySerializer() + + +class RegisterSerializer(serializers.ModelSerializer): + password_repeat = serializers.CharField(required=True) + + class Meta: + model = Customer + fields = ("email", "password", "password_repeat") + extra_kwargs = {"password": {"write_only": True}, "password_repeat": {"write_only": True}} + + def create(self, validated_data): + password = validated_data.pop("password", None) + password_repeat = validated_data.pop("password_repeat", None) + instance = self.Meta.model(**validated_data) + if instance is not None: + if password == password_repeat: + instance.set_password(password) + instance.save() + return instance + raise ValidationError("Passwords do not match") diff --git a/ecommerce/customers/views.py b/ecommerce/customers/views.py index e2d05b6..55d4a29 100644 --- a/ecommerce/customers/views.py +++ b/ecommerce/customers/views.py @@ -1,14 +1,15 @@ from django.shortcuts import get_object_or_404 -from rest_framework import viewsets, permissions, mixins +from rest_framework import viewsets, permissions, mixins, status from rest_framework.viewsets import GenericViewSet - +from rest_framework.views import APIView +from rest_framework.response import Response from core.mixins import DetailedViewSetMixin from core.utils import IsStaffUserAuthenticated from customers.filters import CustomerFilter, AddressFilter, CountryFilter, CityFilter from customers.models import Customer, Address, City, Country from customers.serializers import CustomerSerializer, AddressSerializer, CitySerializer, \ CountrySerializer, \ - AddressDetailedSerializer, CityDetailedSerializer, ProfileSerializer + AddressDetailedSerializer, CityDetailedSerializer, ProfileSerializer,RegisterSerializer class AdminCustomerViewSet(viewsets.ModelViewSet): @@ -65,4 +66,13 @@ def get_queryset(self): return queryset.filter(customer=user) +class CustomerRegistration(APIView): + permission_classes = [permissions.AllowAny] + def post(self, request): + serializer = RegisterSerializer(data=request.data) + if serializer.is_valid(): + new_costumer = serializer.save() + if new_costumer: + return Response(status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/ecommerce/ecommerce/urls.py b/ecommerce/ecommerce/urls.py index 28278ef..3cccd48 100644 --- a/ecommerce/ecommerce/urls.py +++ b/ecommerce/ecommerce/urls.py @@ -21,7 +21,7 @@ from baskets.views import BasketItemViewSet, BasketViewSet from core.views import APITokenObtainPairView from customers.views import AddressViewSet, CityViewSet, \ - CountryViewSet, AdminCustomerViewSet, MyProfileViewSet + CountryViewSet, AdminCustomerViewSet, MyProfileViewSet,CustomerRegistration from ecommerce.router import router from orders.views import OrderItemViewSet, OrderViewSet, BillingAddressViewSet, ShippingAddressViewSet, \ OrderBankAccountViewSet @@ -55,6 +55,7 @@ path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/profile/', MyProfileViewSet.as_view( {"get": "retrieve", "put": "update", "patch": "partial_update"}), name='profile'), + path('api/register/', CustomerRegistration.as_view(),name="create-user") ] if settings.DEBUG: diff --git a/ecommerce/orders/migrations/0001_initial.py b/ecommerce/orders/migrations/0001_initial.py index ebd8d39..dfcc1e4 100644 --- a/ecommerce/orders/migrations/0001_initial.py +++ b/ecommerce/orders/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.9 on 2021-12-04 00:15 +# Generated by Django 3.2.9 on 2021-12-14 19:34 import core.utils from django.conf import settings @@ -11,9 +11,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('customers', '0004_alter_address_district'), - ('baskets', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('customers', '0001_initial'), + ('baskets', '0001_initial'), ] operations = [ @@ -23,12 +23,12 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Name')), + ('full_name', models.CharField(max_length=255, verbose_name='Full Name')), ('line_1', models.CharField(max_length=255, verbose_name='Address Line 1')), - ('line_2', models.CharField(blank=True, max_length=255, verbose_name='Address Line 2')), + ('line_2', models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2')), ('phone', models.CharField(max_length=20, validators=[core.utils.PhoneNumberValidator()], verbose_name='Phone Number')), ('district', models.CharField(max_length=255, verbose_name='District')), - ('zipcode', models.CharField(blank=True, max_length=20, verbose_name='Zip Code')), + ('zipcode', models.CharField(max_length=20, verbose_name='Zip Code')), ('city', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='customers.city', verbose_name='City')), ], options={ @@ -59,9 +59,9 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Name')), + ('full_name', models.CharField(max_length=255, verbose_name='Full Name')), ('line_1', models.CharField(max_length=255, verbose_name='Address Line 1')), - ('line_2', models.CharField(blank=True, max_length=255, verbose_name='Address Line 2')), + ('line_2', models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2')), ('phone', models.CharField(max_length=20, validators=[core.utils.PhoneNumberValidator()], verbose_name='Phone Number')), ('district', models.CharField(max_length=255, verbose_name='District')), ('zipcode', models.CharField(blank=True, max_length=20, verbose_name='Zip Code')), diff --git a/ecommerce/orders/migrations/0002_auto_20211205_0953.py b/ecommerce/orders/migrations/0002_auto_20211205_0953.py deleted file mode 100644 index 81fd53b..0000000 --- a/ecommerce/orders/migrations/0002_auto_20211205_0953.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-05 06:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('orders', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='billingaddress', - name='full_name', - field=models.CharField(max_length=255, verbose_name='Full Name'), - ), - migrations.AlterField( - model_name='billingaddress', - name='line_2', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2'), - ), - migrations.AlterField( - model_name='billingaddress', - name='zipcode', - field=models.CharField(max_length=20, verbose_name='Zip Code'), - ), - migrations.AlterField( - model_name='shippingaddress', - name='full_name', - field=models.CharField(max_length=255, verbose_name='Full Name'), - ), - migrations.AlterField( - model_name='shippingaddress', - name='line_2', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Address Line 2'), - ), - ] diff --git a/ecommerce/payments/migrations/0001_initial.py b/ecommerce/payments/migrations/0001_initial.py index debba57..b5e6bd8 100644 --- a/ecommerce/payments/migrations/0001_initial.py +++ b/ecommerce/payments/migrations/0001_initial.py @@ -1,8 +1,8 @@ -# Generated by Django 3.2.9 on 2021-12-03 21:04 +# Generated by Django 3.2.9 on 2021-12-14 19:34 +import core.utils from django.db import migrations, models import django.db.models.deletion -import payments.models class Migration(migrations.Migration): @@ -33,7 +33,7 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), ('name', models.CharField(max_length=100, verbose_name='Bank Account Name')), - ('iban', models.CharField(max_length=100, validators=[payments.models.IBANValidator()], verbose_name='IBAN')), + ('iban', models.CharField(max_length=100, validators=[core.utils.IBANValidator()], verbose_name='IBAN')), ('bank', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='payments.bank', verbose_name='Bank Name')), ], options={ diff --git a/ecommerce/products/migrations/0001_initial.py b/ecommerce/products/migrations/0001_initial.py index 128d979..90c9f6c 100644 --- a/ecommerce/products/migrations/0001_initial.py +++ b/ecommerce/products/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 3.2.9 on 2021-11-28 10:29 +# Generated by Django 3.2.9 on 2021-12-14 19:34 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -11,6 +12,19 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('name', models.CharField(max_length=255, verbose_name='Category Name')), + ], + options={ + 'verbose_name': 'category', + 'verbose_name_plural': 'categories', + }, + ), migrations.CreateModel( name='Product', fields=[ @@ -22,10 +36,39 @@ class Migration(migrations.Migration): ('description', models.TextField(max_length=2000, verbose_name='Description')), ('color', models.CharField(choices=[('red', 'Red'), ('blue', 'Blue'), ('white', 'White'), ('yellow', 'Yellow')], max_length=20, verbose_name='Color')), ('size', models.CharField(max_length=30, verbose_name='Size')), + ('categories', models.ManyToManyField(to='products.Category', verbose_name='Categories')), ], options={ 'verbose_name': 'product', 'verbose_name_plural': 'products', }, ), + migrations.CreateModel( + name='Stock', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('quantity', models.PositiveIntegerField(verbose_name='Quantity')), + ('product', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product')), + ], + options={ + 'verbose_name': 'stock', + 'verbose_name_plural': 'stocks', + }, + ), + migrations.CreateModel( + name='Price', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), + ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')), + ('product', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product')), + ], + options={ + 'verbose_name': 'price', + 'verbose_name_plural': 'prices', + }, + ), ] diff --git a/ecommerce/products/migrations/0002_price_stock.py b/ecommerce/products/migrations/0002_price_stock.py deleted file mode 100644 index fe839dc..0000000 --- a/ecommerce/products/migrations/0002_price_stock.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.2.9 on 2021-11-28 11:15 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('products', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Stock', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('quantity', models.PositiveIntegerField(verbose_name='Quantity')), - ('product', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product')), - ], - options={ - 'verbose_name': 'stock', - 'verbose_name_plural': 'stocks', - }, - ), - migrations.CreateModel( - name='Price', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')), - ('product', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='products.product', verbose_name='Product')), - ], - options={ - 'verbose_name': 'price', - 'verbose_name_plural': 'prices', - }, - ), - ] diff --git a/ecommerce/products/migrations/0003_auto_20211203_1919.py b/ecommerce/products/migrations/0003_auto_20211203_1919.py deleted file mode 100644 index 0fad1bb..0000000 --- a/ecommerce/products/migrations/0003_auto_20211203_1919.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-03 16:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('products', '0002_price_stock'), - ] - - operations = [ - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Modified at')), - ('name', models.CharField(max_length=255, verbose_name='Category Name')), - ], - options={ - 'verbose_name': 'category', - 'verbose_name_plural': 'categories', - }, - ), - migrations.AddField( - model_name='product', - name='categories', - field=models.ManyToManyField(to='products.Category', verbose_name='Category Name'), - ), - ] diff --git a/ecommerce/products/migrations/0004_alter_product_categories.py b/ecommerce/products/migrations/0004_alter_product_categories.py deleted file mode 100644 index 9e20adb..0000000 --- a/ecommerce/products/migrations/0004_alter_product_categories.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2021-12-05 06:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('products', '0003_auto_20211203_1919'), - ] - - operations = [ - migrations.AlterField( - model_name='product', - name='categories', - field=models.ManyToManyField(to='products.Category', verbose_name='Categories'), - ), - ]