Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion python_anywhere_website/bible/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
from django.contrib import admin
from django.contrib import admin, messages
from django.core.management import call_command
from .models import BibleBook, BibleVerse
import threading
import logging

logger = logging.getLogger(__name__)


def _run_import_kjv(caller_name=None):
"""
Helper that runs the management command. Intended to run in a background thread.
"""
try:
logger.info("Admin-initiated KJV import started (user=%s)", caller_name)
# Always call with clear=True to ensure a clean import (per requirements)
call_command('import_kjv', clear=True)
logger.info("Admin-initiated KJV import completed (user=%s)", caller_name)
except Exception:
logger.exception("Admin-initiated KJV import failed (user=%s)", caller_name)


def import_kjv_action(modeladmin, request, queryset):
"""
Admin action to start the KJV import in a background thread.
- Visible in the actions dropdown on the BibleBook changelist.
- Uses standard admin permissions (requires change permission).
- Starts the import in a daemon thread and returns immediately.
"""
# Limit to staff users as a safety check (admin actions normally require appropriate perms)
if not request.user.is_staff:
modeladmin.message_user(request, "Only staff users may run the KJV import.", level=messages.ERROR)
return

caller = getattr(request.user, 'username', str(request.user))
try:
thread = threading.Thread(target=_run_import_kjv, args=(caller,), daemon=True)
thread.start()
modeladmin.message_user(
request,
"KJV import has been started in the background. Check the server logs for progress and errors.",
level=messages.INFO,
)
except Exception as exc:
logger.exception("Failed to start KJV import thread (user=%s)", caller)
modeladmin.message_user(request, f"Failed to start import: {exc}", level=messages.ERROR)


# Action metadata for admin UI
import_kjv_action.short_description = "Import KJV Bible"
import_kjv_action.allowed_permissions = ('change',)


@admin.register(BibleBook)
Expand All @@ -10,6 +59,9 @@ class BibleBookAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name',)}
ordering = ['order']

# Register the import action on the changelist actions dropdown
actions = [import_kjv_action]


@admin.register(BibleVerse)
class BibleVerseAdmin(admin.ModelAdmin):
Expand Down
82 changes: 81 additions & 1 deletion python_anywhere_website/bible/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from django.test import TestCase, Client
from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.contrib.auth.models import User
from django.contrib.messages.storage.fallback import FallbackStorage
from bible.models import BibleBook, BibleVerse
from bible.gutenberg_parser import parse_gutenberg_kjv, BOOK_INFO
from bible.admin import import_kjv_action, BibleBookAdmin
import tempfile
import os

Expand Down Expand Up @@ -351,3 +354,80 @@ def test_api_rate_limit_headers(self):
self.assertIn('X-RateLimit-Remaining', response)
self.assertEqual(response['X-RateLimit-Limit'], '100')


class AdminActionTest(TestCase):
"""Tests for admin actions."""

def setUp(self):
self.user_staff = User.objects.create_user(
username='staffuser',
password='testpass',
is_staff=True
)
self.user_nonstaff = User.objects.create_user(
username='normaluser',
password='testpass',
is_staff=False
)
self.book = BibleBook.objects.create(
name='John',
slug='john',
order=43,
testament='NT',
chapters=21
)
self.admin = BibleBookAdmin(BibleBook, None)

def test_import_kjv_action_requires_staff(self):
"""Test that non-staff users cannot run the import action."""
factory = RequestFactory()
request = factory.post('/admin/bible/biblebook/')
request.user = self.user_nonstaff

# Add message support to request
setattr(request, 'session', {})
messages = FallbackStorage(request)
setattr(request, '_messages', messages)

# Run the action
queryset = BibleBook.objects.all()
import_kjv_action(self.admin, request, queryset)

# Check that an error message was sent
message_list = list(messages)
self.assertEqual(len(message_list), 1)
self.assertIn('Only staff users', str(message_list[0]))

def test_import_kjv_action_starts_thread_for_staff(self):
"""Test that staff users can start the import action."""
factory = RequestFactory()
request = factory.post('/admin/bible/biblebook/')
request.user = self.user_staff

# Add message support to request
setattr(request, 'session', {})
messages = FallbackStorage(request)
setattr(request, '_messages', messages)

# Run the action
queryset = BibleBook.objects.all()
import_kjv_action(self.admin, request, queryset)

# Check that a success message was sent
message_list = list(messages)
self.assertEqual(len(message_list), 1)
self.assertIn('started in the background', str(message_list[0]))

def test_import_kjv_action_metadata(self):
"""Test that the action has proper metadata."""
self.assertEqual(import_kjv_action.short_description, "Import KJV Bible")
self.assertEqual(import_kjv_action.allowed_permissions, ('change',))

def test_admin_has_action_registered(self):
"""Test that BibleBookAdmin has the import action registered."""
# Get the admin class
admin = BibleBookAdmin(BibleBook, None)

# Check that actions includes our import action
self.assertIn(import_kjv_action, admin.actions)