master - part 2

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-30 09:47:58 +00:00
parent 2af7bb725f
commit 885158e152
13 changed files with 538 additions and 63 deletions

View File

@@ -0,0 +1,48 @@
"""
Migration: Simplify payment methods to global (remove country-specific filtering)
This migration:
1. Updates existing PaymentMethodConfig records to use country_code='*' (global)
2. Removes duplicate payment methods per country, keeping only one global config per method
"""
from django.db import migrations
def migrate_to_global_payment_methods(apps, schema_editor):
"""
Convert country-specific payment methods to global.
For each payment_method type, keep only one configuration with country_code='*'
"""
PaymentMethodConfig = apps.get_model('billing', 'PaymentMethodConfig')
# Get all unique payment methods
payment_methods = PaymentMethodConfig.objects.values_list('payment_method', flat=True).distinct()
for method in payment_methods:
# Get all configs for this payment method
configs = PaymentMethodConfig.objects.filter(payment_method=method).order_by('sort_order', 'id')
if configs.exists():
# Keep the first one and make it global
first_config = configs.first()
first_config.country_code = '*'
first_config.save(update_fields=['country_code'])
# Delete duplicates (other country-specific versions)
configs.exclude(id=first_config.id).delete()
def reverse_migration(apps, schema_editor):
"""Reverse is a no-op - can't restore original country codes"""
pass
class Migration(migrations.Migration):
dependencies = [
('billing', '0007_simplify_payment_statuses'),
]
operations = [
migrations.RunPython(migrate_to_global_payment_methods, reverse_migration),
]

View File

@@ -0,0 +1,218 @@
"""
Migration: Seed AIModelConfig from constants.py
This migration populates the AIModelConfig table with the current models
from ai/constants.py, enabling database-driven model configuration.
"""
from decimal import Decimal
from django.db import migrations
def seed_ai_models(apps, schema_editor):
"""
Seed AIModelConfig with models from constants.py
"""
AIModelConfig = apps.get_model('billing', 'AIModelConfig')
# Text Models (from MODEL_RATES)
text_models = [
{
'model_name': 'gpt-4.1',
'display_name': 'GPT-4.1 - Balanced Performance',
'model_type': 'text',
'provider': 'openai',
'input_cost_per_1m': Decimal('2.00'),
'output_cost_per_1m': Decimal('8.00'),
'context_window': 128000,
'max_output_tokens': 16384,
'supports_json_mode': True,
'supports_vision': True,
'supports_function_calling': True,
'is_active': True,
'is_default': True, # Default text model
'sort_order': 1,
'description': 'Default model - good balance of cost and capability',
},
{
'model_name': 'gpt-4o-mini',
'display_name': 'GPT-4o Mini - Fast & Affordable',
'model_type': 'text',
'provider': 'openai',
'input_cost_per_1m': Decimal('0.15'),
'output_cost_per_1m': Decimal('0.60'),
'context_window': 128000,
'max_output_tokens': 16384,
'supports_json_mode': True,
'supports_vision': True,
'supports_function_calling': True,
'is_active': True,
'is_default': False,
'sort_order': 2,
'description': 'Best for high-volume tasks where cost matters',
},
{
'model_name': 'gpt-4o',
'display_name': 'GPT-4o - High Quality',
'model_type': 'text',
'provider': 'openai',
'input_cost_per_1m': Decimal('2.50'),
'output_cost_per_1m': Decimal('10.00'),
'context_window': 128000,
'max_output_tokens': 16384,
'supports_json_mode': True,
'supports_vision': True,
'supports_function_calling': True,
'is_active': True,
'is_default': False,
'sort_order': 3,
'description': 'Premium model for complex tasks requiring best quality',
},
{
'model_name': 'gpt-5.1',
'display_name': 'GPT-5.1 - Latest Generation',
'model_type': 'text',
'provider': 'openai',
'input_cost_per_1m': Decimal('1.25'),
'output_cost_per_1m': Decimal('10.00'),
'context_window': 200000,
'max_output_tokens': 32768,
'supports_json_mode': True,
'supports_vision': True,
'supports_function_calling': True,
'is_active': True,
'is_default': False,
'sort_order': 4,
'description': 'Next-gen model with improved reasoning',
},
{
'model_name': 'gpt-5.2',
'display_name': 'GPT-5.2 - Most Advanced',
'model_type': 'text',
'provider': 'openai',
'input_cost_per_1m': Decimal('1.75'),
'output_cost_per_1m': Decimal('14.00'),
'context_window': 200000,
'max_output_tokens': 65536,
'supports_json_mode': True,
'supports_vision': True,
'supports_function_calling': True,
'is_active': True,
'is_default': False,
'sort_order': 5,
'description': 'Most capable model for enterprise-grade tasks',
},
]
# Image Models (from IMAGE_MODEL_RATES)
image_models = [
{
'model_name': 'dall-e-3',
'display_name': 'DALL-E 3 - Premium Images',
'model_type': 'image',
'provider': 'openai',
'cost_per_image': Decimal('0.040'),
'valid_sizes': ['1024x1024', '1024x1792', '1792x1024'],
'supports_json_mode': False,
'supports_vision': False,
'supports_function_calling': False,
'is_active': True,
'is_default': True, # Default image model
'sort_order': 1,
'description': 'Best quality image generation, good for hero images and marketing',
},
{
'model_name': 'dall-e-2',
'display_name': 'DALL-E 2 - Standard Images',
'model_type': 'image',
'provider': 'openai',
'cost_per_image': Decimal('0.020'),
'valid_sizes': ['256x256', '512x512', '1024x1024'],
'supports_json_mode': False,
'supports_vision': False,
'supports_function_calling': False,
'is_active': True,
'is_default': False,
'sort_order': 2,
'description': 'Lower cost option for bulk image generation',
},
{
'model_name': 'gpt-image-1',
'display_name': 'GPT Image 1 - Advanced',
'model_type': 'image',
'provider': 'openai',
'cost_per_image': Decimal('0.042'),
'valid_sizes': ['1024x1024', '1024x1792', '1792x1024'],
'supports_json_mode': False,
'supports_vision': False,
'supports_function_calling': False,
'is_active': True,
'is_default': False,
'sort_order': 3,
'description': 'Advanced image model with enhanced capabilities',
},
{
'model_name': 'gpt-image-1-mini',
'display_name': 'GPT Image 1 Mini - Fast',
'model_type': 'image',
'provider': 'openai',
'cost_per_image': Decimal('0.011'),
'valid_sizes': ['1024x1024'],
'supports_json_mode': False,
'supports_vision': False,
'supports_function_calling': False,
'is_active': True,
'is_default': False,
'sort_order': 4,
'description': 'Fastest and most affordable image model',
},
]
# Runware Image Models (from existing integration)
runware_models = [
{
'model_name': 'runware:100@1',
'display_name': 'Runware Standard',
'model_type': 'image',
'provider': 'runware',
'cost_per_image': Decimal('0.008'),
'valid_sizes': ['512x512', '768x768', '1024x1024'],
'supports_json_mode': False,
'supports_vision': False,
'supports_function_calling': False,
'is_active': True,
'is_default': False,
'sort_order': 10,
'description': 'Runware image generation - most affordable',
},
]
# Create all models
all_models = text_models + image_models + runware_models
for model_data in all_models:
AIModelConfig.objects.update_or_create(
model_name=model_data['model_name'],
defaults=model_data
)
def reverse_migration(apps, schema_editor):
"""Remove seeded models"""
AIModelConfig = apps.get_model('billing', 'AIModelConfig')
seeded_models = [
'gpt-4.1', 'gpt-4o-mini', 'gpt-4o', 'gpt-5.1', 'gpt-5.2',
'dall-e-3', 'dall-e-2', 'gpt-image-1', 'gpt-image-1-mini',
'runware:100@1'
]
AIModelConfig.objects.filter(model_name__in=seeded_models).delete()
class Migration(migrations.Migration):
dependencies = [
('billing', '0008_global_payment_methods'),
]
operations = [
migrations.RunPython(seed_ai_models, reverse_migration),
]

View File

@@ -197,17 +197,18 @@ class BillingViewSet(viewsets.GenericViewSet):
Does not expose sensitive configuration details.
Query params:
country: ISO 2-letter country code (default: 'US')
country: ISO 2-letter country code (optional, defaults to global '*')
Returns payment methods filtered by country.
Returns payment methods - prioritizes global methods (country_code='*').
"""
country = request.GET.get('country', 'US').upper()
country = request.GET.get('country', '*').upper()
# Get country-specific methods
# Get global methods first (country_code='*'), then country-specific as fallback
methods = PaymentMethodConfig.objects.filter(
country_code=country,
is_enabled=True
).order_by('sort_order')
).filter(
Q(country_code='*') | Q(country_code=country)
).order_by('sort_order').distinct()
# Serialize using the proper serializer
serializer = PaymentMethodConfigSerializer(methods, many=True)