Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions apps/cart/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CartItemSerializer,
ShoppingCartSerializer,
)
from apps.inventory.models import Inventory


class ShoppingCartViewSet(ModelViewSet):
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/finance/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class Transactions(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = Transaction.objects.all()
queryset = Transaction.objects.all().select_related("user", "order")
serializer_class = TransactionSerializer

def get_queryset(self):
Expand Down
3 changes: 1 addition & 2 deletions apps/inventory/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
8 changes: 4 additions & 4 deletions apps/inventory/views.py
Original file line number Diff line number Diff line change
@@ -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):
queryset = Inventory.objects.all()
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"]]
34 changes: 34 additions & 0 deletions apps/orders/migrations/0005_delivery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 4.2.2 on 2025-09-04 10:04

import apps.orders.models
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('users', '0007_user_otp_secret_created_at'),
('orders', '0004_alter_order_options'),
]

operations = [
migrations.CreateModel(
name='Delivery',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created_at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
('is_active', models.BooleanField(default=True)),
('tracking_id', models.CharField(blank=True, max_length=50)),
('delivery_date', models.DateTimeField(default=apps.orders.models.one_week_from_now)),
('delivery_type', models.CharField(choices=[('P', 'pickup'), ('C', 'courier')], default='C', max_length=1)),
('address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.address')),
('order', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='delivery_details', to='orders.order')),
],
options={
'abstract': False,
},
),
]
23 changes: 23 additions & 0 deletions apps/orders/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from datetime import timedelta

from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from apps.common import models as base_models
Expand Down Expand Up @@ -57,3 +60,23 @@ class OrderItem(base_models.BaseModel):
)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField()


def one_week_from_now():
return timezone.now() + timedelta(days=7)


class Delivery(base_models.BaseModel):
class DeliveryType(models.TextChoices):
PICKUP = ("P", _("pickup"))
COURIER = ("C", _("courier"))

order = models.OneToOneField(
Order, on_delete=models.CASCADE, related_name="delivery_details"
)
tracking_id = models.CharField(max_length=50, blank=True)
delivery_date = models.DateTimeField(default=one_week_from_now)
delivery_type = models.CharField(
max_length=1, choices=DeliveryType.choices, default=DeliveryType.COURIER
)
address = models.ForeignKey(usermodels.Address, on_delete=models.CASCADE)
15 changes: 14 additions & 1 deletion apps/orders/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers

from .models import Order, OrderItem
from .models import Delivery, Order, OrderItem


class OrderItemSerializer(serializers.ModelSerializer):
Expand All @@ -15,3 +15,16 @@ class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = "__all__"


class DeliverySerializer(serializers.ModelSerializer):
class Meta:
model = Delivery
fields = [
"id",
"order",
"tracking_id",
"delivery_date",
"delivery_type",
"address",
]
4 changes: 3 additions & 1 deletion apps/orders/urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter

from apps.orders.views import OrderItemViewset, OrderViewSet
from apps.orders.views import DeliveryViewset, OrderItemViewset, OrderViewSet

app_name = "order"

router = DefaultRouter()

router.register("item", OrderItemViewset, basename="order_item")
router.register("delivery", DeliveryViewset, basename="delivery_details")
router.register("", OrderViewSet, basename="order")


# urlpatterns = router.urls
urlpatterns = [
path("", include(router.urls)),
Expand Down
84 changes: 73 additions & 11 deletions apps/orders/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import time
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.urls import reverse
Expand All @@ -8,12 +7,17 @@
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.orders.models import Order, OrderItem
from apps.orders.serializers import OrderItemSerializer, OrderSerializer
from apps.inventory.models import Inventory
from apps.orders.models import Delivery, Order, OrderItem
from apps.orders.serializers import (
DeliverySerializer,
OrderItemSerializer,
OrderSerializer,
)
from apps.users.models import Address, Profile
from apps.common.email import send_email_template


class OrderViewSet(ModelViewSet):
Expand All @@ -38,14 +42,14 @@ def checkout(self, request):
{"detail": "Cart is empty"}, status=status.HTTP_400_BAD_REQUEST
)

cart_items = CartItem.objects.filter(cart=cart.id).select_related("product")

shopping_items = cart.items.count()
if shopping_items == 0:
return Response(
{"detail": "Add items to cart"}, status=status.HTTP_400_BAD_REQUEST
)

cart_items = CartItem.objects.filter(cart=cart.id).prefetch_related("product")

total_amount = sum(item.product.price * item.quantity for item in cart_items)
delivery_cost = 10
total_cost = total_amount + delivery_cost
Expand All @@ -72,6 +76,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)
Expand Down Expand Up @@ -140,11 +153,22 @@ def payment_verify(self, request):
except Profile.DoesNotExist:
pass

delivery_instance = Delivery.objects.create(
order=order, tracking_id=order.id, address=order.delivery_address
)

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,
"tracking_id": delivery_instance.tracking_id,
}
},
)

return Response(
{"detail": "Payment successful"}, status=status.HTTP_200_OK
Expand All @@ -161,9 +185,28 @@ def payment_verify(self, request):
status=status.HTTP_400_BAD_REQUEST,
)

@action(detail=False, methods=["get"], url_path="cancel")
def cancel_order(self, request):
orderID = request.query_params.get("order_id")
if not orderID:
return Response(
{"detail": " No order ID provided"}, status=status.HTTP_400_BAD_REQUEST
)
print(orderID)

order = get_object_or_404(Order, id=orderID)

if order.status in ["PC", "IND", "CP"]:
return Response({"detail": "This order cannot be cancelled"})

order.status = "CN"
order.save()

return Response({"details": "Order successfully cancelled"})


class OrderItemViewset(ModelViewSet):
queryset = OrderItem.objects.all()
queryset = OrderItem.objects.all().select_related("order", "product")
serializer_class = OrderItemSerializer

http_method_names = [
Expand All @@ -179,3 +222,22 @@ def list(self, request, *args, **kwargs):
queryset = self.queryset.filter(order_id=order_id)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)


class DeliveryViewset(ModelViewSet):
queryset = Delivery.objects.all()
serializer_class = DeliverySerializer

http_method_names = [
m
for m in ModelViewSet.http_method_names
if m not in ["put", "patch", "post", "delete"]
]

def list(self, request, *args, **kwargs):
delivery_id = self.request.query_params.get("delivery_id")
queryset = self.get_queryset()
if delivery_id:
queryset = self.queryset.filter(delivery_id=delivery_id)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
8 changes: 8 additions & 0 deletions apps/products/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions apps/products/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get_permissions(self):


class ProductView(ModelViewSet):
queryset = Product.objects.all().prefetch_related("brand", "category")
queryset = Product.objects.all().select_related("brand", "category")
serializer_class = ProductSerializer
permission_classes = [AllowAny]

Expand All @@ -37,7 +37,7 @@ def get_permissions(self):


class ProductImageView(ModelViewSet):
queryset = ProductImage.objects.all().prefetch_related("product")
queryset = ProductImage.objects.all().select_related("product")
serializer_class = ProductImageSerializer
permission_classes = [AllowAny]

Expand All @@ -50,7 +50,7 @@ def get_permissions(self):


class FavoriteView(ModelViewSet):
queryset = Favorite.objects.all()
queryset = Favorite.objects.all().select_related("product", "user")
serializer_class = FavoriteSerializer
permission_classes = [IsAuthenticated]

Expand Down
Loading