master - part 2
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user