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
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."))
|