keywrods library fixes

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-18 20:55:02 +00:00
parent 4cf27fa875
commit 05bc433c80
8 changed files with 435 additions and 520 deletions

View File

@@ -869,6 +869,8 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
difficulty_max = self.request.query_params.get('difficulty_max')
volume_min = self.request.query_params.get('volume_min')
volume_max = self.request.query_params.get('volume_max')
site_id = self.request.query_params.get('site_id')
available_only = self.request.query_params.get('available_only')
if industry_id:
queryset = queryset.filter(industry_id=industry_id)
@@ -902,6 +904,19 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
queryset = queryset.filter(volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
# Availability filter - exclude keywords already added to the site
if available_only and str(available_only).lower() in ['true', '1', 'yes']:
if site_id:
try:
from igny8_core.business.planning.models import Keywords
attached_ids = Keywords.objects.filter(
site_id=site_id,
seed_keyword__isnull=False
).values_list('seed_keyword_id', flat=True)
queryset = queryset.exclude(id__in=attached_ids)
except Exception:
pass
return queryset
@@ -1152,6 +1167,9 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
# 3. High Volume (>= 10K) - simple threshold
high_volume_count = base_qs.filter(volume__gte=10000).count()
# 3b. Mid Volume (5K-10K)
mid_volume_count = base_qs.filter(volume__gte=5000, volume__lt=10000).count()
# 4. Premium Traffic with dynamic fallback (50K -> 25K -> 10K)
premium_thresholds = [50000, 25000, 10000]
@@ -1182,6 +1200,7 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
'total': {'count': total_count},
'available': {'count': available_count},
'high_volume': {'count': high_volume_count, 'threshold': 10000},
'mid_volume': {'count': mid_volume_count, 'threshold': 5000},
'premium_traffic': premium_result,
'long_tail': long_tail_result,
'quick_wins': quick_wins_result,
@@ -1201,6 +1220,7 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
sector_available = count_available(sector_qs)
sector_high_volume = sector_qs.filter(volume__gte=10000).count()
sector_mid_volume = sector_qs.filter(volume__gte=5000, volume__lt=10000).count()
sector_premium = get_count_with_fallback(sector_qs, premium_thresholds)
sector_long_tail_base = sector_qs.filter(keyword__regex=r'^(\S+\s+){3,}\S+$')
@@ -1218,6 +1238,7 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
'total': {'count': sector_total},
'available': {'count': sector_available},
'high_volume': {'count': sector_high_volume, 'threshold': 10000},
'mid_volume': {'count': sector_mid_volume, 'threshold': 5000},
'premium_traffic': sector_premium,
'long_tail': sector_long_tail,
'quick_wins': sector_quick_wins,
@@ -1243,12 +1264,20 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
"""
Get cascading filter options for Keywords Library.
Returns industries, sectors (filtered by industry), and available filter values.
Supports cascading options based on current filters.
"""
from django.db.models import Count, Min, Max, Q
try:
industry_id = request.query_params.get('industry_id')
sector_id = request.query_params.get('sector_id')
country_filter = request.query_params.get('country')
difficulty_min = request.query_params.get('difficulty_min')
difficulty_max = request.query_params.get('difficulty_max')
volume_min = request.query_params.get('volume_min')
volume_max = request.query_params.get('volume_max')
search_term = request.query_params.get('search')
# Get industries with keyword counts
industries = Industry.objects.annotate(
keyword_count=Count('seed_keywords', filter=Q(seed_keywords__is_active=True))
@@ -1276,31 +1305,120 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
'slug': sec.slug,
'keyword_count': sec.keyword_count,
} for sec in sectors]
# Get difficulty range
difficulty_range = SeedKeyword.objects.filter(is_active=True).aggregate(
# Base queryset for cascading options
base_qs = SeedKeyword.objects.filter(is_active=True)
if industry_id:
base_qs = base_qs.filter(industry_id=industry_id)
if sector_id:
base_qs = base_qs.filter(sector_id=sector_id)
# Countries options - apply all filters except country itself
countries_qs = base_qs
if difficulty_min is not None:
try:
countries_qs = countries_qs.filter(difficulty__gte=int(difficulty_min))
except (ValueError, TypeError):
pass
if difficulty_max is not None:
try:
countries_qs = countries_qs.filter(difficulty__lte=int(difficulty_max))
except (ValueError, TypeError):
pass
if volume_min is not None:
try:
countries_qs = countries_qs.filter(volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
countries_qs = countries_qs.filter(volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
countries_qs = countries_qs.filter(keyword__icontains=search_term)
countries = countries_qs.values('country').annotate(
keyword_count=Count('id')
).order_by('country')
country_label_map = dict(SeedKeyword.COUNTRY_CHOICES)
countries_data = [{
'value': c['country'],
'label': country_label_map.get(c['country'], c['country']),
'keyword_count': c['keyword_count'],
} for c in countries if c['country']]
# Difficulty options - apply all filters except difficulty itself
difficulty_qs = base_qs
if country_filter:
difficulty_qs = difficulty_qs.filter(country=country_filter)
if volume_min is not None:
try:
difficulty_qs = difficulty_qs.filter(volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
difficulty_qs = difficulty_qs.filter(volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
difficulty_qs = difficulty_qs.filter(keyword__icontains=search_term)
difficulty_ranges = [
(1, 'Very Easy', 0, 10),
(2, 'Easy', 11, 30),
(3, 'Medium', 31, 50),
(4, 'Hard', 51, 70),
(5, 'Very Hard', 71, 100),
]
difficulty_levels = []
for level, label, min_val, max_val in difficulty_ranges:
count = difficulty_qs.filter(
difficulty__gte=min_val,
difficulty__lte=max_val
).count()
if count > 0:
difficulty_levels.append({
'level': level,
'label': label,
'backend_range': [min_val, max_val],
'keyword_count': count,
})
# Difficulty range (filtered by current non-difficulty filters)
difficulty_range = difficulty_qs.aggregate(
min_difficulty=Min('difficulty'),
max_difficulty=Max('difficulty')
)
# Get volume range
volume_range = SeedKeyword.objects.filter(is_active=True).aggregate(
# Volume range (filtered by current non-volume filters)
volume_qs = base_qs
if country_filter:
volume_qs = volume_qs.filter(country=country_filter)
if difficulty_min is not None:
try:
volume_qs = volume_qs.filter(difficulty__gte=int(difficulty_min))
except (ValueError, TypeError):
pass
if difficulty_max is not None:
try:
volume_qs = volume_qs.filter(difficulty__lte=int(difficulty_max))
except (ValueError, TypeError):
pass
if search_term:
volume_qs = volume_qs.filter(keyword__icontains=search_term)
volume_range = volume_qs.aggregate(
min_volume=Min('volume'),
max_volume=Max('volume')
)
# Difficulty levels for frontend (maps to backend values)
difficulty_levels = [
{'level': 1, 'label': 'Very Easy', 'backend_range': [0, 20]},
{'level': 2, 'label': 'Easy', 'backend_range': [21, 40]},
{'level': 3, 'label': 'Medium', 'backend_range': [41, 60]},
{'level': 4, 'label': 'Hard', 'backend_range': [61, 80]},
{'level': 5, 'label': 'Very Hard', 'backend_range': [81, 100]},
]
data = {
'industries': industries_data,
'sectors': sectors_data,
'countries': countries_data,
'difficulty': {
'range': difficulty_range,
'levels': difficulty_levels,