master - part 2

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

View File

@@ -96,17 +96,15 @@ class ClustersAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
@admin.register(Keywords)
class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
resource_class = KeywordsResource
list_display = ['keyword', 'seed_keyword', 'site', 'sector', 'cluster', 'volume', 'difficulty', 'country', 'status', 'created_at']
# Use actual DB fields and custom methods with @admin.display for computed values
list_display = ['get_keyword', 'seed_keyword', 'site', 'sector', 'cluster', 'get_volume', 'get_difficulty', 'get_country', 'status', 'created_at']
list_editable = ['status'] # Enable inline editing for status
list_select_related = ['site', 'sector', 'cluster', 'seed_keyword', 'seed_keyword__industry', 'seed_keyword__sector', 'account']
list_filter = [
('status', ChoicesDropdownFilter),
('country', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('cluster', RelatedDropdownFilter),
('volume', RangeNumericFilter),
('difficulty', RangeNumericFilter),
('created_at', RangeDateFilter),
]
search_fields = ['seed_keyword__keyword']
@@ -119,6 +117,30 @@ class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
'bulk_soft_delete',
]
@admin.display(description='Keyword')
def get_keyword(self, obj):
"""Get keyword from seed_keyword"""
return obj.seed_keyword.keyword if obj.seed_keyword else '-'
@admin.display(description='Volume')
def get_volume(self, obj):
"""Get volume from override or seed_keyword"""
if obj.volume_override is not None:
return obj.volume_override
return obj.seed_keyword.volume if obj.seed_keyword else 0
@admin.display(description='Difficulty')
def get_difficulty(self, obj):
"""Get difficulty from override or seed_keyword"""
if obj.difficulty_override is not None:
return obj.difficulty_override
return obj.seed_keyword.difficulty if obj.seed_keyword else 0
@admin.display(description='Country')
def get_country(self, obj):
"""Get country from seed_keyword"""
return obj.seed_keyword.country if obj.seed_keyword else 'US'
def get_site_display(self, obj):
"""Safely get site name"""
try:

View File

@@ -934,6 +934,45 @@ class ClusterViewSet(SiteSectorModelViewSet):
# Save with all required fields explicitly
serializer.save(account=account, site=site, sector=sector)
@action(detail=False, methods=['get'], url_path='summary', url_name='summary')
def summary(self, request):
"""
Get aggregate summary metrics for clusters.
Returns total keywords count and total volume across all clusters (unfiltered).
Used for header metrics display.
"""
from django.db.models import Sum, Count, Case, When, F, IntegerField
queryset = self.get_queryset()
# Get cluster IDs
cluster_ids = list(queryset.values_list('id', flat=True))
# Aggregate keyword stats across all clusters
keyword_stats = (
Keywords.objects
.filter(cluster_id__in=cluster_ids)
.aggregate(
total_keywords=Count('id'),
total_volume=Sum(
Case(
When(volume_override__isnull=False, then=F('volume_override')),
default=F('seed_keyword__volume'),
output_field=IntegerField()
)
)
)
)
return success_response(
data={
'total_clusters': len(cluster_ids),
'total_keywords': keyword_stats['total_keywords'] or 0,
'total_volume': keyword_stats['total_volume'] or 0,
},
request=request
)
@action(detail=False, methods=['POST'], url_path='bulk_delete', url_name='bulk_delete')
def bulk_delete(self, request):
"""Bulk delete clusters"""

View File

@@ -371,16 +371,16 @@ class GlobalModuleSettings(models.Model):
help_text="Enable Automation module platform-wide"
)
site_builder_enabled = models.BooleanField(
default=True,
help_text="Enable Site Builder module platform-wide"
default=False, # DEPRECATED: SiteBuilder module is disabled - Phase 2 feature
help_text="Enable Site Builder module platform-wide (DEPRECATED)"
)
linker_enabled = models.BooleanField(
default=True,
help_text="Enable Linker module platform-wide"
default=False, # Phase 2 feature - not active
help_text="Enable Linker module platform-wide (Phase 2)"
)
optimizer_enabled = models.BooleanField(
default=True,
help_text="Enable Optimizer module platform-wide"
default=False, # Phase 2 feature - not active
help_text="Enable Optimizer module platform-wide (Phase 2)"
)
publisher_enabled = models.BooleanField(
default=True,

View File

@@ -0,0 +1,59 @@
"""
Migration: Disable SiteBuilder, Linker, Optimizer by default
These modules are deprecated (SiteBuilder) or Phase 2 features (Linker, Optimizer).
This migration updates defaults and sets existing records to disabled.
"""
from django.db import migrations
def disable_phase2_modules(apps, schema_editor):
"""
Disable SiteBuilder, Linker, and Optimizer in existing settings records.
These are Phase 2 features not currently active.
"""
GlobalModuleSettings = apps.get_model('system', 'GlobalModuleSettings')
ModuleEnableSettings = apps.get_model('system', 'ModuleEnableSettings')
# Update GlobalModuleSettings (singleton pk=1)
GlobalModuleSettings.objects.filter(pk=1).update(
site_builder_enabled=False,
linker_enabled=False,
optimizer_enabled=False
)
# Update all ModuleEnableSettings (per-account settings)
ModuleEnableSettings.objects.all().update(
site_builder_enabled=False,
linker_enabled=False,
optimizer_enabled=False
)
def reverse_migration(apps, schema_editor):
"""Re-enable modules (reverting to old defaults)"""
GlobalModuleSettings = apps.get_model('system', 'GlobalModuleSettings')
ModuleEnableSettings = apps.get_model('system', 'ModuleEnableSettings')
GlobalModuleSettings.objects.filter(pk=1).update(
site_builder_enabled=True,
linker_enabled=True,
optimizer_enabled=True
)
ModuleEnableSettings.objects.all().update(
site_builder_enabled=True,
linker_enabled=True,
optimizer_enabled=True
)
class Migration(migrations.Migration):
dependencies = [
('system', '0010_globalmodulesettings_and_more'),
]
operations = [
migrations.RunPython(disable_phase2_modules, reverse_migration),
]

View File

@@ -98,9 +98,9 @@ class ModuleEnableSettings(AccountBaseModel):
writer_enabled = models.BooleanField(default=True, help_text="Enable Writer module")
thinker_enabled = models.BooleanField(default=True, help_text="Enable Thinker module")
automation_enabled = models.BooleanField(default=True, help_text="Enable Automation module")
site_builder_enabled = models.BooleanField(default=True, help_text="Enable Site Builder module")
linker_enabled = models.BooleanField(default=True, help_text="Enable Linker module")
optimizer_enabled = models.BooleanField(default=True, help_text="Enable Optimizer module")
site_builder_enabled = models.BooleanField(default=False, help_text="Enable Site Builder module (DEPRECATED)")
linker_enabled = models.BooleanField(default=False, help_text="Enable Linker module (Phase 2)")
optimizer_enabled = models.BooleanField(default=False, help_text="Enable Optimizer module (Phase 2)")
publisher_enabled = models.BooleanField(default=True, help_text="Enable Publisher module")
class Meta: