fitlers fixes

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-15 06:03:06 +00:00
parent 75785aa642
commit 51292bb1b3
17 changed files with 895 additions and 99 deletions

View File

@@ -776,9 +776,12 @@ class KeywordViewSet(SiteSectorModelViewSet):
cluster_filter = request.query_params.get('cluster_id')
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')
# Base queryset for each filter option calculation
# For countries: apply status, cluster, difficulty filters
# For countries: apply status, cluster, difficulty, volume, search filters
countries_qs = queryset
if status_filter:
countries_qs = countries_qs.filter(status=status_filter)
@@ -800,8 +803,23 @@ class KeywordViewSet(SiteSectorModelViewSet):
)
except (ValueError, TypeError):
pass
if volume_min is not None:
try:
countries_qs = countries_qs.filter(seed_keyword__volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
countries_qs = countries_qs.filter(seed_keyword__volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
countries_qs = countries_qs.filter(
Q(seed_keyword__keyword__icontains=search_term) |
Q(cluster__name__icontains=search_term)
)
# For statuses: apply country, cluster, difficulty filters
# For statuses: apply country, cluster, difficulty, volume, search filters
statuses_qs = queryset
if country_filter:
statuses_qs = statuses_qs.filter(seed_keyword__country=country_filter)
@@ -823,8 +841,23 @@ class KeywordViewSet(SiteSectorModelViewSet):
)
except (ValueError, TypeError):
pass
if volume_min is not None:
try:
statuses_qs = statuses_qs.filter(seed_keyword__volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
statuses_qs = statuses_qs.filter(seed_keyword__volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
statuses_qs = statuses_qs.filter(
Q(seed_keyword__keyword__icontains=search_term) |
Q(cluster__name__icontains=search_term)
)
# For clusters: apply status, country, difficulty filters
# For clusters: apply status, country, difficulty, volume, search filters
clusters_qs = queryset
if status_filter:
clusters_qs = clusters_qs.filter(status=status_filter)
@@ -846,8 +879,23 @@ class KeywordViewSet(SiteSectorModelViewSet):
)
except (ValueError, TypeError):
pass
if volume_min is not None:
try:
clusters_qs = clusters_qs.filter(seed_keyword__volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
clusters_qs = clusters_qs.filter(seed_keyword__volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
clusters_qs = clusters_qs.filter(
Q(seed_keyword__keyword__icontains=search_term) |
Q(cluster__name__icontains=search_term)
)
# For difficulties: apply status, country, cluster filters
# For difficulties: apply status, country, cluster, volume, search filters
difficulties_qs = queryset
if status_filter:
difficulties_qs = difficulties_qs.filter(status=status_filter)
@@ -855,6 +903,21 @@ class KeywordViewSet(SiteSectorModelViewSet):
difficulties_qs = difficulties_qs.filter(seed_keyword__country=country_filter)
if cluster_filter:
difficulties_qs = difficulties_qs.filter(cluster_id=cluster_filter)
if volume_min is not None:
try:
difficulties_qs = difficulties_qs.filter(seed_keyword__volume__gte=int(volume_min))
except (ValueError, TypeError):
pass
if volume_max is not None:
try:
difficulties_qs = difficulties_qs.filter(seed_keyword__volume__lte=int(volume_max))
except (ValueError, TypeError):
pass
if search_term:
difficulties_qs = difficulties_qs.filter(
Q(seed_keyword__keyword__icontains=search_term) |
Q(cluster__name__icontains=search_term)
)
# Get distinct countries
countries = list(set(countries_qs.values_list('seed_keyword__country', flat=True)))
@@ -1259,18 +1322,56 @@ class ClusterViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['get'], url_path='filter_options', url_name='filter_options')
def filter_options(self, request):
"""
Get distinct filter values from current data.
Returns only statuses that exist in the current site's clusters.
Get distinct filter values from current data with cascading filter support.
Returns only statuses and difficulties that exist based on other active filters.
"""
import logging
from django.db.models import Q, Sum, Avg, Case, When, F, IntegerField
logger = logging.getLogger(__name__)
try:
# Start with base queryset (already has volume/difficulty annotations from get_queryset)
queryset = self.get_queryset()
# Get filter parameters for cascading
status_filter = request.query_params.get('status', '')
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 = request.query_params.get('search', '')
# ===== GET STATUS OPTIONS =====
# Apply OTHER filters (exclude status) to get valid status options
status_qs = queryset
if difficulty_min:
try:
status_qs = status_qs.filter(_annotated_difficulty__gte=float(difficulty_min))
except ValueError:
pass
if difficulty_max:
try:
status_qs = status_qs.filter(_annotated_difficulty__lte=float(difficulty_max))
except ValueError:
pass
if volume_min:
try:
status_qs = status_qs.filter(_annotated_volume__gte=int(volume_min))
except ValueError:
pass
if volume_max:
try:
status_qs = status_qs.filter(_annotated_volume__lte=int(volume_max))
except ValueError:
pass
if search:
status_qs = status_qs.filter(
Q(name__icontains=search) | Q(description__icontains=search)
)
# Get distinct statuses
statuses = list(set(queryset.values_list('status', flat=True)))
statuses = list(set(status_qs.values_list('status', flat=True)))
statuses = sorted([s for s in statuses if s])
status_labels = {
'new': 'New',
@@ -1281,9 +1382,60 @@ class ClusterViewSet(SiteSectorModelViewSet):
for s in statuses
]
# ===== GET DIFFICULTY OPTIONS =====
# Apply OTHER filters (exclude difficulty) to get valid difficulty options
difficulty_qs = queryset
if status_filter:
difficulty_qs = difficulty_qs.filter(status=status_filter)
if volume_min:
try:
difficulty_qs = difficulty_qs.filter(_annotated_volume__gte=int(volume_min))
except ValueError:
pass
if volume_max:
try:
difficulty_qs = difficulty_qs.filter(_annotated_volume__lte=int(volume_max))
except ValueError:
pass
if search:
difficulty_qs = difficulty_qs.filter(
Q(name__icontains=search) | Q(description__icontains=search)
)
# Get raw difficulty values (0-100) from annotated field
difficulty_values = difficulty_qs.exclude(_annotated_difficulty__isnull=True).values_list('_annotated_difficulty', flat=True)
# Map raw difficulty (0-100) to 1-5 scale and find unique values
difficulty_levels = set()
for d in difficulty_values:
if d is not None:
if d <= 10:
difficulty_levels.add(1)
elif d <= 30:
difficulty_levels.add(2)
elif d <= 50:
difficulty_levels.add(3)
elif d <= 70:
difficulty_levels.add(4)
else:
difficulty_levels.add(5)
difficulty_labels = {
1: '1 - Very Easy',
2: '2 - Easy',
3: '3 - Medium',
4: '4 - Hard',
5: '5 - Very Hard',
}
difficulty_options = [
{'value': str(d), 'label': difficulty_labels[d]}
for d in sorted(difficulty_levels)
]
return success_response(
data={
'statuses': status_options,
'difficulties': difficulty_options,
},
request=request
)
@@ -1476,18 +1628,40 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['get'], url_path='filter_options', url_name='filter_options')
def filter_options(self, request):
"""
Get distinct filter values from current data.
Returns only values that exist in the current site's content ideas.
Get distinct filter values from current data with cascading filter support.
Returns only values that exist based on other active filters.
"""
import logging
from django.db.models import Q
logger = logging.getLogger(__name__)
try:
queryset = self.get_queryset()
# Get distinct statuses
statuses = list(set(queryset.values_list('status', flat=True)))
# Get filter parameters for cascading
status_filter = request.query_params.get('status', '')
content_type_filter = request.query_params.get('content_type', '')
content_structure_filter = request.query_params.get('content_structure', '')
cluster_filter = request.query_params.get('cluster', '')
search = request.query_params.get('search', '')
# Apply search filter to all options if provided
base_qs = queryset
if search:
base_qs = base_qs.filter(
Q(idea_title__icontains=search) | Q(description__icontains=search)
)
# Get statuses (filtered by type, structure, cluster)
status_qs = base_qs
if content_type_filter:
status_qs = status_qs.filter(content_type=content_type_filter)
if content_structure_filter:
status_qs = status_qs.filter(content_structure=content_structure_filter)
if cluster_filter:
status_qs = status_qs.filter(keyword_cluster_id=cluster_filter)
statuses = list(set(status_qs.values_list('status', flat=True)))
statuses = sorted([s for s in statuses if s])
status_labels = {
'new': 'New',
@@ -1499,8 +1673,15 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
for s in statuses
]
# Get distinct content_types
content_types = list(set(queryset.values_list('content_type', flat=True)))
# Get content_types (filtered by status, structure, cluster)
type_qs = base_qs
if status_filter:
type_qs = type_qs.filter(status=status_filter)
if content_structure_filter:
type_qs = type_qs.filter(content_structure=content_structure_filter)
if cluster_filter:
type_qs = type_qs.filter(keyword_cluster_id=cluster_filter)
content_types = list(set(type_qs.values_list('content_type', flat=True)))
content_types = sorted([t for t in content_types if t])
type_labels = {
'post': 'Post',
@@ -1513,8 +1694,15 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
for t in content_types
]
# Get distinct content_structures
structures = list(set(queryset.values_list('content_structure', flat=True)))
# Get content_structures (filtered by status, type, cluster)
structure_qs = base_qs
if status_filter:
structure_qs = structure_qs.filter(status=status_filter)
if content_type_filter:
structure_qs = structure_qs.filter(content_type=content_type_filter)
if cluster_filter:
structure_qs = structure_qs.filter(keyword_cluster_id=cluster_filter)
structures = list(set(structure_qs.values_list('content_structure', flat=True)))
structures = sorted([s for s in structures if s])
structure_labels = {
'article': 'Article', 'guide': 'Guide', 'comparison': 'Comparison',
@@ -1529,9 +1717,16 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
for s in structures
]
# Get distinct clusters with ideas
# Get distinct clusters (filtered by status, type, structure)
cluster_qs = base_qs
if status_filter:
cluster_qs = cluster_qs.filter(status=status_filter)
if content_type_filter:
cluster_qs = cluster_qs.filter(content_type=content_type_filter)
if content_structure_filter:
cluster_qs = cluster_qs.filter(content_structure=content_structure_filter)
cluster_ids = list(set(
queryset.exclude(keyword_cluster_id__isnull=True)
cluster_qs.exclude(keyword_cluster_id__isnull=True)
.values_list('keyword_cluster_id', flat=True)
))
clusters = Clusters.objects.filter(id__in=cluster_ids).values('id', 'name').order_by('name')

View File

@@ -272,6 +272,167 @@ class TasksViewSet(SiteSectorModelViewSet):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=False, methods=['get'], url_path='filter_options', url_name='filter_options')
def filter_options(self, request):
"""
Get distinct filter values from current data with cascading support.
Returns only values that exist based on other active filters.
"""
import logging
from django.db.models import Q
logger = logging.getLogger(__name__)
try:
queryset = self.get_queryset()
# Get filter parameters for cascading
status_filter = request.query_params.get('status', '')
content_type_filter = request.query_params.get('content_type', '')
content_structure_filter = request.query_params.get('content_structure', '')
cluster_filter = request.query_params.get('cluster', '')
source_filter = request.query_params.get('source', '')
search = request.query_params.get('search', '')
# Apply search to base queryset
base_qs = queryset
if search:
base_qs = base_qs.filter(
Q(title__icontains=search) | Q(keywords__icontains=search)
)
# Get statuses (filtered by other fields)
status_qs = base_qs
if content_type_filter:
status_qs = status_qs.filter(content_type=content_type_filter)
if content_structure_filter:
status_qs = status_qs.filter(content_structure=content_structure_filter)
if cluster_filter:
status_qs = status_qs.filter(cluster_id=cluster_filter)
if source_filter:
status_qs = status_qs.filter(source=source_filter)
statuses = list(set(status_qs.values_list('status', flat=True)))
statuses = sorted([s for s in statuses if s])
status_labels = {
'queued': 'Queued',
'processing': 'Processing',
'completed': 'Completed',
'failed': 'Failed',
}
status_options = [
{'value': s, 'label': status_labels.get(s, s.title())}
for s in statuses
]
# Get content types (filtered by other fields)
type_qs = base_qs
if status_filter:
type_qs = type_qs.filter(status=status_filter)
if content_structure_filter:
type_qs = type_qs.filter(content_structure=content_structure_filter)
if cluster_filter:
type_qs = type_qs.filter(cluster_id=cluster_filter)
if source_filter:
type_qs = type_qs.filter(source=source_filter)
content_types = list(set(type_qs.values_list('content_type', flat=True)))
content_types = sorted([t for t in content_types if t])
type_labels = {
'post': 'Post',
'page': 'Page',
'product': 'Product',
'taxonomy': 'Taxonomy',
}
content_type_options = [
{'value': t, 'label': type_labels.get(t, t.title())}
for t in content_types
]
# Get content structures (filtered by other fields)
structure_qs = base_qs
if status_filter:
structure_qs = structure_qs.filter(status=status_filter)
if content_type_filter:
structure_qs = structure_qs.filter(content_type=content_type_filter)
if cluster_filter:
structure_qs = structure_qs.filter(cluster_id=cluster_filter)
if source_filter:
structure_qs = structure_qs.filter(source=source_filter)
structures = list(set(structure_qs.values_list('content_structure', flat=True)))
structures = sorted([s for s in structures if s])
structure_labels = {
'article': 'Article', 'guide': 'Guide', 'comparison': 'Comparison',
'review': 'Review', 'listicle': 'Listicle', 'landing_page': 'Landing Page',
'business_page': 'Business Page', 'service_page': 'Service Page',
'general': 'General', 'cluster_hub': 'Cluster Hub',
'product_page': 'Product Page', 'category_archive': 'Category Archive',
'tag_archive': 'Tag Archive', 'attribute_archive': 'Attribute Archive',
}
content_structure_options = [
{'value': s, 'label': structure_labels.get(s, s.replace('_', ' ').title())}
for s in structures
]
# Get clusters (filtered by other fields)
cluster_qs = base_qs
if status_filter:
cluster_qs = cluster_qs.filter(status=status_filter)
if content_type_filter:
cluster_qs = cluster_qs.filter(content_type=content_type_filter)
if content_structure_filter:
cluster_qs = cluster_qs.filter(content_structure=content_structure_filter)
if source_filter:
cluster_qs = cluster_qs.filter(source=source_filter)
from igny8_core.modules.planner.models import Clusters
cluster_ids = list(set(
cluster_qs.exclude(cluster_id__isnull=True)
.values_list('cluster_id', flat=True)
))
clusters = Clusters.objects.filter(id__in=cluster_ids).values('id', 'name').order_by('name')
cluster_options = [
{'value': str(c['id']), 'label': c['name']}
for c in clusters
]
# Get sources (filtered by other fields)
source_qs = base_qs
if status_filter:
source_qs = source_qs.filter(status=status_filter)
if content_type_filter:
source_qs = source_qs.filter(content_type=content_type_filter)
if content_structure_filter:
source_qs = source_qs.filter(content_structure=content_structure_filter)
if cluster_filter:
source_qs = source_qs.filter(cluster_id=cluster_filter)
sources = list(set(source_qs.values_list('source', flat=True)))
sources = sorted([s for s in sources if s])
source_labels = {
'manual': 'Manual',
'planner': 'Planner',
'ai': 'AI',
}
source_options = [
{'value': s, 'label': source_labels.get(s, s.title())}
for s in sources
]
return success_response(
data={
'statuses': status_options,
'content_types': content_type_options,
'content_structures': content_structure_options,
'clusters': cluster_options,
'sources': source_options,
},
request=request
)
except Exception as e:
logger.error(f"Error in filter_options: {str(e)}", exc_info=True)
return error_response(
error=f'Failed to fetch filter options: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@extend_schema_view(