236 lines
7.5 KiB
Python
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
|