from django import forms from django.contrib import admin, messages from django.db import transaction from django.db.models import Max from .models import Resource, ResourceCheckpoint, SkillResource class SkillResourceInline(admin.TabularInline): model = SkillResource extra = 1 autocomplete_fields = ['skill'] class ResourceCheckpointInline(admin.TabularInline): model = ResourceCheckpoint extra = 0 fields = ('order_index', 'title', 'url_fragment', 'estimated_minutes', 'source') ordering = ('order_index',) class ResourceAdminForm(forms.ModelForm): bulk_checkpoints = forms.CharField( required=False, widget=forms.Textarea(attrs={'rows': 12, 'cols': 80}), label='Bulk add checkpoints', help_text=( 'Paste module/lesson titles, one per line (source=manual). ' 'Only applied when this resource has NO checkpoints yet; if it ' 'already has any, the paste is ignored — edit the inline rows ' 'below instead. Blank lines ignored.' ), ) class Meta: model = Resource exclude = ['skills'] @admin.register(Resource) class ResourceAdmin(admin.ModelAdmin): form = ResourceAdminForm list_display = ('title', 'provider', 'type', 'difficulty_level', 'duration', 'rating') list_filter = ('provider', 'type', 'difficulty_level') search_fields = ('title', 'provider', 'url') inlines = [SkillResourceInline, ResourceCheckpointInline] fieldsets = ( (None, { 'fields': ('title', 'provider', 'url', 'type', 'difficulty_level', 'duration', 'rating'), }), ('Bulk checkpoint paste', { 'classes': ('collapse',), 'fields': ('bulk_checkpoints',), 'description': 'Optional shortcut for adding multiple checkpoints at once.', }), ) def save_model(self, request, obj, form, change): bulk = (form.cleaned_data.get('bulk_checkpoints') or '').strip() lines = [line.strip() for line in bulk.splitlines() if line.strip()] if bulk else [] with transaction.atomic(): super().save_model(request, obj, form, change) if not lines: return # Refuse to append when checkpoints already exist — the admin # should use the inline to edit existing rows. This prevents # accidental duplication and silent orphaning of # UserCheckpointProgress rows when the admin re-edits the list. if obj.checkpoints.exists(): self.message_user( request, "Bulk paste ignored: this resource already has checkpoints. " "Edit them via the inline below instead.", level=messages.WARNING, ) return current_max = obj.checkpoints.aggregate(m=Max('order_index'))['m'] or 0 ResourceCheckpoint.objects.bulk_create([ ResourceCheckpoint( resource=obj, order_index=current_max + offset, title=line, source='manual', ) for offset, line in enumerate(lines, start=1) ]) self.message_user( request, f"Added {len(lines)} checkpoint(s) via bulk paste.", level=messages.SUCCESS, ) @admin.register(ResourceCheckpoint) class ResourceCheckpointAdmin(admin.ModelAdmin): list_display = ('resource', 'order_index', 'title', 'source', 'estimated_minutes') list_filter = ('source', 'resource__provider') search_fields = ('title', 'resource__title') ordering = ('resource', 'order_index') @admin.register(SkillResource) class SkillResourceAdmin(admin.ModelAdmin): list_display = ('skill', 'resource', 'relevance_score') list_filter = ('skill__category',) search_fields = ('skill__skill_name', 'resource__title') autocomplete_fields = ['skill', 'resource']