django fixes restored defautl delte cofnruiiamtions and working accoutna dn sites deletion with cascading
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user