Phase 3 - credts, usage, plans app pages #Migrations
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
"""
|
||||
Management command to backfill usage tracking for existing content.
|
||||
Usage: python manage.py backfill_usage [account_id]
|
||||
|
||||
NOTE: Since the simplification of limits (Jan 2026), this command only
|
||||
tracks Ahrefs queries. All other usage is tracked via CreditUsageLog.
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.apps import apps
|
||||
@@ -9,7 +12,7 @@ from igny8_core.auth.models import Account
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Backfill usage tracking for existing content'
|
||||
help = 'Backfill usage tracking for existing content (Ahrefs queries only)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
@@ -30,10 +33,6 @@ class Command(BaseCommand):
|
||||
else:
|
||||
accounts = Account.objects.filter(plan__isnull=False).select_related('plan')
|
||||
|
||||
ContentIdeas = apps.get_model('planner', 'ContentIdeas')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
Images = apps.get_model('writer', 'Images')
|
||||
|
||||
total_accounts = accounts.count()
|
||||
self.stdout.write(f'Processing {total_accounts} account(s)...\n')
|
||||
|
||||
@@ -43,45 +42,14 @@ class Command(BaseCommand):
|
||||
self.stdout.write(f'Plan: {account.plan.name if account.plan else "No Plan"}')
|
||||
self.stdout.write('=' * 60)
|
||||
|
||||
# Count content ideas
|
||||
ideas_count = ContentIdeas.objects.filter(account=account).count()
|
||||
self.stdout.write(f'Content Ideas: {ideas_count}')
|
||||
# Ahrefs queries are tracked in CreditUsageLog with operation_type='ahrefs_query'
|
||||
# We don't backfill these as they should be tracked in real-time going forward
|
||||
# This command is primarily for verification
|
||||
|
||||
# Count content words
|
||||
from django.db.models import Sum
|
||||
total_words = Content.objects.filter(account=account).aggregate(
|
||||
total=Sum('word_count')
|
||||
)['total'] or 0
|
||||
self.stdout.write(f'Content Words: {total_words}')
|
||||
|
||||
# Count images
|
||||
total_images = Images.objects.filter(account=account).count()
|
||||
images_with_prompts = Images.objects.filter(
|
||||
account=account, prompt__isnull=False
|
||||
).exclude(prompt='').count()
|
||||
self.stdout.write(f'Total Images: {total_images}')
|
||||
self.stdout.write(f'Images with Prompts: {images_with_prompts}')
|
||||
|
||||
# Update account usage fields
|
||||
with transaction.atomic():
|
||||
account.usage_content_ideas = ideas_count
|
||||
account.usage_content_words = total_words
|
||||
account.usage_images_basic = total_images
|
||||
account.usage_images_premium = 0 # Premium not implemented yet
|
||||
account.usage_image_prompts = images_with_prompts
|
||||
account.save(update_fields=[
|
||||
'usage_content_ideas', 'usage_content_words',
|
||||
'usage_images_basic', 'usage_images_premium', 'usage_image_prompts',
|
||||
'updated_at'
|
||||
])
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('\n✅ Updated usage tracking:'))
|
||||
self.stdout.write(f' usage_content_ideas: {account.usage_content_ideas}')
|
||||
self.stdout.write(f' usage_content_words: {account.usage_content_words}')
|
||||
self.stdout.write(f' usage_images_basic: {account.usage_images_basic}')
|
||||
self.stdout.write(f' usage_images_premium: {account.usage_images_premium}')
|
||||
self.stdout.write(f' usage_image_prompts: {account.usage_image_prompts}\n')
|
||||
self.stdout.write(f'Ahrefs queries used this month: {account.usage_ahrefs_queries}')
|
||||
self.stdout.write(self.style.SUCCESS('\n✅ Verified usage tracking'))
|
||||
self.stdout.write(f' usage_ahrefs_queries: {account.usage_ahrefs_queries}\n')
|
||||
|
||||
self.stdout.write('=' * 60)
|
||||
self.stdout.write(self.style.SUCCESS('✅ Backfill complete!'))
|
||||
self.stdout.write(self.style.SUCCESS('✅ Verification complete!'))
|
||||
self.stdout.write('=' * 60)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Limit Service for Plan Limit Enforcement
|
||||
Manages hard limits (sites, users, keywords, clusters) and monthly limits (ideas, words, images, prompts)
|
||||
Manages hard limits (sites, users, keywords) and monthly limits (ahrefs_queries)
|
||||
"""
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
@@ -18,12 +18,12 @@ class LimitExceededError(Exception):
|
||||
|
||||
|
||||
class HardLimitExceededError(LimitExceededError):
|
||||
"""Raised when a hard limit (sites, users, keywords, clusters) is exceeded"""
|
||||
"""Raised when a hard limit (sites, users, keywords) is exceeded"""
|
||||
pass
|
||||
|
||||
|
||||
class MonthlyLimitExceededError(LimitExceededError):
|
||||
"""Raised when a monthly limit (ideas, words, images, prompts) is exceeded"""
|
||||
"""Raised when a monthly limit (ahrefs_queries) is exceeded"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class LimitService:
|
||||
"""Service for managing and enforcing plan limits"""
|
||||
|
||||
# Map limit types to model/field names
|
||||
# Simplified to only 3 hard limits: sites, users, keywords
|
||||
HARD_LIMIT_MAPPINGS = {
|
||||
'sites': {
|
||||
'model': 'igny8_core_auth.Site',
|
||||
@@ -39,10 +40,10 @@ class LimitService:
|
||||
'filter_field': 'account',
|
||||
},
|
||||
'users': {
|
||||
'model': 'igny8_core_auth.SiteUserAccess',
|
||||
'model': 'igny8_core_auth.User',
|
||||
'plan_field': 'max_users',
|
||||
'display_name': 'Team Users',
|
||||
'filter_field': 'site__account',
|
||||
'display_name': 'Team Members',
|
||||
'filter_field': 'account',
|
||||
},
|
||||
'keywords': {
|
||||
'model': 'planner.Keywords',
|
||||
@@ -50,39 +51,15 @@ class LimitService:
|
||||
'display_name': 'Keywords',
|
||||
'filter_field': 'account',
|
||||
},
|
||||
'clusters': {
|
||||
'model': 'planner.Clusters',
|
||||
'plan_field': 'max_clusters',
|
||||
'display_name': 'Clusters',
|
||||
'filter_field': 'account',
|
||||
},
|
||||
}
|
||||
|
||||
# Simplified to only 1 monthly limit: ahrefs_queries
|
||||
# All other consumption is controlled by credits only
|
||||
MONTHLY_LIMIT_MAPPINGS = {
|
||||
'content_ideas': {
|
||||
'plan_field': 'max_content_ideas',
|
||||
'usage_field': 'usage_content_ideas',
|
||||
'display_name': 'Content Ideas',
|
||||
},
|
||||
'content_words': {
|
||||
'plan_field': 'max_content_words',
|
||||
'usage_field': 'usage_content_words',
|
||||
'display_name': 'Content Words',
|
||||
},
|
||||
'images_basic': {
|
||||
'plan_field': 'max_images_basic',
|
||||
'usage_field': 'usage_images_basic',
|
||||
'display_name': 'Basic Images',
|
||||
},
|
||||
'images_premium': {
|
||||
'plan_field': 'max_images_premium',
|
||||
'usage_field': 'usage_images_premium',
|
||||
'display_name': 'Premium Images',
|
||||
},
|
||||
'image_prompts': {
|
||||
'plan_field': 'max_image_prompts',
|
||||
'usage_field': 'usage_image_prompts',
|
||||
'display_name': 'Image Prompts',
|
||||
'ahrefs_queries': {
|
||||
'plan_field': 'max_ahrefs_queries',
|
||||
'usage_field': 'usage_ahrefs_queries',
|
||||
'display_name': 'Keyword Research Queries',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -318,11 +295,8 @@ class LimitService:
|
||||
Returns:
|
||||
dict: Summary of reset operation
|
||||
"""
|
||||
account.usage_content_ideas = 0
|
||||
account.usage_content_words = 0
|
||||
account.usage_images_basic = 0
|
||||
account.usage_images_premium = 0
|
||||
account.usage_image_prompts = 0
|
||||
# Reset only ahrefs_queries (the only monthly limit now)
|
||||
account.usage_ahrefs_queries = 0
|
||||
|
||||
old_period_end = account.usage_period_end
|
||||
|
||||
@@ -341,8 +315,7 @@ class LimitService:
|
||||
account.usage_period_end = new_period_end
|
||||
|
||||
account.save(update_fields=[
|
||||
'usage_content_ideas', 'usage_content_words',
|
||||
'usage_images_basic', 'usage_images_premium', 'usage_image_prompts',
|
||||
'usage_ahrefs_queries',
|
||||
'usage_period_start', 'usage_period_end', 'updated_at'
|
||||
])
|
||||
|
||||
@@ -353,5 +326,5 @@ class LimitService:
|
||||
'old_period_end': old_period_end.isoformat() if old_period_end else None,
|
||||
'new_period_start': new_period_start.isoformat(),
|
||||
'new_period_end': new_period_end.isoformat(),
|
||||
'limits_reset': 5,
|
||||
'limits_reset': 1,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user