fitlers fixes
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user