rfiles-online/files/admin.py

236 lines
7.5 KiB
Python

"""
Admin configuration for rfiles.online file management.
"""
from django import forms
from django.contrib import admin
from django.utils.html import format_html
from .models import MediaFile, PublicShare, FileAccessLog, SharedSpace
class SharedSpaceAdminForm(forms.ModelForm):
password = forms.CharField(
widget=forms.PasswordInput,
required=False,
help_text="Leave blank to keep existing password. Set to change."
)
class Meta:
model = SharedSpace
fields = '__all__'
exclude = ['password_hash']
def save(self, commit=True):
instance = super().save(commit=False)
password = self.cleaned_data.get('password')
if password:
instance.set_password(password)
if commit:
instance.save()
return instance
@admin.register(SharedSpace)
class SharedSpaceAdmin(admin.ModelAdmin):
form = SharedSpaceAdminForm
list_display = [
'name',
'slug',
'url_display',
'file_count_display',
'total_size_display',
'is_active',
'created_at',
]
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'slug', 'description']
readonly_fields = ['id', 'created_at', 'updated_at', 'url_display', 'file_count_display', 'total_size_display']
fieldsets = (
(None, {
'fields': ('id', 'name', 'slug', 'description', 'url_display')
}),
('Access Control', {
'fields': ('password', 'is_active', 'max_file_size_mb')
}),
('Statistics', {
'fields': ('file_count_display', 'total_size_display')
}),
('Metadata', {
'fields': ('created_by', 'created_at', 'updated_at')
}),
)
def url_display(self, obj):
url = obj.get_url()
return format_html('<a href="{}" target="_blank">{}</a>', url, url)
url_display.short_description = 'URL'
def file_count_display(self, obj):
return obj.file_count
file_count_display.short_description = 'Files'
def total_size_display(self, obj):
size = obj.total_size_bytes
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"
total_size_display.short_description = 'Total Size'
@admin.register(MediaFile)
class MediaFileAdmin(admin.ModelAdmin):
list_display = [
'title',
'original_filename',
'mime_type',
'formatted_file_size',
'share_count',
'is_processed',
'created_at',
]
list_filter = ['mime_type', 'is_processed', 'created_at']
search_fields = ['title', 'original_filename', 'description', 'tags']
readonly_fields = ['id', 'file_hash', 'file_size', 'created_at', 'updated_at', 'share_links']
fieldsets = (
(None, {
'fields': ('id', 'title', 'description', 'tags')
}),
('File Info', {
'fields': ('file', 'original_filename', 'mime_type', 'file_size', 'file_hash')
}),
('Processing', {
'fields': ('is_processed', 'processing_error', 'extracted_text'),
'classes': ('collapse',)
}),
('Links', {
'fields': ('shared_space',),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('uploaded_by', 'created_at', 'updated_at')
}),
('Public Shares', {
'fields': ('share_links',)
}),
)
def formatted_file_size(self, obj):
size = obj.file_size
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"
formatted_file_size.short_description = 'Size'
def share_count(self, obj):
count = obj.get_public_shares().count()
if count > 0:
return format_html('<span style="color: green;">{}</span>', count)
return count
share_count.short_description = 'Active Shares'
def share_links(self, obj):
shares = obj.get_public_shares()
if not shares:
return "No active shares"
links = []
for share in shares:
url = share.get_public_url()
status_parts = []
if share.expires_at:
status_parts.append(f"expires {share.expires_at}")
if share.max_downloads:
status_parts.append(f"{share.download_count}/{share.max_downloads} downloads")
if share.is_password_protected:
status_parts.append("password protected")
status_str = f" ({', '.join(status_parts)})" if status_parts else ""
links.append(f'<a href="{url}" target="_blank">{share.token[:12]}...</a>{status_str}')
return format_html('<br>'.join(links))
share_links.short_description = 'Share Links'
@admin.register(PublicShare)
class PublicShareAdmin(admin.ModelAdmin):
list_display = [
'token_display',
'media_file',
'status_display',
'download_count',
'is_password_protected',
'expires_at',
'created_at',
]
list_filter = ['is_active', 'is_password_protected', 'created_at', 'expires_at']
search_fields = ['token', 'media_file__title', 'media_file__original_filename', 'note']
readonly_fields = ['id', 'token', 'download_count', 'created_at', 'public_url_display']
fieldsets = (
(None, {
'fields': ('id', 'token', 'media_file', 'public_url_display')
}),
('Access Control', {
'fields': ('is_active', 'expires_at', 'max_downloads', 'download_count')
}),
('Password Protection', {
'fields': ('is_password_protected', 'password_hash'),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('note', 'created_by', 'created_at')
}),
)
def token_display(self, obj):
return f"{obj.token[:16]}..."
token_display.short_description = 'Token'
def status_display(self, obj):
if not obj.is_active:
return format_html('<span style="color: red;">Revoked</span>')
if obj.is_expired:
return format_html('<span style="color: orange;">Expired</span>')
if obj.is_download_limit_reached:
return format_html('<span style="color: orange;">Limit Reached</span>')
return format_html('<span style="color: green;">Active</span>')
status_display.short_description = 'Status'
def public_url_display(self, obj):
url = obj.get_public_url()
return format_html('<a href="{}" target="_blank">{}</a>', url, url)
public_url_display.short_description = 'Public URL'
@admin.register(FileAccessLog)
class FileAccessLogAdmin(admin.ModelAdmin):
list_display = [
'accessed_at',
'media_file',
'access_type',
'ip_address',
'share_token_display',
]
list_filter = ['access_type', 'accessed_at']
search_fields = ['media_file__title', 'ip_address']
readonly_fields = [
'id', 'media_file', 'share', 'accessed_at',
'ip_address', 'user_agent', 'access_type', 'user',
]
def share_token_display(self, obj):
if obj.share:
return f"{obj.share.token[:12]}..."
return "-"
share_token_display.short_description = 'Share'
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False