latest 2 days work excluding problemetic code
This commit is contained in:
@@ -1,65 +1,11 @@
|
||||
"""
|
||||
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.core.exceptions import PermissionDenied
|
||||
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:
|
||||
"""Mixin for admin classes that need account filtering"""
|
||||
|
||||
@@ -171,19 +117,16 @@ class SiteSectorAdminMixin:
|
||||
from unfold.admin import ModelAdmin as UnfoldModelAdmin
|
||||
|
||||
|
||||
class Igny8ModelAdmin(AdminDeleteMixin, UnfoldModelAdmin):
|
||||
class Igny8ModelAdmin(UnfoldModelAdmin):
|
||||
"""
|
||||
Custom ModelAdmin that:
|
||||
1. Fixes delete actions (no 500 errors, bypasses PROTECT if needed)
|
||||
2. Ensures sidebar_navigation is set correctly on ALL pages
|
||||
3. Uses dropdown filters with Apply button
|
||||
1. Ensures sidebar_navigation is set correctly on ALL pages
|
||||
2. Uses standard Django filters
|
||||
|
||||
AdminDeleteMixin provides:
|
||||
- simple_delete: Safe delete (soft delete if available)
|
||||
"""
|
||||
|
||||
# Enable "Apply Filters" button for dropdown filters
|
||||
list_filter_submit = True
|
||||
# Standard Django filters
|
||||
pass
|
||||
|
||||
def _inject_sidebar_context(self, request, extra_context=None):
|
||||
"""Helper to inject custom sidebar into context"""
|
||||
|
||||
@@ -5,10 +5,6 @@ from django import forms
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
from unfold.contrib.filters.admin import (
|
||||
RelatedDropdownFilter,
|
||||
ChoicesDropdownFilter,
|
||||
)
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
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
|
||||
"""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_filter = [
|
||||
('is_active', ChoicesDropdownFilter),
|
||||
('billing_cycle', ChoicesDropdownFilter),
|
||||
('is_internal', ChoicesDropdownFilter),
|
||||
('is_featured', ChoicesDropdownFilter),
|
||||
]
|
||||
list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
|
||||
search_fields = ['name', 'slug']
|
||||
readonly_fields = ['created_at']
|
||||
actions = [
|
||||
@@ -212,10 +203,7 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
resource_class = AccountResource
|
||||
form = AccountAdminForm
|
||||
list_display = ['name', 'slug', 'owner', 'plan', 'status', 'health_indicator', 'credits', 'created_at']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('plan', RelatedDropdownFilter),
|
||||
]
|
||||
list_filter = ['status', 'plan']
|
||||
search_fields = ['name', 'slug']
|
||||
readonly_fields = ['created_at', 'updated_at', 'health_indicator', 'health_details']
|
||||
actions = [
|
||||
@@ -515,9 +503,7 @@ class SubscriptionResource(resources.ModelResource):
|
||||
class SubscriptionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SubscriptionResource
|
||||
list_display = ['account', 'status', 'current_period_start', 'current_period_end']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
]
|
||||
list_filter = ['status']
|
||||
search_fields = ['account__name', 'stripe_subscription_id']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
@@ -635,13 +621,7 @@ class SiteResource(resources.ModelResource):
|
||||
class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SiteResource
|
||||
list_display = ['name', 'slug', 'account', 'industry', 'domain', 'status', 'is_active', 'get_api_key_status', 'get_sectors_count']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('is_active', ChoicesDropdownFilter),
|
||||
('account', RelatedDropdownFilter),
|
||||
('industry', RelatedDropdownFilter),
|
||||
('hosting_type', ChoicesDropdownFilter),
|
||||
]
|
||||
list_filter = ['status', 'is_active', 'account', 'industry', 'hosting_type']
|
||||
search_fields = ['name', 'slug', 'domain', 'industry__name']
|
||||
readonly_fields = ['created_at', 'updated_at', 'get_api_key_display']
|
||||
inlines = [SectorInline]
|
||||
@@ -784,12 +764,7 @@ class SectorResource(resources.ModelResource):
|
||||
class SectorAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SectorResource
|
||||
list_display = ['name', 'slug', 'site', 'industry_sector', 'get_industry', 'status', 'is_active', 'get_keywords_count', 'get_clusters_count']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('is_active', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('industry_sector__industry', RelatedDropdownFilter),
|
||||
]
|
||||
list_filter = ['status', 'is_active', 'site', 'industry_sector__industry']
|
||||
search_fields = ['name', 'slug', 'site__name', 'industry_sector__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
@@ -1006,12 +981,7 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
|
||||
resource_class = SeedKeywordResource
|
||||
"""SeedKeyword admin - Global reference data, no account filtering"""
|
||||
list_display = ['keyword', 'industry', 'sector', 'volume', 'difficulty', 'country', 'is_active', 'created_at']
|
||||
list_filter = [
|
||||
('is_active', ChoicesDropdownFilter),
|
||||
('industry', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('country', ChoicesDropdownFilter),
|
||||
]
|
||||
list_filter = ['is_active', 'industry', 'sector', 'country']
|
||||
search_fields = ['keyword']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
@@ -1019,7 +989,6 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
|
||||
'bulk_deactivate',
|
||||
'bulk_update_country',
|
||||
]
|
||||
# Delete is handled by AdminDeleteMixin in base Igny8ModelAdmin
|
||||
|
||||
fieldsets = (
|
||||
('Keyword Info', {
|
||||
@@ -1119,12 +1088,7 @@ class UserAdmin(ExportMixin, BaseUserAdmin, Igny8ModelAdmin):
|
||||
"""
|
||||
resource_class = UserResource
|
||||
list_display = ['email', 'username', 'account', 'role', 'is_active', 'is_staff', 'created_at']
|
||||
list_filter = [
|
||||
('role', ChoicesDropdownFilter),
|
||||
('account', RelatedDropdownFilter),
|
||||
('is_active', ChoicesDropdownFilter),
|
||||
('is_staff', ChoicesDropdownFilter),
|
||||
]
|
||||
list_filter = ['role', 'account', 'is_active', 'is_staff']
|
||||
search_fields = ['email', 'username']
|
||||
readonly_fields = ['created_at', 'updated_at', 'password_display']
|
||||
|
||||
|
||||
@@ -5,12 +5,6 @@ from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from unfold.contrib.filters.admin import (
|
||||
RelatedDropdownFilter,
|
||||
ChoicesDropdownFilter,
|
||||
DropdownFilter,
|
||||
RangeDateFilter,
|
||||
)
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from igny8_core.business.billing.models import (
|
||||
@@ -41,11 +35,7 @@ class CreditTransactionResource(resources.ModelResource):
|
||||
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = CreditTransactionResource
|
||||
list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at']
|
||||
list_filter = [
|
||||
('transaction_type', ChoicesDropdownFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
('account', RelatedDropdownFilter),
|
||||
]
|
||||
list_filter = ['transaction_type', 'created_at', 'account']
|
||||
search_fields = ['description', 'account__name']
|
||||
readonly_fields = ['created_at']
|
||||
date_hierarchy = 'created_at'
|
||||
@@ -197,13 +187,7 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
'approved_by',
|
||||
'processed_at',
|
||||
]
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('payment_method', ChoicesDropdownFilter),
|
||||
('currency', ChoicesDropdownFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
('processed_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'payment_method', 'currency', 'created_at', 'processed_at']
|
||||
search_fields = [
|
||||
'invoice__invoice_number',
|
||||
'account__name',
|
||||
@@ -668,12 +652,7 @@ class PlanLimitUsageAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
'period_display',
|
||||
'created_at',
|
||||
]
|
||||
list_filter = [
|
||||
('limit_type', ChoicesDropdownFilter),
|
||||
('period_start', RangeDateFilter),
|
||||
('period_end', RangeDateFilter),
|
||||
('account', RelatedDropdownFilter),
|
||||
]
|
||||
list_filter = ['limit_type', 'period_start', 'period_end', 'account']
|
||||
search_fields = ['account__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
date_hierarchy = 'period_start'
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from unfold.contrib.filters.admin import (
|
||||
RangeDateFilter,
|
||||
RangeNumericFilter,
|
||||
RelatedDropdownFilter,
|
||||
ChoicesDropdownFilter,
|
||||
)
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
|
||||
from .models import Keywords, Clusters, ContentIdeas
|
||||
from import_export.admin import ExportMixin, ImportExportMixin
|
||||
@@ -40,13 +34,7 @@ class ClustersAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = ClustersResource
|
||||
list_display = ['name', 'site', 'sector', 'keywords_count', 'volume', 'status', 'created_at']
|
||||
list_select_related = ['site', 'sector', 'account']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('volume', RangeNumericFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'site', 'sector', 'volume', 'created_at']
|
||||
search_fields = ['name']
|
||||
ordering = ['name']
|
||||
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_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),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('cluster', RelatedDropdownFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'site', 'sector', 'cluster', 'created_at']
|
||||
search_fields = ['seed_keyword__keyword']
|
||||
ordering = ['-created_at']
|
||||
autocomplete_fields = ['cluster', 'site', 'sector', 'seed_keyword']
|
||||
@@ -114,6 +96,7 @@ class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
'bulk_assign_cluster',
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_inactive',
|
||||
'bulk_soft_delete',
|
||||
]
|
||||
|
||||
@admin.display(description='Keyword')
|
||||
@@ -242,16 +225,7 @@ class ContentIdeasAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin
|
||||
resource_class = ContentIdeasResource
|
||||
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_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('content_type', ChoicesDropdownFilter),
|
||||
('content_structure', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('keyword_cluster', RelatedDropdownFilter),
|
||||
('estimated_word_count', RangeNumericFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector', 'keyword_cluster', 'estimated_word_count', 'created_at']
|
||||
search_fields = ['idea_title', 'target_keywords', 'description']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
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 .models import Tasks, Images, Content, ImagePrompts
|
||||
from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap
|
||||
@@ -39,15 +33,7 @@ class TasksAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = TaskResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'status', 'cluster', 'created_at']
|
||||
list_editable = ['status'] # Enable inline editing for status
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('content_type', ChoicesDropdownFilter),
|
||||
('content_structure', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('cluster', RelatedDropdownFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector', 'cluster', 'created_at']
|
||||
search_fields = ['title', 'description']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
@@ -315,13 +301,7 @@ class ImagePromptsAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = ImagePromptsResource
|
||||
|
||||
list_display = ['get_content_title', 'site', 'sector', 'image_type', 'get_prompt_preview', 'status', 'created_at']
|
||||
list_filter = [
|
||||
('image_type', ChoicesDropdownFilter),
|
||||
('status', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['image_type', 'status', 'site', 'sector', 'created_at']
|
||||
search_fields = ['content__title', 'prompt', 'caption']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['get_content_title', 'site', 'sector', 'image_type', 'prompt', 'caption',
|
||||
@@ -450,17 +430,7 @@ class ContentResource(resources.ModelResource):
|
||||
class ContentAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = ContentResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'source', 'status', 'word_count', 'get_taxonomy_count', 'created_at']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
('content_type', ChoicesDropdownFilter),
|
||||
('content_structure', ChoicesDropdownFilter),
|
||||
('source', ChoicesDropdownFilter),
|
||||
('site', RelatedDropdownFilter),
|
||||
('sector', RelatedDropdownFilter),
|
||||
('cluster', RelatedDropdownFilter),
|
||||
('word_count', RangeNumericFilter),
|
||||
('created_at', RangeDateFilter),
|
||||
]
|
||||
list_filter = ['status', 'content_type', 'content_structure', 'source', 'site', 'sector', 'cluster', 'word_count', 'created_at']
|
||||
search_fields = ['title', 'content_html', 'external_url', 'meta_title', 'primary_keyword']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['created_at', 'updated_at', 'word_count', 'get_tags_display', 'get_categories_display']
|
||||
|
||||
@@ -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>
|
||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
||||
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||
› {% 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 %}
|
||||
Reference in New Issue
Block a user