django fixes restored defautl delte cofnruiiamtions and working accoutna dn sites deletion with cascading

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-12 12:02:57 +00:00
parent a3e75e654e
commit 28cb698579
2 changed files with 126 additions and 149 deletions

View File

@@ -2,15 +2,14 @@
Admin interface for auth models
"""
from django import forms
from django.contrib import admin, messages
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import ChoicesDropdownFilter, RelatedDropdownFilter
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
from import_export.admin import ExportMixin, ImportExportMixin
from import_export import resources, fields, widgets
from import_export import resources
class AccountAdminForm(forms.ModelForm):
@@ -214,8 +213,6 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
'bulk_set_status_cancelled',
'bulk_add_credits',
'bulk_subtract_credits',
'bulk_soft_delete',
'bulk_hard_delete',
]
def get_queryset(self, request):
@@ -454,41 +451,6 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
'action': 'bulk_subtract_credits',
})
bulk_subtract_credits.short_description = 'Subtract credits from accounts'
def bulk_soft_delete(self, request, queryset):
"""Soft delete selected accounts and all related data"""
count = 0
for account in queryset:
if account.slug != 'aws-admin': # Protect admin account
account.delete() # Soft delete via SoftDeletableModel (now cascades)
count += 1
self.message_user(request, f'{count} account(s) and all related data soft deleted.', messages.SUCCESS)
bulk_soft_delete.short_description = 'Soft delete accounts (with cascade)'
def bulk_hard_delete(self, request, queryset):
"""PERMANENTLY delete selected accounts and ALL related data - cannot be undone!"""
import traceback
count = 0
errors = []
for account in queryset:
if account.slug == 'aws-admin': # Protect admin account
errors.append(f'{account.name}: Protected system account')
continue
try:
account.hard_delete_with_cascade() # Permanently delete everything
count += 1
except Exception as e:
# Log full traceback for debugging
import logging
logger = logging.getLogger(__name__)
logger.error(f'Hard delete failed for account {account.pk} ({account.name}): {traceback.format_exc()}')
errors.append(f'{account.name}: {str(e)}')
if count > 0:
self.message_user(request, f'{count} account(s) and ALL related data permanently deleted.', messages.SUCCESS)
if errors:
self.message_user(request, f'Errors: {"; ".join(errors)}', messages.ERROR)
bulk_hard_delete.short_description = '⚠️ PERMANENTLY delete accounts (irreversible!)'
class SubscriptionResource(resources.ModelResource):
@@ -631,7 +593,6 @@ class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
'bulk_set_status_active',
'bulk_set_status_inactive',
'bulk_set_status_maintenance',
'bulk_soft_delete',
]
fieldsets = (
@@ -677,36 +638,15 @@ class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
get_api_key_status.short_description = 'API Key'
def generate_api_keys(self, request, queryset):
"""Generate API keys for selected sites. API key is stored ONLY in Site.wp_api_key (single source of truth)."""
"""Generate API keys for selected sites"""
import secrets
from igny8_core.business.integration.models import SiteIntegration
updated_count = 0
for site in queryset:
if not site.wp_api_key:
api_key = f"igny8_{''.join(secrets.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for _ in range(40))}"
# SINGLE SOURCE OF TRUTH: Store API key ONLY in Site.wp_api_key
site.wp_api_key = api_key
site.wp_api_key = f"igny8_{''.join(secrets.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for _ in range(40))}"
site.save()
# Ensure SiteIntegration exists for status tracking (without API key)
SiteIntegration.objects.get_or_create(
site=site,
platform='wordpress',
defaults={
'account': site.account,
'platform': 'wordpress',
'platform_type': 'cms',
'is_active': True,
'sync_enabled': True,
'credentials_json': {}, # Empty - API key is on Site model
'config_json': {}
}
)
updated_count += 1
self.message_user(request, f'Generated API keys for {updated_count} site(s). API keys stored in Site.wp_api_key (single source of truth).')
self.message_user(request, f'Generated API keys for {updated_count} site(s). Sites with existing keys were skipped.')
generate_api_keys.short_description = 'Generate WordPress API Keys'
def bulk_set_status_active(self, request, queryset):
@@ -727,15 +667,6 @@ class SiteAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
self.message_user(request, f'{updated} site(s) set to maintenance mode.', messages.SUCCESS)
bulk_set_status_maintenance.short_description = 'Set status to Maintenance'
def bulk_soft_delete(self, request, queryset):
"""Soft delete selected sites"""
count = 0
for site in queryset:
site.delete() # Soft delete via SoftDeletableModel
count += 1
self.message_user(request, f'{count} site(s) soft deleted.', messages.SUCCESS)
bulk_soft_delete.short_description = 'Soft delete selected sites'
def get_sectors_count(self, obj):
try:
return obj.get_active_sectors_count()
@@ -899,10 +830,7 @@ class IndustrySectorResource(resources.ModelResource):
class IndustrySectorAdmin(ImportExportMixin, Igny8ModelAdmin):
resource_class = IndustrySectorResource
list_display = ['name', 'slug', 'industry', 'is_active']
list_filter = [
('is_active', ChoicesDropdownFilter),
('industry', RelatedDropdownFilter),
]
list_filter = ['is_active', 'industry']
search_fields = ['name', 'slug', 'description']
readonly_fields = ['created_at', 'updated_at']
actions = [
@@ -928,53 +856,13 @@ class IndustrySectorAdmin(ImportExportMixin, Igny8ModelAdmin):
class SeedKeywordResource(resources.ModelResource):
"""Resource class for importing/exporting Seed Keywords"""
industry = fields.Field(
column_name='industry',
attribute='industry',
widget=widgets.ForeignKeyWidget(Industry, 'name')
)
sector = fields.Field(
column_name='sector',
attribute='sector',
widget=widgets.ForeignKeyWidget(IndustrySector, 'name')
)
class Meta:
model = SeedKeyword
fields = ('id', 'keyword', 'industry', 'sector', 'volume',
fields = ('id', 'keyword', 'industry__name', 'sector__name', 'volume',
'difficulty', 'country', 'is_active', 'created_at')
export_order = fields
import_id_fields = ('keyword', 'industry', 'sector') # Use natural keys for import
import_id_fields = ('id',)
skip_unchanged = True
def before_import_row(self, row, **kwargs):
"""Clean and validate row data before import"""
# Ensure volume is an integer
if 'volume' in row:
try:
row['volume'] = int(row['volume']) if row['volume'] else 0
except (ValueError, TypeError):
row['volume'] = 0
# Ensure difficulty is an integer between 0-100
if 'difficulty' in row:
try:
difficulty = int(row['difficulty']) if row['difficulty'] else 0
row['difficulty'] = max(0, min(100, difficulty)) # Clamp to 0-100
except (ValueError, TypeError):
row['difficulty'] = 0
# Ensure country is valid
if 'country' in row:
valid_countries = [code for code, name in SeedKeyword.COUNTRY_CHOICES]
if row['country'] not in valid_countries:
row['country'] = 'US' # Default to US if invalid
# Set defaults for optional fields
if 'is_active' not in row or row['is_active'] == '':
row['is_active'] = True
return row
@admin.register(SeedKeyword)
@@ -986,10 +874,11 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
search_fields = ['keyword']
readonly_fields = ['created_at', 'updated_at']
actions = [
'delete_selected',
'bulk_activate',
'bulk_deactivate',
'bulk_update_country',
]
] # Enable bulk delete
fieldsets = (
('Keyword Info', {
@@ -1003,38 +892,18 @@ class SeedKeywordAdmin(ImportExportMixin, Igny8ModelAdmin):
}),
)
def has_delete_permission(self, request, obj=None):
"""Allow deletion for superusers and developers"""
return request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer())
def bulk_activate(self, request, queryset):
"""Activate selected keywords"""
try:
updated = queryset.update(is_active=True)
self.message_user(
request,
f'{updated} seed keyword(s) activated successfully.',
messages.SUCCESS
)
except Exception as e:
self.message_user(
request,
f'Error activating keywords: {str(e)}',
messages.ERROR
)
updated = queryset.update(is_active=True)
self.message_user(request, f'{updated} seed keyword(s) activated.', messages.SUCCESS)
bulk_activate.short_description = 'Activate selected keywords'
def bulk_deactivate(self, request, queryset):
"""Deactivate selected keywords"""
try:
updated = queryset.update(is_active=False)
self.message_user(
request,
f'{updated} seed keyword(s) deactivated successfully.',
messages.SUCCESS
)
except Exception as e:
self.message_user(
request,
f'Error deactivating keywords: {str(e)}',
messages.ERROR
)
updated = queryset.update(is_active=False)
self.message_user(request, f'{updated} seed keyword(s) deactivated.', messages.SUCCESS)
bulk_deactivate.short_description = 'Deactivate selected keywords'
def bulk_update_country(self, request, queryset):