bulk actions & some next audits docs
This commit is contained in:
@@ -8,7 +8,7 @@ from unfold.admin import ModelAdmin, TabularInline
|
||||
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
|
||||
from import_export.admin import ExportMixin, ImportExportMixin
|
||||
from import_export import resources
|
||||
|
||||
|
||||
@@ -112,13 +112,30 @@ class AccountAdminForm(forms.ModelForm):
|
||||
return instance
|
||||
|
||||
|
||||
class PlanResource(resources.ModelResource):
|
||||
"""Resource class for importing/exporting Plans"""
|
||||
class Meta:
|
||||
model = Plan
|
||||
fields = ('id', 'name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users',
|
||||
'max_keywords', 'max_content_words', 'included_credits', 'is_active', 'is_featured')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(Plan)
|
||||
class PlanAdmin(Igny8ModelAdmin):
|
||||
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_content_words', 'included_credits', 'is_active', 'is_featured']
|
||||
list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
|
||||
search_fields = ['name', 'slug']
|
||||
readonly_fields = ['created_at']
|
||||
actions = [
|
||||
'bulk_set_active',
|
||||
'bulk_set_inactive',
|
||||
'bulk_clone_plans',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Plan Info', {
|
||||
@@ -144,6 +161,32 @@ class PlanAdmin(Igny8ModelAdmin):
|
||||
'fields': ('stripe_product_id', 'stripe_price_id')
|
||||
}),
|
||||
)
|
||||
|
||||
def bulk_set_active(self, request, queryset):
|
||||
"""Set selected plans to active"""
|
||||
updated = queryset.update(is_active=True)
|
||||
self.message_user(request, f'{updated} plan(s) set to active.', messages.SUCCESS)
|
||||
bulk_set_active.short_description = 'Set plans to Active'
|
||||
|
||||
def bulk_set_inactive(self, request, queryset):
|
||||
"""Set selected plans to inactive"""
|
||||
updated = queryset.update(is_active=False)
|
||||
self.message_user(request, f'{updated} plan(s) set to inactive.', messages.SUCCESS)
|
||||
bulk_set_inactive.short_description = 'Set plans to Inactive'
|
||||
|
||||
def bulk_clone_plans(self, request, queryset):
|
||||
"""Clone selected plans"""
|
||||
count = 0
|
||||
for plan in queryset:
|
||||
plan_copy = Plan.objects.get(pk=plan.pk)
|
||||
plan_copy.pk = None
|
||||
plan_copy.name = f"{plan.name} (Copy)"
|
||||
plan_copy.slug = f"{plan.slug}-copy"
|
||||
plan_copy.is_active = False
|
||||
plan_copy.save()
|
||||
count += 1
|
||||
self.message_user(request, f'{count} plan(s) cloned.', messages.SUCCESS)
|
||||
bulk_clone_plans.short_description = 'Clone selected plans'
|
||||
|
||||
|
||||
class AccountResource(resources.ModelResource):
|
||||
@@ -163,6 +206,15 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
list_filter = ['status', 'plan']
|
||||
search_fields = ['name', 'slug']
|
||||
readonly_fields = ['created_at', 'updated_at', 'health_indicator', 'health_details']
|
||||
actions = [
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_suspended',
|
||||
'bulk_set_status_trial',
|
||||
'bulk_set_status_cancelled',
|
||||
'bulk_add_credits',
|
||||
'bulk_subtract_credits',
|
||||
'bulk_soft_delete',
|
||||
]
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Override to filter by account for non-superusers"""
|
||||
@@ -317,14 +369,171 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
if obj and getattr(obj, 'slug', '') == 'aws-admin':
|
||||
return False
|
||||
return super().has_delete_permission(request, obj)
|
||||
|
||||
# Bulk Actions
|
||||
def bulk_set_status_active(self, request, queryset):
|
||||
"""Set selected accounts to active status"""
|
||||
updated = queryset.update(status='active')
|
||||
self.message_user(request, f'{updated} account(s) set to active.', messages.SUCCESS)
|
||||
bulk_set_status_active.short_description = 'Set status to Active'
|
||||
|
||||
def bulk_set_status_suspended(self, request, queryset):
|
||||
"""Set selected accounts to suspended status"""
|
||||
updated = queryset.update(status='suspended')
|
||||
self.message_user(request, f'{updated} account(s) set to suspended.', messages.SUCCESS)
|
||||
bulk_set_status_suspended.short_description = 'Set status to Suspended'
|
||||
|
||||
def bulk_set_status_trial(self, request, queryset):
|
||||
"""Set selected accounts to trial status"""
|
||||
updated = queryset.update(status='trial')
|
||||
self.message_user(request, f'{updated} account(s) set to trial.', messages.SUCCESS)
|
||||
bulk_set_status_trial.short_description = 'Set status to Trial'
|
||||
|
||||
def bulk_set_status_cancelled(self, request, queryset):
|
||||
"""Set selected accounts to cancelled status"""
|
||||
updated = queryset.update(status='cancelled')
|
||||
self.message_user(request, f'{updated} account(s) set to cancelled.', messages.SUCCESS)
|
||||
bulk_set_status_cancelled.short_description = 'Set status to Cancelled'
|
||||
|
||||
def bulk_add_credits(self, request, queryset):
|
||||
"""Add credits to selected accounts"""
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
amount = int(request.POST.get('credits', 0))
|
||||
if amount > 0:
|
||||
for account in queryset:
|
||||
account.credits += amount
|
||||
account.save()
|
||||
self.message_user(request, f'Added {amount} credits to {queryset.count()} account(s).', messages.SUCCESS)
|
||||
return
|
||||
|
||||
class CreditForm(forms.Form):
|
||||
credits = forms.IntegerField(
|
||||
min_value=1,
|
||||
label="Credits to Add",
|
||||
help_text=f"Add credits to {queryset.count()} selected account(s)"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Add Credits to Accounts',
|
||||
'queryset': queryset,
|
||||
'form': CreditForm(),
|
||||
'action': 'bulk_add_credits',
|
||||
})
|
||||
bulk_add_credits.short_description = 'Add credits to accounts'
|
||||
|
||||
def bulk_subtract_credits(self, request, queryset):
|
||||
"""Subtract credits from selected accounts"""
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
amount = int(request.POST.get('credits', 0))
|
||||
if amount > 0:
|
||||
for account in queryset:
|
||||
account.credits = max(0, account.credits - amount)
|
||||
account.save()
|
||||
self.message_user(request, f'Subtracted {amount} credits from {queryset.count()} account(s).', messages.SUCCESS)
|
||||
return
|
||||
|
||||
class CreditForm(forms.Form):
|
||||
credits = forms.IntegerField(
|
||||
min_value=1,
|
||||
label="Credits to Subtract",
|
||||
help_text=f"Subtract credits from {queryset.count()} selected account(s)"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Subtract Credits from Accounts',
|
||||
'queryset': queryset,
|
||||
'form': CreditForm(),
|
||||
'action': 'bulk_subtract_credits',
|
||||
})
|
||||
bulk_subtract_credits.short_description = 'Subtract credits from accounts'
|
||||
|
||||
def bulk_soft_delete(self, request, queryset):
|
||||
"""Soft delete selected accounts"""
|
||||
count = 0
|
||||
for account in queryset:
|
||||
if account.slug != 'aws-admin': # Protect admin account
|
||||
account.delete() # Soft delete via SoftDeletableModel
|
||||
count += 1
|
||||
self.message_user(request, f'{count} account(s) soft deleted.', messages.SUCCESS)
|
||||
bulk_soft_delete.short_description = 'Soft delete selected accounts'
|
||||
|
||||
|
||||
class SubscriptionResource(resources.ModelResource):
|
||||
"""Resource class for exporting Subscriptions"""
|
||||
class Meta:
|
||||
model = Subscription
|
||||
fields = ('id', 'account__name', 'status', 'current_period_start', 'current_period_end',
|
||||
'stripe_subscription_id', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(Subscription)
|
||||
class SubscriptionAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class SubscriptionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SubscriptionResource
|
||||
list_display = ['account', 'status', 'current_period_start', 'current_period_end']
|
||||
list_filter = ['status']
|
||||
search_fields = ['account__name', 'stripe_subscription_id']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_cancelled',
|
||||
'bulk_set_status_suspended',
|
||||
'bulk_set_status_trialing',
|
||||
'bulk_renew',
|
||||
]
|
||||
actions = [
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_cancelled',
|
||||
'bulk_set_status_suspended',
|
||||
'bulk_set_status_trialing',
|
||||
'bulk_renew',
|
||||
]
|
||||
|
||||
def bulk_set_status_active(self, request, queryset):
|
||||
"""Set subscriptions to active"""
|
||||
updated = queryset.update(status='active')
|
||||
self.message_user(request, f'{updated} subscription(s) set to active.', messages.SUCCESS)
|
||||
bulk_set_status_active.short_description = 'Set status to Active'
|
||||
|
||||
def bulk_set_status_cancelled(self, request, queryset):
|
||||
"""Set subscriptions to cancelled"""
|
||||
updated = queryset.update(status='cancelled')
|
||||
self.message_user(request, f'{updated} subscription(s) set to cancelled.', messages.SUCCESS)
|
||||
bulk_set_status_cancelled.short_description = 'Set status to Cancelled'
|
||||
|
||||
def bulk_set_status_suspended(self, request, queryset):
|
||||
"""Set subscriptions to suspended"""
|
||||
updated = queryset.update(status='suspended')
|
||||
self.message_user(request, f'{updated} subscription(s) set to suspended.', messages.SUCCESS)
|
||||
bulk_set_status_suspended.short_description = 'Set status to Suspended'
|
||||
|
||||
def bulk_set_status_trialing(self, request, queryset):
|
||||
"""Set subscriptions to trialing"""
|
||||
updated = queryset.update(status='trialing')
|
||||
self.message_user(request, f'{updated} subscription(s) set to trialing.', messages.SUCCESS)
|
||||
bulk_set_status_trialing.short_description = 'Set status to Trialing'
|
||||
|
||||
def bulk_renew(self, request, queryset):
|
||||
"""Renew selected subscriptions"""
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
count = 0
|
||||
for subscription in queryset:
|
||||
# Extend subscription by one billing period
|
||||
if subscription.current_period_end:
|
||||
subscription.current_period_end = subscription.current_period_end + timedelta(days=30)
|
||||
subscription.status = 'active'
|
||||
subscription.save()
|
||||
count += 1
|
||||
self.message_user(request, f'{count} subscription(s) renewed for 30 days.', messages.SUCCESS)
|
||||
bulk_renew.short_description = 'Renew subscriptions'
|
||||
|
||||
|
||||
@admin.register(PasswordResetToken)
|
||||
@@ -372,23 +581,31 @@ class SectorInline(TabularInline):
|
||||
|
||||
|
||||
class SiteResource(resources.ModelResource):
|
||||
"""Resource class for exporting Sites"""
|
||||
"""Resource class for importing/exporting Sites"""
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ('id', 'name', 'slug', 'account__name', 'industry__name', 'domain',
|
||||
'status', 'is_active', 'site_type', 'hosting_type', 'created_at')
|
||||
'status', 'is_active', 'site_type', 'hosting_type', 'description', 'created_at')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(Site)
|
||||
class SiteAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
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', '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]
|
||||
actions = ['generate_api_keys']
|
||||
actions = [
|
||||
'generate_api_keys',
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_inactive',
|
||||
'bulk_set_status_maintenance',
|
||||
'bulk_soft_delete',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Site Info', {
|
||||
@@ -444,6 +661,33 @@ class SiteAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
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):
|
||||
"""Set selected sites to active status"""
|
||||
updated = queryset.update(status='active', is_active=True)
|
||||
self.message_user(request, f'{updated} site(s) set to active.', messages.SUCCESS)
|
||||
bulk_set_status_active.short_description = 'Set status to Active'
|
||||
|
||||
def bulk_set_status_inactive(self, request, queryset):
|
||||
"""Set selected sites to inactive status"""
|
||||
updated = queryset.update(status='inactive', is_active=False)
|
||||
self.message_user(request, f'{updated} site(s) set to inactive.', messages.SUCCESS)
|
||||
bulk_set_status_inactive.short_description = 'Set status to Inactive'
|
||||
|
||||
def bulk_set_status_maintenance(self, request, queryset):
|
||||
"""Set selected sites to maintenance status"""
|
||||
updated = queryset.update(status='maintenance')
|
||||
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()
|
||||
@@ -460,12 +704,27 @@ class SiteAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
get_industry_display.short_description = 'Industry'
|
||||
|
||||
|
||||
class SectorResource(resources.ModelResource):
|
||||
"""Resource class for exporting Sectors"""
|
||||
class Meta:
|
||||
model = Sector
|
||||
fields = ('id', 'name', 'slug', 'site__name', 'industry_sector__name', 'status',
|
||||
'is_active', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(Sector)
|
||||
class SectorAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
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', 'is_active', 'site', 'industry_sector__industry']
|
||||
search_fields = ['name', 'slug', 'site__name', 'industry_sector__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_set_status_active',
|
||||
'bulk_set_status_inactive',
|
||||
'bulk_soft_delete',
|
||||
]
|
||||
|
||||
def get_industry(self, obj):
|
||||
"""Safely get industry name"""
|
||||
@@ -496,6 +755,27 @@ class SectorAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
pass
|
||||
return 0
|
||||
get_clusters_count.short_description = 'Clusters'
|
||||
|
||||
def bulk_set_status_active(self, request, queryset):
|
||||
"""Set selected sectors to active status"""
|
||||
updated = queryset.update(status='active', is_active=True)
|
||||
self.message_user(request, f'{updated} sector(s) set to active.', messages.SUCCESS)
|
||||
bulk_set_status_active.short_description = 'Set status to Active'
|
||||
|
||||
def bulk_set_status_inactive(self, request, queryset):
|
||||
"""Set selected sectors to inactive status"""
|
||||
updated = queryset.update(status='inactive', is_active=False)
|
||||
self.message_user(request, f'{updated} sector(s) set to inactive.', messages.SUCCESS)
|
||||
bulk_set_status_inactive.short_description = 'Set status to Inactive'
|
||||
|
||||
def bulk_soft_delete(self, request, queryset):
|
||||
"""Soft delete selected sectors"""
|
||||
count = 0
|
||||
for sector in queryset:
|
||||
sector.delete() # Soft delete via SoftDeletableModel
|
||||
count += 1
|
||||
self.message_user(request, f'{count} sector(s) soft deleted.', messages.SUCCESS)
|
||||
bulk_soft_delete.short_description = 'Soft delete selected sectors'
|
||||
|
||||
|
||||
@admin.register(SiteUserAccess)
|
||||
@@ -514,14 +794,29 @@ class IndustrySectorInline(TabularInline):
|
||||
readonly_fields = []
|
||||
|
||||
|
||||
class IndustryResource(resources.ModelResource):
|
||||
"""Resource class for importing/exporting Industries"""
|
||||
class Meta:
|
||||
model = Industry
|
||||
fields = ('id', 'name', 'slug', 'description', 'is_active', 'created_at')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(Industry)
|
||||
class IndustryAdmin(Igny8ModelAdmin):
|
||||
class IndustryAdmin(ImportExportMixin, Igny8ModelAdmin):
|
||||
resource_class = IndustryResource
|
||||
list_display = ['name', 'slug', 'is_active', 'get_sectors_count', 'created_at']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
inlines = [IndustrySectorInline]
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
actions = [
|
||||
'delete_selected',
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
] # Enable bulk delete
|
||||
|
||||
def get_sectors_count(self, obj):
|
||||
return obj.sectors.filter(is_active=True).count()
|
||||
@@ -530,29 +825,81 @@ class IndustryAdmin(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):
|
||||
updated = queryset.update(is_active=True)
|
||||
self.message_user(request, f'{updated} industry/industries activated.', messages.SUCCESS)
|
||||
bulk_activate.short_description = 'Activate selected industries'
|
||||
|
||||
def bulk_deactivate(self, request, queryset):
|
||||
updated = queryset.update(is_active=False)
|
||||
self.message_user(request, f'{updated} industry/industries deactivated.', messages.SUCCESS)
|
||||
bulk_deactivate.short_description = 'Deactivate selected industries'
|
||||
|
||||
|
||||
class IndustrySectorResource(resources.ModelResource):
|
||||
"""Resource class for importing/exporting Industry Sectors"""
|
||||
class Meta:
|
||||
model = IndustrySector
|
||||
fields = ('id', 'name', 'slug', 'industry__name', 'description', 'is_active', 'created_at')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(IndustrySector)
|
||||
class IndustrySectorAdmin(Igny8ModelAdmin):
|
||||
class IndustrySectorAdmin(ImportExportMixin, Igny8ModelAdmin):
|
||||
resource_class = IndustrySectorResource
|
||||
list_display = ['name', 'slug', 'industry', 'is_active']
|
||||
list_filter = ['is_active', 'industry']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
actions = [
|
||||
'delete_selected',
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
] # Enable bulk delete
|
||||
|
||||
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):
|
||||
updated = queryset.update(is_active=True)
|
||||
self.message_user(request, f'{updated} sector(s) activated.', messages.SUCCESS)
|
||||
bulk_activate.short_description = 'Activate selected sectors'
|
||||
|
||||
def bulk_deactivate(self, request, queryset):
|
||||
updated = queryset.update(is_active=False)
|
||||
self.message_user(request, f'{updated} sector(s) deactivated.', messages.SUCCESS)
|
||||
bulk_deactivate.short_description = 'Deactivate selected sectors'
|
||||
|
||||
|
||||
class SeedKeywordResource(resources.ModelResource):
|
||||
"""Resource class for importing/exporting Seed Keywords"""
|
||||
class Meta:
|
||||
model = SeedKeyword
|
||||
fields = ('id', 'keyword', 'industry__name', 'sector__name', 'volume',
|
||||
'difficulty', 'country', 'is_active', 'created_at')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(SeedKeyword)
|
||||
class SeedKeywordAdmin(Igny8ModelAdmin):
|
||||
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', 'industry', 'sector', 'country']
|
||||
search_fields = ['keyword']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
actions = [
|
||||
'delete_selected',
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
'bulk_update_country',
|
||||
] # Enable bulk delete
|
||||
|
||||
fieldsets = (
|
||||
('Keyword Info', {
|
||||
@@ -569,6 +916,50 @@ class SeedKeywordAdmin(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):
|
||||
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):
|
||||
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):
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
country = request.POST.get('country')
|
||||
if country:
|
||||
updated = queryset.update(country=country)
|
||||
self.message_user(request, f'{updated} seed keyword(s) country updated to: {country}', messages.SUCCESS)
|
||||
return
|
||||
|
||||
COUNTRY_CHOICES = [
|
||||
('US', 'United States'),
|
||||
('GB', 'United Kingdom'),
|
||||
('CA', 'Canada'),
|
||||
('AU', 'Australia'),
|
||||
('IN', 'India'),
|
||||
]
|
||||
|
||||
class CountryForm(forms.Form):
|
||||
country = forms.ChoiceField(
|
||||
choices=COUNTRY_CHOICES,
|
||||
label="Select Country",
|
||||
help_text=f"Update country for {queryset.count()} seed keyword(s)"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Update Country',
|
||||
'queryset': queryset,
|
||||
'form': CountryForm(),
|
||||
'action': 'bulk_update_country',
|
||||
})
|
||||
bulk_update_country.short_description = 'Update country'
|
||||
|
||||
|
||||
class UserResource(resources.ModelResource):
|
||||
@@ -600,6 +991,15 @@ class UserAdmin(ExportMixin, BaseUserAdmin, Igny8ModelAdmin):
|
||||
add_fieldsets = BaseUserAdmin.add_fieldsets + (
|
||||
('IGNY8 Info', {'fields': ('account', 'role')}),
|
||||
)
|
||||
actions = [
|
||||
'bulk_set_role_owner',
|
||||
'bulk_set_role_admin',
|
||||
'bulk_set_role_editor',
|
||||
'bulk_set_role_viewer',
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
'bulk_send_password_reset',
|
||||
]
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Filter users by account for non-superusers"""
|
||||
@@ -619,4 +1019,44 @@ class UserAdmin(ExportMixin, BaseUserAdmin, Igny8ModelAdmin):
|
||||
except:
|
||||
return '-'
|
||||
get_account_display.short_description = 'Account'
|
||||
|
||||
def bulk_set_role_owner(self, request, queryset):
|
||||
updated = queryset.update(role='owner')
|
||||
self.message_user(request, f'{updated} user(s) role set to Owner.', messages.SUCCESS)
|
||||
bulk_set_role_owner.short_description = 'Set role to Owner'
|
||||
|
||||
def bulk_set_role_admin(self, request, queryset):
|
||||
updated = queryset.update(role='admin')
|
||||
self.message_user(request, f'{updated} user(s) role set to Admin.', messages.SUCCESS)
|
||||
bulk_set_role_admin.short_description = 'Set role to Admin'
|
||||
|
||||
def bulk_set_role_editor(self, request, queryset):
|
||||
updated = queryset.update(role='editor')
|
||||
self.message_user(request, f'{updated} user(s) role set to Editor.', messages.SUCCESS)
|
||||
bulk_set_role_editor.short_description = 'Set role to Editor'
|
||||
|
||||
def bulk_set_role_viewer(self, request, queryset):
|
||||
updated = queryset.update(role='viewer')
|
||||
self.message_user(request, f'{updated} user(s) role set to Viewer.', messages.SUCCESS)
|
||||
bulk_set_role_viewer.short_description = 'Set role to Viewer'
|
||||
|
||||
def bulk_activate(self, request, queryset):
|
||||
updated = queryset.update(is_active=True)
|
||||
self.message_user(request, f'{updated} user(s) activated.', messages.SUCCESS)
|
||||
bulk_activate.short_description = 'Activate users'
|
||||
|
||||
def bulk_deactivate(self, request, queryset):
|
||||
updated = queryset.update(is_active=False)
|
||||
self.message_user(request, f'{updated} user(s) deactivated.', messages.SUCCESS)
|
||||
bulk_deactivate.short_description = 'Deactivate users'
|
||||
|
||||
def bulk_send_password_reset(self, request, queryset):
|
||||
# TODO: Implement password reset email sending
|
||||
count = queryset.count()
|
||||
self.message_user(
|
||||
request,
|
||||
f'{count} password reset email(s) queued for sending. (Email integration required)',
|
||||
messages.INFO
|
||||
)
|
||||
bulk_send_password_reset.short_description = 'Send password reset email'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user