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': ('모든사람보기권한', '비밀번호설정필요', 'TITLE', 'SEQUENCE', 'keyword1', '가입일시') }), ) class Media: css = { 'all': ('admin/css/custom_admin.css',) } def 사진미리보기(self, obj): if obj.사진: return format_html( '', obj.사진.url ) return "사진 없음" 사진미리보기.short_description = '사진 미리보기' def 모든사람보기권한(self, obj): if obj.모든사람보기권한: return format_html('✓ 모든 사람 보기') else: return format_html('👤 회원가입자만 보기') 모든사람보기권한.short_description = '보기 권한' def 비밀번호설정필요(self, obj): if obj.비밀번호설정필요: return format_html('⚠️ 비밀번호 설정 필요') else: return format_html('✓ 비밀번호 설정 완료') 비밀번호설정필요.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('{}', obj.person.이름) elif obj.user: return format_html('{}', obj.user.username) else: return format_html('익명') 사용자명.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( '{}', color, obj.get_action_display() ) action_display.short_description = '활동' action_display.admin_order_field = 'action' 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 = ['
'] for field_name, change_data in changes.items(): old_value = change_data.get('old', '') new_value = change_data.get('new', '') html_parts.append(f'
') html_parts.append(f'{field_name}:
') html_parts.append(f'이전: "{old_value}"
') html_parts.append(f'이후: "{new_value}"') html_parts.append('
') html_parts.append('
') 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('{} ({})', obj.person.이름, obj.user.username) else: # 탈퇴 승인된 경우 백업 데이터에서 정보 가져오기 username = obj.backup_data.get('user_info', {}).get('username', '탈퇴됨') return format_html('{} ({})', 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( '{}', 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