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

This commit is contained in:
vickytechkey 2026-08-18 11:30:22 +05:30
parent 4f5274248a
commit ff6f776b85
7 changed files with 179 additions and 30 deletions

View file

@ -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'),
),
]

View file

@ -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)

View file

@ -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
return 4
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

View file

@ -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):

View file

@ -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)

View file

@ -82,28 +82,53 @@ WSGI_APPLICATION = 'config.wsgi.application'
# ---------------------------------------------------------------------------
# Database Configuration
# ---------------------------------------------------------------------------
db_credentials = {
'ENGINE': 'django.db.backends.postgresql',
'USER': os.environ.get('DB_USER', 'vignesh'),
'PASSWORD': os.environ.get('DB_PASSWORD', 'vtechnosoft@123A'),
'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
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
DATABASES = {
'default': {
'NAME': 'sellerprofile',
**db_credentials
},
'customerprofile': {
'NAME': 'customerprofile',
**db_credentials
},
'productprofile': {
'NAME': 'productprofile',
**db_credentials
if is_postgres_available():
db_credentials = {
'ENGINE': 'django.db.backends.postgresql',
'USER': os.environ.get('DB_USER', 'vignesh'),
'PASSWORD': os.environ.get('DB_PASSWORD', 'vtechnosoft@123A'),
'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
DATABASES = {
'default': {
'NAME': 'sellerprofile',
**db_credentials
},
'customerprofile': {
'NAME': 'customerprofile',
**db_credentials
},
'productprofile': {
'NAME': 'productprofile',
**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
View 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__"