latest 2 days work excluding problemetic code

This commit is contained in:
alorig
2026-01-12 14:23:05 +05:00
parent b390e02aa5
commit e9f02f5e9f
6 changed files with 22 additions and 284 deletions

View File

@@ -1,65 +1,11 @@
""" """
Base Admin Mixins for account and site/sector filtering. Base Admin Mixins for account and site/sector filtering.
ADMIN DELETE FIX:
- Admin can delete anything without 500 errors
- Simple delete that just works
""" """
from django.contrib import admin, messages from django.contrib import admin, messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import models, transaction from django.db import models, transaction
class AdminDeleteMixin:
"""
Mixin that provides a simple working delete action for admin.
"""
def get_actions(self, request):
"""Replace default delete_selected with simple working version"""
actions = super().get_actions(request)
# Remove Django's default delete that causes 500 errors
if 'delete_selected' in actions:
del actions['delete_selected']
# Add our simple delete action
actions['simple_delete'] = (
self.__class__.simple_delete,
'simple_delete',
'Delete selected items'
)
return actions
def simple_delete(self, request, queryset):
"""
Simple delete that just works. Deletes items one by one with error handling.
"""
success = 0
errors = []
for obj in queryset:
try:
# Get object info before delete
try:
obj_str = str(obj)
except Exception:
obj_str = f'#{obj.pk}'
# Just delete it - let the model handle soft vs hard delete
obj.delete()
success += 1
except Exception as e:
errors.append(f'{obj_str}: {str(e)[:50]}')
if success:
self.message_user(request, f'Deleted {success} item(s).', messages.SUCCESS)
if errors:
self.message_user(request, f'Failed to delete {len(errors)}: {"; ".join(errors[:3])}', messages.ERROR)
class AccountAdminMixin: class AccountAdminMixin:
"""Mixin for admin classes that need account filtering""" """Mixin for admin classes that need account filtering"""
@@ -171,19 +117,16 @@ class SiteSectorAdminMixin:
from unfold.admin import ModelAdmin as UnfoldModelAdmin from unfold.admin import ModelAdmin as UnfoldModelAdmin
class Igny8ModelAdmin(AdminDeleteMixin, UnfoldModelAdmin): class Igny8ModelAdmin(UnfoldModelAdmin):
""" """
Custom ModelAdmin that: Custom ModelAdmin that:
1. Fixes delete actions (no 500 errors, bypasses PROTECT if needed) 1. Ensures sidebar_navigation is set correctly on ALL pages
2. Ensures sidebar_navigation is set correctly on ALL pages 2. Uses standard Django filters
3. Uses dropdown filters with Apply button
AdminDeleteMixin provides:
- simple_delete: Safe delete (soft delete if available)
""" """
# Enable "Apply Filters" button for dropdown filters # Standard Django filters
list_filter_submit = True pass
def _inject_sidebar_context(self, request, extra_context=None): def _inject_sidebar_context(self, request, extra_context=None):
"""Helper to inject custom sidebar into context""" """Helper to inject custom sidebar into context"""

View File

@@ -5,10 +5,6 @@ from django import forms
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import (
RelatedDropdownFilter,
ChoicesDropdownFilter,
)
from simple_history.admin import SimpleHistoryAdmin from simple_history.admin import SimpleHistoryAdmin
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword, PasswordResetToken from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword, PasswordResetToken
@@ -132,12 +128,7 @@ class PlanAdmin(ImportExportMixin, Igny8ModelAdmin):
resource_class = PlanResource resource_class = PlanResource
"""Plan admin - Global, no account filtering needed""" """Plan admin - Global, no account filtering needed"""
list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_ahrefs_queries', 'included_credits', 'is_active', 'is_featured'] list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_ahrefs_queries', 'included_credits', 'is_active', 'is_featured']
list_filter = [ list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
('is_active', ChoicesDropdownFilter),
('billing_cycle', ChoicesDropdownFilter),
('is_internal', ChoicesDropdownFilter),
('is_featured', ChoicesDropdownFilter),
]
search_fields = ['name', 'slug'] search_fields = ['name', 'slug']
readonly_fields = ['created_at'] readonly_fields = ['created_at']
actions = [ actions = [
@@ -212,10 +203,7 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
resource_class = AccountResource resource_class = AccountResource
form = AccountAdminForm form = AccountAdminForm
list_display = ['name', 'slug', 'owner', 'plan', 'status', 'health_indicator', 'credits', 'created_at'] list_display = ['name', 'slug', 'owner', 'plan', 'status', 'health_indicator', 'credits', 'created_at']
list_filter = [ list_filter = ['status', 'plan']
('status', ChoicesDropdownFilter),
('plan', RelatedDropdownFilter),
]
search_fields = ['name', 'slug'] search_fields = ['name', 'slug']
readonly_fields = ['created_at', 'updated_at', 'health_indicator', 'health_details'] readonly_fields = ['created_at', 'updated_at', 'health_indicator', 'health_details']
actions = [ actions = [
@@ -515,9 +503,7 @@ class SubscriptionResource(resources.ModelResource):
class SubscriptionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin): class SubscriptionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = SubscriptionResource resource_class = SubscriptionResource
list_display = ['account', 'status', 'current_period_start', 'current_period_end'] list_display = ['account', 'status', 'current_period_start', 'current_period_end']
list_filter = [ list_filter = ['status']
('status', ChoicesDropdownFilter),
]
search_fields = ['account__name', 'stripe_subscription_id'] search_fields = ['account__name', 'stripe_subscription_id']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']
actions = [ actions = [
@@ -635,13 +621,7 @@ class SiteResource(resources.ModelResource):
class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin): class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = SiteResource resource_class = SiteResource
list_display = ['name', 'slug', 'account', 'industry', 'domain', 'status', 'is_active', 'get_api_key_status', 'get_sectors_count'] list_display = ['name', 'slug', 'account', 'industry', 'domain', 'status', 'is_active', 'get_api_key_status', 'get_sectors_count']
list_filter = [ list_filter = ['status', 'is_active', 'account', 'industry', 'hosting_type']
('status', ChoicesDropdownFilter),
('is_active', ChoicesDropdownFilter),
('account', RelatedDropdownFilter),
('industry', RelatedDropdownFilter),
('hosting_type', ChoicesDropdownFilter),
]
search_fields = ['name', 'slug', 'domain', 'industry__name'] search_fields = ['name', 'slug', 'domain', 'industry__name']
readonly_fields = ['created_at', 'updated_at', 'get_api_key_display'] readonly_fields = ['created_at', 'updated_at', 'get_api_key_display']
inlines = [SectorInline] inlines = [SectorInline]
@@ -784,12 +764,7 @@ class SectorResource(resources.ModelResource):
class SectorAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin): class SectorAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = SectorResource resource_class = SectorResource
list_display = ['name', 'slug', 'site', 'industry_sector', 'get_industry', 'status', 'is_active', 'get_keywords_count', 'get_clusters_count'] list_display = ['name', 'slug', 'site', 'industry_sector', 'get_industry', 'status', 'is_active', 'get_keywords_count', 'get_clusters_count']
list_filter = [ list_filter = ['status', 'is_active', 'site', 'industry_sector__industry']
('status', ChoicesDropdownFilter),
('is_active', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('industry_sector__industry', RelatedDropdownFilter),
]
search_fields = ['name', 'slug', 'site__name', 'industry_sector__name'] search_fields = ['name', 'slug', 'site__name', 'industry_sector__name']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']
actions = [ actions = [
@@ -1006,12 +981,7 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
resource_class = SeedKeywordResource resource_class = SeedKeywordResource
"""SeedKeyword admin - Global reference data, no account filtering""" """SeedKeyword admin - Global reference data, no account filtering"""
list_display = ['keyword', 'industry', 'sector', 'volume', 'difficulty', 'country', 'is_active', 'created_at'] list_display = ['keyword', 'industry', 'sector', 'volume', 'difficulty', 'country', 'is_active', 'created_at']
list_filter = [ list_filter = ['is_active', 'industry', 'sector', 'country']
('is_active', ChoicesDropdownFilter),
('industry', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('country', ChoicesDropdownFilter),
]
search_fields = ['keyword'] search_fields = ['keyword']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']
actions = [ actions = [
@@ -1019,7 +989,6 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
'bulk_deactivate', 'bulk_deactivate',
'bulk_update_country', 'bulk_update_country',
] ]
# Delete is handled by AdminDeleteMixin in base Igny8ModelAdmin
fieldsets = ( fieldsets = (
('Keyword Info', { ('Keyword Info', {
@@ -1119,12 +1088,7 @@ class UserAdmin(ExportMixin, BaseUserAdmin, Igny8ModelAdmin):
""" """
resource_class = UserResource resource_class = UserResource
list_display = ['email', 'username', 'account', 'role', 'is_active', 'is_staff', 'created_at'] list_display = ['email', 'username', 'account', 'role', 'is_active', 'is_staff', 'created_at']
list_filter = [ list_filter = ['role', 'account', 'is_active', 'is_staff']
('role', ChoicesDropdownFilter),
('account', RelatedDropdownFilter),
('is_active', ChoicesDropdownFilter),
('is_staff', ChoicesDropdownFilter),
]
search_fields = ['email', 'username'] search_fields = ['email', 'username']
readonly_fields = ['created_at', 'updated_at', 'password_display'] readonly_fields = ['created_at', 'updated_at', 'password_display']

View File

@@ -5,12 +5,6 @@ from django.contrib import admin
from django.utils.html import format_html from django.utils.html import format_html
from django.contrib import messages from django.contrib import messages
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from unfold.contrib.filters.admin import (
RelatedDropdownFilter,
ChoicesDropdownFilter,
DropdownFilter,
RangeDateFilter,
)
from simple_history.admin import SimpleHistoryAdmin from simple_history.admin import SimpleHistoryAdmin
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
from igny8_core.business.billing.models import ( from igny8_core.business.billing.models import (
@@ -41,11 +35,7 @@ class CreditTransactionResource(resources.ModelResource):
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin): class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = CreditTransactionResource resource_class = CreditTransactionResource
list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at'] list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at']
list_filter = [ list_filter = ['transaction_type', 'created_at', 'account']
('transaction_type', ChoicesDropdownFilter),
('created_at', RangeDateFilter),
('account', RelatedDropdownFilter),
]
search_fields = ['description', 'account__name'] search_fields = ['description', 'account__name']
readonly_fields = ['created_at'] readonly_fields = ['created_at']
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
@@ -197,13 +187,7 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
'approved_by', 'approved_by',
'processed_at', 'processed_at',
] ]
list_filter = [ list_filter = ['status', 'payment_method', 'currency', 'created_at', 'processed_at']
('status', ChoicesDropdownFilter),
('payment_method', ChoicesDropdownFilter),
('currency', ChoicesDropdownFilter),
('created_at', RangeDateFilter),
('processed_at', RangeDateFilter),
]
search_fields = [ search_fields = [
'invoice__invoice_number', 'invoice__invoice_number',
'account__name', 'account__name',
@@ -668,12 +652,7 @@ class PlanLimitUsageAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
'period_display', 'period_display',
'created_at', 'created_at',
] ]
list_filter = [ list_filter = ['limit_type', 'period_start', 'period_end', 'account']
('limit_type', ChoicesDropdownFilter),
('period_start', RangeDateFilter),
('period_end', RangeDateFilter),
('account', RelatedDropdownFilter),
]
search_fields = ['account__name'] search_fields = ['account__name']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']
date_hierarchy = 'period_start' date_hierarchy = 'period_start'

View File

@@ -1,12 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.contrib import messages from django.contrib import messages
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from unfold.contrib.filters.admin import (
RangeDateFilter,
RangeNumericFilter,
RelatedDropdownFilter,
ChoicesDropdownFilter,
)
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
from .models import Keywords, Clusters, ContentIdeas from .models import Keywords, Clusters, ContentIdeas
from import_export.admin import ExportMixin, ImportExportMixin from import_export.admin import ExportMixin, ImportExportMixin
@@ -40,13 +34,7 @@ class ClustersAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
resource_class = ClustersResource resource_class = ClustersResource
list_display = ['name', 'site', 'sector', 'keywords_count', 'volume', 'status', 'created_at'] list_display = ['name', 'site', 'sector', 'keywords_count', 'volume', 'status', 'created_at']
list_select_related = ['site', 'sector', 'account'] list_select_related = ['site', 'sector', 'account']
list_filter = [ list_filter = ['status', 'site', 'sector', 'volume', 'created_at']
('status', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('volume', RangeNumericFilter),
('created_at', RangeDateFilter),
]
search_fields = ['name'] search_fields = ['name']
ordering = ['name'] ordering = ['name']
autocomplete_fields = ['site', 'sector'] autocomplete_fields = ['site', 'sector']
@@ -100,13 +88,7 @@ class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
list_display = ['get_keyword', 'seed_keyword', 'site', 'sector', 'cluster', 'get_volume', 'get_difficulty', 'get_country', 'status', 'created_at'] 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_editable = ['status'] # Enable inline editing for status
list_select_related = ['site', 'sector', 'cluster', 'seed_keyword', 'seed_keyword__industry', 'seed_keyword__sector', 'account'] list_select_related = ['site', 'sector', 'cluster', 'seed_keyword', 'seed_keyword__industry', 'seed_keyword__sector', 'account']
list_filter = [ list_filter = ['status', 'site', 'sector', 'cluster', 'created_at']
('status', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('cluster', RelatedDropdownFilter),
('created_at', RangeDateFilter),
]
search_fields = ['seed_keyword__keyword'] search_fields = ['seed_keyword__keyword']
ordering = ['-created_at'] ordering = ['-created_at']
autocomplete_fields = ['cluster', 'site', 'sector', 'seed_keyword'] autocomplete_fields = ['cluster', 'site', 'sector', 'seed_keyword']
@@ -114,6 +96,7 @@ class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
'bulk_assign_cluster', 'bulk_assign_cluster',
'bulk_set_status_active', 'bulk_set_status_active',
'bulk_set_status_inactive', 'bulk_set_status_inactive',
'bulk_soft_delete',
] ]
@admin.display(description='Keyword') @admin.display(description='Keyword')
@@ -242,16 +225,7 @@ class ContentIdeasAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin
resource_class = ContentIdeasResource resource_class = ContentIdeasResource
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'content_type', 'content_structure', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at'] list_display = ['idea_title', 'site', 'sector', 'description_preview', 'content_type', 'content_structure', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
list_select_related = ['site', 'sector', 'keyword_cluster', 'account'] list_select_related = ['site', 'sector', 'keyword_cluster', 'account']
list_filter = [ list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector', 'keyword_cluster', 'estimated_word_count', 'created_at']
('status', ChoicesDropdownFilter),
('content_type', ChoicesDropdownFilter),
('content_structure', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('keyword_cluster', RelatedDropdownFilter),
('estimated_word_count', RangeNumericFilter),
('created_at', RangeDateFilter),
]
search_fields = ['idea_title', 'target_keywords', 'description'] search_fields = ['idea_title', 'target_keywords', 'description']
ordering = ['-created_at'] ordering = ['-created_at']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']

View File

@@ -1,12 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.contrib import messages from django.contrib import messages
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import (
RangeDateFilter,
RangeNumericFilter,
RelatedDropdownFilter,
ChoicesDropdownFilter,
)
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
from .models import Tasks, Images, Content, ImagePrompts from .models import Tasks, Images, Content, ImagePrompts
from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap
@@ -39,15 +33,7 @@ class TasksAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
resource_class = TaskResource resource_class = TaskResource
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'status', 'cluster', 'created_at'] list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'status', 'cluster', 'created_at']
list_editable = ['status'] # Enable inline editing for status list_editable = ['status'] # Enable inline editing for status
list_filter = [ list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector', 'cluster', 'created_at']
('status', ChoicesDropdownFilter),
('content_type', ChoicesDropdownFilter),
('content_structure', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('cluster', RelatedDropdownFilter),
('created_at', RangeDateFilter),
]
search_fields = ['title', 'description'] search_fields = ['title', 'description']
ordering = ['-created_at'] ordering = ['-created_at']
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ['created_at', 'updated_at']
@@ -315,13 +301,7 @@ class ImagePromptsAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
resource_class = ImagePromptsResource resource_class = ImagePromptsResource
list_display = ['get_content_title', 'site', 'sector', 'image_type', 'get_prompt_preview', 'status', 'created_at'] list_display = ['get_content_title', 'site', 'sector', 'image_type', 'get_prompt_preview', 'status', 'created_at']
list_filter = [ list_filter = ['image_type', 'status', 'site', 'sector', 'created_at']
('image_type', ChoicesDropdownFilter),
('status', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('created_at', RangeDateFilter),
]
search_fields = ['content__title', 'prompt', 'caption'] search_fields = ['content__title', 'prompt', 'caption']
ordering = ['-created_at'] ordering = ['-created_at']
readonly_fields = ['get_content_title', 'site', 'sector', 'image_type', 'prompt', 'caption', readonly_fields = ['get_content_title', 'site', 'sector', 'image_type', 'prompt', 'caption',
@@ -450,17 +430,7 @@ class ContentResource(resources.ModelResource):
class ContentAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin): class ContentAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
resource_class = ContentResource resource_class = ContentResource
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'source', 'status', 'word_count', 'get_taxonomy_count', 'created_at'] list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'source', 'status', 'word_count', 'get_taxonomy_count', 'created_at']
list_filter = [ list_filter = ['status', 'content_type', 'content_structure', 'source', 'site', 'sector', 'cluster', 'word_count', 'created_at']
('status', ChoicesDropdownFilter),
('content_type', ChoicesDropdownFilter),
('content_structure', ChoicesDropdownFilter),
('source', ChoicesDropdownFilter),
('site', RelatedDropdownFilter),
('sector', RelatedDropdownFilter),
('cluster', RelatedDropdownFilter),
('word_count', RangeNumericFilter),
('created_at', RangeDateFilter),
]
search_fields = ['title', 'content_html', 'external_url', 'meta_title', 'primary_keyword'] search_fields = ['title', 'content_html', 'external_url', 'meta_title', 'primary_keyword']
ordering = ['-created_at'] ordering = ['-created_at']
readonly_fields = ['created_at', 'updated_at', 'word_count', 'get_tags_display', 'get_categories_display'] readonly_fields = ['created_at', 'updated_at', 'word_count', 'get_tags_display', 'get_categories_display']

View File

@@ -1,92 +0,0 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% trans 'Delete multiple objects' %}
</div>
{% endblock %}
{% block content %}
<div class="delete-confirmation">
{% if subtitle %}
<h1>{{ title }}</h1>
<p class="subtitle">{{ subtitle }}</p>
{% else %}
<h1>{{ title }}</h1>
{% endif %}
{% if can_delete_items %}
<div class="can-delete-section" style="margin: 20px 0; padding: 15px; background: #e8f5e9; border-left: 4px solid #4caf50;">
<h3 style="margin-top: 0; color: #2e7d32;">✅ Can Delete ({{ can_delete_items|length }} item{{ can_delete_items|length|pluralize }})</h3>
<p>The following seed keywords can be safely deleted:</p>
<ul>
{% for obj in can_delete_items %}
<li><strong>{{ obj.keyword }}</strong> ({{ obj.industry.name }} - {{ obj.sector.name }})</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if protected_items %}
<div class="protected-section" style="margin: 20px 0; padding: 15px; background: #fff3e0; border-left: 4px solid #ff9800;">
<h3 style="margin-top: 0; color: #e65100;">⚠️ Cannot Delete ({{ protected_items|length }} item{{ protected_items|length|pluralize }})</h3>
<p>The following seed keywords are being used by site keywords and <strong>cannot be deleted</strong>:</p>
<ul>
{% for item in protected_items %}
<li>
<strong>{{ item.object.keyword }}</strong> ({{ item.object.industry.name }} - {{ item.object.sector.name }})
<br>
<span style="color: #666; font-size: 0.9em;">
→ Used by <strong>{{ item.related_count }}</strong> keyword{{ item.related_count|pluralize }} on sites:
{% for site in item.sites %}{{ site }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% if item.related_count > 5 %}(+{{ item.related_count|add:"-5" }} more){% endif %}
</span>
</li>
{% endfor %}
</ul>
<p style="margin-top: 15px; padding: 10px; background: #fff; border: 1px solid #ff9800;">
<strong>💡 Tip:</strong> To delete these seed keywords, you must first remove or deactivate the site keywords that reference them.
</p>
</div>
{% endif %}
{% if can_delete_items %}
<form method="post">
{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
{% endfor %}
<input type="hidden" name="action" value="{{ action }}">
<input type="hidden" name="post" value="yes">
<div style="margin: 20px 0;">
<input type="submit" value="{% trans 'Yes, delete selected items' %}" class="button" style="background: #dc3545; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">
<a href="{% url opts|admin_urlname:'changelist' %}" class="button cancel-link" style="margin-left: 10px; padding: 10px 20px; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; display: inline-block;">{% trans 'No, take me back' %}</a>
</div>
{% if protected_items %}
<p style="color: #e65100;">
<strong>Note:</strong> Only the {{ can_delete_items|length }} deletable keyword{{ can_delete_items|length|pluralize }} will be deleted.
The {{ protected_items|length }} protected keyword{{ protected_items|length|pluralize }} will remain unchanged.
</p>
{% endif %}
</div>
</form>
{% else %}
<div style="margin: 20px 0;">
<p style="background: #ffebee; padding: 15px; border-left: 4px solid #f44336;">
<strong>⛔ No keywords can be deleted.</strong><br>
All selected keywords are currently in use by site keywords and are protected from deletion.
</p>
<a href="{% url opts|admin_urlname:'changelist' %}" class="button" style="padding: 10px 20px; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; display: inline-block;">{% trans 'Back to list' %}</a>
</div>
{% endif %}
</div>
{% endblock %}