Spaces:
Sleeping
Sleeping
| 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'] | |
| 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, | |
| ) | |
| 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') | |
| 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'] | |