models delte and trash and cascade issues fixes

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-12 17:37:21 +00:00
parent 4ba3870878
commit ad828a9fcd
5 changed files with 198 additions and 12 deletions

View File

@@ -12,8 +12,9 @@ from unfold.admin import ModelAdmin
# Import all soft-deletable models
from igny8_core.auth.models import Account, Site, Sector
from igny8_core.business.content.models import Tasks, Content, Images
from igny8_core.business.content.models import Tasks, Content, Images, ContentTaxonomy
from igny8_core.business.planning.models import Clusters, Keywords, ContentIdeas
from django.db import transaction
class DeletedOnlyManager:
@@ -157,9 +158,17 @@ class BaseTrashAdmin(ModelAdmin):
expired = queryset.filter(restore_until__lt=now)
restored_count = 0
failed_count = 0
failed_items = []
for obj in restorable:
obj.restore()
restored_count += 1
try:
with transaction.atomic():
obj.restore()
restored_count += 1
except Exception as e:
failed_count += 1
failed_items.append(f"{obj} ({str(e)[:50]}...)")
if restored_count > 0:
self.message_user(
@@ -168,6 +177,13 @@ class BaseTrashAdmin(ModelAdmin):
messages.SUCCESS
)
if failed_count > 0:
self.message_user(
request,
f'{failed_count} record(s) could not be restored (constraint violation). Items: {", ".join(failed_items[:3])}',
messages.ERROR
)
if expired.exists():
self.message_user(
request,
@@ -179,14 +195,71 @@ class BaseTrashAdmin(ModelAdmin):
def permanently_delete_records(self, request, queryset):
"""Permanently delete selected records (irreversible)."""
count = queryset.count()
for obj in queryset:
obj.hard_delete()
failed_count = 0
failed_items = []
deleted_count = 0
self.message_user(
request,
f'Permanently deleted {count} record(s). This cannot be undone.',
messages.SUCCESS
)
for obj in queryset:
try:
with transaction.atomic():
# For Sector, cascade delete related soft-deleted records first
if isinstance(obj, Sector):
self._cascade_delete_sector_relations(obj)
# For Site, cascade delete related soft-deleted records first
elif isinstance(obj, Site):
self._cascade_delete_site_relations(obj)
obj.hard_delete()
deleted_count += 1
except Exception as e:
failed_count += 1
failed_items.append(f"{obj} ({str(e)[:100]}...)")
if deleted_count > 0:
self.message_user(
request,
f'Permanently deleted {deleted_count} record(s). This cannot be undone.',
messages.SUCCESS
)
if failed_count > 0:
self.message_user(
request,
f'{failed_count} record(s) could not be deleted. Items: {", ".join(failed_items[:3])}',
messages.ERROR
)
def _cascade_delete_sector_relations(self, sector):
"""Cascade delete all related soft-deleted records for a sector."""
# Delete in order of dependencies
# 1. Images (depends on Content/Tasks)
Images.all_objects.filter(sector=sector).delete()
# 2. Content (depends on Tasks)
Content.all_objects.filter(sector=sector).delete()
# 3. Tasks (depends on Clusters)
Tasks.all_objects.filter(sector=sector).delete()
# 4. ContentIdeas (depends on Clusters)
ContentIdeas.all_objects.filter(sector=sector).delete()
# 5. Keywords (depends on Clusters)
Keywords.all_objects.filter(sector=sector).delete()
# 6. Clusters
Clusters.all_objects.filter(sector=sector).delete()
# 7. ContentTaxonomy (if exists)
ContentTaxonomy.objects.filter(sector=sector).delete()
def _cascade_delete_site_relations(self, site):
"""Cascade delete all related soft-deleted records for a site."""
# Delete sectors first (which will cascade to their relations)
for sector in Sector.all_objects.filter(site=site):
self._cascade_delete_sector_relations(sector)
sector.hard_delete()
# Delete any site-level records
Images.all_objects.filter(site=site).delete()
Content.all_objects.filter(site=site).delete()
Tasks.all_objects.filter(site=site).delete()
ContentIdeas.all_objects.filter(site=site).delete()
Keywords.all_objects.filter(site=site).delete()
Clusters.all_objects.filter(site=site).delete()
ContentTaxonomy.objects.filter(site=site).delete()
actions = ['restore_records', 'permanently_delete_records']
@@ -442,6 +515,31 @@ class ContentIdeasTrashAdmin(BaseTrashAdmin):
site_name.short_description = 'Site'
class ContentTaxonomyTrashAdmin(BaseTrashAdmin):
"""Trash view for deleted Content Taxonomies (Tags/Categories)."""
list_display = [
'name', 'taxonomy_type', 'site_name', 'deleted_at_display',
'time_until_permanent_delete', 'can_restore_display'
]
list_filter = [RestorableTimePeriodFilter, DeletedInLastFilter, 'taxonomy_type']
search_fields = ['name', 'slug', 'site__name']
ordering = ['-deleted_at']
readonly_fields = [
'name', 'slug', 'taxonomy_type', 'description', 'account', 'site', 'sector',
'external_id', 'external_taxonomy', 'count',
'is_deleted', 'deleted_at', 'restore_until', 'delete_reason', 'deleted_by'
]
def site_name(self, obj):
if obj.site:
try:
return Site.all_objects.get(pk=obj.site_id).name
except Site.DoesNotExist:
return f'[Deleted #{obj.site_id}]'
return '-'
site_name.short_description = 'Site'
# ============================================================================
# PROXY MODELS FOR TRASH VIEWS
# ============================================================================
@@ -527,6 +625,15 @@ class ContentIdeasTrash(ContentIdeas):
verbose_name_plural = 'Content Ideas (Trash)'
class ContentTaxonomyTrash(ContentTaxonomy):
"""Proxy model for Content Taxonomy (Tags/Categories) trash view."""
class Meta:
proxy = True
app_label = 'writer'
verbose_name = 'Taxonomy (Trash)'
verbose_name_plural = 'Taxonomies (Trash)'
# ============================================================================
# REGISTER TRASH ADMINS
# ============================================================================
@@ -540,3 +647,4 @@ admin.site.register(ImagesTrash, ImagesTrashAdmin)
admin.site.register(ClustersTrash, ClustersTrashAdmin)
admin.site.register(KeywordsTrash, KeywordsTrashAdmin)
admin.site.register(ContentIdeasTrash, ContentIdeasTrashAdmin)
admin.site.register(ContentTaxonomyTrash, ContentTaxonomyTrashAdmin)