extensions-website/files/admin.py
Anna Sirota aeaeee7ff9 Files: add extension field to the model
This makes it easier to identify which files belong to which extensions
(because they can be linked via 4 different ways: as icons, featured
images, version files and preview files), and makes this relation
obvious at the database level.
2024-05-09 17:55:46 +02:00

217 lines
5.7 KiB
Python

import logging
from django.conf import settings
from django.contrib import admin
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.safestring import mark_safe
import background_task.admin
import background_task.models
from .models import File, FileValidation
import files.signals
logger = logging.getLogger(__name__)
def schedule_scan(self, request, queryset):
"""Scan selected files."""
for instance in queryset:
files.signals.schedule_scan(instance)
def make_thumbnails(self, request, queryset):
"""Make thumbnails for selected files."""
for instance in queryset.filter(type__in=(File.TYPES.IMAGE, File.TYPES.VIDEO)):
files.tasks.make_thumbnails.task_function(file_id=instance.pk)
class FileValidationInlineAdmin(admin.StackedInline):
model = FileValidation
readonly_fields = ('date_created', 'date_modified', 'is_ok', 'results')
extra = 0
def _nope(self, request, obj):
return False
has_add_permission = _nope
has_change_permission = _nope
has_delete_permission = _nope
@admin.register(File)
class FileAdmin(admin.ModelAdmin):
class Media:
css = {'all': ('files/admin/file.css',)}
def thumbnails(self, obj):
if not obj or not (obj.is_image or obj.is_video):
return ''
try:
context = {'file': obj, 'MEDIA_URL': settings.MEDIA_URL}
return render_to_string('files/admin/thumbnails.html', context)
except Exception:
# Make sure any exception happening here is always logged
# (e.g. admin eats exceptions in ModelAdmin properties, making it hard to debug)
logger.exception('Failed to render thumbnails')
raise
def get_form(self, request, obj=None, **kwargs):
"""Override metadata help text depending on file type."""
if obj and (obj.is_image or obj.is_video):
help_text = 'Additional information about the file, e.g. existing thumbnails.'
kwargs.update({'help_texts': {'metadata': help_text}})
return super().get_form(request, obj, **kwargs)
view_on_site = False
save_on_top = True
list_filter = (
'validation__is_ok',
'type',
'status',
'date_status_changed',
'date_approved',
('extension', admin.EmptyFieldListFilter),
)
list_display = (
'original_name',
'extension_link',
'user',
'date_created',
'type',
'status',
'is_ok',
)
list_select_related = ('version__extension', 'user')
readonly_fields = (
'id',
'date_created',
'date_modified',
'date_approved',
'date_status_changed',
'size_bytes',
'thumbnails',
'thumbnail',
'type',
'user',
'original_hash',
'original_name',
'hash',
'content_type',
)
search_fields = (
'^version__extension__slug',
'^version__extension__name',
'extensions__slug',
'extensions__name',
'original_name',
'hash',
'source',
)
fieldsets = (
(
None,
{
'fields': (
'id',
('source', 'thumbnails', 'thumbnail'),
('type', 'content_type', 'original_name'),
'status',
)
},
),
(
'Dates',
{
'fields': (
'date_created',
'date_modified',
'date_status_changed',
'date_approved',
)
},
),
(
'Details',
{
'fields': (
('hash', 'original_hash'),
'metadata',
'size_bytes',
'user',
),
},
),
)
inlines = [FileValidationInlineAdmin]
actions = [schedule_scan, make_thumbnails]
def extension_link(self, obj):
return (
mark_safe(
'<a href="{}" target="_blank">{}</a>'.format(
reverse('admin:extensions_extension_change', args=(obj.extension_id,)),
obj.extension,
)
)
if obj.extension_id
else '-'
)
extension_link.short_description = 'Extension'
def is_ok(self, obj):
return obj.validation.is_ok if hasattr(obj, 'validation') else None
is_ok.boolean = True
try:
admin.site.unregister(background_task.models.Task)
admin.site.unregister(background_task.models.CompletedTask)
except admin.site.NotRegistered:
pass
class TaskMixin:
"""Modify a few properties of background tasks displayed in admin."""
def no_errors(self, obj):
"""Replace background_task's "has_error".
Make Django's red/green boolean icons less confusing
in the context of "there's an error during task run".
"""
return not bool(obj.last_error)
no_errors.boolean = True
@admin.register(background_task.models.Task)
@admin.register(background_task.models.CompletedTask)
class TaskAdmin(background_task.admin.TaskAdmin, TaskMixin):
date_hierarchy = 'run_at'
list_display = [
'run_at',
'task_name',
'task_params',
'attempts',
'no_errors',
'locked_by',
'locked_by_pid_running',
]
list_filter = (
'task_name',
'run_at',
'failed_at',
'locked_at',
'attempts',
'creator_content_type',
)
search_fields = ['task_name', 'task_params', 'last_error', 'verbose_name']