성능개선 문자인증오류수정 텔레그램메시포함 등
This commit is contained in:
parent
0761716ef5
commit
d80a0ad464
Binary file not shown.
Binary file not shown.
BIN
A_core/__pycache__/telegram_utils.cpython-38.pyc
Normal file
BIN
A_core/__pycache__/telegram_utils.cpython-38.pyc
Normal file
Binary file not shown.
@ -30,6 +30,14 @@ SECRET_KEY = 'django-insecure-kst@+h&50%!m$(d!l*qbb0l7f@z#@#me__yye^$5kg%0m%1=im
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
# SMS 테스트 모드 설정
|
||||
SMS_TEST_MODE = False # True: 테스트 모드 (콘솔 출력), False: 실제 SMS 발송
|
||||
|
||||
# 텔레그램 봇 설정
|
||||
TELEGRAM_BOT_TOKEN = "5094633886:AAF_Cy3mD-3rqKQMfbgpVO39jNZDXNFZN-o" # BotFather에서 받은 토큰
|
||||
TELEGRAM_CHAT_ID = "5085456424" # getUpdates로 얻은 chat_id
|
||||
TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
|
||||
ALLOWED_HOSTS = ['www.sillaamp.com', 'sillaamp.com', '192.168.1.119', 'localhost', '127.0.0.1', '*']
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
'http://www.sillaamp.com',
|
||||
|
||||
@ -144,23 +144,81 @@ sms_service = NaverCloudSMS()
|
||||
|
||||
def send_verification_sms(phone_number: str, verification_code: str) -> Dict[str, Any]:
|
||||
"""인증번호 SMS 발송 함수 (편의 함수)"""
|
||||
# 실제 SMS 발송 시도
|
||||
print(f"[DEBUG] SMS 발송 시도: {phone_number} - {verification_code}")
|
||||
print(f"[DEBUG] Access Key: {sms_service.access_key}")
|
||||
print(f"[DEBUG] Service ID: {sms_service.service_id}")
|
||||
print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
|
||||
|
||||
result = sms_service.send_verification_code(phone_number, verification_code)
|
||||
print(f"[DEBUG] SMS 발송 결과: {result}")
|
||||
# SMS 테스트 모드 확인
|
||||
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
|
||||
|
||||
return result
|
||||
if sms_test_mode:
|
||||
# 테스트 모드: 콘솔에 인증번호 출력
|
||||
print("=" * 60)
|
||||
print("🔔 [SMS 테스트 모드] 인증번호 발송")
|
||||
print(f"📱 수신번호: {phone_number}")
|
||||
print(f"🔑 인증번호: {verification_code}")
|
||||
print(f"📝 메시지: [신라AMP] 인증번호는 [{verification_code}]입니다. 3분 이내에 입력해주세요.")
|
||||
print("=" * 60)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'SMS 테스트 모드: 콘솔에 인증번호가 출력되었습니다.',
|
||||
'test_mode': True,
|
||||
'verification_code': verification_code # 테스트 모드에서만 포함
|
||||
}
|
||||
else:
|
||||
# 실제 SMS 발송 모드
|
||||
print(f"[DEBUG] 실제 SMS 발송 시도: {phone_number} - {verification_code}")
|
||||
print(f"[DEBUG] Access Key: {sms_service.access_key}")
|
||||
print(f"[DEBUG] Service ID: {sms_service.service_id}")
|
||||
print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
|
||||
|
||||
result = sms_service.send_verification_code(phone_number, verification_code)
|
||||
print(f"[DEBUG] SMS 발송 결과: {result}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def send_withdrawal_approval_sms(phone_number: str, name: str) -> Dict[str, Any]:
|
||||
"""탈퇴 승인 SMS 발송 함수 (편의 함수)"""
|
||||
return sms_service.send_withdrawal_approval_sms(phone_number, name)
|
||||
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
|
||||
|
||||
if sms_test_mode:
|
||||
message = f"[신라AMP] {name}님의 회원탈퇴가 승인되었습니다. 그동안 이용해주셔서 감사했습니다."
|
||||
print("=" * 60)
|
||||
print("🔔 [SMS 테스트 모드] 탈퇴 승인 알림")
|
||||
print(f"📱 수신번호: {phone_number}")
|
||||
print(f"👤 대상자: {name}")
|
||||
print(f"📝 메시지: {message}")
|
||||
print("=" * 60)
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'SMS 테스트 모드: 탈퇴 승인 알림이 콘솔에 출력되었습니다.',
|
||||
'test_mode': True
|
||||
}
|
||||
else:
|
||||
return sms_service.send_withdrawal_approval_sms(phone_number, name)
|
||||
|
||||
|
||||
def send_withdrawal_rejection_sms(phone_number: str, name: str, reason: str = None) -> Dict[str, Any]:
|
||||
"""탈퇴 거부 SMS 발송 함수 (편의 함수)"""
|
||||
return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)
|
||||
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
|
||||
|
||||
if sms_test_mode:
|
||||
if reason:
|
||||
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 사유: {reason}"
|
||||
else:
|
||||
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 사항은 관리자에게 문의해주세요."
|
||||
|
||||
print("=" * 60)
|
||||
print("🔔 [SMS 테스트 모드] 탈퇴 거부 알림")
|
||||
print(f"📱 수신번호: {phone_number}")
|
||||
print(f"👤 대상자: {name}")
|
||||
print(f"📝 메시지: {message}")
|
||||
if reason:
|
||||
print(f"❌ 거부 사유: {reason}")
|
||||
print("=" * 60)
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'SMS 테스트 모드: 탈퇴 거부 알림이 콘솔에 출력되었습니다.',
|
||||
'test_mode': True
|
||||
}
|
||||
else:
|
||||
return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)
|
||||
|
||||
271
A_core/telegram_utils.py
Normal file
271
A_core/telegram_utils.py
Normal file
@ -0,0 +1,271 @@
|
||||
"""
|
||||
텔레그램 봇 유틸리티
|
||||
회원가입 알림 등을 위한 텔레그램 메시지 전송 기능
|
||||
"""
|
||||
|
||||
import requests
|
||||
import threading
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_client_ip(request):
|
||||
"""
|
||||
클라이언트의 실제 IP 주소를 추출
|
||||
프록시, 로드밸런서, CDN 등을 고려하여 실제 IP를 찾음
|
||||
"""
|
||||
# 프록시나 로드밸런서를 통한 실제 클라이언트 IP 확인
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
# 첫 번째 IP가 실제 클라이언트 IP (쉼표로 구분된 경우)
|
||||
ip = x_forwarded_for.split(',')[0].strip()
|
||||
if ip and ip != '127.0.0.1':
|
||||
return ip
|
||||
|
||||
# Cloudflare 등 CDN에서 사용하는 헤더
|
||||
cf_connecting_ip = request.META.get('HTTP_CF_CONNECTING_IP')
|
||||
if cf_connecting_ip and cf_connecting_ip != '127.0.0.1':
|
||||
return cf_connecting_ip
|
||||
|
||||
# Nginx 등 리버스 프록시에서 사용
|
||||
x_real_ip = request.META.get('HTTP_X_REAL_IP')
|
||||
if x_real_ip and x_real_ip != '127.0.0.1':
|
||||
return x_real_ip
|
||||
|
||||
# 기본 REMOTE_ADDR
|
||||
remote_addr = request.META.get('REMOTE_ADDR', '알 수 없음')
|
||||
|
||||
# 로컬 환경 표시
|
||||
if remote_addr == '127.0.0.1':
|
||||
return f"{remote_addr} (로컬 개발환경)"
|
||||
elif remote_addr.startswith('192.168.') or remote_addr.startswith('10.') or remote_addr.startswith('172.'):
|
||||
return f"{remote_addr} (내부 네트워크)"
|
||||
|
||||
return remote_addr
|
||||
|
||||
|
||||
def get_device_info(user_agent):
|
||||
"""User-Agent에서 기기 정보 추출"""
|
||||
if not user_agent:
|
||||
return "알 수 없음"
|
||||
|
||||
# 모바일 기기 체크
|
||||
if any(mobile in user_agent for mobile in ['iPhone', 'iPad', 'Android', 'Mobile']):
|
||||
if 'iPhone' in user_agent:
|
||||
return "iPhone"
|
||||
elif 'iPad' in user_agent:
|
||||
return "iPad"
|
||||
elif 'Android' in user_agent:
|
||||
if 'Mobile' in user_agent:
|
||||
return "Android 폰"
|
||||
else:
|
||||
return "Android 태블릿"
|
||||
else:
|
||||
return "모바일"
|
||||
|
||||
# 데스크톱 OS 체크
|
||||
if 'Windows NT' in user_agent:
|
||||
if 'Windows NT 10.0' in user_agent:
|
||||
return "Windows 10/11"
|
||||
elif 'Windows NT 6.3' in user_agent:
|
||||
return "Windows 8.1"
|
||||
elif 'Windows NT 6.1' in user_agent:
|
||||
return "Windows 7"
|
||||
else:
|
||||
return "Windows"
|
||||
elif 'Macintosh' in user_agent or 'Mac OS X' in user_agent:
|
||||
return "Mac"
|
||||
elif 'Linux' in user_agent and 'Android' not in user_agent:
|
||||
return "Linux"
|
||||
|
||||
return "알 수 없음"
|
||||
|
||||
|
||||
def get_browser_info(user_agent):
|
||||
"""User-Agent에서 브라우저 정보 추출"""
|
||||
if not user_agent:
|
||||
return "알 수 없음"
|
||||
|
||||
# 브라우저 체크 (순서 중요 - Chrome이 Safari 문자열도 포함하므로)
|
||||
if 'Edg/' in user_agent:
|
||||
return "Microsoft Edge"
|
||||
elif 'Chrome/' in user_agent and 'Safari/' in user_agent:
|
||||
return "Chrome"
|
||||
elif 'Firefox/' in user_agent:
|
||||
return "Firefox"
|
||||
elif 'Safari/' in user_agent and 'Chrome' not in user_agent:
|
||||
return "Safari"
|
||||
elif 'Opera' in user_agent or 'OPR/' in user_agent:
|
||||
return "Opera"
|
||||
|
||||
return "알 수 없음"
|
||||
|
||||
|
||||
def send_telegram_message(message, chat_id=None):
|
||||
"""
|
||||
텔레그램 봇으로 메시지 전송
|
||||
|
||||
Args:
|
||||
message (str): 전송할 메시지
|
||||
chat_id (str, optional): 채팅 ID. 없으면 기본 설정값 사용
|
||||
|
||||
Returns:
|
||||
dict: {'success': bool, 'error': str}
|
||||
"""
|
||||
try:
|
||||
if not hasattr(settings, 'TELEGRAM_BOT_TOKEN') or not settings.TELEGRAM_BOT_TOKEN:
|
||||
return {'success': False, 'error': 'TELEGRAM_BOT_TOKEN이 설정되지 않았습니다.'}
|
||||
|
||||
if not chat_id:
|
||||
chat_id = getattr(settings, 'TELEGRAM_CHAT_ID', None)
|
||||
if not chat_id:
|
||||
return {'success': False, 'error': 'TELEGRAM_CHAT_ID가 설정되지 않았습니다.'}
|
||||
|
||||
url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
|
||||
payload = {
|
||||
'chat_id': chat_id,
|
||||
'text': message,
|
||||
'parse_mode': 'HTML' # HTML 포맷 지원
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('ok'):
|
||||
print(f"[TELEGRAM_SUCCESS] 메시지 전송 성공: {message[:50]}...")
|
||||
return {'success': True, 'error': None}
|
||||
else:
|
||||
error_msg = result.get('description', '알 수 없는 오류')
|
||||
print(f"[TELEGRAM_ERROR] API 오류: {error_msg}")
|
||||
return {'success': False, 'error': f'텔레그램 API 오류: {error_msg}'}
|
||||
else:
|
||||
print(f"[TELEGRAM_ERROR] HTTP 오류: {response.status_code}")
|
||||
return {'success': False, 'error': f'HTTP 오류: {response.status_code}'}
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print("[TELEGRAM_ERROR] 요청 시간 초과")
|
||||
return {'success': False, 'error': '요청 시간 초과'}
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"[TELEGRAM_ERROR] 네트워크 오류: {str(e)}")
|
||||
return {'success': False, 'error': f'네트워크 오류: {str(e)}'}
|
||||
except Exception as e:
|
||||
print(f"[TELEGRAM_ERROR] 예상치 못한 오류: {str(e)}")
|
||||
return {'success': False, 'error': f'예상치 못한 오류: {str(e)}'}
|
||||
|
||||
|
||||
def send_telegram_message_async(message, chat_id=None):
|
||||
"""
|
||||
비동기로 텔레그램 메시지 전송 (백그라운드)
|
||||
|
||||
Args:
|
||||
message (str): 전송할 메시지
|
||||
chat_id (str, optional): 채팅 ID
|
||||
"""
|
||||
def _send():
|
||||
send_telegram_message(message, chat_id)
|
||||
|
||||
thread = threading.Thread(target=_send)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def send_signup_notification(name, phone, request):
|
||||
"""
|
||||
회원가입 문자인증 요청 알림
|
||||
|
||||
Args:
|
||||
name (str): 가입자 이름
|
||||
phone (str): 전화번호
|
||||
request: Django request 객체 (IP, User-Agent 정보)
|
||||
"""
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 클라이언트 정보 추출
|
||||
ip = get_client_ip(request)
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
|
||||
device_info = get_device_info(user_agent)
|
||||
browser_info = get_browser_info(user_agent)
|
||||
|
||||
# 디버그 정보 출력
|
||||
print(f"[TELEGRAM_DEBUG] 회원가입 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
|
||||
|
||||
# 상세 헤더 정보 (개발 환경에서만)
|
||||
if ip.endswith('(로컬 개발환경)'):
|
||||
print(f"[DEBUG] REMOTE_ADDR: {request.META.get('REMOTE_ADDR')}")
|
||||
print(f"[DEBUG] HTTP_X_FORWARDED_FOR: {request.META.get('HTTP_X_FORWARDED_FOR')}")
|
||||
print(f"[DEBUG] HTTP_X_REAL_IP: {request.META.get('HTTP_X_REAL_IP')}")
|
||||
print(f"[DEBUG] HTTP_CF_CONNECTING_IP: {request.META.get('HTTP_CF_CONNECTING_IP')}")
|
||||
print(f"[DEBUG] User-Agent: {user_agent[:100]}...")
|
||||
|
||||
message = f"""
|
||||
🔔 <b>신라AMP 회원가입 알림</b>
|
||||
|
||||
👤 <b>이름:</b> {name}
|
||||
📱 <b>전화번호:</b> {phone}
|
||||
⏰ <b>요청시간:</b> {current_time}
|
||||
|
||||
🌐 <b>접속 정보:</b>
|
||||
• IP 주소: <code>{ip}</code>
|
||||
• 기기: {device_info}
|
||||
• 브라우저: {browser_info}
|
||||
|
||||
💡 새로운 회원가입 요청이 있습니다!
|
||||
""".strip()
|
||||
|
||||
# 비동기로 전송 (사용자 대기시간 없음)
|
||||
send_telegram_message_async(message)
|
||||
|
||||
|
||||
def send_password_reset_notification(phone, request):
|
||||
"""
|
||||
비밀번호 찾기 문자인증 요청 알림
|
||||
|
||||
Args:
|
||||
phone (str): 전화번호
|
||||
request: Django request 객체 (IP, User-Agent 정보)
|
||||
"""
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 클라이언트 정보 추출
|
||||
ip = get_client_ip(request)
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
|
||||
device_info = get_device_info(user_agent)
|
||||
browser_info = get_browser_info(user_agent)
|
||||
|
||||
# 디버그 정보 출력
|
||||
print(f"[TELEGRAM_DEBUG] 비밀번호 찾기 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
|
||||
|
||||
message = f"""
|
||||
🔑 <b>신라AMP 비밀번호 찾기 알림</b>
|
||||
|
||||
📱 <b>전화번호:</b> {phone}
|
||||
⏰ <b>요청시간:</b> {current_time}
|
||||
|
||||
🌐 <b>접속 정보:</b>
|
||||
• IP 주소: <code>{ip}</code>
|
||||
• 기기: {device_info}
|
||||
• 브라우저: {browser_info}
|
||||
|
||||
💡 비밀번호 찾기 요청이 있습니다!
|
||||
""".strip()
|
||||
|
||||
# 비동기로 전송 (사용자 대기시간 없음)
|
||||
send_telegram_message_async(message)
|
||||
|
||||
|
||||
def test_telegram_bot():
|
||||
"""
|
||||
텔레그램 봇 연결 테스트
|
||||
"""
|
||||
test_message = f"🧪 <b>신라AMP 봇 테스트</b>\n\n⏰ 테스트 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n✅ 봇이 정상적으로 작동하고 있습니다!"
|
||||
|
||||
result = send_telegram_message(test_message)
|
||||
|
||||
if result['success']:
|
||||
print("✅ 텔레그램 봇 테스트 성공!")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 텔레그램 봇 테스트 실패: {result['error']}")
|
||||
return False
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -305,6 +305,6 @@ class PersonForm(forms.ModelForm):
|
||||
}),
|
||||
'keyword1': forms.TextInput(attrs={
|
||||
'class': 'w-full px-4 py-3 rounded-xl bg-gray-700 bg-opacity-80 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition',
|
||||
'placeholder': '검색 키워드 (예: 회계감사)'
|
||||
'placeholder': '검색 키워드 (예: 잘생김, 골프천재, 댄스머신 ...)'
|
||||
}),
|
||||
}
|
||||
|
||||
@ -304,7 +304,7 @@
|
||||
|
||||
// 소개글 설정
|
||||
if (intro && intro.trim() !== '') {
|
||||
modalIntro.textContent = intro;
|
||||
modalIntro.innerHTML = intro.replace(/\n/g, '<br>');
|
||||
modalIntroSection.classList.remove('hidden');
|
||||
modalNoIntro.classList.add('hidden');
|
||||
} else {
|
||||
|
||||
@ -1,34 +1,38 @@
|
||||
{% load static %}
|
||||
<div class="flex bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 h-[210px] relative border border-gray-200 dark:border-gray-700 transition-colors duration-300">
|
||||
<div class="flex bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 min-h-[210px] relative border border-gray-200 dark:border-gray-700 transition-colors duration-300">
|
||||
<!-- 좌측: 사진 + 버튼 -->
|
||||
<div class="flex flex-col items-center mr-4 w-[150px]">
|
||||
{% if person.사진 and person.사진.url and 'media/' in person.사진.url %}
|
||||
<img
|
||||
src="{{ person.사진.url }}"
|
||||
alt="{{ person.이름 }}"
|
||||
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
|
||||
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
|
||||
>
|
||||
{% else %}
|
||||
<img
|
||||
src="{% static 'B_main/images/default_user.png' %}"
|
||||
alt="{{ person.이름 }}"
|
||||
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
|
||||
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
|
||||
>
|
||||
{% endif %}
|
||||
{% if person.이름 %}
|
||||
<a
|
||||
href="{% url 'vcard_download' person.이름 %}"
|
||||
class="inline-block bg-blue-600 dark:bg-blue-900 text-white text-xs px-3 py-1 rounded hover:bg-blue-700 dark:hover:bg-blue-800 text-center transition-colors duration-200"
|
||||
<div class="flex flex-col mr-4 w-[150px]">
|
||||
<div class="flex flex-col items-center flex-grow">
|
||||
{% if person.사진 and person.사진.url and 'media/' in person.사진.url %}
|
||||
<img
|
||||
src="{{ person.사진.url }}"
|
||||
alt="{{ person.이름 }}"
|
||||
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
|
||||
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
|
||||
>
|
||||
📇연락처저장
|
||||
</a>
|
||||
{% else %}
|
||||
<img
|
||||
src="{% static 'B_main/images/default_user.png' %}"
|
||||
alt="{{ person.이름 }}"
|
||||
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
|
||||
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if person.이름 %}
|
||||
<div class="mt-auto">
|
||||
<a
|
||||
href="{% url 'vcard_download' person.이름 %}"
|
||||
class="inline-block bg-blue-600 dark:bg-blue-900 text-white text-xs px-3 py-1 rounded hover:bg-blue-700 dark:hover:bg-blue-800 text-center transition-colors duration-200 w-full"
|
||||
>
|
||||
📇연락처저장
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 우측: 텍스트 정보 -->
|
||||
<div class="flex flex-col justify-start w-full overflow-hidden">
|
||||
<div class="flex flex-col w-full overflow-hidden">
|
||||
<div class="flex justify-between items-baseline">
|
||||
<div class="mb-4">
|
||||
<h2 class="text-lg font-semibold truncate leading-tight flex-1 {% if person.user %}text-gray-900 dark:text-gray-100{% else %}text-blue-600 dark:text-blue-400{% endif %}">
|
||||
@ -55,27 +59,40 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-xs leading-snug overflow-hidden max-h-[138px] flex flex-col gap-y-1">
|
||||
<div class="flex mb-1">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">소속:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.소속 }}</span>
|
||||
<div class="flex-grow flex flex-col">
|
||||
<div class="text-xs leading-tight flex flex-col gap-y-0.5">
|
||||
<div class="flex mb-0.5">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">소속:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.소속 }}</span>
|
||||
</div>
|
||||
<div class="flex mb-0.5">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">직책:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.직책 }}</span>
|
||||
</div>
|
||||
<div class="flex mb-0.5">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">연락처:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.연락처 }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
{% if person.이름 == '허남식' %}
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">이메일:</span>
|
||||
{% else %}
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">주소:</span>
|
||||
{% endif %}
|
||||
<span class="flex-1 text-gray-800 dark:text-gray-200">{{ person.주소 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">직책:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.직책 }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">연락처:</span>
|
||||
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.연락처 }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
{% if person.이름 == '허남식' %}
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">이메일:</span>
|
||||
{% else %}
|
||||
<span class="w-[60px] text-gray-500 dark:text-gray-400">주소:</span>
|
||||
{% endif %}
|
||||
<span class="flex-1 text-gray-800 dark:text-gray-200">{{ person.주소 }}</span>
|
||||
|
||||
<!-- 소개글 박스 추가 (하단 정렬) -->
|
||||
{% if person.소개글 %}
|
||||
<div class="mt-auto pt-1">
|
||||
<div class="bg-gray-100 dark:bg-gray-700 rounded-md px-2 py-1">
|
||||
<div class="text-gray-700 dark:text-gray-300 text-xs leading-tight line-clamp-2">
|
||||
{{ person.소개글 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -31,6 +31,12 @@
|
||||
<div class="text-green-400 text-sm mb-4 text-center">{{ message }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if success_message %}
|
||||
<div class="text-green-400 text-sm mb-4 text-center bg-green-900 bg-opacity-30 p-3 rounded-lg border border-green-600">
|
||||
{{ success_message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if step == 1 %}
|
||||
<form method="POST" class="space-y-4" id="signup-form-step1">
|
||||
{% csrf_token %}
|
||||
|
||||
78
B_main/templates/B_main/test_telegram.html
Normal file
78
B_main/templates/B_main/test_telegram.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>텔레그램 봇 테스트 | 신라 AMP</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif']
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-gray-900 via-gray-800 to-black text-white min-h-screen flex items-center justify-center px-4 font-sans">
|
||||
|
||||
<div class="bg-gray-800 bg-opacity-70 backdrop-blur-lg p-8 rounded-2xl shadow-2xl w-full max-w-md transition-all">
|
||||
<div class="text-center mb-6">
|
||||
<h1 class="text-3xl font-bold tracking-tight text-white">📱 텔레그램 봇 테스트</h1>
|
||||
<p class="text-sm text-gray-400 mt-2">관리자 전용 기능</p>
|
||||
</div>
|
||||
|
||||
<!-- Django 메시지 표시 -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="p-4 rounded-lg mb-4 {% if message.tags == 'success' %}bg-green-600{% else %}bg-red-600{% endif %} text-white">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-gray-700 bg-opacity-50 p-4 rounded-xl">
|
||||
<h3 class="text-lg font-semibold mb-2">🤖 봇 정보</h3>
|
||||
<div class="text-sm text-gray-300 space-y-1">
|
||||
<p><strong>토큰:</strong> {{ settings.TELEGRAM_BOT_TOKEN|slice:":10" }}...</p>
|
||||
<p><strong>채팅 ID:</strong> {{ settings.TELEGRAM_CHAT_ID }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="w-full py-3 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 rounded-xl text-white font-semibold text-base transition duration-200 shadow-md hover:shadow-lg">
|
||||
🚀 테스트 메시지 전송
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="{% url 'main' %}" class="text-gray-400 hover:text-gray-300 transition text-sm">
|
||||
← 메인 페이지로 돌아가기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 메시지 자동 숨기기
|
||||
setTimeout(function() {
|
||||
const messages = document.querySelectorAll('.bg-green-600, .bg-red-600');
|
||||
messages.forEach(function(message) {
|
||||
message.style.transition = 'opacity 0.5s';
|
||||
message.style.opacity = '0';
|
||||
setTimeout(function() {
|
||||
message.remove();
|
||||
}, 500);
|
||||
});
|
||||
}, 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -13,4 +13,5 @@ urlpatterns = [
|
||||
path('signup/', views.signup_view, name='signup'),
|
||||
path('privacy-policy/', views.privacy_policy, name='privacy_policy'),
|
||||
path('test-500/', views.test_500_error, name='test_500_error'),
|
||||
path('test-telegram/', views.test_telegram_bot, name='test_telegram_bot'),
|
||||
]
|
||||
@ -268,13 +268,26 @@ def signup_view(request):
|
||||
from .forms import is_allowed_person
|
||||
from django.contrib.auth import login
|
||||
|
||||
# 강제 리셋 파라미터 확인 (로그인 페이지에서 오는 경우)
|
||||
force_reset = request.GET.get('reset', '').lower() == 'true'
|
||||
|
||||
# GET 요청 시 세션 초기화 (새로운 회원가입 시작)
|
||||
# 단, 인증번호 확인 후 리다이렉트된 경우는 세션 유지
|
||||
if request.method == 'GET' and not request.session.get('signup_verified'):
|
||||
for key in ['signup_code', 'signup_name', 'signup_phone', 'signup_verified', 'signup_step']:
|
||||
request.session.pop(key, None)
|
||||
request.session['signup_step'] = 1
|
||||
request.session['signup_verified'] = False
|
||||
# 또한 step이 2이고 verified가 True인 경우도 세션 유지
|
||||
current_step = request.session.get('signup_step', 1)
|
||||
current_verified = request.session.get('signup_verified', False)
|
||||
|
||||
if request.method == 'GET' and (force_reset or not (current_verified and current_step == 2)):
|
||||
# 강제 리셋이거나 인증되지 않았거나 step이 2가 아닌 경우에만 세션 초기화
|
||||
if force_reset or not current_verified:
|
||||
for key in ['signup_code', 'signup_name', 'signup_phone', 'signup_verified', 'signup_step']:
|
||||
request.session.pop(key, None)
|
||||
request.session['signup_step'] = 1
|
||||
request.session['signup_verified'] = False
|
||||
|
||||
# 강제 리셋인 경우 디버그 메시지 출력
|
||||
if force_reset:
|
||||
print("[DEBUG] 회원가입 세션이 강제로 리셋되었습니다.")
|
||||
|
||||
step = request.session.get('signup_step', 1)
|
||||
name = request.session.get('signup_name')
|
||||
@ -308,6 +321,10 @@ def signup_view(request):
|
||||
# 전화번호 인증 로그 기록
|
||||
log_phone_verification(request, phone)
|
||||
|
||||
# 텔레그램 알림 전송 (비동기)
|
||||
from A_core.telegram_utils import send_signup_notification
|
||||
send_signup_notification(name, phone, request)
|
||||
|
||||
return render(request, 'B_main/signup.html', {
|
||||
'step': 1, 'form1': form, 'code_sent': True, 'message': '인증번호가 발송되었습니다.'
|
||||
})
|
||||
@ -349,7 +366,15 @@ def signup_view(request):
|
||||
# 인증 성공
|
||||
request.session['signup_verified'] = True
|
||||
request.session['signup_step'] = 2
|
||||
return redirect('signup')
|
||||
# 인증 성공 메시지와 함께 2단계로 직접 이동
|
||||
form2 = Step2AccountForm()
|
||||
return render(request, 'B_main/signup.html', {
|
||||
'step': 2,
|
||||
'form2': form2,
|
||||
'name': name,
|
||||
'phone': phone,
|
||||
'success_message': '인증이 완료되었습니다. 비밀번호를 설정해주세요.'
|
||||
})
|
||||
else:
|
||||
return render(request, 'B_main/signup.html', {
|
||||
'step': 1, 'form1': form, 'code_sent': True, 'error': '인증번호가 올바르지 않습니다.'
|
||||
@ -410,3 +435,20 @@ def test_500_error(request):
|
||||
"""500 에러 페이지 테스트용 뷰"""
|
||||
# 강제로 에러를 발생시킵니다
|
||||
raise Exception("500 에러 페이지 테스트를 위한 의도적인 에러입니다.")
|
||||
|
||||
def test_telegram_bot(request):
|
||||
"""텔레그램 봇 테스트용 뷰 (관리자만 접근 가능)"""
|
||||
if not request.user.is_superuser:
|
||||
return redirect('/')
|
||||
|
||||
from A_core.telegram_utils import test_telegram_bot
|
||||
|
||||
if request.method == 'POST':
|
||||
success = test_telegram_bot()
|
||||
if success:
|
||||
messages.success(request, '✅ 텔레그램 봇 테스트 성공! 메시지가 전송되었습니다.')
|
||||
else:
|
||||
messages.error(request, '❌ 텔레그램 봇 테스트 실패! 설정을 확인해주세요.')
|
||||
return redirect('test_telegram_bot')
|
||||
|
||||
return render(request, 'B_main/test_telegram.html')
|
||||
Binary file not shown.
@ -233,6 +233,10 @@ def password_reset(request):
|
||||
message = '인증번호가 발송되었습니다.'
|
||||
code_sent = True
|
||||
print(f"[DEBUG] 비밀번호 찾기 SMS 발송 성공: {phone} - {verification_code}")
|
||||
|
||||
# 텔레그램 알림 전송 (비동기)
|
||||
from A_core.telegram_utils import send_password_reset_notification
|
||||
send_password_reset_notification(phone, request)
|
||||
else:
|
||||
error = '인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.'
|
||||
print(f"[DEBUG] 비밀번호 찾기 SMS 발송 실패: {sms_result['error']}")
|
||||
@ -252,7 +256,14 @@ def password_reset(request):
|
||||
elif input_code == stored_code:
|
||||
request.session['password_reset_verified'] = True
|
||||
request.session['password_reset_step'] = 2
|
||||
return redirect('accounts:password_reset')
|
||||
|
||||
print(f"[DEBUG] 비밀번호 찾기 인증 성공! 2단계로 직접 이동: {phone}")
|
||||
|
||||
# 리다이렉트 대신 직접 2단계 렌더링
|
||||
form2 = ForcePasswordSetForm()
|
||||
return render(request, 'C_accounts/password_reset.html', {
|
||||
'step': 2, 'form2': form2, 'phone': phone
|
||||
})
|
||||
else:
|
||||
error = '인증번호가 일치하지 않습니다.'
|
||||
else:
|
||||
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
@ -97,7 +97,7 @@
|
||||
<div class="mt-6 text-center text-sm">
|
||||
{% if not request.session.authenticated and not user.is_authenticated %}
|
||||
계정이 없으신가요?
|
||||
<a href="{% url 'account_signup' %}" class="text-green-400 hover:text-green-500 transition">회원가입</a>
|
||||
<a href="{% url 'signup' %}?reset=true" class="text-green-400 hover:text-green-500 transition">회원가입</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user