feat: extend SupplierProfile with business details, category support, and dynamic multi-database configuration
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 26s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 26s
This commit is contained in:
parent
4f5274248a
commit
ff6f776b85
7 changed files with 179 additions and 30 deletions
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 6.1 on 2026-08-18 05:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0003_product_is_active_product_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierprofile',
|
||||
name='business_type',
|
||||
field=models.CharField(blank=True, choices=[('registered_company', 'Registered Company'), ('self_help_group', 'Self Help Group'), ('individual_maker', 'Individual Maker')], max_length=50, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierprofile',
|
||||
name='store_slug',
|
||||
field=models.SlugField(blank=True, max_length=255, null=True, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierprofile',
|
||||
name='support_email',
|
||||
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierprofile',
|
||||
name='support_phone',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierprofile',
|
||||
name='categories',
|
||||
field=models.ManyToManyField(blank=True, related_name='suppliers', to='api.category'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class SupplierProfile(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('unverified', 'Unverified'),
|
||||
|
|
@ -12,12 +18,22 @@ class SupplierProfile(models.Model):
|
|||
('rejected', 'Rejected'),
|
||||
('suspended', 'Suspended'),
|
||||
]
|
||||
BUSINESS_TYPE_CHOICES = [
|
||||
('registered_company', 'Registered Company'),
|
||||
('self_help_group', 'Self Help Group'),
|
||||
('individual_maker', 'Individual Maker'),
|
||||
]
|
||||
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=15, blank=True, null=True)
|
||||
is_gstin_verified = models.BooleanField(default=False)
|
||||
business_type = models.CharField(max_length=50, choices=BUSINESS_TYPE_CHOICES, blank=True, null=True)
|
||||
store_slug = models.SlugField(max_length=255, unique=True, blank=True, null=True)
|
||||
support_email = models.EmailField(blank=True, null=True)
|
||||
support_phone = models.CharField(max_length=20, blank=True, null=True)
|
||||
categories = models.ManyToManyField(Category, blank=True, related_name='suppliers')
|
||||
aadhar_file = models.CharField(max_length=255, blank=True, null=True) # retaining old files or S3 keys
|
||||
pan_file = models.CharField(max_length=255, blank=True, null=True)
|
||||
aadhar_s3_key = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
import re
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth.models import User
|
||||
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction
|
||||
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, Category
|
||||
|
||||
class CategoryListField(serializers.RelatedField):
|
||||
def to_representation(self, value):
|
||||
return value.name
|
||||
|
||||
def to_internal_value(self, data):
|
||||
category, _ = Category.objects.get_or_create(name=data)
|
||||
return category
|
||||
|
||||
class SupplierProfileSerializer(serializers.ModelSerializer):
|
||||
onboarding_step = serializers.SerializerMethodField()
|
||||
categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = SupplierProfile
|
||||
exclude = ('user',)
|
||||
|
||||
def get_onboarding_step(self, obj):
|
||||
if not obj.phone_verified or not obj.email_verified:
|
||||
return 1
|
||||
if not obj.is_gstin_verified or not obj.aadhar_s3_key or not obj.pan_s3_key:
|
||||
if obj.is_profile_complete:
|
||||
return 7
|
||||
if not obj.business_type or not obj.gstin or not obj.is_gstin_verified:
|
||||
return 2
|
||||
if not obj.store_name or not obj.logo_s3_key or not obj.street or not obj.city:
|
||||
if not obj.aadhar_s3_key or not obj.pan_s3_key:
|
||||
return 3
|
||||
if not obj.phone_verified or not obj.email_verified:
|
||||
return 4
|
||||
if obj.categories.count() == 0:
|
||||
return 5
|
||||
if not obj.store_name or not obj.store_slug or not obj.logo_s3_key or not obj.street:
|
||||
return 6
|
||||
return 7
|
||||
|
||||
def validate_pincode(self, value):
|
||||
if value and (not value.isdigit() or len(value) != 6):
|
||||
|
|
@ -29,7 +44,7 @@ class SupplierProfileSerializer(serializers.ModelSerializer):
|
|||
|
||||
def validate_logo_s3_key(self, value):
|
||||
user = self.context['request'].user
|
||||
if value and not value.startswith(f"suppliers/{user.id}/"):
|
||||
if value and not value.startswith(f"suppliers/{user.id}/") and not value.startswith(f"supplierdocument/{user.id}/"):
|
||||
raise serializers.ValidationError("logo_s3_key must belong to the logged-in supplier.")
|
||||
return value
|
||||
|
||||
|
|
|
|||
31
api/tests.py
31
api/tests.py
|
|
@ -13,6 +13,25 @@ def api_client():
|
|||
def create_user(db):
|
||||
user = User.objects.create_user(username='demo_seller', email='demo@example.com', password='Password123!')
|
||||
SupplierProfile.objects.create(user=user, phone='1234567890')
|
||||
Order.objects.create(
|
||||
supplier=user,
|
||||
order_id='OR-8721-demo',
|
||||
date='2026-08-05',
|
||||
item='Handmade Wool Blanket',
|
||||
quantity=1,
|
||||
customer='Alice Smith',
|
||||
total=85.00,
|
||||
status='Pending Acceptance'
|
||||
)
|
||||
ReturnRequest.objects.create(
|
||||
supplier=user,
|
||||
return_id='RET-5510-demo',
|
||||
order_id='OR-3920-demo',
|
||||
customer='Bob Johnson',
|
||||
item='Silk Scarf',
|
||||
reason='Ordered wrong color variant.',
|
||||
status='Pending Approval'
|
||||
)
|
||||
return user
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -64,13 +83,23 @@ def test_profile_update(api_client, create_user):
|
|||
'is_gstin_verified': True,
|
||||
'aadhar_s3_key': 'suppliers/1/aadhar.pdf',
|
||||
'pan_s3_key': 'suppliers/1/pan.pdf',
|
||||
'logo_s3_key': 'suppliers/1/logo.jpg'
|
||||
'logo_s3_key': 'suppliers/1/logo.jpg',
|
||||
'business_type': 'individual_maker',
|
||||
'store_slug': 'new-artisan-handloom',
|
||||
'support_email': 'support@newartisan.com',
|
||||
'support_phone': '+919999988888',
|
||||
'categories': ['Sustainable Products', 'Home Decor']
|
||||
}
|
||||
response = api_client.put(url, data, format='json')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
create_user.profile.refresh_from_db()
|
||||
assert create_user.profile.store_name == 'New Artisan Handloom'
|
||||
assert create_user.profile.is_gstin_verified is True
|
||||
assert create_user.profile.business_type == 'individual_maker'
|
||||
assert create_user.profile.store_slug == 'new-artisan-handloom'
|
||||
assert create_user.profile.support_email == 'support@newartisan.com'
|
||||
assert create_user.profile.support_phone == '+919999988888'
|
||||
assert set(create_user.profile.categories.values_list('name', flat=True)) == {'Sustainable Products', 'Home Decor'}
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_product_crud(api_client, create_user):
|
||||
|
|
|
|||
17
api/views.py
17
api/views.py
|
|
@ -18,7 +18,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, OTPRecord, BulkUploadLog
|
||||
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, OTPRecord, BulkUploadLog, Category
|
||||
from .serializers import (
|
||||
UserSerializer, RegisterSerializer, ProductSerializer,
|
||||
OrderSerializer, ReturnRequestSerializer, WalletSerializer
|
||||
|
|
@ -196,7 +196,7 @@ class ProfileView(APIView):
|
|||
|
||||
# Check permissions & s3 keys
|
||||
logo_key = request.data.get('logo_s3_key', profile.logo_s3_key)
|
||||
if logo_key and not logo_key.startswith(f"suppliers/{user.id}/") and not logo_key.startswith("suppliers/default/"):
|
||||
if logo_key and not logo_key.startswith(f"suppliers/{user.id}/") and not logo_key.startswith("suppliers/default/") and not logo_key.startswith(f"supplierdocument/{user.id}/"):
|
||||
return Response({'error': 'Invalid logo_s3_key ownership'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
profile.phone = request.data.get('phone', profile.phone)
|
||||
|
|
@ -204,6 +204,19 @@ class ProfileView(APIView):
|
|||
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.business_type = request.data.get('business_type', profile.business_type)
|
||||
profile.store_slug = request.data.get('store_slug', profile.store_slug)
|
||||
profile.support_email = request.data.get('support_email', profile.support_email)
|
||||
profile.support_phone = request.data.get('support_phone', profile.support_phone)
|
||||
|
||||
categories_data = request.data.get('categories')
|
||||
if categories_data is not None:
|
||||
category_objs = []
|
||||
for cat_name in categories_data:
|
||||
cat_obj, _ = Category.objects.get_or_create(name=cat_name)
|
||||
category_objs.append(cat_obj)
|
||||
profile.categories.set(category_objs)
|
||||
|
||||
profile.aadhar_file = request.data.get('aadhar_file', profile.aadhar_file)
|
||||
profile.pan_file = request.data.get('pan_file', profile.pan_file)
|
||||
profile.aadhar_s3_key = request.data.get('aadhar_s3_key', profile.aadhar_s3_key)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,16 @@ WSGI_APPLICATION = 'config.wsgi.application'
|
|||
# ---------------------------------------------------------------------------
|
||||
# Database Configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
import socket
|
||||
def is_postgres_available():
|
||||
try:
|
||||
s = socket.create_connection(('127.0.0.1', 5432), timeout=1)
|
||||
s.close()
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
if is_postgres_available():
|
||||
db_credentials = {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'USER': os.environ.get('DB_USER', 'vignesh'),
|
||||
|
|
@ -104,6 +114,21 @@ DATABASES = {
|
|||
**db_credentials
|
||||
}
|
||||
}
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
},
|
||||
'customerprofile': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
},
|
||||
'productprofile': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
DATABASE_ROUTERS = ['config.db_router.DatabaseRouter']
|
||||
|
||||
|
|
|
|||
6
conftest.py
Normal file
6
conftest.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import pytest
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
for marker in item.iter_markers(name="django_db"):
|
||||
marker.kwargs["databases"] = "__all__"
|
||||
Loading…
Reference in a new issue