You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

155 lines
6.7 KiB

from django.contrib import admin
from django.utils import timezone
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin, StackedInline
from unfold.decorators import display, action
from apps.account.models.verification import VerificationDocument
from utils.admin import project_admin_site
class VerificationDocumentInline(StackedInline):
model = VerificationDocument
fk_name = 'user'
extra = 0
fields = ('document_type', 'title', 'document_file', 'document_preview', 'status', 'admin_notes', 'reviewed_by', 'uploaded_at')
readonly_fields = ('uploaded_at', 'document_preview')
@display(description=_('Preview / Download'))
def document_preview(self, obj):
if not obj or not obj.document_file:
return _("No file uploaded")
file_url = obj.document_file.url
ext = obj.document_file.name.lower().split('.')[-1] if obj.document_file.name else ''
if ext in ['jpg', 'jpeg', 'png', 'webp', 'gif']:
return format_html(
'<a href="{url}" target="_blank" rel="noopener noreferrer">'
'<img src="{url}" style="max-height: 120px; max-width: 200px; border-radius: 6px; object-fit: contain; margin-bottom: 4px;" />'
'</a><br/><a href="{url}" target="_blank" rel="noopener noreferrer" style="color: #3b82f6; font-size: 12px;">↗ Open Full File</a>',
url=file_url
)
return format_html(
'<a href="{url}" target="_blank" rel="noopener noreferrer" style="color: #3b82f6; font-size: 12px; font-weight: 500;">'
'↗ Open / Download Document'
'</a>',
url=file_url
)
@admin.register(VerificationDocument, site=project_admin_site)
class VerificationDocumentAdmin(ModelAdmin):
list_display = (
'id',
'user_display',
'document_type',
'title',
'document_link',
'status_badge',
'uploaded_at',
'reviewed_by',
'reviewed_at'
)
list_display_links = ('id', 'user_display', 'document_type', 'title')
list_filter = ('status', 'document_type', 'uploaded_at')
search_fields = ('user__email', 'user__fullname', 'title', 'admin_notes')
readonly_fields = ('uploaded_at', 'reviewed_at', 'document_preview')
autocomplete_fields = ('user', 'reviewed_by')
actions = ['approve_documents', 'reject_documents']
fieldsets = (
(_('Document Information'), {
'fields': ('user', 'document_type', 'title', 'document_file', 'document_preview')
}),
(_('Review Status'), {
'fields': ('status', 'admin_notes', 'reviewed_by', 'reviewed_at', 'uploaded_at')
}),
)
@display(description=_('User'))
def user_display(self, obj):
return obj.user.fullname or obj.user.email
@display(description=_('File'))
def document_link(self, obj):
if not obj.document_file:
return "-"
return format_html(
'<a href="{url}" target="_blank" rel="noopener noreferrer" style="color: #3b82f6; font-weight: 500;">'
'View File ↗'
'</a>',
url=obj.document_file.url
)
@display(description=_('Document Preview / File'))
def document_preview(self, obj):
if not obj or not obj.document_file:
return _("No file uploaded")
file_url = obj.document_file.url
ext = obj.document_file.name.lower().split('.')[-1] if obj.document_file.name else ''
if ext in ['jpg', 'jpeg', 'png', 'webp', 'gif']:
return format_html(
'<div style="display: flex; flex-direction: column; gap: 8px;">'
'<a href="{url}" target="_blank" rel="noopener noreferrer">'
'<img src="{url}" style="max-height: 250px; max-width: 400px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); object-fit: contain; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);" />'
'</a>'
'<a href="{url}" target="_blank" rel="noopener noreferrer" style="display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: #3b82f6; font-weight: 500;">'
'↗ Open Full Image in New Tab'
'</a>'
'</div>',
url=file_url
)
elif ext == 'pdf':
return format_html(
'<div style="display: flex; flex-direction: column; gap: 8px;">'
'<iframe src="{url}" style="width: 100%; max-width: 600px; height: 350px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15);"></iframe>'
'<a href="{url}" target="_blank" rel="noopener noreferrer" style="display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: #3b82f6; font-weight: 500;">'
'↗ Open PDF in New Tab'
'</a>'
'</div>',
url=file_url
)
else:
return format_html(
'<a href="{url}" target="_blank" rel="noopener noreferrer" style="display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: #3b82f6; font-weight: 500;">'
'📄 Open / Download File ({name}) ↗'
'</a>',
url=file_url,
name=obj.document_file.name.split('/')[-1]
)
@display(description=_('Status'), label={
VerificationDocument.Status.VERIFIED: "success",
VerificationDocument.Status.PENDING: "warning",
VerificationDocument.Status.REJECTED: "danger",
})
def status_badge(self, obj):
return obj.get_status_display()
def save_model(self, request, obj, form, change):
if obj.status in [VerificationDocument.Status.VERIFIED, VerificationDocument.Status.REJECTED]:
if not obj.reviewed_by:
obj.reviewed_by = request.user
if not obj.reviewed_at:
obj.reviewed_at = timezone.now()
super().save_model(request, obj, form, change)
@action(description=_('Approve selected verification documents'))
def approve_documents(self, request, queryset):
queryset.update(
status=VerificationDocument.Status.VERIFIED,
reviewed_by=request.user,
reviewed_at=timezone.now()
)
self.message_user(request, _("Selected documents have been approved."))
@action(description=_('Reject selected verification documents'))
def reject_documents(self, request, queryset):
queryset.update(
status=VerificationDocument.Status.REJECTED,
reviewed_by=request.user,
reviewed_at=timezone.now()
)
self.message_user(request, _("Selected documents have been rejected."))