""" Django Admin Configuration for Plugin Distribution System """ from django import forms from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from unfold.admin import ModelAdmin, TabularInline from .models import Plugin, PluginVersion, PluginInstallation, PluginDownload class PluginVersionForm(forms.ModelForm): """ Simplified form for creating new plugin versions. Auto-fills most fields from the latest version, only requires: - Plugin (select) - Version number - Changelog - Status (defaults to 'draft') All other fields are either: - Auto-filled from previous version (min_api_version, min_php_version, etc.) - Auto-generated on release (file_path, file_size, checksum) - Auto-calculated (version_code) """ class Meta: model = PluginVersion fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # If this is a new version (no instance), auto-fill from latest if not self.instance.pk: # Check if plugin is already selected (from initial data or POST data) plugin_id = None # Try to get plugin from POST data (when form is submitted) if self.data: plugin_id = self.data.get('plugin') # Try to get plugin from initial data (when form is pre-filled) elif self.initial: plugin_id = self.initial.get('plugin') if plugin_id: try: plugin = Plugin.objects.get(pk=plugin_id) latest = plugin.get_latest_version() if latest: # Auto-fill from latest version (only if not POST) if not self.data: if 'min_api_version' in self.fields: self.fields['min_api_version'].initial = latest.min_api_version if 'min_platform_version' in self.fields: self.fields['min_platform_version'].initial = latest.min_platform_version if 'min_php_version' in self.fields: self.fields['min_php_version'].initial = latest.min_php_version if 'force_update' in self.fields: self.fields['force_update'].initial = False except (Plugin.DoesNotExist, ValueError, TypeError): pass # Set helpful help texts for fields that exist in the form # (some fields may be readonly and not in self.fields) if 'version' in self.fields: self.fields['version'].help_text = "Semantic version (e.g., 1.2.0)" if 'changelog' in self.fields: self.fields['changelog'].help_text = "What's new in this version" class PluginVersionInline(TabularInline): """Inline admin for plugin versions.""" model = PluginVersion extra = 0 fields = ['version', 'status', 'file_size', 'released_at', 'download_count'] readonly_fields = ['download_count'] ordering = ['-version_code'] def download_count(self, obj): return obj.get_download_count() download_count.short_description = 'Downloads' @admin.register(Plugin) class PluginAdmin(ModelAdmin): """Admin configuration for Plugin model.""" list_display = ['name', 'slug', 'platform', 'is_active', 'latest_version', 'total_downloads', 'created_at'] list_filter = ['platform', 'is_active', 'created_at'] search_fields = ['name', 'slug', 'description'] readonly_fields = ['created_at', 'updated_at', 'total_downloads', 'active_installations'] fieldsets = [ ('Basic Info', { 'fields': ['name', 'slug', 'platform', 'description', 'homepage_url', 'is_active'] }), ('Statistics', { 'fields': ['total_downloads', 'active_installations'], 'classes': ['collapse'] }), ('Timestamps', { 'fields': ['created_at', 'updated_at'], 'classes': ['collapse'] }), ] inlines = [PluginVersionInline] def latest_version(self, obj): latest = obj.get_latest_version() if latest: return f"v{latest.version}" return "-" latest_version.short_description = 'Latest Version' def total_downloads(self, obj): return obj.get_download_count() total_downloads.short_description = 'Total Downloads' def active_installations(self, obj): return PluginInstallation.objects.filter(plugin=obj, is_active=True).count() active_installations.short_description = 'Active Installations' @admin.register(PluginVersion) class PluginVersionAdmin(ModelAdmin): """Admin configuration for PluginVersion model.""" form = PluginVersionForm list_display = ['plugin', 'version', 'status', 'file_size_display', 'download_count', 'released_at'] list_filter = ['plugin', 'status', 'released_at'] search_fields = ['plugin__name', 'version', 'changelog'] readonly_fields = ['version_code', 'file_path', 'file_size', 'checksum', 'created_at', 'released_at', 'download_count'] fieldsets = [ ('Required Fields', { 'fields': ['plugin', 'version', 'status', 'changelog'], 'description': 'Only these fields are required. Others are auto-filled from previous version or auto-generated.' }), ('Requirements (Auto-filled from previous version)', { 'fields': ['min_api_version', 'min_platform_version', 'min_php_version'], 'classes': ['collapse'] }), ('File Info (Auto-generated on release)', { 'fields': ['file_path', 'file_size', 'checksum'], 'classes': ['collapse'] }), ('Advanced Options', { 'fields': ['version_code', 'force_update'], 'classes': ['collapse'] }), ('Metadata', { 'fields': ['released_at', 'download_count', 'created_at'], 'classes': ['collapse'] }), ] actions = ['release_versions', 'mark_as_update_ready', 'mark_as_deprecated'] def file_size_display(self, obj): if obj.file_size: kb = obj.file_size / 1024 if kb > 1024: return f"{kb / 1024:.1f} MB" return f"{kb:.1f} KB" return "-" file_size_display.short_description = 'Size' def download_count(self, obj): return obj.get_download_count() download_count.short_description = 'Downloads' @admin.action(description="✅ Release selected versions (builds ZIP automatically)") def release_versions(self, request, queryset): from django.utils import timezone count = 0 for version in queryset.filter(status__in=['draft', 'testing', 'staged']): version.status = 'released' version.save() # Triggers signal to build ZIP count += 1 self.message_user(request, f"Released {count} version(s). ZIP files are being built automatically.") @admin.action(description="📢 Mark as update ready (notify WordPress sites)") def mark_as_update_ready(self, request, queryset): count = queryset.filter(status='released').update(status='update_ready') self.message_user(request, f"Marked {count} version(s) as update ready. WordPress sites will be notified.") @admin.action(description="🗑️ Mark as deprecated") def mark_as_deprecated(self, request, queryset): count = queryset.update(status='deprecated') self.message_user(request, f"Marked {count} version(s) as deprecated") @admin.register(PluginInstallation) class PluginInstallationAdmin(ModelAdmin): """Admin configuration for PluginInstallation model.""" list_display = ['site', 'plugin', 'current_version', 'is_active', 'health_status', 'last_health_check'] list_filter = ['plugin', 'is_active', 'health_status'] search_fields = ['site__name', 'plugin__name'] readonly_fields = ['created_at', 'updated_at'] fieldsets = [ ('Installation', { 'fields': ['site', 'plugin', 'current_version', 'is_active'] }), ('Health', { 'fields': ['health_status', 'last_health_check'] }), ('Updates', { 'fields': ['pending_update', 'update_notified_at'] }), ('Timestamps', { 'fields': ['created_at', 'updated_at'], 'classes': ['collapse'] }), ] @admin.register(PluginDownload) class PluginDownloadAdmin(ModelAdmin): """Admin configuration for PluginDownload model.""" list_display = ['plugin', 'version', 'site', 'download_type', 'ip_address', 'created_at'] list_filter = ['plugin', 'download_type', 'created_at'] search_fields = ['plugin__name', 'site__name', 'ip_address'] readonly_fields = ['created_at'] date_hierarchy = 'created_at' fieldsets = [ ('Download Info', { 'fields': ['plugin', 'version', 'download_type'] }), ('Context', { 'fields': ['site', 'account', 'ip_address', 'user_agent'] }), ('Timestamp', { 'fields': ['created_at'] }), ]