From f89570a21cd74e2627f35777a6e5cb7f7f9c370e Mon Sep 17 00:00:00 2001 From: kryzbone Date: Wed, 26 Jun 2024 13:01:20 +0000 Subject: [PATCH 01/11] fix: cors error --- config/settings/prod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config/settings/prod.py b/config/settings/prod.py index f32312c..2e0100d 100644 --- a/config/settings/prod.py +++ b/config/settings/prod.py @@ -13,6 +13,7 @@ # MIDDLEWARE # ---------------------------------------------------------------------------- MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", From fc76a387cb8a9a3fef75eb9a894cec81f87b9642 Mon Sep 17 00:00:00 2001 From: meeklife Date: Mon, 31 Mar 2025 21:07:52 +0100 Subject: [PATCH 02/11] json parsing --- apps/users/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 76a33d3..9663dc6 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -5,7 +5,7 @@ from rest_framework.decorators import action from rest_framework.generics import CreateAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin -from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.parsers import FormParser, MultiPartParser, JSONParser from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -154,7 +154,7 @@ class ProfileView(ModelViewSet): queryset = Profile.objects.all().prefetch_related("user", "role") serializer_class = ProfileSerializer filterset_fields = ("user", "role") - parser_classes = (FormParser, MultiPartParser) + parser_classes = (FormParser, MultiPartParser, JSONParser) http_method_names = [m for m in ModelViewSet.http_method_names if m not in ["put"]] From 033334f9c4e50d607e5202658f7377f6822e312a Mon Sep 17 00:00:00 2001 From: meeklife Date: Thu, 3 Apr 2025 17:40:45 +0100 Subject: [PATCH 03/11] refactor invite --- apps/invite/models.py | 2 +- apps/invite/views.py | 4 ++++ config/settings/prod.py | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/invite/models.py b/apps/invite/models.py index be0440e..76cb44d 100644 --- a/apps/invite/models.py +++ b/apps/invite/models.py @@ -37,7 +37,7 @@ def send_invitation_email(sender, instance, created, **kwargs): { instance.email: { "username": instance.inviter.username, - "referral_code": instance.referral_code, + "referral_code": instance.inviter.username, } }, ) diff --git a/apps/invite/views.py b/apps/invite/views.py index f0b8ca0..a747647 100644 --- a/apps/invite/views.py +++ b/apps/invite/views.py @@ -12,6 +12,10 @@ class InvitationCreateListView(CreateAPIView, ListAPIView): serializer_class = InvitationSerializer permission_classes = [IsAuthenticated] + def get_queryset(self): + user = self.request.user + return super().get_queryset().filter(inviter=user) + def create(self, request, *args, **kwargs): """ Handle POST requests to create a new Invitation. diff --git a/config/settings/prod.py b/config/settings/prod.py index 3938675..41b79c5 100644 --- a/config/settings/prod.py +++ b/config/settings/prod.py @@ -135,7 +135,9 @@ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference # https://anymail.readthedocs.io/en/stable/esps/sendgrid/ -EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend" +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" +) ANYMAIL = { "SENDGRID_API_KEY": env("SENDGRID_API_KEY", default=""), "SENDGRID_GENERATE_MESSAGE_ID": env("SENDGRID_GENERATE_MESSAGE_ID", default=""), From f11f873949eceb606318f7de0105bf5bdb268f02 Mon Sep 17 00:00:00 2001 From: meeklife Date: Sat, 5 Apr 2025 11:25:21 +0100 Subject: [PATCH 04/11] refactor some apps --- apps/common/email.py | 4 +--- apps/orders/views.py | 1 + apps/products/serializers.py | 13 +++++++++++-- apps/users/views.py | 12 +++++++----- config/settings/prod.py | 2 -- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/common/email.py b/apps/common/email.py index c106fbf..2e38ce6 100644 --- a/apps/common/email.py +++ b/apps/common/email.py @@ -4,12 +4,11 @@ def send_email(subject, message, recipient, fail_silently=False): - msg = EmailMessage( + msg = AnymailMessage( subject, message, from_email=settings.FROM_EMAIL, to=[recipient], - reply_to=[recipient], ) return msg.send(fail_silently=fail_silently) @@ -27,7 +26,6 @@ def send_email_template( msg = AnymailMessage( from_email=settings.FROM_EMAIL, to=[email], - reply_to=[settings.FROM_EMAIL], ) msg.template_id = template_id diff --git a/apps/orders/views.py b/apps/orders/views.py index 3dbc91c..c04777f 100644 --- a/apps/orders/views.py +++ b/apps/orders/views.py @@ -113,6 +113,7 @@ def payment_verify(self, request): if response["data"]["status"] == "success": order = get_object_or_404(Order, id=reference) order.status = "PC" + order.ordered = True order.save() Transaction.objects.create( diff --git a/apps/products/serializers.py b/apps/products/serializers.py index 9d85884..0ae9c7a 100644 --- a/apps/products/serializers.py +++ b/apps/products/serializers.py @@ -51,9 +51,18 @@ def get_img_url(self, obj): class FavoriteSerializer(serializers.ModelSerializer): product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all()) - user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all()) product_name = serializers.CharField(source="product.name", read_only=True) class Meta: model = Favorite - fields = ["id", "product", "product_name", "user"] + fields = ["id", "product", "product_name"] + + def create(self, validated_data): + request = self.context.get("request") + user = request.user + + if Favorite.objects.filter(user=user, product=validated_data["product"]).exists(): + raise serializers.ValidationError("Product already added to favorites") + + favorite = Favorite.objects.create(user=user, product=validated_data["product"]) + return favorite diff --git a/apps/users/views.py b/apps/users/views.py index 76a33d3..c8c6b6a 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -5,7 +5,7 @@ from rest_framework.decorators import action from rest_framework.generics import CreateAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin -from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.parsers import FormParser, MultiPartParser, JSONParser from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -154,7 +154,7 @@ class ProfileView(ModelViewSet): queryset = Profile.objects.all().prefetch_related("user", "role") serializer_class = ProfileSerializer filterset_fields = ("user", "role") - parser_classes = (FormParser, MultiPartParser) + parser_classes = (FormParser, MultiPartParser, JSONParser) http_method_names = [m for m in ModelViewSet.http_method_names if m not in ["put"]] @@ -191,9 +191,11 @@ def get(self, request): code, _ = OTPUtils.generate_otp(user) recipient = user.email - subject = "Email Verification Code" message = f"Your email verification code is {code}" - send_email(subject, message, recipient) - return Response("OK") + if not user.is_verified: + send_email(subject, message, recipient) + return Response("Email Verification Code sent successfully") + + return Response("Email already verified") diff --git a/config/settings/prod.py b/config/settings/prod.py index 41b79c5..b9cd34c 100644 --- a/config/settings/prod.py +++ b/config/settings/prod.py @@ -140,8 +140,6 @@ ) ANYMAIL = { "SENDGRID_API_KEY": env("SENDGRID_API_KEY", default=""), - "SENDGRID_GENERATE_MESSAGE_ID": env("SENDGRID_GENERATE_MESSAGE_ID", default=""), - "SENDGRID_MERGE_FIELD_FORMAT": env("SENDGRID_MERGE_FIELD_FORMAT", default=""), "SENDGRID_API_URL": env("SENDGRID_API_URL", default="https://api.sendgrid.com/v3/"), } From ec725a052780f0c63d7ecc73fd5512a863a6b548 Mon Sep 17 00:00:00 2001 From: meeklife Date: Sat, 5 Apr 2025 21:02:12 +0100 Subject: [PATCH 05/11] refactor email --- apps/users/serializers.py | 46 ++++++++++++++++++++++++++------------- apps/users/views.py | 16 +++++++++----- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/apps/users/serializers.py b/apps/users/serializers.py index 4ab739a..5e1de12 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -2,6 +2,7 @@ from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken +import logging from apps.common.email import send_email_template from apps.common.utils import OTPUtils @@ -79,11 +80,14 @@ def create(self, validated_data: dict): profile.phone_number = member_data["data"]["phone_number"] profile.save() - send_email_template( - email, - "d-84ad6c792bf64437bb592b604214806a", - {email: {"username": username, "otp": code}}, - ) + try: + send_email_template( + email, + "d-84ad6c792bf64437bb592b604214806a", + {email: {"username": username, "otp": code}}, + ) + except Exception as e: + logging.error(f"Error sending OTP email: {e}") if referral_code: try: @@ -159,9 +163,14 @@ def create(self, validated_data: dict): email = validated_data.get("email") if user := User.objects.filter(email=email).first(): code, token = OTPUtils.generate_otp(user) - send_email_template( - email, "d-45557d1b684442b6aef71ae69d50c495", {email: {"code": code}} - ) + + try: + send_email_template( + email, "d-45557d1b684442b6aef71ae69d50c495", {email: {"code": code}} + ) + + except Exception as e: + logging.error(f"Error sending OTP email: {e}") return {"token": token} @@ -198,8 +207,12 @@ def create(self, validated_data): user.set_password(raw_password=password) user.save() - # send_email_template(user.email, "d-e4bf355645044030af3f6fbb6f360153", \ - # {user.email: {"username": user.username}}) + try: + send_email_template(user.email, "d-e4bf355645044030af3f6fbb6f360153", + {user.email: {"username": user.username}}) + + except Exception as e: + logging.error(f"Error sending password reset confirmation email: {e}") return { "email": user.email, @@ -224,11 +237,14 @@ def create(self, validated_data): user.set_password(raw_password=validated_data.get("new_password")) user.save() - send_email_template( - user.email, - "d-7989ffbb4f114616846ef7ddff10a965", - {user.email: {"username": user.username}}, - ) + try: + send_email_template( + user.email, + "d-7989ffbb4f114616846ef7ddff10a965", + {user.email: {"username": user.username}}, + ) + except Exception as e: + logging.error(f"Error sending password change email: {e}") return {"old_password": "", "new_password": ""} diff --git a/apps/users/views.py b/apps/users/views.py index c8c6b6a..c7806e4 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -10,8 +10,9 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet +import logging -from apps.common.email import send_email +from apps.common.email import send_email, send_email_template from apps.common.utils import OTPUtils from .models import Address, Profile, Role @@ -191,11 +192,16 @@ def get(self, request): code, _ = OTPUtils.generate_otp(user) recipient = user.email - subject = "Email Verification Code" - message = f"Your email verification code is {code}" + # subject = "Email Verification Code" + # message = f"Your email verification code is {code}" if not user.is_verified: - send_email(subject, message, recipient) - return Response("Email Verification Code sent successfully") + # send_email(subject, message, recipient) + # return Response("Email Verification Code sent successfully") + try: + send_email_template(recipient, "d-491be22360794a6782913ffb274e9224", {recipient: {"code": code}}) + + except Exception as e: + logging.error(f"Error sending email verification OTP: {e}") return Response("Email already verified") From 65f00a436868408eaf85596e512f0e158c0b4c96 Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 8 Apr 2025 13:03:40 +0100 Subject: [PATCH 06/11] chore order confirmation email --- apps/orders/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/orders/views.py b/apps/orders/views.py index c04777f..5595dd2 100644 --- a/apps/orders/views.py +++ b/apps/orders/views.py @@ -1,3 +1,4 @@ +import time from django.db import transaction from django.shortcuts import get_object_or_404 from django.urls import reverse @@ -12,6 +13,7 @@ from apps.orders.models import Order, OrderItem from apps.orders.serializers import OrderItemSerializer, OrderSerializer from apps.users.models import Address, Profile +from apps.common.email import send_email_template class OrderViewSet(ModelViewSet): @@ -138,6 +140,12 @@ def payment_verify(self, request): except Profile.DoesNotExist: pass + email = order.user.email + send_email_template(email, "d-f28075a5e4074706b817fe32b6260506", {email: { + "order_id": order.id, + "address": order.delivery_address, + }}) + return Response( {"detail": "Payment successful"}, status=status.HTTP_200_OK ) From 2895c66a706e5aad9d3cec2bf934b53b21b13df4 Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 15 Jul 2025 11:03:11 +0100 Subject: [PATCH 07/11] fix: merge conflict, main into dev --- apps/users/serializers.py | 45 --------------------------------------- 1 file changed, 45 deletions(-) diff --git a/apps/users/serializers.py b/apps/users/serializers.py index d11005b..eb75a6f 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -2,13 +2,10 @@ from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken -<<<<<<< HEAD from django.utils import timezone from django.utils.crypto import constant_time_compare from django.contrib.auth.password_validation import validate_password -======= import logging ->>>>>>> main from apps.common.email import send_email_template from apps.common.utils import OTPUtils @@ -173,7 +170,6 @@ def create(self, validated_data: dict): """ token = "" email = validated_data.get("email") -<<<<<<< HEAD try: if user := User.objects.filter(email=email).first(): code, token = OTPUtils.generate_otp(user) @@ -188,18 +184,6 @@ def create(self, validated_data: dict): except Exception as e: raise serializers.ValidationError(f"Error sending email: {e}") -======= - if user := User.objects.filter(email=email).first(): - code, token = OTPUtils.generate_otp(user) - - try: - send_email_template( - email, "d-45557d1b684442b6aef71ae69d50c495", {email: {"code": code}} - ) - - except Exception as e: - logging.error(f"Error sending OTP email: {e}") ->>>>>>> main return {"token": token} @@ -218,7 +202,6 @@ def create(self, validated_data): code = validated_data.get("code") password = validated_data.get("password") -<<<<<<< HEAD try: data = OTPUtils.decode_token(token) if not data or not isinstance(data, dict): @@ -262,34 +245,6 @@ def create(self, validated_data): user.otp_secret_created_at = None user.save(update_fields=["otp_secret", "otp_secret_created_at"]) raise serializers.ValidationError(f"Password reset failed: {str(e)}") -======= - data = OTPUtils.decode_token(token) - - if not data or not isinstance(data, dict): - raise serializers.ValidationError("Invalid token") - - if not (user := User.objects.filter(id=data.get("user_id")).first()): - raise serializers.ValidationError("User does not exist") - - # validate code - if not OTPUtils.verify_otp(code, data["secret"]): - raise serializers.ValidationError("Invalid code") - - # reset password - user.set_password(raw_password=password) - user.save() - - try: - send_email_template(user.email, "d-e4bf355645044030af3f6fbb6f360153", - {user.email: {"username": user.username}}) - - except Exception as e: - logging.error(f"Error sending password reset confirmation email: {e}") - - return { - "email": user.email, - } ->>>>>>> main class ChangePasswordSerializer(serializers.Serializer): From f42b3a542086cb4d3d24cfd5292612181de6b4cf Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 15 Jul 2025 13:41:29 +0100 Subject: [PATCH 08/11] fix(cart): invalid field while using select_related --- .gitignore | 1 + .pre-commit-config.yaml | 10 +++++----- apps/cart/views.py | 2 +- requirements/local.txt | 2 +- requirements/prod.txt | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 654e720..bf33a7d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +my-venv/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7e7ce1..35838e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,11 +15,11 @@ repos: hooks: - id: black - - repo: https://github.com/dhruvmanila/remove-print-statements - rev: 'v0.5.0' - hooks: - - id: remove-print-statements - args: ['--verbose'] # Show all the print statements to be removed + # - repo: https://github.com/dhruvmanila/remove-print-statements + # rev: 'v0.5.0' + # hooks: + # - id: remove-print-statements + # args: ['--verbose'] # Show all the print statements to be removed - repo: https://github.com/timothycrosley/isort rev: 5.12.0 diff --git a/apps/cart/views.py b/apps/cart/views.py index 474d77a..5da25d5 100644 --- a/apps/cart/views.py +++ b/apps/cart/views.py @@ -21,7 +21,7 @@ class ShoppingCartViewSet(ModelViewSet): def get_queryset(self): user = self.request.user if user.is_authenticated: - return ShoppingCart.objects.filter(user=self.request.user).select_related( + return ShoppingCart.objects.filter(user=self.request.user).prefetch_related( "items__product", "items__product__brand", "items__product__category" ) return ShoppingCart.objects.none() diff --git a/requirements/local.txt b/requirements/local.txt index b188452..a4ed0a6 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -3,7 +3,7 @@ -r base.txt -psycopg2==2.9.6 +psycopg2==2.9.10 pytest-django==4.5.2 pytest-factoryboy==2.5.1 django-extensions==3.2.3 diff --git a/requirements/prod.txt b/requirements/prod.txt index 15873da..7d008ae 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -3,7 +3,7 @@ -r base.txt -psycopg2==2.9.6 +psycopg2==2.9.10 gunicorn==20.1.0 django-storages==1.13.2 django-anymail==10.0 From e28f6f9f413dea835e6d91c45d441da976fa6ca1 Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 15 Jul 2025 13:47:04 +0100 Subject: [PATCH 09/11] refactor(inventory): allow read and write for modelviewset --- apps/inventory/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/inventory/views.py b/apps/inventory/views.py index d09cfce..20cf7fe 100644 --- a/apps/inventory/views.py +++ b/apps/inventory/views.py @@ -1,13 +1,13 @@ from rest_framework.permissions import IsAdminUser -from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet from .models import Inventory from .serializers import InventorySerializer -class InventoryView(ReadOnlyModelViewSet): +class InventoryView(ModelViewSet): queryset = Inventory.objects.all().select_related("product") serializer_class = InventorySerializer permission_classes = [IsAdminUser] - # http_method_names = [m for m in ModelViewSet.http_method_names if m not in ["put"]] + http_method_names = [m for m in ModelViewSet.http_method_names if m not in ["put"]] From 08cd36a98438354694204cd532b6b430be64cd83 Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 15 Jul 2025 15:01:06 +0100 Subject: [PATCH 10/11] feat(cart&order): check inventory before completing order --- apps/cart/views.py | 14 ++++++++++++++ apps/orders/views.py | 27 +++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/apps/cart/views.py b/apps/cart/views.py index 5da25d5..e14099f 100644 --- a/apps/cart/views.py +++ b/apps/cart/views.py @@ -9,6 +9,7 @@ CartItemSerializer, ShoppingCartSerializer, ) +from apps.inventory.models import Inventory class ShoppingCartViewSet(ModelViewSet): @@ -47,6 +48,19 @@ def add_to_cart(self, request): cart = self.get_object() + try: + inventory = Inventory.objects.get(product=product) + if inventory.quantity < quantity: + return Response( + {"detail": f"Not enough stock for {product.name}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + except Inventory.DoesNotExist: + return Response( + {"detail": f"{product.name} out of stock"}, + status=status.HTTP_400_BAD_REQUEST, + ) + cart_item, _ = CartItem.objects.get_or_create(cart=cart, product=product) cart_item.quantity = quantity cart_item.price = product.price * quantity diff --git a/apps/orders/views.py b/apps/orders/views.py index 036323a..8f55333 100644 --- a/apps/orders/views.py +++ b/apps/orders/views.py @@ -1,4 +1,3 @@ -import time from django.db import transaction from django.shortcuts import get_object_or_404 from django.urls import reverse @@ -8,12 +7,13 @@ from rest_framework.viewsets import ModelViewSet from apps.cart.models import CartItem, ShoppingCart +from apps.common.email import send_email_template from apps.finance.models import Transaction from apps.finance.paystack import PaystackUtils +from apps.inventory.models import Inventory from apps.orders.models import Order, OrderItem from apps.orders.serializers import OrderItemSerializer, OrderSerializer from apps.users.models import Address, Profile -from apps.common.email import send_email_template class OrderViewSet(ModelViewSet): @@ -72,6 +72,15 @@ def checkout(self, request): price=cart_item.price, ) + inventory = Inventory.objects.get(product=cart_item.product) + if inventory.quantity < cart_item.quantity: + return Response( + {"detail": f"Not enough stock for {cart_item.product.name}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + inventory.quantity -= cart_item.quantity + inventory.save() + serializer = self.get_serializer(order) # noqa kobo_amount = int(total_cost * 100) @@ -141,10 +150,16 @@ def payment_verify(self, request): pass email = order.user.email - send_email_template(email, "d-f28075a5e4074706b817fe32b6260506", {email: { - "order_id": order.id, - "address": order.delivery_address, - }}) + send_email_template( + email, + "d-f28075a5e4074706b817fe32b6260506", + { + email: { + "order_id": order.id, + "address": order.delivery_address, + } + }, + ) return Response( {"detail": "Payment successful"}, status=status.HTTP_200_OK From f5959037edc07bf93c94b4c136f5a3ffc413991a Mon Sep 17 00:00:00 2001 From: meeklife Date: Tue, 15 Jul 2025 22:41:08 +0100 Subject: [PATCH 11/11] chore(inventory): override Product save method --- apps/inventory/models.py | 3 +-- apps/products/models.py | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/inventory/models.py b/apps/inventory/models.py index 61d4870..5748974 100644 --- a/apps/inventory/models.py +++ b/apps/inventory/models.py @@ -1,8 +1,7 @@ from django.db import models from apps.common import models as base_models - -from ..products.models import Product +from apps.products.models import Product class Inventory(base_models.BaseModel): diff --git a/apps/products/models.py b/apps/products/models.py index 4950785..89f526d 100644 --- a/apps/products/models.py +++ b/apps/products/models.py @@ -33,6 +33,14 @@ class Meta: "created_at", ) + def save(self, *args, **kwargs): + created = not self.pk + super().save(*args, **kwargs) + if created: + from apps.inventory.models import Inventory + + Inventory.objects.create(product=self, quantity=1) + def __str__(self): return self.name