docs re-org
This commit is contained in:
@@ -1,373 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Payment Workflow API Integration Examples
|
||||
Demonstrates how to interact with the payment APIs programmatically
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from decimal import Decimal
|
||||
|
||||
# Base URL for the API
|
||||
BASE_URL = "http://localhost:8011/api/v1"
|
||||
|
||||
class PaymentAPIClient:
|
||||
"""Example API client for payment workflow"""
|
||||
|
||||
def __init__(self, base_url=BASE_URL):
|
||||
self.base_url = base_url
|
||||
self.token = None
|
||||
self.session = requests.Session()
|
||||
|
||||
def register_free_trial(self, email, password, first_name, last_name):
|
||||
"""Register a new free trial user"""
|
||||
url = f"{self.base_url}/auth/register/"
|
||||
data = {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"password_confirm": password,
|
||||
"first_name": first_name,
|
||||
"last_name": last_name
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
print(f"✓ Free trial account created: {result['data']['account']['name']}")
|
||||
print(f" Status: {result['data']['account']['status']}")
|
||||
print(f" Credits: {result['data']['account']['credits']}")
|
||||
|
||||
return result['data']
|
||||
|
||||
def register_paid_user(self, email, password, first_name, last_name,
|
||||
plan_slug, billing_info):
|
||||
"""Register a new paid user with billing information"""
|
||||
url = f"{self.base_url}/auth/register/"
|
||||
data = {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"password_confirm": password,
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"plan_slug": plan_slug,
|
||||
**billing_info
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
print(f"✓ Paid account created: {result['data']['account']['name']}")
|
||||
print(f" Status: {result['data']['account']['status']}")
|
||||
print(f" Credits: {result['data']['account']['credits']}")
|
||||
|
||||
if 'invoice' in result['data']:
|
||||
inv = result['data']['invoice']
|
||||
print(f" Invoice: {inv['invoice_number']} - ${inv['total']}")
|
||||
|
||||
return result['data']
|
||||
|
||||
def login(self, email, password):
|
||||
"""Login and get authentication token"""
|
||||
url = f"{self.base_url}/auth/login/"
|
||||
data = {
|
||||
"email": email,
|
||||
"password": password
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
self.token = result['data']['token']
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {self.token}'
|
||||
})
|
||||
|
||||
print(f"✓ Logged in as: {email}")
|
||||
return result['data']
|
||||
|
||||
def get_payment_methods(self, country_code=None):
|
||||
"""Get available payment methods for a country"""
|
||||
url = f"{self.base_url}/billing/admin/payment-methods/"
|
||||
params = {}
|
||||
if country_code:
|
||||
params['country'] = country_code
|
||||
|
||||
response = self.session.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
methods = response.json()
|
||||
|
||||
print(f"✓ Payment methods available: {len(methods)}")
|
||||
for method in methods:
|
||||
print(f" - {method['display_name']} ({method['payment_method']})")
|
||||
|
||||
return methods
|
||||
|
||||
def confirm_payment(self, invoice_id, payment_method, amount,
|
||||
manual_reference, manual_notes=""):
|
||||
"""Submit payment confirmation for manual payments"""
|
||||
url = f"{self.base_url}/billing/admin/payments/confirm/"
|
||||
data = {
|
||||
"invoice_id": invoice_id,
|
||||
"payment_method": payment_method,
|
||||
"amount": str(amount),
|
||||
"manual_reference": manual_reference,
|
||||
"manual_notes": manual_notes
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
payment = result['data']
|
||||
print(f"✓ Payment confirmation submitted")
|
||||
print(f" Payment ID: {payment['payment_id']}")
|
||||
print(f" Invoice: {payment['invoice_number']}")
|
||||
print(f" Status: {payment['status']}")
|
||||
print(f" Reference: {payment['manual_reference']}")
|
||||
|
||||
return result['data']
|
||||
|
||||
def approve_payment(self, payment_id, admin_notes=""):
|
||||
"""Approve a pending payment (admin only)"""
|
||||
url = f"{self.base_url}/billing/admin/payments/{payment_id}/approve/"
|
||||
data = {
|
||||
"admin_notes": admin_notes
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
payment = result['data']
|
||||
print(f"✓ Payment approved")
|
||||
print(f" Account Status: {payment['account_status']}")
|
||||
print(f" Subscription Status: {payment['subscription_status']}")
|
||||
print(f" Credits Added: {payment['credits_added']}")
|
||||
print(f" Total Credits: {payment['total_credits']}")
|
||||
|
||||
return result['data']
|
||||
|
||||
def reject_payment(self, payment_id, admin_notes):
|
||||
"""Reject a pending payment (admin only)"""
|
||||
url = f"{self.base_url}/billing/admin/payments/{payment_id}/reject/"
|
||||
data = {
|
||||
"admin_notes": admin_notes
|
||||
}
|
||||
|
||||
response = self.session.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
payment = result['data']
|
||||
print(f"✓ Payment rejected")
|
||||
print(f" Status: {payment['status']}")
|
||||
print(f" Reason: {admin_notes}")
|
||||
|
||||
return result['data']
|
||||
|
||||
|
||||
def example_free_trial_workflow():
|
||||
"""Example: Free trial signup workflow"""
|
||||
print("\n" + "="*60)
|
||||
print("EXAMPLE 1: FREE TRIAL SIGNUP")
|
||||
print("="*60 + "\n")
|
||||
|
||||
client = PaymentAPIClient()
|
||||
|
||||
# Step 1: Register free trial user
|
||||
user_data = client.register_free_trial(
|
||||
email="freetrial_demo@example.com",
|
||||
password="SecurePass123!",
|
||||
first_name="Free",
|
||||
last_name="Trial"
|
||||
)
|
||||
|
||||
# Step 2: Login
|
||||
login_data = client.login(
|
||||
email="freetrial_demo@example.com",
|
||||
password="SecurePass123!"
|
||||
)
|
||||
|
||||
print(f"\n✓ Free trial workflow complete!")
|
||||
print(f" User can now create {user_data['account']['max_sites']} site(s)")
|
||||
print(f" Available credits: {user_data['account']['credits']}")
|
||||
|
||||
|
||||
def example_paid_signup_workflow():
|
||||
"""Example: Paid signup with manual payment approval"""
|
||||
print("\n" + "="*60)
|
||||
print("EXAMPLE 2: PAID SIGNUP WITH MANUAL PAYMENT")
|
||||
print("="*60 + "\n")
|
||||
|
||||
client = PaymentAPIClient()
|
||||
|
||||
# Step 1: Check available payment methods
|
||||
print("Step 1: Check Payment Methods for Pakistan")
|
||||
methods = client.get_payment_methods(country_code="PK")
|
||||
|
||||
# Step 2: Register with paid plan
|
||||
print("\nStep 2: Register Paid User")
|
||||
billing_info = {
|
||||
"billing_email": "billing@example.com",
|
||||
"billing_address_line1": "123 Main Street",
|
||||
"billing_city": "Karachi",
|
||||
"billing_country": "PK",
|
||||
"payment_method": "bank_transfer"
|
||||
}
|
||||
|
||||
user_data = client.register_paid_user(
|
||||
email="paiduser_demo@example.com",
|
||||
password="SecurePass123!",
|
||||
first_name="Paid",
|
||||
last_name="User",
|
||||
plan_slug="starter",
|
||||
billing_info=billing_info
|
||||
)
|
||||
|
||||
# Step 3: Login
|
||||
print("\nStep 3: User Login")
|
||||
login_data = client.login(
|
||||
email="paiduser_demo@example.com",
|
||||
password="SecurePass123!"
|
||||
)
|
||||
|
||||
# Step 4: User makes external payment and submits confirmation
|
||||
print("\nStep 4: Submit Payment Confirmation")
|
||||
invoice_id = user_data['invoice']['id']
|
||||
invoice_total = user_data['invoice']['total']
|
||||
|
||||
payment_data = client.confirm_payment(
|
||||
invoice_id=invoice_id,
|
||||
payment_method="bank_transfer",
|
||||
amount=invoice_total,
|
||||
manual_reference="DEMO-BANK-2025-001",
|
||||
manual_notes="Transferred via ABC Bank on Dec 8, 2025"
|
||||
)
|
||||
|
||||
print(f"\n✓ Payment submitted! Waiting for admin approval...")
|
||||
print(f" Payment ID: {payment_data['payment_id']}")
|
||||
print(f" Account remains in 'pending_payment' status")
|
||||
|
||||
# Step 5: Admin approves (requires admin token)
|
||||
print("\nStep 5: Admin Approval (requires admin credentials)")
|
||||
print(" → Admin would login separately and approve the payment")
|
||||
print(f" → POST /billing/admin/payments/{payment_data['payment_id']}/approve/")
|
||||
print(" → Account status changes to 'active'")
|
||||
print(" → Credits allocated: 1000")
|
||||
|
||||
return payment_data
|
||||
|
||||
|
||||
def example_admin_approval():
|
||||
"""Example: Admin approving a payment"""
|
||||
print("\n" + "="*60)
|
||||
print("EXAMPLE 3: ADMIN PAYMENT APPROVAL")
|
||||
print("="*60 + "\n")
|
||||
|
||||
# This requires admin credentials
|
||||
admin_client = PaymentAPIClient()
|
||||
|
||||
print("Step 1: Admin Login")
|
||||
try:
|
||||
admin_client.login(
|
||||
email="dev@igny8.com", # Replace with actual admin email
|
||||
password="admin_password" # Replace with actual password
|
||||
)
|
||||
|
||||
print("\nStep 2: Approve Payment")
|
||||
# Replace with actual payment ID
|
||||
payment_id = 5 # Example payment ID
|
||||
|
||||
result = admin_client.approve_payment(
|
||||
payment_id=payment_id,
|
||||
admin_notes="Verified payment in bank statement. Reference matches."
|
||||
)
|
||||
|
||||
print(f"\n✓ Payment approval complete!")
|
||||
print(f" Account activated with {result['total_credits']} credits")
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"✗ Admin approval failed: {e}")
|
||||
print(" (This is expected if you don't have admin credentials)")
|
||||
|
||||
|
||||
def example_payment_rejection():
|
||||
"""Example: Admin rejecting a payment"""
|
||||
print("\n" + "="*60)
|
||||
print("EXAMPLE 4: ADMIN PAYMENT REJECTION")
|
||||
print("="*60 + "\n")
|
||||
|
||||
admin_client = PaymentAPIClient()
|
||||
|
||||
print("Step 1: Admin Login")
|
||||
try:
|
||||
admin_client.login(
|
||||
email="dev@igny8.com",
|
||||
password="admin_password"
|
||||
)
|
||||
|
||||
print("\nStep 2: Reject Payment")
|
||||
payment_id = 7 # Example payment ID
|
||||
|
||||
result = admin_client.reject_payment(
|
||||
payment_id=payment_id,
|
||||
admin_notes="Reference number not found in bank statement. Please verify and resubmit."
|
||||
)
|
||||
|
||||
print(f"\n✓ Payment rejected!")
|
||||
print(f" User can resubmit with correct reference")
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"✗ Payment rejection failed: {e}")
|
||||
print(" (This is expected if you don't have admin credentials)")
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all examples"""
|
||||
print("\n" + "="*60)
|
||||
print("PAYMENT WORKFLOW API INTEGRATION EXAMPLES")
|
||||
print("="*60)
|
||||
print("\nThese examples demonstrate how to integrate with the")
|
||||
print("multi-tenancy payment workflow APIs.\n")
|
||||
|
||||
try:
|
||||
# Example 1: Free trial
|
||||
example_free_trial_workflow()
|
||||
|
||||
# Example 2: Paid signup
|
||||
# example_paid_signup_workflow()
|
||||
|
||||
# Example 3: Admin approval (requires admin credentials)
|
||||
# example_admin_approval()
|
||||
|
||||
# Example 4: Payment rejection (requires admin credentials)
|
||||
# example_payment_rejection()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\n✗ API Error: {e}")
|
||||
print("\nMake sure the backend is running on http://localhost:8011")
|
||||
except Exception as e:
|
||||
print(f"\n✗ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Note: Uncomment examples you want to run
|
||||
# Some examples may create actual data in the database
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("API INTEGRATION EXAMPLES - READ ONLY MODE")
|
||||
print("="*60)
|
||||
print("\nTo run examples, uncomment the desired function calls")
|
||||
print("in the main() function.\n")
|
||||
print("Available examples:")
|
||||
print(" 1. example_free_trial_workflow()")
|
||||
print(" 2. example_paid_signup_workflow()")
|
||||
print(" 3. example_admin_approval()")
|
||||
print(" 4. example_payment_rejection()")
|
||||
print("\nWarning: Running these will create data in the database!")
|
||||
print("="*60 + "\n")
|
||||
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import django
|
||||
import json
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
from django.test import RequestFactory
|
||||
from igny8_core.modules.integration.views import IntegrationViewSet
|
||||
|
||||
# Create a fake request
|
||||
factory = RequestFactory()
|
||||
request = factory.get('/api/v1/integration/integrations/1/content-types/')
|
||||
|
||||
# Create view and call the action
|
||||
integration = SiteIntegration.objects.get(id=1)
|
||||
viewset = IntegrationViewSet()
|
||||
viewset.format_kwarg = None
|
||||
viewset.request = request
|
||||
viewset.kwargs = {'pk': 1}
|
||||
|
||||
# Get the response data
|
||||
response = viewset.content_types_summary(request, pk=1)
|
||||
|
||||
print("Response Status:", response.status_code)
|
||||
print("\nResponse Data:")
|
||||
print(json.dumps(response.data, indent=2, default=str))
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Script to check current database state for tenancy system
|
||||
DO NOT MAKE ANY CHANGES - READ ONLY
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Set up Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
django.setup()
|
||||
|
||||
from igny8_core.auth.models import Plan, Account, User, Site, Sector, Subscription
|
||||
from igny8_core.business.billing.models import CreditTransaction
|
||||
|
||||
print("=" * 80)
|
||||
print("CURRENT DATABASE STATE ANALYSIS (READ-ONLY)")
|
||||
print("=" * 80)
|
||||
|
||||
# Check Plans
|
||||
print("\n=== EXISTING PLANS ===")
|
||||
plans = Plan.objects.all()
|
||||
if plans.exists():
|
||||
for p in plans:
|
||||
print(f"{p.id}. [{p.slug}] {p.name}")
|
||||
print(f" Price: ${p.price}/{p.billing_cycle}")
|
||||
print(f" Credits: {p.included_credits} (legacy: {p.credits_per_month})")
|
||||
print(f" Max Sites: {p.max_sites}, Max Users: {p.max_users}, Max Industries: {p.max_industries}")
|
||||
print(f" Active: {p.is_active}")
|
||||
print(f" Features: {p.features}")
|
||||
print()
|
||||
else:
|
||||
print("No plans found in database")
|
||||
|
||||
print(f"Total plans: {plans.count()}\n")
|
||||
|
||||
# Check Accounts
|
||||
print("=== EXISTING ACCOUNTS ===")
|
||||
accounts = Account.objects.select_related('plan', 'owner').all()[:10]
|
||||
if accounts.exists():
|
||||
for acc in accounts:
|
||||
print(f"{acc.id}. [{acc.slug}] {acc.name}")
|
||||
print(f" Owner: {acc.owner.email if acc.owner else 'None'}")
|
||||
print(f" Plan: {acc.plan.slug if acc.plan else 'None'}")
|
||||
print(f" Credits: {acc.credits}")
|
||||
print(f" Status: {acc.status}")
|
||||
print(f" Has payment_method field: {hasattr(acc, 'payment_method')}")
|
||||
try:
|
||||
print(f" Payment method: {acc.payment_method if hasattr(acc, 'payment_method') else 'Field does not exist'}")
|
||||
except:
|
||||
print(f" Payment method: Field does not exist in DB")
|
||||
print()
|
||||
else:
|
||||
print("No accounts found in database")
|
||||
|
||||
print(f"Total accounts: {Account.objects.count()}\n")
|
||||
|
||||
# Check Users
|
||||
print("=== USER ROLES ===")
|
||||
users = User.objects.select_related('account').all()[:10]
|
||||
if users.exists():
|
||||
for u in users:
|
||||
print(f"{u.id}. {u.email} - Role: {u.role}")
|
||||
print(f" Account: {u.account.slug if u.account else 'None'}")
|
||||
print(f" Is superuser: {u.is_superuser}")
|
||||
print()
|
||||
else:
|
||||
print("No users found in database")
|
||||
|
||||
print(f"Total users: {User.objects.count()}\n")
|
||||
|
||||
# Check Sites
|
||||
print("=== SITES AND ACCOUNT RELATIONSHIP ===")
|
||||
sites = Site.objects.select_related('account', 'industry').all()[:10]
|
||||
if sites.exists():
|
||||
for site in sites:
|
||||
print(f"{site.id}. [{site.slug}] {site.name}")
|
||||
print(f" Account: {site.account.slug if site.account else 'None'}")
|
||||
print(f" Industry: {site.industry.name if site.industry else 'None'}")
|
||||
print(f" Active: {site.is_active}, Status: {site.status}")
|
||||
print(f" Sectors: {site.sectors.filter(is_active=True).count()}")
|
||||
print()
|
||||
else:
|
||||
print("No sites found in database")
|
||||
|
||||
print(f"Total sites: {Site.objects.count()}\n")
|
||||
|
||||
# Check Subscriptions
|
||||
print("=== SUBSCRIPTIONS ===")
|
||||
subscriptions = Subscription.objects.select_related('account').all()
|
||||
if subscriptions.exists():
|
||||
for sub in subscriptions:
|
||||
print(f"{sub.id}. Account: {sub.account.slug}")
|
||||
print(f" Stripe ID: {sub.stripe_subscription_id}")
|
||||
print(f" Status: {sub.status}")
|
||||
print(f" Period: {sub.current_period_start} to {sub.current_period_end}")
|
||||
print(f" Has payment_method field: {hasattr(sub, 'payment_method')}")
|
||||
try:
|
||||
print(f" Payment method: {sub.payment_method if hasattr(sub, 'payment_method') else 'Field does not exist'}")
|
||||
print(f" External payment ID: {sub.external_payment_id if hasattr(sub, 'external_payment_id') else 'Field does not exist'}")
|
||||
except:
|
||||
print(f" Payment method fields: Do not exist in DB")
|
||||
print()
|
||||
else:
|
||||
print("No subscriptions found in database")
|
||||
|
||||
print(f"Total subscriptions: {Subscription.objects.count()}\n")
|
||||
|
||||
# Check Credit Transactions
|
||||
print("=== CREDIT TRANSACTIONS (Sample) ===")
|
||||
transactions = CreditTransaction.objects.select_related('account').order_by('-created_at')[:5]
|
||||
if transactions.exists():
|
||||
for tx in transactions:
|
||||
print(f"{tx.id}. Account: {tx.account.slug}")
|
||||
print(f" Type: {tx.transaction_type}, Amount: {tx.amount}")
|
||||
print(f" Balance after: {tx.balance_after}")
|
||||
print(f" Description: {tx.description}")
|
||||
print(f" Created: {tx.created_at}")
|
||||
print()
|
||||
else:
|
||||
print("No credit transactions found")
|
||||
|
||||
print(f"Total credit transactions: {CreditTransaction.objects.count()}\n")
|
||||
|
||||
# Model Field Analysis
|
||||
print("=== MODEL FIELD ANALYSIS ===")
|
||||
print("\nAccount Model Fields:")
|
||||
for field in Account._meta.get_fields():
|
||||
if not field.many_to_many and not field.one_to_many:
|
||||
print(f" - {field.name}: {field.get_internal_type()}")
|
||||
|
||||
print("\nSubscription Model Fields:")
|
||||
for field in Subscription._meta.get_fields():
|
||||
if not field.many_to_many and not field.one_to_many:
|
||||
print(f" - {field.name}: {field.get_internal_type()}")
|
||||
|
||||
print("\nSite Model Fields:")
|
||||
for field in Site._meta.get_fields():
|
||||
if not field.many_to_many and not field.one_to_many:
|
||||
field_name = field.name
|
||||
field_type = field.get_internal_type()
|
||||
if field_name in ['account', 'industry']:
|
||||
print(f" - {field_name}: {field_type} (RELATIONSHIP)")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("END OF ANALYSIS")
|
||||
print("=" * 80)
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Check recent keyword creation"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
recent = timezone.now() - timedelta(hours=24)
|
||||
recent_keywords = Keywords.objects.filter(created_at__gte=recent)
|
||||
|
||||
print(f'Keywords created in last 24 hours: {recent_keywords.count()}')
|
||||
if recent_keywords.exists():
|
||||
print('\nRecent keyword statuses:')
|
||||
for k in recent_keywords[:10]:
|
||||
print(f' ID {k.id}: status={k.status}, created={k.created_at}')
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Clean up structure-based categories that were incorrectly created
|
||||
This will remove categories like "Guide", "Article", etc. that match content_structure values
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import transaction
|
||||
from igny8_core.business.content.models import ContentTaxonomy
|
||||
|
||||
# List of structure values that were incorrectly added as categories
|
||||
STRUCTURE_VALUES = ['Guide', 'Article', 'Listicle', 'How To', 'Tutorial', 'Review', 'Comparison']
|
||||
|
||||
print("=" * 80)
|
||||
print("CLEANING UP STRUCTURE-BASED CATEGORIES")
|
||||
print("=" * 80)
|
||||
|
||||
for structure_name in STRUCTURE_VALUES:
|
||||
categories = ContentTaxonomy.objects.filter(
|
||||
taxonomy_type='category',
|
||||
name=structure_name
|
||||
)
|
||||
|
||||
if categories.exists():
|
||||
count = categories.count()
|
||||
print(f"\nRemoving {count} '{structure_name}' categor{'y' if count == 1 else 'ies'}...")
|
||||
categories.delete()
|
||||
print(f" ✓ Deleted {count} '{structure_name}' categor{'y' if count == 1 else 'ies'}")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("CLEANUP COMPLETE")
|
||||
print("=" * 80)
|
||||
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Create API test data for billing endpoints
|
||||
All test records are marked with 'API_TEST' in name/description/notes
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
from igny8_core.auth.models import Account, Plan
|
||||
from igny8_core.business.billing.models import (
|
||||
Invoice, Payment, CreditTransaction, AccountPaymentMethod, PaymentMethodConfig
|
||||
)
|
||||
from decimal import Decimal
|
||||
from datetime import timedelta
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
print("Creating API test data...")
|
||||
|
||||
# Get or create test account
|
||||
try:
|
||||
account = Account.objects.get(name__icontains='scale')
|
||||
print(f"✓ Using existing account: {account.name} (ID: {account.id})")
|
||||
except Account.DoesNotExist:
|
||||
# Get a plan
|
||||
plan = Plan.objects.filter(is_active=True).first()
|
||||
account = Account.objects.create(
|
||||
name='API_TEST_ACCOUNT',
|
||||
slug='api-test-account',
|
||||
plan=plan,
|
||||
credits=5000,
|
||||
status='active'
|
||||
)
|
||||
print(f"✓ Created test account: {account.name} (ID: {account.id})")
|
||||
|
||||
# Create test invoices
|
||||
invoice1, created = Invoice.objects.get_or_create(
|
||||
account=account,
|
||||
invoice_number='INV-API-TEST-001',
|
||||
defaults={
|
||||
'status': 'pending',
|
||||
'subtotal': Decimal('99.99'),
|
||||
'tax': Decimal('0.00'),
|
||||
'total': Decimal('99.99'),
|
||||
'currency': 'USD',
|
||||
'invoice_date': timezone.now().date(),
|
||||
'due_date': (timezone.now() + timedelta(days=30)).date(),
|
||||
'billing_email': 'test@igny8.com',
|
||||
'notes': 'API_TEST: Invoice for approval test',
|
||||
'line_items': [{'description': 'API Test Service', 'amount': 99.99, 'quantity': 1}],
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created test invoice 1 (ID: {invoice1.id})")
|
||||
else:
|
||||
print(f"✓ Existing test invoice 1 (ID: {invoice1.id})")
|
||||
|
||||
invoice2, created = Invoice.objects.get_or_create(
|
||||
account=account,
|
||||
invoice_number='INV-API-TEST-002',
|
||||
defaults={
|
||||
'status': 'pending',
|
||||
'subtotal': Decimal('49.99'),
|
||||
'tax': Decimal('0.00'),
|
||||
'total': Decimal('49.99'),
|
||||
'currency': 'USD',
|
||||
'invoice_date': timezone.now().date(),
|
||||
'due_date': (timezone.now() + timedelta(days=30)).date(),
|
||||
'billing_email': 'test@igny8.com',
|
||||
'notes': 'API_TEST: Invoice for rejection test',
|
||||
'line_items': [{'description': 'API Test Service', 'amount': 49.99, 'quantity': 1}],
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created test invoice 2 (ID: {invoice2.id})")
|
||||
else:
|
||||
print(f"✓ Existing test invoice 2 (ID: {invoice2.id})")
|
||||
|
||||
# Create test payment for approval
|
||||
pending_payment, created = Payment.objects.get_or_create(
|
||||
account=account,
|
||||
invoice=invoice1,
|
||||
manual_reference='API_TEST_REF_001',
|
||||
defaults={
|
||||
'status': 'pending_approval',
|
||||
'payment_method': 'bank_transfer',
|
||||
'amount': Decimal('99.99'),
|
||||
'currency': 'USD',
|
||||
'manual_notes': 'API_TEST: Test payment for approval endpoint',
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created pending payment (ID: {pending_payment.id}) for approve_payment endpoint")
|
||||
else:
|
||||
print(f"✓ Existing pending payment (ID: {pending_payment.id})")
|
||||
|
||||
# Create test payment for rejection
|
||||
reject_payment, created = Payment.objects.get_or_create(
|
||||
account=account,
|
||||
invoice=invoice2,
|
||||
manual_reference='API_TEST_REF_002',
|
||||
defaults={
|
||||
'status': 'pending_approval',
|
||||
'payment_method': 'manual',
|
||||
'amount': Decimal('49.99'),
|
||||
'currency': 'USD',
|
||||
'manual_notes': 'API_TEST: Test payment for rejection endpoint',
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created pending payment (ID: {reject_payment.id}) for reject_payment endpoint")
|
||||
else:
|
||||
print(f"✓ Existing pending payment (ID: {reject_payment.id})")
|
||||
|
||||
# Get or create test payment method config
|
||||
configs = PaymentMethodConfig.objects.filter(payment_method='bank_transfer')
|
||||
if configs.exists():
|
||||
config = configs.first()
|
||||
print(f"✓ Using existing payment method config (ID: {config.id})")
|
||||
created = False
|
||||
else:
|
||||
config = PaymentMethodConfig.objects.create(
|
||||
payment_method='bank_transfer',
|
||||
display_name='API_TEST Bank Transfer',
|
||||
instructions='API_TEST: Transfer to account 123456789',
|
||||
is_enabled=True,
|
||||
sort_order=1,
|
||||
)
|
||||
print(f"✓ Created payment method config (ID: {config.id})")
|
||||
created = True
|
||||
|
||||
# Create test account payment method
|
||||
account_method, created = AccountPaymentMethod.objects.get_or_create(
|
||||
account=account,
|
||||
type='bank_transfer',
|
||||
defaults={
|
||||
'display_name': 'API_TEST Account Bank Transfer',
|
||||
'instructions': 'API_TEST: Test account-specific payment method',
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created account payment method (ID: {account_method.id})")
|
||||
else:
|
||||
print(f"✓ Existing account payment method (ID: {account_method.id})")
|
||||
|
||||
# Create test credit transaction
|
||||
transaction, created = CreditTransaction.objects.get_or_create(
|
||||
account=account,
|
||||
transaction_type='adjustment',
|
||||
amount=1000,
|
||||
defaults={
|
||||
'balance_after': account.credits,
|
||||
'description': 'API_TEST: Test credit adjustment',
|
||||
'metadata': {'test': True, 'reason': 'API testing'},
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f"✓ Created credit transaction (ID: {transaction.id})")
|
||||
else:
|
||||
print(f"✓ Existing credit transaction (ID: {transaction.id})")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("API Test Data Summary:")
|
||||
print("="*60)
|
||||
print(f"Account ID: {account.id}")
|
||||
print(f"Pending Payment (approve): ID {pending_payment.id}")
|
||||
print(f"Pending Payment (reject): ID {reject_payment.id}")
|
||||
print(f"Payment Method Config: ID {config.id}")
|
||||
print(f"Account Payment Method: ID {account_method.id}")
|
||||
print(f"Credit Transaction: ID {transaction.id}")
|
||||
print("="*60)
|
||||
print("\nTest endpoints:")
|
||||
print(f"POST /v1/admin/billing/{pending_payment.id}/approve_payment/")
|
||||
print(f"POST /v1/admin/billing/{reject_payment.id}/reject_payment/")
|
||||
print(f"POST /v1/admin/users/{account.id}/adjust-credits/")
|
||||
print(f"GET /v1/billing/payment-methods/{account_method.id}/set_default/")
|
||||
print("="*60)
|
||||
Binary file not shown.
@@ -1,116 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Automation System Deployment Script
|
||||
# Run this script to complete the automation system deployment
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "========================================="
|
||||
echo "IGNY8 Automation System Deployment"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if running from correct directory
|
||||
if [ ! -f "manage.py" ]; then
|
||||
echo -e "${RED}Error: Please run this script from the backend directory${NC}"
|
||||
echo "cd /data/app/igny8/backend && ./deploy_automation.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Step 1: Creating log directory...${NC}"
|
||||
mkdir -p logs/automation
|
||||
chmod 755 logs/automation
|
||||
echo -e "${GREEN}✓ Log directory created${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 2: Running database migrations...${NC}"
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
echo -e "${GREEN}✓ Migrations complete${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 3: Checking Celery services...${NC}"
|
||||
if docker ps | grep -q celery; then
|
||||
echo -e "${GREEN}✓ Celery worker is running${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠ Celery worker is NOT running${NC}"
|
||||
echo "Start with: docker-compose up -d celery"
|
||||
fi
|
||||
|
||||
if docker ps | grep -q beat; then
|
||||
echo -e "${GREEN}✓ Celery beat is running${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠ Celery beat is NOT running${NC}"
|
||||
echo "Start with: docker-compose up -d celery-beat"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 4: Verifying cache backend...${NC}"
|
||||
python3 -c "
|
||||
from django.core.cache import cache
|
||||
try:
|
||||
cache.set('test_key', 'test_value', 10)
|
||||
if cache.get('test_key') == 'test_value':
|
||||
print('${GREEN}✓ Cache backend working${NC}')
|
||||
else:
|
||||
print('${RED}⚠ Cache backend not working properly${NC}')
|
||||
except Exception as e:
|
||||
print('${RED}⚠ Cache backend error:', str(e), '${NC}')
|
||||
" || echo -e "${RED}⚠ Could not verify cache backend${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 5: Testing automation API...${NC}"
|
||||
python3 manage.py shell << EOF
|
||||
from igny8_core.business.automation.services import AutomationService
|
||||
from igny8_core.modules.system.models import Account, Site
|
||||
|
||||
try:
|
||||
account = Account.objects.first()
|
||||
site = Site.objects.first()
|
||||
if account and site:
|
||||
service = AutomationService(account, site)
|
||||
estimate = service.estimate_credits()
|
||||
print('${GREEN}✓ AutomationService working - Estimated credits:', estimate, '${NC}')
|
||||
else:
|
||||
print('${YELLOW}⚠ No account or site found - create one first${NC}')
|
||||
except Exception as e:
|
||||
print('${RED}⚠ AutomationService error:', str(e), '${NC}')
|
||||
EOF
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 6: Checking Celery beat schedule...${NC}"
|
||||
if docker ps | grep -q celery; then
|
||||
CELERY_CONTAINER=$(docker ps | grep celery | grep -v beat | awk '{print $1}')
|
||||
docker exec $CELERY_CONTAINER celery -A igny8_core inspect scheduled 2>/dev/null | grep -q "check-scheduled-automations" && \
|
||||
echo -e "${GREEN}✓ Automation task scheduled in Celery beat${NC}" || \
|
||||
echo -e "${YELLOW}⚠ Automation task not found in schedule (may need restart)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Celery worker not running - cannot check schedule${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "========================================="
|
||||
echo -e "${GREEN}Deployment Steps Completed!${NC}"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
echo "Next steps:"
|
||||
echo "1. Restart Celery services to pick up new tasks:"
|
||||
echo " docker-compose restart celery celery-beat"
|
||||
echo ""
|
||||
echo "2. Access the frontend at /automation page"
|
||||
echo ""
|
||||
echo "3. Test the automation:"
|
||||
echo " - Click [Configure] to set up schedule"
|
||||
echo " - Click [Run Now] to start automation"
|
||||
echo " - Monitor progress in real-time"
|
||||
echo ""
|
||||
echo "4. Check logs:"
|
||||
echo " tail -f logs/automation/{account_id}/{site_id}/{run_id}/automation_run.log"
|
||||
echo ""
|
||||
echo -e "${YELLOW}For troubleshooting, see: AUTOMATION-DEPLOYMENT-CHECKLIST.md${NC}"
|
||||
@@ -1,393 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Diagnostic script for generate_content function issues
|
||||
Tests each layer of the content generation pipeline to identify where it's failing
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
import logging
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.auth.models import Account
|
||||
from igny8_core.modules.writer.models import Tasks, Content
|
||||
from igny8_core.modules.system.models import IntegrationSettings
|
||||
from igny8_core.ai.registry import get_function_instance
|
||||
from igny8_core.ai.engine import AIEngine
|
||||
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def print_section(title):
|
||||
"""Print a section header"""
|
||||
print("\n" + "=" * 80)
|
||||
print(f" {title}")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
def test_prerequisites():
|
||||
"""Test that prerequisites are met"""
|
||||
print_section("1. TESTING PREREQUISITES")
|
||||
|
||||
# Check if account exists
|
||||
try:
|
||||
account = Account.objects.first()
|
||||
if not account:
|
||||
print("❌ FAIL: No account found in database")
|
||||
return None
|
||||
print(f"✅ PASS: Found account: {account.id} ({account.email})")
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error getting account: {e}")
|
||||
return None
|
||||
|
||||
# Check OpenAI integration settings
|
||||
try:
|
||||
openai_settings = IntegrationSettings.objects.filter(
|
||||
integration_type='openai',
|
||||
account=account,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
if not openai_settings:
|
||||
print("❌ FAIL: No active OpenAI integration settings found")
|
||||
return None
|
||||
|
||||
if not openai_settings.config or not openai_settings.config.get('apiKey'):
|
||||
print("❌ FAIL: OpenAI API key not configured in IntegrationSettings")
|
||||
return None
|
||||
|
||||
api_key_preview = openai_settings.config['apiKey'][:10] + "..." if openai_settings.config.get('apiKey') else "None"
|
||||
model = openai_settings.config.get('model', 'Not set')
|
||||
print(f"✅ PASS: OpenAI settings found (API key: {api_key_preview}, Model: {model})")
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error checking OpenAI settings: {e}")
|
||||
return None
|
||||
|
||||
# Check if tasks exist
|
||||
try:
|
||||
tasks = Tasks.objects.filter(account=account, status='pending')[:5]
|
||||
task_count = tasks.count()
|
||||
|
||||
if task_count == 0:
|
||||
print("⚠️ WARNING: No pending tasks found, will try to use any task")
|
||||
tasks = Tasks.objects.filter(account=account)[:5]
|
||||
task_count = tasks.count()
|
||||
|
||||
if task_count == 0:
|
||||
print("❌ FAIL: No tasks found at all")
|
||||
return None
|
||||
|
||||
print(f"✅ PASS: Found {task_count} task(s)")
|
||||
for task in tasks:
|
||||
print(f" - Task {task.id}: {task.title or 'Untitled'} (status: {task.status})")
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error getting tasks: {e}")
|
||||
return None
|
||||
|
||||
return {
|
||||
'account': account,
|
||||
'tasks': list(tasks),
|
||||
'openai_settings': openai_settings
|
||||
}
|
||||
|
||||
def test_function_registry():
|
||||
"""Test that the generate_content function is registered"""
|
||||
print_section("2. TESTING FUNCTION REGISTRY")
|
||||
|
||||
try:
|
||||
fn = get_function_instance('generate_content')
|
||||
if not fn:
|
||||
print("❌ FAIL: generate_content function not found in registry")
|
||||
return False
|
||||
|
||||
print(f"✅ PASS: Function registered: {fn.get_name()}")
|
||||
metadata = fn.get_metadata()
|
||||
print(f" - Display name: {metadata.get('display_name')}")
|
||||
print(f" - Description: {metadata.get('description')}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error loading function: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_function_validation(context):
|
||||
"""Test function validation"""
|
||||
print_section("3. TESTING FUNCTION VALIDATION")
|
||||
|
||||
try:
|
||||
fn = get_function_instance('generate_content')
|
||||
account = context['account']
|
||||
task = context['tasks'][0]
|
||||
|
||||
payload = {'ids': [task.id]}
|
||||
print(f"Testing with payload: {payload}")
|
||||
|
||||
result = fn.validate(payload, account)
|
||||
|
||||
if result['valid']:
|
||||
print(f"✅ PASS: Validation succeeded")
|
||||
else:
|
||||
print(f"❌ FAIL: Validation failed: {result.get('error')}")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error during validation: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_function_prepare(context):
|
||||
"""Test function prepare phase"""
|
||||
print_section("4. TESTING FUNCTION PREPARE")
|
||||
|
||||
try:
|
||||
fn = get_function_instance('generate_content')
|
||||
account = context['account']
|
||||
task = context['tasks'][0]
|
||||
|
||||
payload = {'ids': [task.id]}
|
||||
print(f"Preparing task {task.id}: {task.title or 'Untitled'}")
|
||||
|
||||
data = fn.prepare(payload, account)
|
||||
|
||||
if not data:
|
||||
print("❌ FAIL: Prepare returned no data")
|
||||
return False
|
||||
|
||||
if isinstance(data, list):
|
||||
print(f"✅ PASS: Prepared {len(data)} task(s)")
|
||||
for t in data:
|
||||
print(f" - Task {t.id}: {t.title or 'Untitled'}")
|
||||
print(f" Cluster: {t.cluster.name if t.cluster else 'None'}")
|
||||
print(f" Taxonomy: {t.taxonomy_term.name if t.taxonomy_term else 'None'}")
|
||||
print(f" Keywords: {t.keywords.count()} keyword(s)")
|
||||
else:
|
||||
print(f"✅ PASS: Prepared data: {type(data)}")
|
||||
|
||||
context['prepared_data'] = data
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error during prepare: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_function_build_prompt(context):
|
||||
"""Test prompt building"""
|
||||
print_section("5. TESTING PROMPT BUILDING")
|
||||
|
||||
try:
|
||||
fn = get_function_instance('generate_content')
|
||||
account = context['account']
|
||||
data = context['prepared_data']
|
||||
|
||||
prompt = fn.build_prompt(data, account)
|
||||
|
||||
if not prompt:
|
||||
print("❌ FAIL: No prompt generated")
|
||||
return False
|
||||
|
||||
print(f"✅ PASS: Prompt generated ({len(prompt)} characters)")
|
||||
print("\nPrompt preview (first 500 chars):")
|
||||
print("-" * 80)
|
||||
print(prompt[:500])
|
||||
if len(prompt) > 500:
|
||||
print(f"\n... ({len(prompt) - 500} more characters)")
|
||||
print("-" * 80)
|
||||
|
||||
context['prompt'] = prompt
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error building prompt: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_model_config(context):
|
||||
"""Test model configuration"""
|
||||
print_section("6. TESTING MODEL CONFIGURATION")
|
||||
|
||||
try:
|
||||
from igny8_core.ai.settings import get_model_config
|
||||
account = context['account']
|
||||
|
||||
model_config = get_model_config('generate_content', account=account)
|
||||
|
||||
if not model_config:
|
||||
print("❌ FAIL: No model config returned")
|
||||
return False
|
||||
|
||||
print(f"✅ PASS: Model configuration loaded")
|
||||
print(f" - Model: {model_config.get('model')}")
|
||||
print(f" - Max tokens: {model_config.get('max_tokens')}")
|
||||
print(f" - Temperature: {model_config.get('temperature')}")
|
||||
print(f" - Response format: {model_config.get('response_format')}")
|
||||
|
||||
context['model_config'] = model_config
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error getting model config: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_ai_core_request(context):
|
||||
"""Test AI core request (actual API call)"""
|
||||
print_section("7. TESTING AI CORE REQUEST (ACTUAL API CALL)")
|
||||
|
||||
# Ask user for confirmation
|
||||
print("⚠️ WARNING: This will make an actual API call to OpenAI and cost money!")
|
||||
print("Do you want to proceed? (yes/no): ", end='')
|
||||
response = input().strip().lower()
|
||||
|
||||
if response != 'yes':
|
||||
print("Skipping API call test")
|
||||
return True
|
||||
|
||||
try:
|
||||
from igny8_core.ai.ai_core import AICore
|
||||
account = context['account']
|
||||
prompt = context['prompt']
|
||||
model_config = context['model_config']
|
||||
|
||||
# Use a shorter test prompt to save costs
|
||||
test_prompt = prompt[:1000] + "\n\n[TEST MODE - Generate only title and first paragraph]"
|
||||
|
||||
print(f"Making test API call with shortened prompt ({len(test_prompt)} chars)...")
|
||||
|
||||
ai_core = AICore(account=account)
|
||||
result = ai_core.run_ai_request(
|
||||
prompt=test_prompt,
|
||||
model=model_config['model'],
|
||||
max_tokens=500, # Limit tokens for testing
|
||||
temperature=model_config.get('temperature', 0.7),
|
||||
response_format=model_config.get('response_format'),
|
||||
function_name='generate_content_test'
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
print(f"❌ FAIL: API call returned error: {result['error']}")
|
||||
return False
|
||||
|
||||
if not result.get('content'):
|
||||
print(f"❌ FAIL: API call returned no content")
|
||||
return False
|
||||
|
||||
print(f"✅ PASS: API call successful")
|
||||
print(f" - Tokens: {result.get('total_tokens', 0)}")
|
||||
print(f" - Cost: ${result.get('cost', 0):.6f}")
|
||||
print(f" - Model: {result.get('model')}")
|
||||
print(f"\nContent preview (first 300 chars):")
|
||||
print("-" * 80)
|
||||
print(result['content'][:300])
|
||||
print("-" * 80)
|
||||
|
||||
context['ai_response'] = result
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error during API call: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_service_layer(context):
|
||||
"""Test the content generation service"""
|
||||
print_section("8. TESTING CONTENT GENERATION SERVICE")
|
||||
|
||||
print("⚠️ WARNING: This will make a full API call and create content!")
|
||||
print("Do you want to proceed? (yes/no): ", end='')
|
||||
response = input().strip().lower()
|
||||
|
||||
if response != 'yes':
|
||||
print("Skipping service test")
|
||||
return True
|
||||
|
||||
try:
|
||||
account = context['account']
|
||||
task = context['tasks'][0]
|
||||
|
||||
service = ContentGenerationService()
|
||||
|
||||
print(f"Calling generate_content with task {task.id}...")
|
||||
|
||||
result = service.generate_content([task.id], account)
|
||||
|
||||
if not result:
|
||||
print("❌ FAIL: Service returned None")
|
||||
return False
|
||||
|
||||
if not result.get('success'):
|
||||
print(f"❌ FAIL: Service failed: {result.get('error')}")
|
||||
return False
|
||||
|
||||
print(f"✅ PASS: Service call successful")
|
||||
|
||||
if 'task_id' in result:
|
||||
print(f" - Celery task ID: {result['task_id']}")
|
||||
print(f" - Message: {result.get('message')}")
|
||||
print("\n⚠️ Note: Content generation is running in background (Celery)")
|
||||
print(" Check Celery logs for actual execution status")
|
||||
else:
|
||||
print(f" - Content created: {result.get('content_id')}")
|
||||
print(f" - Word count: {result.get('word_count')}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL: Error in service layer: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Run all diagnostic tests"""
|
||||
print("\n" + "=" * 80)
|
||||
print(" GENERATE_CONTENT DIAGNOSTIC TOOL")
|
||||
print("=" * 80)
|
||||
print("\nThis tool will test each layer of the content generation pipeline")
|
||||
print("to identify where the function is failing.")
|
||||
|
||||
# Run tests
|
||||
context = test_prerequisites()
|
||||
if not context:
|
||||
print("\n❌ FATAL: Prerequisites test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
if not test_function_registry():
|
||||
print("\n❌ FATAL: Function registry test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
if not test_function_validation(context):
|
||||
print("\n❌ FATAL: Validation test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
if not test_function_prepare(context):
|
||||
print("\n❌ FATAL: Prepare test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
if not test_function_build_prompt(context):
|
||||
print("\n❌ FATAL: Prompt building test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
if not test_model_config(context):
|
||||
print("\n❌ FATAL: Model config test failed. Cannot continue.")
|
||||
return
|
||||
|
||||
# Optional tests (require API calls)
|
||||
test_ai_core_request(context)
|
||||
test_service_layer(context)
|
||||
|
||||
print_section("DIAGNOSTIC COMPLETE")
|
||||
print("Review the results above to identify where the generate_content")
|
||||
print("function is failing.\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Final verification that the WordPress content types are properly synced
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
import json
|
||||
|
||||
print("=" * 70)
|
||||
print("WORDPRESS SYNC FIX VERIFICATION")
|
||||
print("=" * 70)
|
||||
|
||||
# Get site 5
|
||||
site = Site.objects.get(id=5)
|
||||
print(f"\n✓ Site: {site.name} (ID: {site.id})")
|
||||
|
||||
# Get WordPress integration
|
||||
integration = SiteIntegration.objects.get(site=site, platform='wordpress')
|
||||
print(f"✓ Integration: {integration.platform.upper()} (ID: {integration.id})")
|
||||
print(f"✓ Active: {integration.is_active}")
|
||||
print(f"✓ Sync Enabled: {integration.sync_enabled}")
|
||||
|
||||
# Verify config data
|
||||
config = integration.config_json or {}
|
||||
content_types = config.get('content_types', {})
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("CONTENT TYPES STRUCTURE")
|
||||
print("=" * 70)
|
||||
|
||||
# Post Types
|
||||
post_types = content_types.get('post_types', {})
|
||||
print(f"\n📝 Post Types: ({len(post_types)} total)")
|
||||
for pt_name, pt_data in post_types.items():
|
||||
print(f" • {pt_data['label']} ({pt_name})")
|
||||
print(f" - Count: {pt_data['count']}")
|
||||
print(f" - Enabled: {pt_data['enabled']}")
|
||||
print(f" - Fetch Limit: {pt_data['fetch_limit']}")
|
||||
|
||||
# Taxonomies
|
||||
taxonomies = content_types.get('taxonomies', {})
|
||||
print(f"\n🏷️ Taxonomies: ({len(taxonomies)} total)")
|
||||
for tax_name, tax_data in taxonomies.items():
|
||||
print(f" • {tax_data['label']} ({tax_name})")
|
||||
print(f" - Count: {tax_data['count']}")
|
||||
print(f" - Enabled: {tax_data['enabled']}")
|
||||
print(f" - Fetch Limit: {tax_data['fetch_limit']}")
|
||||
|
||||
# Last fetch time
|
||||
last_fetch = content_types.get('last_structure_fetch')
|
||||
print(f"\n🕐 Last Structure Fetch: {last_fetch}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ SUCCESS! WordPress content types are properly configured")
|
||||
print("=" * 70)
|
||||
print("\nNext Steps:")
|
||||
print("1. Refresh the IGNY8 app page in your browser")
|
||||
print("2. Navigate to Sites → Settings → Content Types tab")
|
||||
print("3. You should now see all Post Types and Taxonomies listed")
|
||||
print("=" * 70)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Fix remaining cluster with old status"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.planning.models import Clusters
|
||||
|
||||
cluster = Clusters.objects.filter(status='active').first()
|
||||
if cluster:
|
||||
print(f"Found cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}")
|
||||
print(f"Ideas count: {cluster.ideas.count()}")
|
||||
if cluster.ideas.exists():
|
||||
cluster.status = 'mapped'
|
||||
else:
|
||||
cluster.status = 'new'
|
||||
cluster.save()
|
||||
print(f"Updated to: {cluster.status}")
|
||||
else:
|
||||
print("No clusters with 'active' status found")
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
from django.utils import timezone
|
||||
|
||||
try:
|
||||
# Get site 5
|
||||
site = Site.objects.get(id=5)
|
||||
print(f"✓ Site found: {site.name}")
|
||||
|
||||
# Get or create WordPress integration
|
||||
integration, created = SiteIntegration.objects.get_or_create(
|
||||
site=site,
|
||||
platform='wordpress',
|
||||
defaults={
|
||||
'is_active': True,
|
||||
'sync_enabled': True,
|
||||
'config_json': {}
|
||||
}
|
||||
)
|
||||
|
||||
print(f"✓ Integration ID: {integration.id} (created: {created})")
|
||||
|
||||
# Add structure data
|
||||
integration.config_json = {
|
||||
'content_types': {
|
||||
'post_types': {
|
||||
'post': {
|
||||
'label': 'Posts',
|
||||
'count': 150,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
},
|
||||
'page': {
|
||||
'label': 'Pages',
|
||||
'count': 25,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
},
|
||||
'product': {
|
||||
'label': 'Products',
|
||||
'count': 89,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
}
|
||||
},
|
||||
'taxonomies': {
|
||||
'category': {
|
||||
'label': 'Categories',
|
||||
'count': 15,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
},
|
||||
'post_tag': {
|
||||
'label': 'Tags',
|
||||
'count': 234,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
},
|
||||
'product_cat': {
|
||||
'label': 'Product Categories',
|
||||
'count': 12,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100
|
||||
}
|
||||
},
|
||||
'last_structure_fetch': timezone.now().isoformat()
|
||||
},
|
||||
'plugin_connection_enabled': True,
|
||||
'two_way_sync_enabled': True
|
||||
}
|
||||
|
||||
integration.save()
|
||||
print("✓ Structure data saved successfully!")
|
||||
print(f"✓ Integration ID: {integration.id}")
|
||||
print("\n✅ READY: Refresh the page to see the content types!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ ERROR: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Fix missing site_url in integration config
|
||||
Adds site_url to config_json from site.domain or site.wp_url
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django environment
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
|
||||
def fix_integration_site_urls():
|
||||
"""Add site_url to integration config if missing"""
|
||||
|
||||
integrations = SiteIntegration.objects.filter(platform='wordpress')
|
||||
|
||||
fixed_count = 0
|
||||
skipped_count = 0
|
||||
error_count = 0
|
||||
|
||||
for integration in integrations:
|
||||
try:
|
||||
config = integration.config_json or {}
|
||||
|
||||
# Check if site_url is already set
|
||||
if config.get('site_url'):
|
||||
print(f"✓ Integration {integration.id} already has site_url: {config.get('site_url')}")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Try to get site URL from multiple sources
|
||||
site_url = None
|
||||
|
||||
# First, try legacy wp_url
|
||||
if integration.site.wp_url:
|
||||
site_url = integration.site.wp_url
|
||||
print(f"→ Using legacy wp_url for integration {integration.id}: {site_url}")
|
||||
|
||||
# Fallback to domain
|
||||
elif integration.site.domain:
|
||||
site_url = integration.site.domain
|
||||
print(f"→ Using domain for integration {integration.id}: {site_url}")
|
||||
|
||||
if site_url:
|
||||
# Update config
|
||||
config['site_url'] = site_url
|
||||
integration.config_json = config
|
||||
integration.save(update_fields=['config_json'])
|
||||
print(f"✓ Updated integration {integration.id} with site_url: {site_url}")
|
||||
fixed_count += 1
|
||||
else:
|
||||
print(f"✗ Integration {integration.id} has no site URL available (site: {integration.site.name}, id: {integration.site.id})")
|
||||
error_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error fixing integration {integration.id}: {e}")
|
||||
error_count += 1
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(f"Summary:")
|
||||
print(f" Fixed: {fixed_count}")
|
||||
print(f" Skipped (already set): {skipped_count}")
|
||||
print(f" Errors: {error_count}")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Fixing WordPress integration site URLs...")
|
||||
print("="*60)
|
||||
fix_integration_site_urls()
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Script to inject WordPress structure data into the backend"""
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
from django.utils import timezone
|
||||
|
||||
# Get site 5
|
||||
try:
|
||||
site = Site.objects.get(id=5)
|
||||
print(f"✓ Found site: {site.name}")
|
||||
except Site.DoesNotExist:
|
||||
print("✗ Site with ID 5 not found!")
|
||||
exit(1)
|
||||
|
||||
# Get or create WordPress integration for this site
|
||||
integration, created = SiteIntegration.objects.get_or_create(
|
||||
site=site,
|
||||
platform='wordpress',
|
||||
defaults={
|
||||
'is_active': True,
|
||||
'sync_enabled': True,
|
||||
'config_json': {}
|
||||
}
|
||||
)
|
||||
|
||||
print(f"✓ Integration ID: {integration.id} (newly created: {created})")
|
||||
|
||||
# Add structure data
|
||||
integration.config_json = {
|
||||
'content_types': {
|
||||
'post_types': {
|
||||
'post': {
|
||||
'label': 'Posts',
|
||||
'count': 150,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'page': {
|
||||
'label': 'Pages',
|
||||
'count': 25,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'product': {
|
||||
'label': 'Products',
|
||||
'count': 89,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
}
|
||||
},
|
||||
'taxonomies': {
|
||||
'category': {
|
||||
'label': 'Categories',
|
||||
'count': 15,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'post_tag': {
|
||||
'label': 'Tags',
|
||||
'count': 234,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'product_cat': {
|
||||
'label': 'Product Categories',
|
||||
'count': 12,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
}
|
||||
},
|
||||
'last_structure_fetch': timezone.now().isoformat()
|
||||
},
|
||||
'plugin_connection_enabled': True,
|
||||
'two_way_sync_enabled': True
|
||||
}
|
||||
|
||||
integration.save()
|
||||
print("✓ Structure data saved!")
|
||||
print(f"✓ Post Types: {len(integration.config_json['content_types']['post_types'])}")
|
||||
print(f"✓ Taxonomies: {len(integration.config_json['content_types']['taxonomies'])}")
|
||||
print(f"✓ Last fetch: {integration.config_json['content_types']['last_structure_fetch']}")
|
||||
print("\n🎉 SUCCESS! Now refresh: https://app.igny8.com/sites/5/settings?tab=content-types")
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Fix missing taxonomy relationships for existing content
|
||||
This script will:
|
||||
1. Find content that should have tags/categories based on their keywords
|
||||
2. Create appropriate taxonomy terms
|
||||
3. Link them to the content
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.text import slugify
|
||||
from igny8_core.business.content.models import Content, ContentTaxonomy
|
||||
|
||||
print("=" * 80)
|
||||
print("FIXING MISSING TAXONOMY RELATIONSHIPS")
|
||||
print("=" * 80)
|
||||
|
||||
# Get all content without taxonomy terms
|
||||
content_without_tags = Content.objects.filter(taxonomy_terms__isnull=True).distinct()
|
||||
print(f"\nFound {content_without_tags.count()} content items without tags/categories")
|
||||
|
||||
fixed_count = 0
|
||||
for content in content_without_tags:
|
||||
print(f"\nProcessing Content #{content.id}: {content.title[:50]}...")
|
||||
|
||||
# Generate tags from keywords
|
||||
tags_to_add = []
|
||||
categories_to_add = []
|
||||
|
||||
# Use primary keyword as a tag
|
||||
if content.primary_keyword:
|
||||
tags_to_add.append(content.primary_keyword)
|
||||
|
||||
# Use secondary keywords as tags
|
||||
if content.secondary_keywords and isinstance(content.secondary_keywords, list):
|
||||
tags_to_add.extend(content.secondary_keywords[:3]) # Limit to 3
|
||||
|
||||
# Create category based on cluster only
|
||||
if content.cluster:
|
||||
categories_to_add.append(content.cluster.name)
|
||||
|
||||
with transaction.atomic():
|
||||
# Process tags
|
||||
for tag_name in tags_to_add:
|
||||
if tag_name and isinstance(tag_name, str):
|
||||
tag_name = tag_name.strip()
|
||||
if tag_name:
|
||||
try:
|
||||
tag_obj, created = ContentTaxonomy.objects.get_or_create(
|
||||
site=content.site,
|
||||
name=tag_name,
|
||||
taxonomy_type='tag',
|
||||
defaults={
|
||||
'slug': slugify(tag_name),
|
||||
'sector': content.sector,
|
||||
'account': content.account,
|
||||
'description': '',
|
||||
'external_taxonomy': '',
|
||||
'sync_status': '',
|
||||
'count': 0,
|
||||
'metadata': {},
|
||||
}
|
||||
)
|
||||
content.taxonomy_terms.add(tag_obj)
|
||||
print(f" + Tag: {tag_name} ({'created' if created else 'existing'})")
|
||||
except Exception as e:
|
||||
print(f" ✗ Failed to add tag '{tag_name}': {e}")
|
||||
|
||||
# Process categories
|
||||
for category_name in categories_to_add:
|
||||
if category_name and isinstance(category_name, str):
|
||||
category_name = category_name.strip()
|
||||
if category_name:
|
||||
try:
|
||||
category_obj, created = ContentTaxonomy.objects.get_or_create(
|
||||
site=content.site,
|
||||
name=category_name,
|
||||
taxonomy_type='category',
|
||||
defaults={
|
||||
'slug': slugify(category_name),
|
||||
'sector': content.sector,
|
||||
'account': content.account,
|
||||
'description': '',
|
||||
'external_taxonomy': '',
|
||||
'sync_status': '',
|
||||
'count': 0,
|
||||
'metadata': {},
|
||||
}
|
||||
)
|
||||
content.taxonomy_terms.add(category_obj)
|
||||
print(f" + Category: {category_name} ({'created' if created else 'existing'})")
|
||||
except Exception as e:
|
||||
print(f" ✗ Failed to add category '{category_name}': {e}")
|
||||
|
||||
fixed_count += 1
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print(f"FIXED {fixed_count} CONTENT ITEMS")
|
||||
print("=" * 80)
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Force cancel stuck automation runs and clear cache locks"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.automation.models import AutomationRun
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
|
||||
print("=" * 80)
|
||||
print("AUTOMATION RUN FORCE CANCEL & CLEANUP")
|
||||
print("=" * 80)
|
||||
|
||||
# Check and cancel active runs
|
||||
runs = AutomationRun.objects.filter(status__in=['running', 'paused']).order_by('-started_at')
|
||||
print(f"\nFound {runs.count()} active run(s)")
|
||||
|
||||
if runs.count() == 0:
|
||||
print(" No runs to cancel\n")
|
||||
else:
|
||||
for r in runs:
|
||||
duration = (timezone.now() - r.started_at).total_seconds() / 60
|
||||
print(f"\nRun ID: {r.run_id}")
|
||||
print(f" Site: {r.site_id}")
|
||||
print(f" Status: {r.status}")
|
||||
print(f" Stage: {r.current_stage}")
|
||||
print(f" Started: {r.started_at} ({duration:.1f}m ago)")
|
||||
print(f" Credits: {r.total_credits_used}")
|
||||
|
||||
# Force cancel
|
||||
print(f" >>> FORCE CANCELLING...")
|
||||
r.status = 'cancelled'
|
||||
r.save()
|
||||
print(f" >>> Status: {r.status}")
|
||||
|
||||
# Clear cache lock
|
||||
lock_key = f'automation_lock_{r.site_id}'
|
||||
cache.delete(lock_key)
|
||||
print(f" >>> Lock cleared: {lock_key}")
|
||||
|
||||
print("\n" + "=" * 40)
|
||||
print("Cache lock status:")
|
||||
for site_id in [5, 16]:
|
||||
lock_key = f'automation_lock_{site_id}'
|
||||
lock_val = cache.get(lock_key)
|
||||
status = lock_val or 'UNLOCKED ✓'
|
||||
print(f" Site {site_id}: {status}")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("✓ CLEANUP COMPLETE - You can now start a new automation run")
|
||||
print("=" * 80)
|
||||
@@ -99,8 +99,9 @@ class SiteSerializer(serializers.ModelSerializer):
|
||||
- If domain has no protocol, add https://
|
||||
- Validates that the final URL is valid
|
||||
"""
|
||||
if not value:
|
||||
return value
|
||||
# Allow empty/None values
|
||||
if not value or value.strip() == '':
|
||||
return None
|
||||
|
||||
value = value.strip()
|
||||
|
||||
|
||||
@@ -496,8 +496,9 @@ class SiteViewSet(AccountModelViewSet):
|
||||
from rest_framework.permissions import AllowAny
|
||||
return [AllowAny()]
|
||||
if self.action == 'create':
|
||||
# For create, only require authentication - not active account status
|
||||
return [permissions.IsAuthenticated()]
|
||||
return [IsEditorOrAbove()]
|
||||
return [IsAuthenticatedAndActive(), HasTenantAccess(), IsEditorOrAbove()]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return sites accessible to the current user."""
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
"""
|
||||
Django Management Command to Manually Add WordPress Structure Data
|
||||
Run this in Django shell or as a management command
|
||||
"""
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.auth.models import Site
|
||||
from django.utils import timezone
|
||||
|
||||
# Get site 5
|
||||
site = Site.objects.get(id=5)
|
||||
print(f"Site: {site.name}")
|
||||
|
||||
# Get or create WordPress integration for this site
|
||||
integration, created = SiteIntegration.objects.get_or_create(
|
||||
site=site,
|
||||
platform='wordpress',
|
||||
defaults={
|
||||
'is_active': True,
|
||||
'sync_enabled': True,
|
||||
'config_json': {}
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Integration: {integration.id} (created: {created})")
|
||||
|
||||
# Add structure data
|
||||
integration.config_json = {
|
||||
'content_types': {
|
||||
'post_types': {
|
||||
'post': {
|
||||
'label': 'Posts',
|
||||
'count': 150,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'page': {
|
||||
'label': 'Pages',
|
||||
'count': 25,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'product': {
|
||||
'label': 'Products',
|
||||
'count': 89,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
}
|
||||
},
|
||||
'taxonomies': {
|
||||
'category': {
|
||||
'label': 'Categories',
|
||||
'count': 15,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'post_tag': {
|
||||
'label': 'Tags',
|
||||
'count': 234,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
},
|
||||
'product_cat': {
|
||||
'label': 'Product Categories',
|
||||
'count': 12,
|
||||
'enabled': True,
|
||||
'fetch_limit': 100,
|
||||
'synced_count': 0
|
||||
}
|
||||
},
|
||||
'last_structure_fetch': timezone.now().isoformat()
|
||||
},
|
||||
'plugin_connection_enabled': True,
|
||||
'two_way_sync_enabled': True
|
||||
}
|
||||
|
||||
integration.save()
|
||||
print("✓ Structure data saved!")
|
||||
print(f"Integration ID: {integration.id}")
|
||||
print(f"Content Types: {len(integration.config_json['content_types']['post_types'])} post types, {len(integration.config_json['content_types']['taxonomies'])} taxonomies")
|
||||
print("\nNow refresh: https://app.igny8.com/sites/5/settings?tab=content-types")
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
-- COMPREHENSIVE FIELD RENAME MIGRATION
|
||||
-- Renames all entity_type, cluster_role, site_entity_type columns to content_type and content_structure
|
||||
-- Date: 2025-11-26
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. ContentIdeas table (igny8_content_ideas)
|
||||
ALTER TABLE igny8_content_ideas RENAME COLUMN site_entity_type TO content_type;
|
||||
ALTER TABLE igny8_content_ideas RENAME COLUMN cluster_role TO content_structure;
|
||||
|
||||
-- Update index names for ContentIdeas
|
||||
DROP INDEX IF EXISTS igny8_content_ideas_site_entity_type_idx;
|
||||
DROP INDEX IF EXISTS igny8_content_ideas_cluster_role_idx;
|
||||
CREATE INDEX igny8_content_ideas_content_type_idx ON igny8_content_ideas(content_type);
|
||||
CREATE INDEX igny8_content_ideas_content_structure_idx ON igny8_content_ideas(content_structure);
|
||||
|
||||
-- 2. Tasks table (igny8_tasks)
|
||||
ALTER TABLE igny8_tasks RENAME COLUMN entity_type TO content_type;
|
||||
-- cluster_role already mapped via db_column, but let's check if column exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_tasks' AND column_name = 'cluster_role') THEN
|
||||
ALTER TABLE igny8_tasks RENAME COLUMN cluster_role TO content_structure;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 3. Content table (igny8_content)
|
||||
ALTER TABLE igny8_content RENAME COLUMN entity_type TO content_type;
|
||||
-- cluster_role already mapped via db_column, but let's check if column exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content' AND column_name = 'cluster_role') THEN
|
||||
ALTER TABLE igny8_content RENAME COLUMN cluster_role TO content_structure;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4. ContentTaxonomy table (igny8_content_taxonomy)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content_taxonomy' AND column_name = 'entity_type') THEN
|
||||
ALTER TABLE igny8_content_taxonomy RENAME COLUMN entity_type TO content_type;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 5. AITaskExecution table (igny8_ai_task_execution)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_ai_task_execution' AND column_name = 'entity_type') THEN
|
||||
ALTER TABLE igny8_ai_task_execution RENAME COLUMN entity_type TO content_type;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Sync idea status from completed tasks
|
||||
One-time script to fix existing data
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.content.models import Tasks
|
||||
from igny8_core.business.planning.models import ContentIdeas
|
||||
|
||||
# Find all completed tasks with ideas
|
||||
completed_tasks = Tasks.objects.filter(status='completed', idea__isnull=False)
|
||||
|
||||
synced = 0
|
||||
for task in completed_tasks:
|
||||
if task.idea and task.idea.status != 'completed':
|
||||
task.idea.status = 'completed'
|
||||
task.idea.save(update_fields=['status', 'updated_at'])
|
||||
synced += 1
|
||||
print(f"Synced idea {task.idea.id} to completed (from task {task.id})")
|
||||
|
||||
print(f"\nTotal synced: {synced} ideas to completed status")
|
||||
@@ -1,444 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
End-to-End Payment Workflow Test Script
|
||||
Tests the complete manual payment approval flow
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from decimal import Decimal
|
||||
from datetime import timedelta
|
||||
|
||||
from igny8_core.auth.models import Account, Subscription, Plan
|
||||
from igny8_core.business.billing.models import (
|
||||
Invoice, Payment, AccountPaymentMethod, CreditTransaction
|
||||
)
|
||||
from igny8_core.business.billing.services.invoice_service import InvoiceService
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class Colors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKCYAN = '\033[96m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
|
||||
def print_header(text):
|
||||
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}")
|
||||
print(f"{Colors.HEADER}{Colors.BOLD}{text:^60}{Colors.ENDC}")
|
||||
print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n")
|
||||
|
||||
def print_success(text):
|
||||
print(f"{Colors.OKGREEN}✓ {text}{Colors.ENDC}")
|
||||
|
||||
def print_error(text):
|
||||
print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}")
|
||||
|
||||
def print_info(text):
|
||||
print(f"{Colors.OKCYAN}→ {text}{Colors.ENDC}")
|
||||
|
||||
def cleanup_test_data():
|
||||
"""Remove test data from previous runs"""
|
||||
print_header("CLEANUP TEST DATA")
|
||||
|
||||
# Delete test accounts
|
||||
test_emails = [
|
||||
'workflow_test_free@example.com',
|
||||
'workflow_test_paid@example.com'
|
||||
]
|
||||
|
||||
for email in test_emails:
|
||||
try:
|
||||
user = User.objects.filter(email=email).first()
|
||||
if user:
|
||||
# Delete associated account (cascade will handle related objects)
|
||||
account = Account.objects.filter(owner=user).first()
|
||||
if account:
|
||||
account.delete()
|
||||
print_success(f"Deleted account for {email}")
|
||||
user.delete()
|
||||
print_success(f"Deleted user {email}")
|
||||
except Exception as e:
|
||||
print_error(f"Error cleaning up {email}: {e}")
|
||||
|
||||
def test_free_trial_signup():
|
||||
"""Test free trial user registration"""
|
||||
print_header("TEST 1: FREE TRIAL SIGNUP")
|
||||
|
||||
try:
|
||||
# Get free plan
|
||||
free_plan = Plan.objects.get(slug='free')
|
||||
print_info(f"Free Plan: {free_plan.name} - {free_plan.included_credits} credits")
|
||||
|
||||
# Create user
|
||||
with transaction.atomic():
|
||||
user = User.objects.create_user(
|
||||
username='workflow_test_free',
|
||||
email='workflow_test_free@example.com',
|
||||
password='TestPass123!',
|
||||
first_name='Free',
|
||||
last_name='Trial'
|
||||
)
|
||||
print_success(f"Created user: {user.email}")
|
||||
|
||||
# Create account
|
||||
account = Account.objects.create(
|
||||
name=f"{user.first_name}'s Account",
|
||||
slug=f'free-trial-{timezone.now().timestamp()}',
|
||||
owner=user,
|
||||
plan=free_plan,
|
||||
status='trial',
|
||||
credits=free_plan.included_credits
|
||||
)
|
||||
print_success(f"Created account: {account.name} (ID: {account.id})")
|
||||
|
||||
# Create credit transaction
|
||||
CreditTransaction.objects.create(
|
||||
account=account,
|
||||
transaction_type='plan_allocation',
|
||||
amount=free_plan.included_credits,
|
||||
balance_after=account.credits,
|
||||
description=f'Initial credits from {free_plan.name} plan'
|
||||
)
|
||||
print_success(f"Allocated {free_plan.included_credits} credits")
|
||||
|
||||
# Verify
|
||||
account.refresh_from_db()
|
||||
assert account.status == 'trial', "Status should be 'trial'"
|
||||
assert account.credits == 1000, "Credits should be 1000"
|
||||
assert account.plan.slug == 'free', "Plan should be 'free'"
|
||||
|
||||
# Check no subscription or invoice created
|
||||
sub_count = Subscription.objects.filter(account=account).count()
|
||||
invoice_count = Invoice.objects.filter(account=account).count()
|
||||
|
||||
assert sub_count == 0, "Free trial should not have subscription"
|
||||
assert invoice_count == 0, "Free trial should not have invoice"
|
||||
|
||||
print_success("No subscription created (correct for free trial)")
|
||||
print_success("No invoice created (correct for free trial)")
|
||||
|
||||
print_success("\nFREE TRIAL TEST PASSED ✓")
|
||||
return account
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Free trial test failed: {e}")
|
||||
raise
|
||||
|
||||
def test_paid_signup():
|
||||
"""Test paid user registration with manual payment"""
|
||||
print_header("TEST 2: PAID SIGNUP WORKFLOW")
|
||||
|
||||
try:
|
||||
# Get starter plan
|
||||
starter_plan = Plan.objects.get(slug='starter')
|
||||
print_info(f"Starter Plan: {starter_plan.name} - ${starter_plan.price} - {starter_plan.included_credits} credits")
|
||||
|
||||
# Step 1: Create user with billing info
|
||||
print_info("\nStep 1: User Registration")
|
||||
with transaction.atomic():
|
||||
user = User.objects.create_user(
|
||||
username='workflow_test_paid',
|
||||
email='workflow_test_paid@example.com',
|
||||
password='TestPass123!',
|
||||
first_name='Paid',
|
||||
last_name='User'
|
||||
)
|
||||
print_success(f"Created user: {user.email}")
|
||||
|
||||
# Create account with billing info
|
||||
account = Account.objects.create(
|
||||
name=f"{user.first_name}'s Account",
|
||||
slug=f'paid-user-{timezone.now().timestamp()}',
|
||||
owner=user,
|
||||
plan=starter_plan,
|
||||
status='pending_payment',
|
||||
credits=0, # No credits until payment approved
|
||||
billing_email='billing@example.com',
|
||||
billing_address_line1='123 Main Street',
|
||||
billing_city='Karachi',
|
||||
billing_country='PK'
|
||||
)
|
||||
print_success(f"Created account: {account.name} (ID: {account.id})")
|
||||
print_info(f" Status: {account.status}")
|
||||
print_info(f" Credits: {account.credits}")
|
||||
|
||||
# Create subscription
|
||||
subscription = Subscription.objects.create(
|
||||
account=account,
|
||||
plan=starter_plan,
|
||||
status='pending_payment',
|
||||
current_period_start=timezone.now(),
|
||||
current_period_end=timezone.now() + timedelta(days=30)
|
||||
)
|
||||
print_success(f"Created subscription (ID: {subscription.id})")
|
||||
print_info(f" Status: {subscription.status}")
|
||||
|
||||
# Create invoice
|
||||
invoice_service = InvoiceService()
|
||||
invoice = invoice_service.create_subscription_invoice(
|
||||
subscription=subscription,
|
||||
billing_period_start=subscription.current_period_start,
|
||||
billing_period_end=subscription.current_period_end
|
||||
)
|
||||
print_success(f"Created invoice: {invoice.invoice_number}")
|
||||
print_info(f" Status: {invoice.status}")
|
||||
print_info(f" Total: ${invoice.total}")
|
||||
print_info(f" Has billing snapshot: {'billing_snapshot' in invoice.metadata}")
|
||||
|
||||
# Create payment method
|
||||
payment_method = AccountPaymentMethod.objects.create(
|
||||
account=account,
|
||||
type='bank_transfer',
|
||||
is_default=True
|
||||
)
|
||||
print_success(f"Created payment method: {payment_method.type}")
|
||||
|
||||
# Step 2: User submits payment confirmation
|
||||
print_info("\nStep 2: User Payment Confirmation")
|
||||
payment = Payment.objects.create(
|
||||
invoice=invoice,
|
||||
account=account,
|
||||
amount=invoice.total,
|
||||
currency=invoice.currency,
|
||||
payment_method='bank_transfer',
|
||||
status='pending_approval',
|
||||
manual_reference='BT-TEST-20251208-001',
|
||||
manual_notes='Test payment via ABC Bank'
|
||||
)
|
||||
print_success(f"Created payment (ID: {payment.id})")
|
||||
print_info(f" Status: {payment.status}")
|
||||
print_info(f" Reference: {payment.manual_reference}")
|
||||
|
||||
# Verify pending state
|
||||
account.refresh_from_db()
|
||||
subscription.refresh_from_db()
|
||||
invoice.refresh_from_db()
|
||||
|
||||
assert account.status == 'pending_payment', "Account should be pending_payment"
|
||||
assert account.credits == 0, "Credits should be 0 before approval"
|
||||
assert subscription.status == 'pending_payment', "Subscription should be pending_payment"
|
||||
assert invoice.status == 'pending', "Invoice should be pending"
|
||||
assert payment.status == 'pending_approval', "Payment should be pending_approval"
|
||||
|
||||
print_success("\nPending state verified ✓")
|
||||
|
||||
# Step 3: Admin approves payment
|
||||
print_info("\nStep 3: Admin Payment Approval")
|
||||
|
||||
# Create admin user for approval
|
||||
admin_user = User.objects.filter(is_superuser=True).first()
|
||||
if not admin_user:
|
||||
admin_user = User.objects.create_superuser(
|
||||
username='test_admin',
|
||||
email='test_admin@example.com',
|
||||
password='admin123',
|
||||
first_name='Test',
|
||||
last_name='Admin'
|
||||
)
|
||||
print_info(f"Created admin user: {admin_user.email}")
|
||||
|
||||
with transaction.atomic():
|
||||
# Update payment
|
||||
payment.status = 'succeeded'
|
||||
payment.approved_by = admin_user
|
||||
payment.approved_at = timezone.now()
|
||||
payment.admin_notes = 'Verified in bank statement'
|
||||
payment.save()
|
||||
print_success("Payment approved")
|
||||
|
||||
# Update invoice
|
||||
invoice.status = 'paid'
|
||||
invoice.paid_at = timezone.now()
|
||||
invoice.save()
|
||||
print_success("Invoice marked as paid")
|
||||
|
||||
# Update subscription
|
||||
subscription.status = 'active'
|
||||
subscription.save()
|
||||
print_success("Subscription activated")
|
||||
|
||||
# Update account and add credits
|
||||
account.status = 'active'
|
||||
account.credits = starter_plan.included_credits
|
||||
account.save()
|
||||
print_success(f"Account activated with {starter_plan.included_credits} credits")
|
||||
|
||||
# Log credit transaction
|
||||
credit_txn = CreditTransaction.objects.create(
|
||||
account=account,
|
||||
transaction_type='plan_allocation',
|
||||
amount=starter_plan.included_credits,
|
||||
balance_after=account.credits,
|
||||
description=f'Credits from approved payment (Invoice: {invoice.invoice_number})'
|
||||
)
|
||||
print_success("Credit transaction logged")
|
||||
|
||||
# Final verification
|
||||
print_info("\nStep 4: Final Verification")
|
||||
account.refresh_from_db()
|
||||
subscription.refresh_from_db()
|
||||
invoice.refresh_from_db()
|
||||
payment.refresh_from_db()
|
||||
|
||||
assert account.status == 'active', "Account should be active"
|
||||
assert account.credits == 1000, "Credits should be 1000"
|
||||
assert subscription.status == 'active', "Subscription should be active"
|
||||
assert invoice.status == 'paid', "Invoice should be paid"
|
||||
assert payment.status == 'succeeded', "Payment should be succeeded"
|
||||
assert payment.approved_by == admin_user, "Payment should have approved_by"
|
||||
|
||||
print_success(f"Account: {account.status} ✓")
|
||||
print_success(f"Credits: {account.credits} ✓")
|
||||
print_success(f"Subscription: {subscription.status} ✓")
|
||||
print_success(f"Invoice: {invoice.status} ✓")
|
||||
print_success(f"Payment: {payment.status} ✓")
|
||||
print_success(f"Approved by: {payment.approved_by.email} ✓")
|
||||
|
||||
# Check credit transaction
|
||||
txn = CreditTransaction.objects.filter(account=account).latest('created_at')
|
||||
print_success(f"Credit Transaction: {txn.transaction_type} | {txn.amount} credits ✓")
|
||||
|
||||
print_success("\nPAID SIGNUP TEST PASSED ✓")
|
||||
return account
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Paid signup test failed: {e}")
|
||||
raise
|
||||
|
||||
def test_payment_rejection():
|
||||
"""Test payment rejection flow"""
|
||||
print_header("TEST 3: PAYMENT REJECTION")
|
||||
|
||||
try:
|
||||
# Use the paid account from previous test
|
||||
account = Account.objects.get(owner__email='workflow_test_paid@example.com')
|
||||
|
||||
# Create a second invoice for testing rejection
|
||||
print_info("Creating second invoice for rejection test")
|
||||
|
||||
subscription = Subscription.objects.get(account=account)
|
||||
invoice_service = InvoiceService()
|
||||
|
||||
with transaction.atomic():
|
||||
invoice2 = invoice_service.create_subscription_invoice(
|
||||
subscription=subscription,
|
||||
billing_period_start=subscription.current_period_start + timedelta(days=30),
|
||||
billing_period_end=subscription.current_period_end + timedelta(days=30)
|
||||
)
|
||||
print_success(f"Created invoice: {invoice2.invoice_number}")
|
||||
|
||||
# Submit payment
|
||||
payment2 = Payment.objects.create(
|
||||
invoice=invoice2,
|
||||
account=account,
|
||||
amount=invoice2.total,
|
||||
currency=invoice2.currency,
|
||||
payment_method='bank_transfer',
|
||||
status='pending_approval',
|
||||
manual_reference='BT-INVALID-REF',
|
||||
manual_notes='Test invalid payment reference'
|
||||
)
|
||||
print_success(f"Created payment (ID: {payment2.id})")
|
||||
|
||||
# Admin rejects payment
|
||||
print_info("\nRejecting payment...")
|
||||
admin_user = User.objects.filter(is_superuser=True).first()
|
||||
|
||||
with transaction.atomic():
|
||||
payment2.status = 'failed'
|
||||
payment2.approved_by = admin_user
|
||||
payment2.approved_at = timezone.now()
|
||||
payment2.admin_notes = 'Reference number not found in bank statement'
|
||||
payment2.save()
|
||||
print_success("Payment rejected")
|
||||
|
||||
# Verify rejection
|
||||
payment2.refresh_from_db()
|
||||
invoice2.refresh_from_db()
|
||||
|
||||
assert payment2.status == 'failed', "Payment should be failed"
|
||||
assert invoice2.status == 'pending', "Invoice should remain pending"
|
||||
|
||||
print_success(f"Payment status: {payment2.status} ✓")
|
||||
print_success(f"Invoice status: {invoice2.status} ✓")
|
||||
print_success(f"Rejection reason: {payment2.admin_notes} ✓")
|
||||
|
||||
print_success("\nPAYMENT REJECTION TEST PASSED ✓")
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Payment rejection test failed: {e}")
|
||||
raise
|
||||
|
||||
def print_summary():
|
||||
"""Print test summary"""
|
||||
print_header("TEST SUMMARY")
|
||||
|
||||
# Count accounts by status
|
||||
from django.db.models import Count
|
||||
|
||||
status_counts = Account.objects.values('status').annotate(count=Count('id'))
|
||||
print_info("Account Status Distribution:")
|
||||
for item in status_counts:
|
||||
print(f" {item['status']:20} {item['count']} account(s)")
|
||||
|
||||
# Count payments by status
|
||||
payment_counts = Payment.objects.values('status').annotate(count=Count('id'))
|
||||
print_info("\nPayment Status Distribution:")
|
||||
for item in payment_counts:
|
||||
print(f" {item['status']:20} {item['count']} payment(s)")
|
||||
|
||||
# Count subscriptions by status
|
||||
sub_counts = Subscription.objects.values('status').annotate(count=Count('id'))
|
||||
print_info("\nSubscription Status Distribution:")
|
||||
for item in sub_counts:
|
||||
print(f" {item['status']:20} {item['count']} subscription(s)")
|
||||
|
||||
print()
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print_header("PAYMENT WORKFLOW E2E TEST SUITE")
|
||||
print(f"{Colors.BOLD}Date: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}{Colors.ENDC}\n")
|
||||
|
||||
try:
|
||||
# Cleanup
|
||||
cleanup_test_data()
|
||||
|
||||
# Run tests
|
||||
test_free_trial_signup()
|
||||
test_paid_signup()
|
||||
test_payment_rejection()
|
||||
|
||||
# Summary
|
||||
print_summary()
|
||||
|
||||
# Final success
|
||||
print_header("ALL TESTS PASSED ✓")
|
||||
print(f"{Colors.OKGREEN}{Colors.BOLD}The payment workflow is functioning correctly!{Colors.ENDC}\n")
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print_header("TESTS FAILED ✗")
|
||||
print(f"{Colors.FAIL}{Colors.BOLD}Error: {e}{Colors.ENDC}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script to detect and reproduce session contamination bugs
|
||||
Usage: docker exec igny8_backend python test_session_contamination.py
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.contrib.sessions.models import Session
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import RequestFactory
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from igny8_core.auth.middleware import AccountContextMiddleware
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
def test_session_isolation():
|
||||
"""Test that sessions are properly isolated between users"""
|
||||
print("\n=== SESSION CONTAMINATION TEST ===\n")
|
||||
|
||||
# Get test users
|
||||
try:
|
||||
developer = User.objects.get(username='developer')
|
||||
scale_user = User.objects.filter(account__slug='scale-account').first()
|
||||
|
||||
if not scale_user:
|
||||
print("⚠️ No scale account user found, creating one...")
|
||||
from igny8_core.auth.models import Account
|
||||
scale_account = Account.objects.filter(slug='scale-account').first()
|
||||
if scale_account:
|
||||
scale_user = User.objects.create_user(
|
||||
username='scale_test',
|
||||
email='scale@test.com',
|
||||
password='testpass123',
|
||||
account=scale_account,
|
||||
role='owner'
|
||||
)
|
||||
else:
|
||||
print("❌ No scale account found")
|
||||
return False
|
||||
|
||||
print(f"✓ Developer user: {developer.username} (account: {developer.account.slug})")
|
||||
print(f"✓ Scale user: {scale_user.username} (account: {scale_user.account.slug if scale_user.account else 'None'})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to get test users: {e}")
|
||||
return False
|
||||
|
||||
# Check active sessions
|
||||
active_sessions = Session.objects.filter(expire_date__gte=datetime.now())
|
||||
print(f"\n📊 Total active sessions: {active_sessions.count()}")
|
||||
|
||||
# Count sessions by user
|
||||
user_sessions = {}
|
||||
for session in active_sessions:
|
||||
try:
|
||||
data = session.get_decoded()
|
||||
user_id = data.get('_auth_user_id')
|
||||
if user_id:
|
||||
user = User.objects.get(id=user_id)
|
||||
key = f"{user.username} ({user.account.slug if user.account else 'no-account'})"
|
||||
user_sessions[key] = user_sessions.get(key, 0) + 1
|
||||
except:
|
||||
pass
|
||||
|
||||
print("\n📈 Sessions by user:")
|
||||
for user_key, count in sorted(user_sessions.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f" {user_key}: {count} sessions")
|
||||
|
||||
# Check for session contamination patterns
|
||||
contamination_found = False
|
||||
|
||||
# Pattern 1: Too many sessions for one user
|
||||
for user_key, count in user_sessions.items():
|
||||
if count > 20:
|
||||
print(f"\n⚠️ WARNING: {user_key} has {count} sessions (possible proliferation)")
|
||||
contamination_found = True
|
||||
|
||||
# Pattern 2: Check session cookie settings
|
||||
from django.conf import settings
|
||||
print(f"\n🔧 Session Configuration:")
|
||||
print(f" SESSION_COOKIE_NAME: {settings.SESSION_COOKIE_NAME}")
|
||||
print(f" SESSION_COOKIE_DOMAIN: {getattr(settings, 'SESSION_COOKIE_DOMAIN', 'Not set (good)')}")
|
||||
print(f" SESSION_COOKIE_SAMESITE: {getattr(settings, 'SESSION_COOKIE_SAMESITE', 'Not set')}")
|
||||
print(f" SESSION_COOKIE_HTTPONLY: {settings.SESSION_COOKIE_HTTPONLY}")
|
||||
print(f" SESSION_ENGINE: {settings.SESSION_ENGINE}")
|
||||
|
||||
if getattr(settings, 'SESSION_COOKIE_SAMESITE', None) != 'Strict':
|
||||
print(f"\n⚠️ WARNING: SESSION_COOKIE_SAMESITE should be 'Strict' (currently: {getattr(settings, 'SESSION_COOKIE_SAMESITE', 'Not set')})")
|
||||
contamination_found = True
|
||||
|
||||
# Test middleware isolation
|
||||
print(f"\n🧪 Testing Middleware Isolation...")
|
||||
factory = RequestFactory()
|
||||
|
||||
# Simulate two requests from different users
|
||||
request1 = factory.get('/api/v1/test/')
|
||||
request1.user = developer
|
||||
request1.session = {}
|
||||
|
||||
request2 = factory.get('/api/v1/test/')
|
||||
request2.user = scale_user
|
||||
request2.session = {}
|
||||
|
||||
middleware = AccountContextMiddleware(lambda x: None)
|
||||
|
||||
# Process requests
|
||||
middleware.process_request(request1)
|
||||
middleware.process_request(request2)
|
||||
|
||||
# Check isolation
|
||||
account1 = getattr(request1, 'account', None)
|
||||
account2 = getattr(request2, 'account', None)
|
||||
|
||||
print(f" Request 1 account: {account1.slug if account1 else 'None'}")
|
||||
print(f" Request 2 account: {account2.slug if account2 else 'None'}")
|
||||
|
||||
if account1 and account2 and account1.id == account2.id:
|
||||
print(f"\n❌ CONTAMINATION DETECTED: Both requests have same account!")
|
||||
contamination_found = True
|
||||
else:
|
||||
print(f"\n✓ Middleware isolation working correctly")
|
||||
|
||||
# Final result
|
||||
if contamination_found:
|
||||
print(f"\n❌ SESSION CONTAMINATION DETECTED")
|
||||
print(f"\nRecommended fixes:")
|
||||
print(f"1. Set SESSION_COOKIE_SAMESITE='Strict' in settings.py")
|
||||
print(f"2. Clear all existing sessions: Session.objects.all().delete()")
|
||||
print(f"3. Ensure users logout and re-login with fresh cookies")
|
||||
return False
|
||||
else:
|
||||
print(f"\n✅ No contamination detected - sessions appear isolated")
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = test_session_isolation()
|
||||
exit(0 if result else 1)
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Add the backend directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.modules.writer.models import Content
|
||||
from igny8_core.modules.writer.serializers import ContentSerializer
|
||||
|
||||
print("Testing ContentSerializer tags and categories fields...")
|
||||
print("=" * 60)
|
||||
|
||||
# Get a content record
|
||||
content = Content.objects.first()
|
||||
if content:
|
||||
serializer = ContentSerializer(content)
|
||||
data = serializer.data
|
||||
print(f"Content ID: {data['id']}")
|
||||
print(f"Title: {data.get('title', 'N/A')}")
|
||||
print(f"Tags: {data.get('tags', [])}")
|
||||
print(f"Categories: {data.get('categories', [])}")
|
||||
print(f"Taxonomy Terms Data: {len(data.get('taxonomy_terms_data', []))} items")
|
||||
|
||||
# Show taxonomy terms breakdown
|
||||
taxonomy_terms = data.get('taxonomy_terms_data', [])
|
||||
if taxonomy_terms:
|
||||
print("\nTaxonomy Terms Details:")
|
||||
for term in taxonomy_terms:
|
||||
print(f" - {term['name']} ({term['taxonomy_type']})")
|
||||
|
||||
print("\n✓ Serializer fields test passed!")
|
||||
else:
|
||||
print("No content found in database")
|
||||
print("This is expected if no content has been generated yet")
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
import json
|
||||
|
||||
integration = SiteIntegration.objects.get(id=1)
|
||||
print("Current config_json:")
|
||||
print(json.dumps(integration.config_json, indent=2))
|
||||
print("\nIntegration ID:", integration.id)
|
||||
print("Site:", integration.site.name)
|
||||
print("Platform:", integration.platform)
|
||||
print("Is Active:", integration.is_active)
|
||||
print("Sync Enabled:", integration.sync_enabled)
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database Migration Verification Script
|
||||
Checks for orphaned SiteBlueprint tables and verifies new migrations
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from django.core.management import call_command
|
||||
|
||||
|
||||
def check_orphaned_tables():
|
||||
"""Check for orphaned blueprint tables"""
|
||||
print("\n" + "="*60)
|
||||
print("CHECKING FOR ORPHANED SITEBLUEPRINT TABLES")
|
||||
print("="*60 + "\n")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name LIKE '%blueprint%'
|
||||
ORDER BY table_name;
|
||||
""")
|
||||
tables = cursor.fetchall()
|
||||
|
||||
if tables:
|
||||
print("⚠️ Found blueprint-related tables:")
|
||||
for table in tables:
|
||||
print(f" - {table[0]}")
|
||||
print("\n💡 These tables can be safely dropped if no longer needed.")
|
||||
else:
|
||||
print("✅ No orphaned blueprint tables found.")
|
||||
|
||||
return len(tables) if tables else 0
|
||||
|
||||
|
||||
def verify_cluster_constraint():
|
||||
"""Verify cluster unique constraint is per-site/sector"""
|
||||
print("\n" + "="*60)
|
||||
print("VERIFYING CLUSTER UNIQUE CONSTRAINT")
|
||||
print("="*60 + "\n")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.constraint_type,
|
||||
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) as columns
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_name = 'igny8_clusters'
|
||||
AND tc.constraint_type = 'UNIQUE'
|
||||
GROUP BY tc.constraint_name, tc.constraint_type;
|
||||
""")
|
||||
constraints = cursor.fetchall()
|
||||
|
||||
if constraints:
|
||||
print("Found unique constraints on igny8_clusters:")
|
||||
for constraint in constraints:
|
||||
name, ctype, columns = constraint
|
||||
print(f" {name}: {columns}")
|
||||
|
||||
# Check if it includes site and sector
|
||||
if 'site' in columns.lower() and 'sector' in columns.lower():
|
||||
print(f" ✅ Constraint is scoped per-site/sector")
|
||||
else:
|
||||
print(f" ⚠️ Constraint may need updating")
|
||||
else:
|
||||
print("⚠️ No unique constraints found on igny8_clusters")
|
||||
|
||||
|
||||
def verify_automation_delays():
|
||||
"""Verify automation delay fields exist"""
|
||||
print("\n" + "="*60)
|
||||
print("VERIFYING AUTOMATION DELAY CONFIGURATION")
|
||||
print("="*60 + "\n")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'igny8_automationconfig'
|
||||
AND column_name IN ('within_stage_delay', 'between_stage_delay')
|
||||
ORDER BY column_name;
|
||||
""")
|
||||
columns = cursor.fetchall()
|
||||
|
||||
if len(columns) == 2:
|
||||
print("✅ Delay configuration fields found:")
|
||||
for col in columns:
|
||||
name, dtype, default = col
|
||||
print(f" {name}: {dtype} (default: {default})")
|
||||
else:
|
||||
print(f"⚠️ Expected 2 delay fields, found {len(columns)}")
|
||||
|
||||
|
||||
def check_migration_status():
|
||||
"""Check migration status"""
|
||||
print("\n" + "="*60)
|
||||
print("CHECKING MIGRATION STATUS")
|
||||
print("="*60 + "\n")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT app, name, applied
|
||||
FROM django_migrations
|
||||
WHERE name LIKE '%cluster%' OR name LIKE '%delay%'
|
||||
ORDER BY applied DESC
|
||||
LIMIT 10;
|
||||
""")
|
||||
migrations = cursor.fetchall()
|
||||
|
||||
if migrations:
|
||||
print("Recent relevant migrations:")
|
||||
for mig in migrations:
|
||||
app, name, applied = mig
|
||||
status = "✅" if applied else "⏳"
|
||||
print(f" {status} {app}.{name}")
|
||||
print(f" Applied: {applied}")
|
||||
else:
|
||||
print("No relevant migrations found in history")
|
||||
|
||||
|
||||
def check_data_integrity():
|
||||
"""Check for data integrity issues"""
|
||||
print("\n" + "="*60)
|
||||
print("DATA INTEGRITY CHECKS")
|
||||
print("="*60 + "\n")
|
||||
|
||||
from igny8_core.business.planning.models import Clusters, Keywords
|
||||
|
||||
# Check for clusters with 'active' status (should all be 'new' or 'mapped')
|
||||
active_clusters = Clusters.objects.filter(status='active').count()
|
||||
if active_clusters > 0:
|
||||
print(f"⚠️ Found {active_clusters} clusters with status='active'")
|
||||
print(" These should be updated to 'new' or 'mapped'")
|
||||
else:
|
||||
print("✅ No clusters with invalid 'active' status")
|
||||
|
||||
# Check for duplicate cluster names in same site/sector
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, site_id, sector_id, COUNT(*) as count
|
||||
FROM igny8_clusters
|
||||
GROUP BY name, site_id, sector_id
|
||||
HAVING COUNT(*) > 1;
|
||||
""")
|
||||
duplicates = cursor.fetchall()
|
||||
|
||||
if duplicates:
|
||||
print(f"\n⚠️ Found {len(duplicates)} duplicate cluster names in same site/sector:")
|
||||
for dup in duplicates[:5]: # Show first 5
|
||||
print(f" - '{dup[0]}' (site={dup[1]}, sector={dup[2]}): {dup[3]} duplicates")
|
||||
else:
|
||||
print("✅ No duplicate cluster names within same site/sector")
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "#"*60)
|
||||
print("# IGNY8 DATABASE MIGRATION VERIFICATION")
|
||||
print("# Date:", __import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
print("#"*60)
|
||||
|
||||
try:
|
||||
orphaned = check_orphaned_tables()
|
||||
verify_cluster_constraint()
|
||||
verify_automation_delays()
|
||||
check_migration_status()
|
||||
check_data_integrity()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("VERIFICATION COMPLETE")
|
||||
print("="*60)
|
||||
|
||||
if orphaned > 0:
|
||||
print(f"\n⚠️ {orphaned} orphaned table(s) found - review recommended")
|
||||
else:
|
||||
print("\n✅ All verifications passed!")
|
||||
|
||||
print("\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Verify all status fixes"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.planning.models import Keywords, Clusters, ContentIdeas
|
||||
from igny8_core.business.content.models import Tasks
|
||||
from django.db.models import Count
|
||||
|
||||
print("=" * 60)
|
||||
print("STATUS VERIFICATION REPORT")
|
||||
print("=" * 60)
|
||||
|
||||
# Keywords
|
||||
print("\n1. KEYWORDS STATUS:")
|
||||
kw_status = Keywords.objects.values('status').annotate(count=Count('id')).order_by('status')
|
||||
for item in kw_status:
|
||||
print(f" {item['status']}: {item['count']}")
|
||||
print(f" Total: {Keywords.objects.count()}")
|
||||
|
||||
# Clusters
|
||||
print("\n2. CLUSTERS STATUS:")
|
||||
cl_status = Clusters.objects.values('status').annotate(count=Count('id')).order_by('status')
|
||||
for item in cl_status:
|
||||
print(f" {item['status']}: {item['count']}")
|
||||
print(f" Total: {Clusters.objects.count()}")
|
||||
|
||||
# Content Ideas
|
||||
print("\n3. IDEAS STATUS:")
|
||||
idea_status = ContentIdeas.objects.values('status').annotate(count=Count('id')).order_by('status')
|
||||
for item in idea_status:
|
||||
print(f" {item['status']}: {item['count']}")
|
||||
print(f" Total: {ContentIdeas.objects.count()}")
|
||||
|
||||
# Verify idea-task sync
|
||||
print("\n4. IDEA-TASK STATUS SYNC:")
|
||||
completed_tasks = Tasks.objects.filter(status='completed', idea__isnull=False)
|
||||
mismatched = 0
|
||||
for task in completed_tasks:
|
||||
if task.idea and task.idea.status != 'completed':
|
||||
mismatched += 1
|
||||
print(f" MISMATCH: Task {task.id} completed, Idea {task.idea.id} is {task.idea.status}")
|
||||
|
||||
if mismatched == 0:
|
||||
print(f" ✓ All {completed_tasks.count()} completed tasks have ideas with 'completed' status")
|
||||
else:
|
||||
print(f" ✗ {mismatched} mismatches found")
|
||||
|
||||
# Check for old status values
|
||||
print("\n5. OLD STATUS VALUES CHECK:")
|
||||
old_keywords = Keywords.objects.filter(status__in=['pending', 'active', 'archived']).count()
|
||||
old_clusters = Clusters.objects.filter(status__in=['active']).exclude(status='mapped').exclude(status='new').count()
|
||||
old_ideas = ContentIdeas.objects.filter(status__in=['scheduled', 'published']).count()
|
||||
|
||||
if old_keywords == 0 and old_clusters == 0 and old_ideas == 0:
|
||||
print(" ✓ No old status values found")
|
||||
else:
|
||||
print(f" ✗ Found old values:")
|
||||
if old_keywords > 0:
|
||||
print(f" Keywords with pending/active/archived: {old_keywords}")
|
||||
if old_clusters > 0:
|
||||
print(f" Clusters with old 'active': {old_clusters}")
|
||||
if old_ideas > 0:
|
||||
print(f" Ideas with scheduled/published: {old_ideas}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("VERIFICATION COMPLETE")
|
||||
print("=" * 60)
|
||||
@@ -1,129 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Verify Tags and Categories Implementation
|
||||
Tests that ContentTaxonomy integration is working correctly
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Add the backend directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.business.content.models import Content, ContentTaxonomy
|
||||
from igny8_core.modules.writer.serializers import ContentSerializer
|
||||
|
||||
print("=" * 80)
|
||||
print("VERIFYING TAGS AND CATEGORIES IMPLEMENTATION")
|
||||
print("=" * 80)
|
||||
|
||||
# Check if ContentTaxonomy model is accessible
|
||||
print("\n1. ContentTaxonomy Model Check:")
|
||||
try:
|
||||
taxonomy_count = ContentTaxonomy.objects.count()
|
||||
print(f" ✓ ContentTaxonomy model accessible")
|
||||
print(f" ✓ Total taxonomy terms in database: {taxonomy_count}")
|
||||
|
||||
# Show breakdown by type
|
||||
tag_count = ContentTaxonomy.objects.filter(taxonomy_type='tag').count()
|
||||
category_count = ContentTaxonomy.objects.filter(taxonomy_type='category').count()
|
||||
print(f" - Tags: {tag_count}")
|
||||
print(f" - Categories: {category_count}")
|
||||
except Exception as e:
|
||||
print(f" ✗ Error accessing ContentTaxonomy: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check Content model has taxonomy_terms field
|
||||
print("\n2. Content Model Taxonomy Field Check:")
|
||||
try:
|
||||
content = Content.objects.first()
|
||||
if content:
|
||||
taxonomy_terms = content.taxonomy_terms.all()
|
||||
print(f" ✓ Content.taxonomy_terms field accessible")
|
||||
print(f" ✓ Sample content (ID: {content.id}) has {taxonomy_terms.count()} taxonomy terms")
|
||||
for term in taxonomy_terms:
|
||||
print(f" - {term.name} ({term.taxonomy_type})")
|
||||
else:
|
||||
print(" ⚠ No content found in database")
|
||||
except Exception as e:
|
||||
print(f" ✗ Error accessing Content.taxonomy_terms: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check serializer includes tags and categories
|
||||
print("\n3. ContentSerializer Tags/Categories Check:")
|
||||
try:
|
||||
if content:
|
||||
serializer = ContentSerializer(content)
|
||||
data = serializer.data
|
||||
|
||||
# Check if fields exist
|
||||
has_tags_field = 'tags' in data
|
||||
has_categories_field = 'categories' in data
|
||||
has_taxonomy_data = 'taxonomy_terms_data' in data
|
||||
|
||||
print(f" ✓ Serializer includes 'tags' field: {has_tags_field}")
|
||||
print(f" ✓ Serializer includes 'categories' field: {has_categories_field}")
|
||||
print(f" ✓ Serializer includes 'taxonomy_terms_data' field: {has_taxonomy_data}")
|
||||
|
||||
if has_tags_field:
|
||||
print(f" - Tags: {data.get('tags', [])}")
|
||||
if has_categories_field:
|
||||
print(f" - Categories: {data.get('categories', [])}")
|
||||
|
||||
else:
|
||||
print(" ⚠ No content to serialize")
|
||||
except Exception as e:
|
||||
print(f" ✗ Error serializing content: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Check if we can create taxonomy terms
|
||||
print("\n4. Creating Test Taxonomy Terms:")
|
||||
try:
|
||||
from django.utils.text import slugify
|
||||
|
||||
# Try to create a test tag
|
||||
test_tag, created = ContentTaxonomy.objects.get_or_create(
|
||||
name="Test Tag",
|
||||
taxonomy_type='tag',
|
||||
defaults={
|
||||
'slug': slugify("Test Tag"),
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f" ✓ Created new test tag: {test_tag.name}")
|
||||
else:
|
||||
print(f" ✓ Test tag already exists: {test_tag.name}")
|
||||
|
||||
# Try to create a test category
|
||||
test_category, created = ContentTaxonomy.objects.get_or_create(
|
||||
name="Test Category",
|
||||
taxonomy_type='category',
|
||||
defaults={
|
||||
'slug': slugify("Test Category"),
|
||||
}
|
||||
)
|
||||
if created:
|
||||
print(f" ✓ Created new test category: {test_category.name}")
|
||||
else:
|
||||
print(f" ✓ Test category already exists: {test_category.name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ Error creating taxonomy terms: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("VERIFICATION COMPLETE")
|
||||
print("=" * 80)
|
||||
print("\nNext steps:")
|
||||
print("1. Access Django admin at /admin/writer/contenttaxonomy/")
|
||||
print("2. Generate content via AI and check if tags/categories are saved")
|
||||
print("3. Check API response includes 'tags' and 'categories' fields")
|
||||
print("=" * 80)
|
||||
Reference in New Issue
Block a user