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 from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap from import_export.admin import ExportMixin, ImportExportMixin from import_export import resources class ContentTaxonomyInline(TabularInline): """Inline admin for managing content taxonomy relationships""" model = ContentTaxonomyRelation extra = 1 autocomplete_fields = ['taxonomy'] verbose_name = 'Taxonomy Term' verbose_name_plural = 'Taxonomy Terms (Tags & Categories)' class TaskResource(resources.ModelResource): """Resource class for importing/exporting Tasks""" class Meta: model = Tasks fields = ('id', 'title', 'description', 'status', 'content_type', 'content_structure', 'site__name', 'sector__name', 'cluster__name', 'created_at', 'updated_at') export_order = fields import_id_fields = ('id',) skip_unchanged = True @admin.register(Tasks) 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), ] search_fields = ['title', 'description'] ordering = ['-created_at'] readonly_fields = ['created_at', 'updated_at'] autocomplete_fields = ['cluster', 'site', 'sector'] actions = [ 'bulk_set_status_draft', 'bulk_set_status_in_progress', 'bulk_set_status_completed', 'bulk_assign_cluster', 'bulk_soft_delete', 'bulk_update_content_type', ] fieldsets = ( ('Basic Info', { 'fields': ('title', 'description', 'status', 'site', 'sector') }), ('Content Classification', { 'fields': ('content_type', 'content_structure', 'taxonomy_term') }), ('Planning', { 'fields': ('cluster', 'keywords') }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) def bulk_set_status_draft(self, request, queryset): """Set selected tasks to draft status""" updated = queryset.update(status='draft') self.message_user(request, f'{updated} task(s) set to draft.', messages.SUCCESS) bulk_set_status_draft.short_description = 'Set status to Draft' def bulk_set_status_in_progress(self, request, queryset): """Set selected tasks to in-progress status""" updated = queryset.update(status='in_progress') self.message_user(request, f'{updated} task(s) set to in progress.', messages.SUCCESS) bulk_set_status_in_progress.short_description = 'Set status to In Progress' def bulk_set_status_completed(self, request, queryset): """Set selected tasks to completed status""" updated = queryset.update(status='completed') self.message_user(request, f'{updated} task(s) set to completed.', messages.SUCCESS) bulk_set_status_completed.short_description = 'Set status to Completed' def bulk_assign_cluster(self, request, queryset): """Assign selected tasks to a cluster - requires form input""" from django import forms from igny8_core.modules.planner.models import Clusters # If this is the POST request with cluster selection if 'apply' in request.POST: cluster_id = request.POST.get('cluster') if cluster_id: cluster = Clusters.objects.get(pk=cluster_id) updated = queryset.update(cluster=cluster) self.message_user(request, f'{updated} task(s) assigned to cluster: {cluster.name}', messages.SUCCESS) return # Get first task's site/sector for filtering clusters first_task = queryset.first() if first_task: clusters = Clusters.objects.filter(site=first_task.site, sector=first_task.sector) else: clusters = Clusters.objects.all() # Create form for cluster selection class ClusterForm(forms.Form): cluster = forms.ModelChoiceField( queryset=clusters, label="Select Cluster", help_text=f"Assign {queryset.count()} selected task(s) to:" ) if clusters.exists(): from django.shortcuts import render return render(request, 'admin/bulk_action_form.html', { 'title': 'Assign Tasks to Cluster', 'queryset': queryset, 'form': ClusterForm(), 'action': 'bulk_assign_cluster', }) else: self.message_user(request, 'No clusters available for the selected tasks.', messages.WARNING) bulk_assign_cluster.short_description = 'Assign to Cluster' def bulk_soft_delete(self, request, queryset): """Soft delete selected tasks""" count = 0 for task in queryset: task.delete() # Soft delete via SoftDeletableModel count += 1 self.message_user(request, f'{count} task(s) soft deleted.', messages.SUCCESS) bulk_soft_delete.short_description = 'Soft delete selected tasks' def bulk_update_content_type(self, request, queryset): """Update content type for selected tasks""" from django import forms if 'apply' in request.POST: content_type = request.POST.get('content_type') if content_type: updated = queryset.update(content_type=content_type) self.message_user(request, f'{updated} task(s) updated to content type: {content_type}', messages.SUCCESS) return # Get content type choices from model CONTENT_TYPE_CHOICES = [ ('blog_post', 'Blog Post'), ('article', 'Article'), ('product', 'Product'), ('service', 'Service'), ('page', 'Page'), ('landing_page', 'Landing Page'), ] class ContentTypeForm(forms.Form): content_type = forms.ChoiceField( choices=CONTENT_TYPE_CHOICES, label="Select Content Type", help_text=f"Update content type for {queryset.count()} selected task(s)" ) from django.shortcuts import render return render(request, 'admin/bulk_action_form.html', { 'title': 'Update Content Type', 'queryset': queryset, 'form': ContentTypeForm(), 'action': 'bulk_update_content_type', }) bulk_update_content_type.short_description = 'Update content type' def get_site_display(self, obj): """Safely get site name""" try: return obj.site.name if obj.site else '-' except: return '-' get_site_display.short_description = 'Site' def get_sector_display(self, obj): """Safely get sector name""" try: return obj.sector.name if obj.sector else '-' except: return '-' def get_cluster_display(self, obj): """Safely get cluster name""" try: return obj.cluster.name if obj.cluster else '-' except: return '-' get_cluster_display.short_description = 'Cluster' class ImagesResource(resources.ModelResource): """Resource class for importing/exporting Images""" class Meta: model = Images fields = ('id', 'content__title', 'site__name', 'sector__name', 'image_type', 'status', 'position', 'created_at') export_order = fields @admin.register(Images) class ImagesAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin): resource_class = ImagesResource list_display = ['get_content_title', 'site', 'sector', 'image_type', 'status', 'position', 'created_at'] list_filter = ['image_type', 'status', 'site', 'sector'] search_fields = ['content__title'] ordering = ['-id'] # Sort by ID descending (newest first) actions = [ 'bulk_set_status_published', 'bulk_set_status_draft', 'bulk_set_type_featured', 'bulk_set_type_inline', 'bulk_set_type_thumbnail', 'bulk_soft_delete', ] def get_content_title(self, obj): """Get content title, fallback to task title if no content""" if obj.content: return obj.content.title or obj.content.meta_title or f"Content #{obj.content.id}" elif obj.task: return obj.task.title or f"Task #{obj.task.id}" return '-' get_content_title.short_description = 'Content Title' def get_site_display(self, obj): """Safely get site name""" try: return obj.site.name if obj.site else '-' except: return '-' get_site_display.short_description = 'Site' def get_sector_display(self, obj): """Safely get sector name""" try: return obj.sector.name if obj.sector else '-' except: return '-' def bulk_set_status_published(self, request, queryset): """Set selected images to published status""" updated = queryset.update(status='published') self.message_user(request, f'{updated} image(s) set to published.', messages.SUCCESS) bulk_set_status_published.short_description = 'Set status to Published' def bulk_set_status_draft(self, request, queryset): """Set selected images to draft status""" updated = queryset.update(status='draft') self.message_user(request, f'{updated} image(s) set to draft.', messages.SUCCESS) bulk_set_status_draft.short_description = 'Set status to Draft' def bulk_set_type_featured(self, request, queryset): """Set selected images to featured type""" updated = queryset.update(image_type='featured') self.message_user(request, f'{updated} image(s) set to featured.', messages.SUCCESS) bulk_set_type_featured.short_description = 'Set type to Featured' def bulk_set_type_inline(self, request, queryset): """Set selected images to inline type""" updated = queryset.update(image_type='inline') self.message_user(request, f'{updated} image(s) set to inline.', messages.SUCCESS) bulk_set_type_inline.short_description = 'Set type to Inline' def bulk_set_type_thumbnail(self, request, queryset): """Set selected images to thumbnail type""" updated = queryset.update(image_type='thumbnail') self.message_user(request, f'{updated} image(s) set to thumbnail.', messages.SUCCESS) bulk_set_type_thumbnail.short_description = 'Set type to Thumbnail' def bulk_soft_delete(self, request, queryset): """Soft delete selected images""" count = 0 for image in queryset: image.delete() # Soft delete via SoftDeletableModel count += 1 self.message_user(request, f'{count} image(s) soft deleted.', messages.SUCCESS) bulk_soft_delete.short_description = 'Soft delete selected images' class ContentResource(resources.ModelResource): """Resource class for importing/exporting Content""" class Meta: model = Content fields = ('id', 'title', 'content_type', 'content_structure', 'status', 'source', 'site__name', 'sector__name', 'cluster__name', 'word_count', 'meta_title', 'meta_description', 'primary_keyword', 'secondary_keywords', 'content_html', 'external_url', 'created_at') export_order = fields import_id_fields = ('id',) skip_unchanged = True @admin.register(Content) 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), ] 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'] autocomplete_fields = ['cluster', 'site', 'sector'] inlines = [ContentTaxonomyInline] actions = [ 'bulk_set_status_published', 'bulk_set_status_draft', 'bulk_add_taxonomy', 'bulk_soft_delete', 'bulk_publish_to_wordpress', 'bulk_unpublish_from_wordpress', ] fieldsets = ( ('Basic Info', { 'fields': ('title', 'site', 'sector', 'cluster', 'status') }), ('Content Classification', { 'fields': ('content_type', 'content_structure', 'source') }), ('Taxonomies (Read-only - manage below)', { 'fields': ('get_tags_display', 'get_categories_display'), 'classes': ('collapse',), 'description': 'View tags and categories. To add/remove, use the Taxonomy Terms section below.' }), ('Content', { 'fields': ('content_html', 'word_count') }), ('SEO', { 'fields': ('meta_title', 'meta_description', 'primary_keyword', 'secondary_keywords'), 'classes': ('collapse',) }), ('WordPress Sync', { 'fields': ('external_id', 'external_url'), 'classes': ('collapse',) }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) def get_taxonomy_count(self, obj): """Display count of associated taxonomy terms""" return obj.taxonomy_terms.count() get_taxonomy_count.short_description = 'Taxonomy Count' def get_tags_display(self, obj): """Display tags""" tags = obj.taxonomy_terms.filter(taxonomy_type='tag') if tags.exists(): return ', '.join([tag.name for tag in tags]) return 'No tags' get_tags_display.short_description = 'Tags' def get_categories_display(self, obj): """Display categories""" categories = obj.taxonomy_terms.filter(taxonomy_type='category') if categories.exists(): return ', '.join([cat.name for cat in categories]) return 'No categories' get_categories_display.short_description = 'Categories' def bulk_set_status_published(self, request, queryset): """Set selected content to published status""" updated = queryset.update(status='published') self.message_user(request, f'{updated} content item(s) set to published.', messages.SUCCESS) bulk_set_status_published.short_description = 'Set status to Published' def bulk_set_status_draft(self, request, queryset): """Set selected content to draft status""" updated = queryset.update(status='draft') self.message_user(request, f'{updated} content item(s) set to draft.', messages.SUCCESS) bulk_set_status_draft.short_description = 'Set status to Draft' def bulk_add_taxonomy(self, request, queryset): """Add taxonomy terms to selected content""" from django import forms from igny8_core.business.content.models import ContentTaxonomy, ContentTaxonomyRelation # If this is the POST request with taxonomy selection if 'apply' in request.POST: taxonomy_ids = request.POST.getlist('taxonomies') if taxonomy_ids: count = 0 for content in queryset: for tax_id in taxonomy_ids: taxonomy = ContentTaxonomy.objects.get(pk=tax_id) ContentTaxonomyRelation.objects.get_or_create( content=content, taxonomy=taxonomy ) count += 1 self.message_user(request, f'Added {count} taxonomy relation(s) to {queryset.count()} content item(s).', messages.SUCCESS) return # Get first content's site/sector for filtering taxonomies first_content = queryset.first() if first_content: taxonomies = ContentTaxonomy.objects.filter(site=first_content.site, sector=first_content.sector) else: taxonomies = ContentTaxonomy.objects.all() # Create form for taxonomy selection class TaxonomyForm(forms.Form): taxonomies = forms.ModelMultipleChoiceField( queryset=taxonomies, label="Select Taxonomies", help_text=f"Add taxonomy terms to {queryset.count()} selected content item(s)", widget=forms.CheckboxSelectMultiple ) if taxonomies.exists(): from django.shortcuts import render return render(request, 'admin/bulk_action_form.html', { 'title': 'Add Taxonomies to Content', 'queryset': queryset, 'form': TaxonomyForm(), 'action': 'bulk_add_taxonomy', }) else: self.message_user(request, 'No taxonomies available for the selected content.', messages.WARNING) bulk_add_taxonomy.short_description = 'Add Taxonomy Terms' def bulk_soft_delete(self, request, queryset): """Soft delete selected content""" count = 0 for content in queryset: content.delete() # Soft delete via SoftDeletableModel count += 1 self.message_user(request, f'{count} content item(s) soft deleted.', messages.SUCCESS) bulk_soft_delete.short_description = 'Soft delete selected content' def bulk_publish_to_wordpress(self, request, queryset): """Publish selected content to WordPress""" from igny8_core.business.publishing.models import PublishingRecord count = 0 for content in queryset: if content.site: # Create publishing record for WordPress PublishingRecord.objects.get_or_create( content=content, site=content.site, sector=content.sector, account=content.account, destination='wordpress', defaults={ 'status': 'pending', 'metadata': {} } ) count += 1 self.message_user(request, f'{count} content item(s) queued for WordPress publishing.', messages.SUCCESS) bulk_publish_to_wordpress.short_description = 'Publish to WordPress' def bulk_unpublish_from_wordpress(self, request, queryset): """Unpublish/remove selected content from WordPress""" from igny8_core.business.publishing.models import PublishingRecord count = 0 for content in queryset: # Update existing publishing records to mark for removal records = PublishingRecord.objects.filter( content=content, destination='wordpress', status__in=['published', 'pending', 'publishing'] ) records.update(status='failed', error_message='Unpublish requested from admin') count += records.count() self.message_user(request, f'{count} publishing record(s) marked for unpublish.', messages.SUCCESS) bulk_unpublish_from_wordpress.short_description = 'Unpublish from WordPress' def get_site_display(self, obj): """Safely get site name""" try: return obj.site.name if obj.site else '-' except: return '-' get_site_display.short_description = 'Site' def get_sector_display(self, obj): """Safely get sector name""" try: return obj.sector.name if obj.sector else '-' except: return '-' class ContentTaxonomyResource(resources.ModelResource): """Resource class for importing/exporting Content Taxonomies""" class Meta: model = ContentTaxonomy fields = ('id', 'name', 'slug', 'taxonomy_type', 'description', 'site__name', 'sector__name', 'count', 'external_id', 'external_taxonomy', 'created_at') export_order = fields import_id_fields = ('id',) skip_unchanged = True @admin.register(ContentTaxonomy) class ContentTaxonomyAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin): resource_class = ContentTaxonomyResource list_display = ['name', 'taxonomy_type', 'slug', 'count', 'external_id', 'external_taxonomy', 'site', 'sector'] list_filter = ['taxonomy_type', 'site', 'sector'] search_fields = ['name', 'slug', 'external_taxonomy'] ordering = ['taxonomy_type', 'name'] readonly_fields = ['count', 'created_at', 'updated_at'] actions = [ 'bulk_soft_delete', 'bulk_merge_taxonomies', ] fieldsets = ( ('Basic Info', { 'fields': ('name', 'slug', 'taxonomy_type', 'description', 'site', 'sector') }), ('Usage', { 'fields': ('count',), 'description': 'Number of content items using this term' }), ('WordPress Sync', { 'fields': ('external_id', 'external_taxonomy', 'metadata'), 'classes': ('collapse',) }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('site', 'sector') def bulk_soft_delete(self, request, queryset): """Delete selected taxonomies""" count = queryset.count() queryset.delete() self.message_user(request, f'{count} taxonomy/taxonomies deleted.', messages.SUCCESS) bulk_soft_delete.short_description = 'Delete selected taxonomies' def bulk_merge_taxonomies(self, request, queryset): """Merge selected taxonomies into one""" from django import forms from igny8_core.business.content.models import ContentTaxonomyRelation if 'apply' in request.POST: target_id = request.POST.get('target_taxonomy') if target_id: target = ContentTaxonomy.objects.get(pk=target_id) merged_count = 0 for taxonomy in queryset.exclude(pk=target.pk): # Move all relations to target ContentTaxonomyRelation.objects.filter(taxonomy=taxonomy).update(taxonomy=target) taxonomy.delete() merged_count += 1 # Update target count target.count = ContentTaxonomyRelation.objects.filter(taxonomy=target).count() target.save() self.message_user(request, f'{merged_count} taxonomies merged into: {target.name}', messages.SUCCESS) return class MergeForm(forms.Form): target_taxonomy = forms.ModelChoiceField( queryset=queryset, label="Merge into", help_text="Select the taxonomy to keep (others will be merged into this one)" ) from django.shortcuts import render return render(request, 'admin/bulk_action_form.html', { 'title': 'Merge Taxonomies', 'queryset': queryset, 'form': MergeForm(), 'action': 'bulk_merge_taxonomies', }) bulk_merge_taxonomies.short_description = 'Merge selected taxonomies' class ContentAttributeResource(resources.ModelResource): """Resource class for importing/exporting Content Attributes""" class Meta: model = ContentAttribute fields = ('id', 'name', 'value', 'attribute_type', 'content__title', 'cluster__name', 'external_id', 'source', 'site__name', 'sector__name', 'created_at') export_order = fields import_id_fields = ('id',) skip_unchanged = True @admin.register(ContentAttribute) class ContentAttributeAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin): resource_class = ContentAttributeResource list_display = ['name', 'value', 'attribute_type', 'content', 'cluster', 'external_id', 'source', 'site', 'sector'] list_filter = ['attribute_type', 'source', 'site', 'sector'] search_fields = ['name', 'value', 'external_attribute_name', 'content__title'] ordering = ['attribute_type', 'name'] actions = [ 'bulk_soft_delete', 'bulk_update_attribute_type', ] fieldsets = ( ('Basic Info', { 'fields': ('attribute_type', 'name', 'value', 'site', 'sector') }), ('Relationships', { 'fields': ('content', 'cluster'), 'description': 'Link to content (products/services) or cluster (semantic attributes).' }), ('WordPress/WooCommerce Sync', { 'fields': ('external_id', 'external_attribute_name', 'source', 'metadata') }), ) def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('content', 'cluster', 'site', 'sector') def bulk_soft_delete(self, request, queryset): """Delete selected attributes""" count = queryset.count() queryset.delete() self.message_user(request, f'{count} attribute(s) deleted.', messages.SUCCESS) bulk_soft_delete.short_description = 'Delete selected attributes' def bulk_update_attribute_type(self, request, queryset): """Update attribute type for selected attributes""" from django import forms if 'apply' in request.POST: attr_type = request.POST.get('attribute_type') if attr_type: updated = queryset.update(attribute_type=attr_type) self.message_user(request, f'{updated} attribute(s) updated to type: {attr_type}', messages.SUCCESS) return ATTR_TYPE_CHOICES = [ ('product', 'Product Attribute'), ('service', 'Service Attribute'), ('semantic', 'Semantic Attribute'), ('technical', 'Technical Attribute'), ] class AttributeTypeForm(forms.Form): attribute_type = forms.ChoiceField( choices=ATTR_TYPE_CHOICES, label="Select Attribute Type", help_text=f"Update attribute type for {queryset.count()} selected attribute(s)" ) from django.shortcuts import render return render(request, 'admin/bulk_action_form.html', { 'title': 'Update Attribute Type', 'queryset': queryset, 'form': AttributeTypeForm(), 'action': 'bulk_update_attribute_type', }) bulk_update_attribute_type.short_description = 'Update attribute type' class ContentTaxonomyRelationResource(resources.ModelResource): """Resource class for exporting Content Taxonomy Relations""" class Meta: model = ContentTaxonomyRelation fields = ('id', 'content__title', 'taxonomy__name', 'taxonomy__taxonomy_type', 'created_at') export_order = fields @admin.register(ContentTaxonomyRelation) class ContentTaxonomyRelationAdmin(ExportMixin, Igny8ModelAdmin): resource_class = ContentTaxonomyRelationResource list_display = ['content', 'taxonomy', 'created_at'] search_fields = ['content__title', 'taxonomy__name'] readonly_fields = ['created_at', 'updated_at'] actions = [ 'bulk_delete_relations', 'bulk_reassign_taxonomy', ] def bulk_delete_relations(self, request, queryset): count = queryset.count() queryset.delete() self.message_user(request, f'{count} content taxonomy relation(s) deleted.', messages.SUCCESS) bulk_delete_relations.short_description = 'Delete selected relations' def bulk_reassign_taxonomy(self, request, queryset): """Admin action to bulk reassign taxonomy - requires intermediate page""" if 'apply' in request.POST: from django import forms from .models import ContentTaxonomy class TaxonomyForm(forms.Form): taxonomy = forms.ModelChoiceField( queryset=ContentTaxonomy.objects.filter(is_active=True), required=True, label='New Taxonomy' ) form = TaxonomyForm(request.POST) if form.is_valid(): new_taxonomy = form.cleaned_data['taxonomy'] count = queryset.update(taxonomy=new_taxonomy) self.message_user(request, f'{count} relation(s) reassigned to {new_taxonomy.name}.', messages.SUCCESS) return from django import forms from .models import ContentTaxonomy class TaxonomyForm(forms.Form): taxonomy = forms.ModelChoiceField( queryset=ContentTaxonomy.objects.filter(is_active=True), required=True, label='New Taxonomy', help_text='Select the taxonomy to reassign these relations to' ) context = { 'title': 'Bulk Reassign Taxonomy', 'queryset': queryset, 'form': TaxonomyForm(), 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, } return render(request, 'admin/bulk_action_form.html', context) bulk_reassign_taxonomy.short_description = 'Reassign taxonomy' class ContentClusterMapResource(resources.ModelResource): """Resource class for exporting Content Cluster Maps""" class Meta: model = ContentClusterMap fields = ('id', 'content__title', 'task__title', 'cluster__name', 'role', 'source', 'site__name', 'sector__name', 'created_at') export_order = fields @admin.register(ContentClusterMap) class ContentClusterMapAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin): resource_class = ContentClusterMapResource list_display = ['content', 'task', 'cluster', 'role', 'source', 'site', 'sector', 'created_at'] list_filter = ['role', 'source', 'site', 'sector'] search_fields = ['content__title', 'task__title', 'cluster__name'] readonly_fields = ['created_at', 'updated_at'] actions = [ 'bulk_delete_maps', 'bulk_update_role', 'bulk_reassign_cluster', ] def bulk_delete_maps(self, request, queryset): count = queryset.count() queryset.delete() self.message_user(request, f'{count} content cluster map(s) deleted.', messages.SUCCESS) bulk_delete_maps.short_description = 'Delete selected maps' def bulk_update_role(self, request, queryset): """Admin action to bulk update role""" if 'apply' in request.POST: from django import forms class RoleForm(forms.Form): ROLE_CHOICES = [ ('pillar', 'Pillar'), ('supporting', 'Supporting'), ('related', 'Related'), ] role = forms.ChoiceField(choices=ROLE_CHOICES, required=True) form = RoleForm(request.POST) if form.is_valid(): new_role = form.cleaned_data['role'] count = queryset.update(role=new_role) self.message_user(request, f'{count} map(s) updated to role: {new_role}.', messages.SUCCESS) return from django import forms class RoleForm(forms.Form): ROLE_CHOICES = [ ('pillar', 'Pillar'), ('supporting', 'Supporting'), ('related', 'Related'), ] role = forms.ChoiceField( choices=ROLE_CHOICES, required=True, help_text='Select the new role for these content cluster maps' ) context = { 'title': 'Bulk Update Role', 'queryset': queryset, 'form': RoleForm(), 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, } return render(request, 'admin/bulk_action_form.html', context) bulk_update_role.short_description = 'Update role' def bulk_reassign_cluster(self, request, queryset): """Admin action to bulk reassign cluster""" if 'apply' in request.POST: from django import forms from igny8_core.business.planner.models import Cluster class ClusterForm(forms.Form): cluster = forms.ModelChoiceField( queryset=Cluster.objects.filter(is_active=True), required=True, label='New Cluster' ) form = ClusterForm(request.POST) if form.is_valid(): new_cluster = form.cleaned_data['cluster'] count = queryset.update(cluster=new_cluster) self.message_user(request, f'{count} map(s) reassigned to cluster: {new_cluster.name}.', messages.SUCCESS) return from django import forms from igny8_core.business.planner.models import Cluster class ClusterForm(forms.Form): cluster = forms.ModelChoiceField( queryset=Cluster.objects.filter(is_active=True), required=True, label='New Cluster', help_text='Select the cluster to reassign these maps to' ) context = { 'title': 'Bulk Reassign Cluster', 'queryset': queryset, 'form': ClusterForm(), 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, } return render(request, 'admin/bulk_action_form.html', context) bulk_reassign_cluster.short_description = 'Reassign cluster'