adding api urls and testing framework
All checks were successful
Build and Deploy Beta / build-and-push (push) Successful in 1m31s
Build and Deploy Beta / deploy (push) Successful in 12s

This commit is contained in:
vickytechkey 2026-08-09 11:42:43 +05:30
parent 7959df9afb
commit 010dc0352e
11 changed files with 667 additions and 1 deletions

View file

@ -0,0 +1,106 @@
# Generated by Django 6.1 on 2026-08-09 06:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order_id', models.CharField(max_length=50, unique=True)),
('date', models.DateField()),
('item', models.CharField(max_length=255)),
('quantity', models.IntegerField(default=1)),
('customer', models.CharField(max_length=255)),
('total', models.DecimalField(decimal_places=2, max_digits=10)),
('status', models.CharField(choices=[('Pending Acceptance', 'Pending Acceptance'), ('Ready to Ship', 'Ready to Ship'), ('Shipped', 'Shipped'), ('Delivered', 'Delivered'), ('Cancelled', 'Cancelled'), ('Rejected', 'Rejected')], default='Pending Acceptance', max_length=50)),
('carrier', models.CharField(default='Pending', max_length=100)),
('tracking', models.CharField(default='Pending', max_length=100)),
('eta', models.CharField(default='N/A', max_length=50)),
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('category', models.CharField(max_length=100)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('stock', models.IntegerField(default=0)),
('sku', models.CharField(max_length=100, unique=True)),
('image', models.TextField(blank=True, null=True)),
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='ReturnRequest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('return_id', models.CharField(max_length=50, unique=True)),
('order_id', models.CharField(max_length=50)),
('customer', models.CharField(max_length=255)),
('item', models.CharField(max_length=255)),
('reason', models.TextField()),
('status', models.CharField(choices=[('Pending Approval', 'Pending Approval'), ('Approved', 'Approved'), ('Rejected', 'Rejected'), ('In Transit', 'In Transit')], default='Pending Approval', max_length=50)),
('image', models.TextField(blank=True, null=True)),
('returning_tracking', models.CharField(blank=True, max_length=100, null=True)),
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='returns', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='SupplierProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone', models.CharField(blank=True, max_length=20, null=True)),
('phone_verified', models.BooleanField(default=False)),
('email_verified', models.BooleanField(default=False)),
('gstin', models.CharField(blank=True, max_length=50, null=True)),
('is_gstin_verified', models.BooleanField(default=False)),
('aadhar_file', models.CharField(blank=True, max_length=255, null=True)),
('pan_file', models.CharField(blank=True, max_length=255, null=True)),
('store_name', models.CharField(blank=True, max_length=255, null=True)),
('store_logo', models.TextField(blank=True, null=True)),
('business_bio', models.TextField(blank=True, null=True)),
('street', models.CharField(blank=True, max_length=255, null=True)),
('city', models.CharField(blank=True, max_length=100, null=True)),
('state', models.CharField(blank=True, max_length=100, null=True)),
('pincode', models.CharField(blank=True, max_length=20, null=True)),
('latitude', models.FloatField(default=12.9716)),
('longitude', models.FloatField(default=77.5946)),
('is_profile_complete', models.BooleanField(default=False)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Wallet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('outstanding', models.DecimalField(decimal_places=2, default=0.0, max_digits=12)),
('withdrawn', models.DecimalField(decimal_places=2, default=0.0, max_digits=12)),
('supplier', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wallet', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='WalletTransaction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tx_id', models.CharField(max_length=50, unique=True)),
('date', models.DateField()),
('amount', models.DecimalField(decimal_places=2, max_digits=12)),
('status', models.CharField(choices=[('Transferred', 'Transferred'), ('Pending', 'Pending')], default='Transferred', max_length=50)),
('wallet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to='api.wallet')),
],
),
]

View file

103
api/models.py Normal file
View file

@ -0,0 +1,103 @@
from django.db import models
from django.contrib.auth.models import User
class SupplierProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
phone = models.CharField(max_length=20, blank=True, null=True)
phone_verified = models.BooleanField(default=False)
email_verified = models.BooleanField(default=False)
gstin = models.CharField(max_length=50, blank=True, null=True)
is_gstin_verified = models.BooleanField(default=False)
aadhar_file = models.CharField(max_length=255, blank=True, null=True)
pan_file = models.CharField(max_length=255, blank=True, null=True)
store_name = models.CharField(max_length=255, blank=True, null=True)
store_logo = models.TextField(blank=True, null=True) # Can store base64 or URL
business_bio = models.TextField(blank=True, null=True)
street = models.CharField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=100, blank=True, null=True)
pincode = models.CharField(max_length=20, blank=True, null=True)
latitude = models.FloatField(default=12.9716)
longitude = models.FloatField(default=77.5946)
is_profile_complete = models.BooleanField(default=False)
def __str__(self):
return f"{self.user.username}'s Profile"
class Product(models.Model):
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products')
title = models.CharField(max_length=255)
category = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField(default=0)
sku = models.CharField(max_length=100, unique=True)
image = models.TextField(blank=True, null=True) # Can store URL or base64
def __str__(self):
return self.title
class Order(models.Model):
STATUS_CHOICES = [
('Pending Acceptance', 'Pending Acceptance'),
('Ready to Ship', 'Ready to Ship'),
('Shipped', 'Shipped'),
('Delivered', 'Delivered'),
('Cancelled', 'Cancelled'),
('Rejected', 'Rejected'),
]
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
order_id = models.CharField(max_length=50, unique=True)
date = models.DateField()
item = models.CharField(max_length=255)
quantity = models.IntegerField(default=1)
customer = models.CharField(max_length=255)
total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Pending Acceptance')
carrier = models.CharField(max_length=100, default='Pending')
tracking = models.CharField(max_length=100, default='Pending')
eta = models.CharField(max_length=50, default='N/A')
def __str__(self):
return self.order_id
class ReturnRequest(models.Model):
STATUS_CHOICES = [
('Pending Approval', 'Pending Approval'),
('Approved', 'Approved'),
('Rejected', 'Rejected'),
('In Transit', 'In Transit'),
]
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='returns')
return_id = models.CharField(max_length=50, unique=True)
order_id = models.CharField(max_length=50)
customer = models.CharField(max_length=255)
item = models.CharField(max_length=255)
reason = models.TextField()
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Pending Approval')
image = models.TextField(blank=True, null=True)
returning_tracking = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return self.return_id
class Wallet(models.Model):
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name='wallet')
outstanding = models.DecimalField(max_digits=12, decimal_places=2, default=0.00)
withdrawn = models.DecimalField(max_digits=12, decimal_places=2, default=0.00)
def __str__(self):
return f"{self.supplier.username}'s Wallet"
class WalletTransaction(models.Model):
STATUS_CHOICES = [
('Transferred', 'Transferred'),
('Pending', 'Pending'),
]
wallet = models.ForeignKey(Wallet, on_delete=models.CASCADE, related_name='transactions')
tx_id = models.CharField(max_length=50, unique=True)
date = models.DateField()
amount = models.DecimalField(max_digits=12, decimal_places=2)
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Transferred')
def __str__(self):
return self.tx_id

68
api/serializers.py Normal file
View file

@ -0,0 +1,68 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction
class SupplierProfileSerializer(serializers.ModelSerializer):
class Meta:
model = SupplierProfile
exclude = ('user',)
class UserSerializer(serializers.ModelSerializer):
profile = SupplierProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'email', 'profile')
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(required=True)
email = serializers.EmailField(required=True)
phone = serializers.CharField(required=True)
password = serializers.CharField(required=True, write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
SupplierProfile.objects.create(
user=user,
phone=validated_data['phone']
)
Wallet.objects.create(
supplier=user,
outstanding=850.00, # Default starter wallet balance matching frontend mock
withdrawn=1250.00
)
return user
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
read_only_fields = ('supplier',)
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = '__all__'
read_only_fields = ('supplier',)
class ReturnRequestSerializer(serializers.ModelSerializer):
class Meta:
model = ReturnRequest
fields = '__all__'
read_only_fields = ('supplier',)
class WalletTransactionSerializer(serializers.ModelSerializer):
class Meta:
model = WalletTransaction
fields = '__all__'
class WalletSerializer(serializers.ModelSerializer):
transactions = WalletTransactionSerializer(many=True, read_only=True)
class Meta:
model = Wallet
fields = ('outstanding', 'withdrawn', 'transactions')

148
api/tests.py Normal file
View file

@ -0,0 +1,148 @@
import pytest
from django.urls import reverse
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APIClient
from api.models import Product, Order, ReturnRequest, Wallet, WalletTransaction
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def create_user(db):
user = User.objects.create_user(username='test_seller', email='test@example.com', password='password123')
return user
@pytest.mark.django_db
def test_user_registration(api_client):
url = reverse('register')
data = {
'username': 'new_seller',
'email': 'new@example.com',
'phone': '1234567890',
'password': 'securepassword'
}
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_201_CREATED
assert response.data['username'] == 'new_seller'
assert User.objects.filter(username='new_seller').exists()
@pytest.mark.django_db
def test_otp_verification(api_client):
url = reverse('verify-otp')
# Correct OTP
response = api_client.post(url, {'otp': '123456'}, format='json')
assert response.status_code == status.HTTP_200_OK
assert response.data['verified'] is True
# Incorrect OTP
response = api_client.post(url, {'otp': '000000'}, format='json')
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data['verified'] is False
@pytest.mark.django_db
def test_profile_update(api_client, create_user):
url = reverse('profile')
api_client.force_authenticate(user=create_user)
data = {
'store_name': 'New Artisan Handloom',
'business_bio': 'Beautiful handmade rugs.',
'gstin': '29AAAAA1111A1Z1',
'is_gstin_verified': True
}
response = api_client.put(url, data, format='json')
assert response.status_code == status.HTTP_200_OK
assert create_user.profile.store_name == 'New Artisan Handloom'
assert create_user.profile.is_gstin_verified is True
@pytest.mark.django_db
def test_product_crud(api_client, create_user):
api_client.force_authenticate(user=create_user)
# Create product
url = reverse('product-list')
data = {
'title': 'Test Indigo Shawl',
'category': 'Apparel',
'price': '85.00',
'stock': 10,
'sku': 'TEST-SHAWL-01'
}
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_201_CREATED
assert Product.objects.filter(sku='TEST-SHAWL-01').exists()
# List products
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
@pytest.mark.django_db
def test_bulk_upload(api_client, create_user):
api_client.force_authenticate(user=create_user)
url = reverse('bulk-upload')
data = {
'products': [
{'title': 'Bulk Bowl', 'category': 'Kitchenware', 'price': '65.00', 'stock': 15, 'sku': 'BOWL-WAL-12'},
{'title': 'Bulk Tablecloth', 'category': 'Linens', 'price': '48.00', 'stock': 30, 'sku': 'LINE-COT-15'}
]
}
response = api_client.post(url, data, format='json')
assert response.status_code == status.HTTP_200_OK
assert response.data['success'] is True
assert Product.objects.filter(sku='BOWL-WAL-12').exists()
assert Product.objects.filter(sku='LINE-COT-15').exists()
@pytest.mark.django_db
def test_order_actions(api_client, create_user):
api_client.force_authenticate(user=create_user)
# Query list to auto-create mock orders
list_url = reverse('order-list')
api_client.get(list_url)
order = Order.objects.filter(supplier=create_user, status='Pending Acceptance').first()
assert order is not None
# Accept order
accept_url = reverse('order-accept', args=[order.id])
response = api_client.post(accept_url)
assert response.status_code == status.HTTP_200_OK
assert response.data['status'] == 'Ready to Ship'
assert response.data['carrier'] == 'DHL Express'
@pytest.mark.django_db
def test_return_actions(api_client, create_user):
api_client.force_authenticate(user=create_user)
# Query list to auto-create mock return requests
list_url = reverse('return-list')
api_client.get(list_url)
ret = ReturnRequest.objects.filter(supplier=create_user, status='Pending Approval').first()
assert ret is not None
# Approve return
action_url = reverse('return-action', args=[ret.id])
response = api_client.post(action_url, {'action': 'Approved'}, format='json')
assert response.status_code == status.HTTP_200_OK
assert response.data['status'] == 'In Transit'
@pytest.mark.django_db
def test_wallet_withdrawal(api_client, create_user):
api_client.force_authenticate(user=create_user)
wallet = Wallet.objects.create(supplier=create_user, outstanding=1000.00, withdrawn=500.00)
url = reverse('withdraw')
# Successful withdrawal
response = api_client.post(url, {'amount': '200.00'}, format='json')
assert response.status_code == status.HTTP_200_OK
assert response.data['outstanding'] == '800.00'
assert response.data['withdrawn'] == '700.00'
assert WalletTransaction.objects.filter(wallet=wallet).exists()
# Invalid amount (exceeds outstanding)
response = api_client.post(url, {'amount': '1500.00'}, format='json')
assert response.status_code == status.HTTP_400_BAD_REQUEST

23
api/urls.py Normal file
View file

@ -0,0 +1,23 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import (
RegisterView, LoginView, VerifyOtpView, ProfileView,
ProductViewSet, BulkUploadView, OrderViewSet, ReturnRequestViewSet,
WalletView, WithdrawView
)
router = DefaultRouter()
router.register(r'products', ProductViewSet, basename='product')
router.register(r'orders', OrderViewSet, basename='order')
router.register(r'returns', ReturnRequestViewSet, basename='return')
urlpatterns = [
path('auth/register/', RegisterView.as_view(), name='register'),
path('auth/login/', LoginView.as_view(), name='login'),
path('auth/verify-otp/', VerifyOtpView.as_view(), name='verify-otp'),
path('profile/', ProfileView.as_view(), name='profile'),
path('products/bulk-upload/', BulkUploadView.as_view(), name='bulk-upload'),
path('wallet/', WalletView.as_view(), name='wallet'),
path('wallet/withdraw/', WithdrawView.as_view(), name='withdraw'),
path('', include(router.urls)),
]

200
api/views.py Normal file
View file

@ -0,0 +1,200 @@
import random
from datetime import date
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
from rest_framework import viewsets, status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import action
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction
from .serializers import (
UserSerializer, RegisterSerializer, ProductSerializer,
OrderSerializer, ReturnRequestSerializer, WalletSerializer
)
def get_active_user(request):
if request.user and request.user.is_authenticated:
return request.user
user, created = User.objects.get_or_create(username='demo_seller', email='demo@example.com')
if created:
user.set_password('super_secure_pass_123')
user.save()
SupplierProfile.objects.get_or_create(user=user, phone='9876543210', store_name='Teak Wood Craft Store')
Wallet.objects.get_or_create(supplier=user, outstanding=850.00, withdrawn=1250.00)
return user
class RegisterView(APIView):
def post(self, request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(username=username, password=password)
if user:
login(request, user)
return Response(UserSerializer(user).data)
return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)
class VerifyOtpView(APIView):
def post(self, request):
otp = request.data.get('otp')
# Simulate verification - code '123456' is always verified
if otp == '123456':
return Response({'verified': True})
return Response({'verified': False, 'error': 'Invalid OTP code'}, status=status.HTTP_400_BAD_REQUEST)
class ProfileView(APIView):
def get(self, request):
user = get_active_user(request)
profile, _ = SupplierProfile.objects.get_or_create(user=user)
return Response(UserSerializer(user).data)
def put(self, request):
user = get_active_user(request)
profile, _ = SupplierProfile.objects.get_or_create(user=user)
# Update fields
profile.phone = request.data.get('phone', profile.phone)
profile.phone_verified = request.data.get('phone_verified', profile.phone_verified)
profile.email_verified = request.data.get('email_verified', profile.email_verified)
profile.gstin = request.data.get('gstin', profile.gstin)
profile.is_gstin_verified = request.data.get('is_gstin_verified', profile.is_gstin_verified)
profile.aadhar_file = request.data.get('aadhar_file', profile.aadhar_file)
profile.pan_file = request.data.get('pan_file', profile.pan_file)
profile.store_name = request.data.get('store_name', profile.store_name)
profile.store_logo = request.data.get('store_logo', profile.store_logo)
profile.business_bio = request.data.get('business_bio', profile.business_bio)
profile.street = request.data.get('street', profile.street)
profile.city = request.data.get('city', profile.city)
profile.state = request.data.get('state', profile.state)
profile.pincode = request.data.get('pincode', profile.pincode)
profile.latitude = request.data.get('latitude', profile.latitude)
profile.longitude = request.data.get('longitude', profile.longitude)
profile.is_profile_complete = request.data.get('is_profile_complete', profile.is_profile_complete)
profile.save()
return Response(UserSerializer(user).data)
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
def get_queryset(self):
user = get_active_user(self.request)
return Product.objects.filter(supplier=user)
def perform_create(self, serializer):
user = get_active_user(self.request)
serializer.save(supplier=user)
class BulkUploadView(APIView):
def post(self, request):
user = get_active_user(request)
products_data = request.data.get('products', [])
created_products = []
for p_data in products_data:
sku = p_data.get('sku')
# Avoid duplicate sku
Product.objects.filter(sku=sku).delete()
product = Product.objects.create(
supplier=user,
title=p_data.get('title'),
category=p_data.get('category'),
price=p_data.get('price'),
stock=p_data.get('stock'),
sku=sku,
image=p_data.get('image', 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100')
)
created_products.append(ProductSerializer(product).data)
return Response({'success': True, 'products': created_products})
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
def get_queryset(self):
user = get_active_user(self.request)
# Ensure default mock orders exist
if not Order.objects.filter(supplier=user).exists():
Order.objects.create(supplier=user, order_id='ORD-8492', date=date.today(), item='Handwoven Indigo Shawl', quantity=1, customer='Alice Vance', total=85.00, status='Ready to Ship', carrier='DHL Express', tracking='DHL-9284102', eta='3 Days')
Order.objects.create(supplier=user, order_id='ORD-8488', date=date.today(), item='Terracotta Clay Pot Set', quantity=2, customer='David Miller', total=84.00, status='Shipped', carrier='FedEx Ground', tracking='FDX-5829104', eta='Delivered')
Order.objects.create(supplier=user, order_id='ORD-8501', date=date.today(), item='Brass Elephant Figurine', quantity=1, customer='Bruce Wayne', total=120.00, status='Pending Acceptance', carrier='Pending', tracking='Pending', eta='N/A')
return Order.objects.filter(supplier=user)
@action(detail=True, methods=['post'])
def accept(self, request, pk=None):
order = self.get_object()
order.status = 'Ready to Ship'
order.carrier = 'DHL Express'
order.tracking = f'DHL-{random.randint(100000, 999000)}'
order.eta = '3 Days'
order.save()
return Response(OrderSerializer(order).data)
@action(detail=True, methods=['post'])
def reject(self, request, pk=None):
order = self.get_object()
order.status = 'Rejected'
order.carrier = 'N/A'
order.tracking = 'N/A'
order.eta = 'N/A'
order.save()
return Response(OrderSerializer(order).data)
class ReturnRequestViewSet(viewsets.ModelViewSet):
serializer_class = ReturnRequestSerializer
def get_queryset(self):
user = get_active_user(self.request)
if not ReturnRequest.objects.filter(supplier=user).exists():
ReturnRequest.objects.create(supplier=user, return_id='RET-019', order_id='ORD-8411', customer='Lillian G.', item='Handwoven Indigo Shawl', reason='Slightly different shade', status='Pending Approval', image='https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100', returning_tracking='DHL-RET-9031')
return ReturnRequest.objects.filter(supplier=user)
@action(detail=True, methods=['post'])
def action(self, request, pk=None):
ret = self.get_object()
action_type = request.data.get('action')
if action_type == 'Approved':
ret.status = 'In Transit'
else:
ret.status = 'Rejected'
ret.save()
return Response(ReturnRequestSerializer(ret).data)
class WalletView(APIView):
def get(self, request):
user = get_active_user(request)
wallet, _ = Wallet.objects.get_or_create(supplier=user)
return Response(WalletSerializer(wallet).data)
class WithdrawView(APIView):
def post(self, request):
user = get_active_user(request)
wallet, _ = Wallet.objects.get_or_create(supplier=user)
amount_str = request.data.get('amount')
try:
amount = float(amount_str)
except (TypeError, ValueError):
return Response({'error': 'Invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
if amount <= 0 or amount > float(wallet.outstanding):
return Response({'error': 'Insufficient funds or invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
wallet.outstanding = float(wallet.outstanding) - amount
wallet.withdrawn = float(wallet.withdrawn) + amount
wallet.save()
tx = WalletTransaction.objects.create(
wallet=wallet,
tx_id=f"TX-{random.randint(1000, 9999)}",
date=date.today(),
amount=amount,
status='Transferred'
)
return Response(WalletSerializer(wallet).data)

View file

@ -37,9 +37,13 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'api',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@ -49,6 +53,10 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
ROOT_URLCONF = 'config.urls'
TEMPLATES = [

View file

@ -15,7 +15,7 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from django.http import HttpResponse
def welcome_view(request):
@ -23,6 +23,7 @@ def welcome_view(request):
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
path('', welcome_view, name='welcome'),
]

3
pytest.ini Normal file
View file

@ -0,0 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = tests.py test_*.py *_tests.py

View file

@ -3,3 +3,9 @@ Django==6.1
gunicorn==26.0.0
packaging==26.3
sqlparse==0.5.5
djangorestframework
django-cors-headers
pytest
pytest-django
pillow