240 lines
8.9 KiB
Python
240 lines
8.9 KiB
Python
"""
|
|
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='draft'):
|
|
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 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']
|
|
}),
|
|
]
|