327 lines
13 KiB
Python
Raw Permalink Normal View History

from django.contrib import admin
from django.utils.html import format_html
from django import forms
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.urls import reverse
from .models import Person, AccessLog, WithdrawalRequest
from .withdrawal_utils import process_withdrawal_approval, reject_withdrawal_request
class PersonAdminForm(forms.ModelForm):
class Meta:
model = Person
fields = '__all__'
widgets = {
'사진': forms.FileInput(attrs={
'style': 'border: 1px solid #ccc; padding: 5px; border-radius: 3px;'
})
}
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
form = PersonAdminForm
list_display = ['SEQUENCE', '이름', '소속', '직책', '연락처', 'user', '모든사람보기권한', '비밀번호설정필요', '가입일시', '사진']
list_filter = ['모든사람보기권한', '비밀번호설정필요', '소속', '직책']
search_fields = ['이름', '소속', '직책', '연락처', 'keyword1', '소개글']
readonly_fields = ['수정일시', '사진미리보기', '가입일시']
list_editable = ['SEQUENCE']
list_display_links = ['이름']
ordering = ['이름']
fieldsets = (
('기본 정보', {
'fields': ('이름', '연락처', 'user')
}),
('상세 정보', {
'fields': ('소속', '직책', '주소', '생년월일')
}),
('미디어', {
'fields': ('사진', '사진미리보기')
}),
('추가 정보', {
'fields': ('keyword1', '소개글')
}),
('설정', {
'fields': ('모든사람보기권한', '비밀번호설정필요', 'TITLE', 'SEQUENCE', '가입일시')
}),
)
class Media:
css = {
'all': ('admin/css/custom_admin.css',)
}
def 사진미리보기(self, obj):
if obj.사진:
return format_html(
'<img src="{}" style="max-width: 100px; max-height: 100px; border-radius: 5px;" />',
obj.사진.url
)
return "사진 없음"
사진미리보기.short_description = '사진 미리보기'
def 모든사람보기권한(self, obj):
if obj.모든사람보기권한:
return format_html('<span style="color: green;">✓ 모든 사람 보기</span>')
else:
return format_html('<span style="color: blue;">👤 회원가입자만 보기</span>')
모든사람보기권한.short_description = '보기 권한'
def 비밀번호설정필요(self, obj):
if obj.비밀번호설정필요:
return format_html('<span style="color: red;">⚠️ 비밀번호 설정 필요</span>')
else:
return format_html('<span style="color: green;">✓ 비밀번호 설정 완료</span>')
비밀번호설정필요.short_description = '비밀번호 설정 상태'
def 수정일시(self, obj):
return obj.user.date_joined if obj.user else 'N/A'
수정일시.short_description = '수정일시'
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def has_add_permission(self, request):
return request.user.is_superuser
def has_change_permission(self, request, obj=None):
return request.user.is_superuser
def has_view_permission(self, request, obj=None):
return request.user.is_superuser
@admin.register(AccessLog)
class AccessLogAdmin(admin.ModelAdmin):
list_display = ['timestamp', '사용자명', 'action_display', 'ip_address', '기기정보', '브라우저정보', 'description']
list_filter = ['action', 'timestamp', 'ip_address']
search_fields = ['user__username', 'person__이름', 'description', 'ip_address']
readonly_fields = ['timestamp', 'user', 'person', 'action', 'description', 'ip_address', 'user_agent', 'session_key', 'metadata', '변경사항_상세보기', '기기정보', '브라우저정보']
date_hierarchy = 'timestamp'
ordering = ['-timestamp']
# 페이지당 표시할 항목 수
list_per_page = 50
def 사용자명(self, obj):
if obj.person:
return format_html('<strong>{}</strong>', obj.person.이름)
elif obj.user:
return format_html('<span style="color: #666;">{}</span>', obj.user.username)
else:
return format_html('<span style="color: #ccc;">익명</span>')
사용자명.short_description = '사용자'
사용자명.admin_order_field = 'person__이름'
def action_display(self, obj):
action_colors = {
'LOGIN': '#28a745', # 초록
'LOGOUT': '#6c757d', # 회색
'SIGNUP': '#007bff', # 파랑
'PROFILE_UPDATE': '#ffc107', # 노랑
'PASSWORD_CHANGE': '#fd7e14', # 주황
'PHONE_VERIFICATION': '#20c997', # 청록
'SEARCH': '#6f42c1', # 보라
'VIEW_PROFILE': '#17a2b8', # 하늘
'MAIN_ACCESS': '#343a40', # 어두운 회색
'ERROR': '#dc3545', # 빨강
'OTHER': '#6c757d', # 회색
}
color = action_colors.get(obj.action, '#6c757d')
return format_html(
'<span style="background-color: {}; color: white; padding: 2px 8px; border-radius: 3px; font-size: 11px;">{}</span>',
color, obj.get_action_display()
)
action_display.short_description = '활동'
action_display.admin_order_field = 'action'
def 기기정보(self, obj):
if obj.metadata and 'device_info' in obj.metadata:
device_info = obj.metadata['device_info']
# 기기별 아이콘 추가
icons = {
'iPhone': '📱',
'iPad': '📱',
'Android 폰': '📱',
'Android 태블릿': '📱',
'Windows 10/11': '💻',
'Windows 8.1': '💻',
'Windows 7': '💻',
'Windows': '💻',
'Mac': '💻',
'Linux': '💻',
}
icon = icons.get(device_info, '🖥️')
return format_html('{} {}', icon, device_info)
return format_html('<span style="color: #ccc;">알 수 없음</span>')
기기정보.short_description = '기기'
def 브라우저정보(self, obj):
if obj.metadata and 'browser_info' in obj.metadata:
browser_info = obj.metadata['browser_info']
# 브라우저별 색상 추가
colors = {
'Chrome': '#4285f4',
'Firefox': '#ff7139',
'Safari': '#1b88ca',
'Microsoft Edge': '#0078d4',
'Opera': '#ff1b2d',
}
color = colors.get(browser_info, '#6c757d')
return format_html(
'<span style="color: {}; font-weight: bold;">{}</span>',
color, browser_info
)
return format_html('<span style="color: #ccc;">알 수 없음</span>')
브라우저정보.short_description = '브라우저'
fieldsets = (
('기본 정보', {
'fields': ('timestamp', 'user', 'person', 'action', 'description')
}),
('접속 정보', {
'fields': ('ip_address', '기기정보', '브라우저정보', 'user_agent', 'session_key')
}),
('상세 변경사항', {
'fields': ('변경사항_상세보기',),
'classes': ('collapse',)
}),
('추가 정보 (JSON)', {
'fields': ('metadata',),
'classes': ('collapse',)
}),
)
def 변경사항_상세보기(self, obj):
"""변경사항을 보기 좋게 표시"""
if obj.action == 'PROFILE_UPDATE' and obj.metadata.get('field_changes'):
changes = obj.metadata['field_changes']
html_parts = ['<div style="font-family: monospace; background: #f8f9fa; padding: 10px; border-radius: 5px;">']
for field_name, change_data in changes.items():
old_value = change_data.get('old', '')
new_value = change_data.get('new', '')
html_parts.append(f'<div style="margin-bottom: 8px;">')
html_parts.append(f'<strong>{field_name}:</strong><br>')
html_parts.append(f'<span style="color: #dc3545;">이전: "{old_value}"</span><br>')
html_parts.append(f'<span style="color: #28a745;">이후: "{new_value}"</span>')
html_parts.append('</div>')
html_parts.append('</div>')
return format_html(''.join(html_parts))
else:
return '변경사항 없음'
변경사항_상세보기.short_description = '필드별 변경사항'
def has_add_permission(self, request):
return False # 로그는 시스템에서만 생성
def has_change_permission(self, request, obj=None):
return False # 로그는 수정 불가
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser # 슈퍼유저만 삭제 가능
@admin.register(WithdrawalRequest)
class WithdrawalRequestAdmin(admin.ModelAdmin):
list_display = ['request_date', '사용자명', 'status_display', 'approved_by', 'approved_date']
list_filter = ['status', 'request_date', 'approved_date']
search_fields = ['user__username', 'person__이름', 'reason']
readonly_fields = ['request_date', 'user', 'person', 'reason', 'backup_data']
date_hierarchy = 'request_date'
ordering = ['-request_date']
# 페이지당 표시할 항목 수
list_per_page = 30
def 사용자명(self, obj):
if obj.user:
return format_html('<strong>{}</strong> ({})', obj.person.이름, obj.user.username)
else:
# 탈퇴 승인된 경우 백업 데이터에서 정보 가져오기
username = obj.backup_data.get('user_info', {}).get('username', '탈퇴됨')
return format_html('<strong>{}</strong> (<span style="color: #dc3545;">{}</span>)', obj.person.이름, username)
사용자명.short_description = '사용자'
사용자명.admin_order_field = 'person__이름'
def status_display(self, obj):
status_colors = {
'PENDING': '#ffc107', # 노랑
'APPROVED': '#28a745', # 초록
'REJECTED': '#dc3545', # 빨강
}
color = status_colors.get(obj.status, '#6c757d')
return format_html(
'<span style="background-color: {}; color: white; padding: 2px 8px; border-radius: 3px; font-size: 11px;">{}</span>',
color, obj.get_status_display()
)
status_display.short_description = '상태'
status_display.admin_order_field = 'status'
fieldsets = (
('기본 정보', {
'fields': ('request_date', 'user', 'person', 'status')
}),
('탈퇴 요청 내용', {
'fields': ('reason',)
}),
('승인 정보', {
'fields': ('approved_by', 'approved_date', 'admin_notes')
}),
('백업 데이터', {
'fields': ('backup_data',),
'classes': ('collapse',)
}),
)
actions = ['approve_withdrawal', 'reject_withdrawal']
def approve_withdrawal(self, request, queryset):
"""탈퇴 요청 승인"""
approved_count = 0
failed_count = 0
for withdrawal_request in queryset.filter(status='PENDING'):
try:
if process_withdrawal_approval(withdrawal_request, request.user, '관리자 일괄 승인'):
approved_count += 1
else:
failed_count += 1
except Exception as e:
failed_count += 1
self.message_user(request, f'{withdrawal_request.person.이름} 탈퇴 처리 실패: {e}', level=messages.ERROR)
if approved_count > 0:
self.message_user(request, f'{approved_count}건의 탈퇴 요청을 승인했습니다.', level=messages.SUCCESS)
if failed_count > 0:
self.message_user(request, f'{failed_count}건의 탈퇴 처리에 실패했습니다.', level=messages.WARNING)
approve_withdrawal.short_description = '선택된 탈퇴 요청 승인'
def reject_withdrawal(self, request, queryset):
"""탈퇴 요청 거부"""
rejected_count = 0
for withdrawal_request in queryset.filter(status='PENDING'):
if reject_withdrawal_request(withdrawal_request, request.user, '관리자 일괄 거부'):
rejected_count += 1
if rejected_count > 0:
self.message_user(request, f'{rejected_count}건의 탈퇴 요청을 거부했습니다.', level=messages.SUCCESS)
reject_withdrawal.short_description = '선택된 탈퇴 요청 거부'
def has_add_permission(self, request):
return False # 탈퇴 요청은 사용자가 직접 생성
def has_change_permission(self, request, obj=None):
# 승인 대기 중인 요청만 수정 가능
if obj and obj.status != 'PENDING':
return False
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser