1주차 OT 8/30
2주차 주제 선정 (타임캡슐) 9/6
3주차 주제 변경 (냉장고 관리 웹사이트) 9/13
4주차 주제 결정 및 plan A, B 설정 9/20
5주차 프로젝트 첫 번째 중간 보고 9/27 (9/21~9/26)
6주차 프로젝트 두 번째 중간 보고 9/27 (9/27~10/3)
7주차 프로젝트 계획 보고서 발표
8주차 시험 주 (자습)
9주차 프로젝트 세 번째 중간 보고
10주차 프로젝트 네 번째 중간 보고(~10/31)
11주차 프로젝트 다섯 번째 중간 보고(~11/07)
12주차 중간점검 (시연)
https://bill1224.tistory.com/256 참고
import smtplib
from email.mime.text import MIMEText
# 이메일 서버 연결 정보
smtp_server = 'smtp.gmail.com'
smtp_port = 587
smtp_username = '보내는 메일
smtp_password = '비밀번호'
# 이메일 메세지 설정
subject = 'Hello, World!'
body = 'This is a test email.'
sender = '보내는 메일'
receiver = '받는 메일'
# 이메일 메세지 생성
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = receiver
# 이메일 발송
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(smtp_username, smtp_password)
server.send_message(msg)
pip install apscheduler
# 이메일 전송 함수
def send_email_function(email, name, date):
# 이메일 서버 연결 정보
smtp_server = 'smtp.gmail.com' # SMTP 서버 주소 (Gmail의 경우)
smtp_port = 587 # SMTP 서버 포트 (Gmail의 TLS 포트), 465와의 차이는?
smtp_username = 'sw.project.django@gmail.com' # 발신자 Gmail 계정
smtp_password = 'brdo ybhi dopd wibz' # 발신자 Gmail 비밀번호
# 이메일 메세지 설정
subject = f' 장고 : {name}의 소비기한이 3일 남았습니다!' # 이메일 제목
body = f'{name}의 소비기한이 3일 남았습니다! +++ [웹크롤링 음식명 내용]' # 이메일 본문 내용
sender = 'sw.project.django@gmail.com' # 발신자 이메일 주소
# receiver = 'taegeong@naver.com' # 수신자 이메일 주소
# 이메일 메세지 생성
msg = MIMEText(body) # 이메일 본문을 MIMEText 객체로 생성
msg['Subject'] = subject # 이메일 제목 설정
msg['From'] = sender # 발신자 이메일 주소 설정
msg['To'] = email # 수신자 이메일 주소 설정
# 이메일 발송
with smtplib.SMTP(smtp_server, smtp_port) as server: # SMTP 서버 연결
server.starttls() # TLS(전송 계층 보안) 시작
server.login(smtp_username, smtp_password) # 이메일 계정 로그인
server.send_message(msg) # 메시지 발송
# 이메일 스케줄링 함수
def schedule_email(user_email, title, event_date):
# 날짜 3일 전 계산
# send_date = datetime.strptime(event_date, "%Y-%m-%d") - timedelta(days=3)
# 'YYYY-MM-DD HH:MM:SS' 형식으로 날짜와 시간 모두를 해석
send_date = datetime.strptime(event_date, "%Y-%m-%d %H:%M:%S") - timedelta(days=3)
timezone = pytz.timezone('Asia/Seoul')
scheduler = BackgroundScheduler()
scheduler.add_job(
send_email_function,
'date',
run_date=send_date.replace(tzinfo=timezone),
args=[user_email, title, event_date]
)
scheduler.start()
# 이메일 전송 함수
def send_email_function(email, name, date):
# 이메일 서버 연결 정보
smtp_server = 'smtp.gmail.com' # SMTP 서버 주소 (Gmail의 경우)
smtp_port = 587 # SMTP 서버 포트 (Gmail의 TLS 포트), 465와의 차이는?
# 이메일 메세지 설정
subject = f' 장고 : {name}의 소비기한이 3일 남았습니다!' # 이메일 제목
body = f'{name}의 소비기한이 3일 남았습니다! +++ [웹크롤링 음식명 내용]' # 이메일 본문 내용
sender = 'sw.project.django@gmail.com' # 발신자 이메일 주소
# receiver = 'taegeong@naver.com' # 수신자 이메일 주소
# 이메일 메세지 생성
msg = MIMEText(body) # 이메일 본문을 MIMEText 객체로 생성
msg['Subject'] = subject # 이메일 제목 설정
msg['From'] = sender # 발신자 이메일 주소 설정
msg['To'] = email # 수신자 이메일 주소 설정
# 이메일 발송
with smtplib.SMTP(smtp_server, smtp_port) as server: # SMTP 서버 연결
server.starttls() # TLS(전송 계층 보안) 시작
server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD) # 이메일 계정 로그인
server.send_message(msg) # 메시지 발송
이런 메일을 받았다.
cd django_calender_prj
git rm --cached settings.py
git commit -m "Remove settings.py from tracking"
git push
send_email_function의 기능을 확인하는 테스트
실제 이메일을 보내지 않으면서 함수의 로직이 올바르게 작동하는지 검증하는 접근이 필요
이를 위해 Python의 표준 라이브러리인 unittest.mock을 사용할 수 있음
이 방법으로 실제 이메일 서버에 연결하지 않고 함수가 호출되었는지, 예상대로 매개변수가 전달되었는지 등을 확인할 수 있음
from django.test import TestCase
from calender.views import send_email_function
from unittest.mock import patch, MagicMock
class EmailTest(TestCase):
@patch('calender.views.smtplib.SMTP')
def test_send_email_function(self, mock_smtp):
# Mock 객체 설정
mock_server = MagicMock()
mock_smtp.return_value.__enter__.return_value = mock_server
# 함수 호출
email = 'test@example.com'
name = '테스트 재료'
date = '2023-11-20'
send_email_function(email, name, date)
# 함수가 호출되었는지 검증
mock_server.send_message.assert_called()
# send_message가 호출된 인자 검증
args, kwargs = mock_server.send_message.call_args
sent_message = args[0]
self.assertIn(email, sent_message['To'])
self.assertIn(name, sent_message.get_payload())
self.assertIn(date, sent_message.get_payload())
이 코드는 send_email_function이 smtplib.SMTP 클래스를 사용하여 이메일을 보내는 로직을 모의(Mock) 객체를 사용해 테스트
@patch 데코레이터를 사용하여 실제로 SMTP 서버에 연결하는 대신 모의 객체를 사용하고, 함수가 호출될 때 예상한 대로 작동하는지 확인
python manage.py test calender.tests.EmailTest.test_send_email_function
보안 주의사항:
이메일 주소와 비밀번호는 민감한 정보입니다. 코드나 GitHub과 같은 공개된 곳에 이러한 정보를 직접 작성하는 것은 보안상 위험할 수 있습니다.
보안을 강화하기 위해 환경 변수나 Django의 secrets 모듈을 사용하여 이메일 계정 정보를 관리하는 것이 좋습니다.
이메일을 보내기 위해 사용하는 계정은 '앱 비밀번호'나 '보안 수준이 낮은 앱의 액세스'를 활성화해야 할 수도 있습니다. (특히 Gmail의 경우)
시도1
'보안 수준이 낮은 앱에서 계정에 액세스하도록 허용'을 하려했음
but ...
계정을 안전하게 보호하기 위해 2022년 5월 30일부터 Google은 사용자 이름과 비밀번호만 사용하여
Google 계정에 로그인하도록 요청하는 서드 파티 앱 또는 기기의 사용을 더 이상 지원하지 않습니다.
?!?!!?!?!
시도2 (해결)
앱 비밀번호 생성 후 진행
https://bill1224.tistory.com/256
datetime.strptime 함수가 event_date 문자열을 해석하는 데 실패
오류 메시지 "unconverted data remains: 00:00:00"는 주어진 형식("%Y-%m-%d")이 문자열의 전체를 해석하지 못했다는 것을 나타냄. 이 경우, 문자열에 시간 정보(00:00:00)가 포함되어 있지만, 지정된 형식에는 날짜만 포함되어 있기 때문에 문제가 발생
(venv) PS C:\django_calender\django_calender> python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
November 15, 2023 - 13:34:38
Django version 4.2.6, using settings 'django_calender_prj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[15/Nov/2023 13:34:46] "GET /add_event?title=20%EC%9D%BC&start=2023-11-20%2000%3A00%3A00&end=2023-11-21%2000%3A00%3A00&f_category=food HTTP/1.1" 301 0
Internal Server Error: /add_event/
Traceback (most recent call last):
File "C:\django_calender\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\contrib\auth\decorators.py", line 23, in _wrapper_view
return view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\django_calender\calender\views.py", line 112, in add_event
schedule_email(user_email, title, event_date)
File "C:\django_calender\django_calender\calender\views.py", line 76, in schedule_email
send_date = datetime.strptime(event_date, "%Y-%m-%d") - timedelta(days=3)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\migyu\AppData\Local\Programs\Python\Python311\Lib\_strptime.py", line 568, in _strptime_datetime
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\migyu\AppData\Local\Programs\Python\Python311\Lib\_strptime.py", line 352, in _strptime
raise ValueError("unconverted data remains: %s" %
ValueError: unconverted data remains: 00:00:00
[15/Nov/2023 13:34:46] "GET /add_event/?title=20%EC%9D%BC&start=2023-11-20%2000%3A00%3A00&end=2023-11-21%2000%3A00%3A00&f_category=food HTTP/1.1" 500 87439
strptime 함수에 사용되는 형식을 문자열에 맞게 수정하여 시간 정보까지 포함하도록 함
기존 코드
# 'YYYY-MM-DD' 형식으로 날짜와 시간 모두를 해석
send_date = datetime.strptime(start, "%Y-%m-%d) - timedelta(days=3)
고친 코드
# 'YYYY-MM-DD HH:MM:SS' 형식으로 날짜와 시간 모두를 해석
send_date = datetime.strptime(start, "%Y-%m-%d %H:%M:%S") - timedelta(days=3)
(venv) PS C:\django_calender\django_calender> python manage.py test calender.tests.EmailTest.test_send_email_function
Found 1 test(s).
Creating test database for alias 'default'...
Traceback (most recent call last):
File "C:\django_calender\venv\Lib\site-packages\django\db\backends\utils.py", line 89, in _execute
return self.cursor.execute(sql, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\db\backends\mysql\base.py", line 75, in execute
return self.cursor.execute(query, args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute
res = self._query(mogrified_query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query
db.query(q)
File "C:\django_calender\venv\Lib\site-packages\MySQLdb\connections.py", line 255, in query
_mysql.connection.query(self, query)
MySQLdb.OperationalError: (1824, "Failed to open the referenced table 'auth_user'")
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\django_calender\django_calender\manage.py", line 22, in <module>
main()
File "C:\django_calender\django_calender\manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "C:\django_calender\venv\Lib\site-packages\django\core\management\__init__.py", line 442, in execute_from_command_line
utility.execute()
File "C:\django_calender\venv\Lib\site-packages\django\core\management\__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "C:\django_calender\venv\Lib\site-packages\django\core\management\commands\test.py", line 24, in run_from_argv
super().run_from_argv(argv)
File "C:\django_calender\venv\Lib\site-packages\django\core\management\base.py", line 412, in run_from_argv
self.execute(*args, **cmd_options)
File "C:\django_calender\venv\Lib\site-packages\django\core\management\base.py", line 458, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\core\management\commands\test.py", line 68, in handle
failures = test_runner.run_tests(test_labels)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\test\runner.py", line 1054, in run_tests
old_config = self.setup_databases(
^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\test\runner.py", line 950, in setup_databases
return _setup_databases(
^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\test\utils.py", line 221, in setup_databases
connection.creation.create_test_db(
File "C:\django_calender\venv\Lib\site-packages\django\db\backends\base\creation.py", line 78, in create_test_db
call_command(
File "C:\django_calender\venv\Lib\site-packages\django\core\management\__init__.py", line 194, in call_command
return command.execute(*args, **defaults)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\core\management\base.py", line 458, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\core\management\base.py", line 106, in wrapper
res = handle_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\django\core\management\commands\migrate.py", line 321, in handle
self.sync_apps(connection, executor.loader.unmigrated_apps)
File "C:\django_calender\venv\Lib\site-packages\django\core\management\commands\migrate.py", line 468, in sync_apps
with connection.schema_editor() as editor:
File "C:\django_calender\venv\Lib\site-packages\django\db\backends\base\schema.py", line 166, in __exit__
self.execute(sql)
File "C:\django_calender\venv\Lib\site-packages\django\db\backends\base\schema.py", line 201, in execute
cursor.execute(sql, params)
res = self._query(mogrified_query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\django_calender\venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query
db.query(q)
File "C:\django_calender\venv\Lib\site-packages\MySQLdb\connections.py", line 255, in query
_mysql.connection.query(self, query)
django.db.utils.OperationalError: (1824, "Failed to open the referenced table 'auth_user'")
C:\django_calender\django_calender\venv\Lib\site-packages\django\conf\global_settings.py
이 경로에서
# Optional SMTP authentication information for EMAIL_HOST.
EMAIL_HOST_USER = "sw.project.django@gmail.com"
EMAIL_HOST_PASSWORD = "brdo ybhi dopd wibz"
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_SSL_CERTFILE = None
EMAIL_SSL_KEYFILE = None
EMAIL_TIMEOUT = None
SMTP
인터넷 메일을 송수신 할 때는 SMTP (Simple Mail Transfer Protocol) 이라는 규약 사용
MIME
인터넷의 전자 메일에서 사용되는 문자 데이터를 표현하기 위한 형식 표준
두 포트가 다른 유형의 보안 연결을 사용한다는 점에서 차이가 있습니다.
포트 587: 이 포트는 일반적으로 메일 전송을 위한 '시작할 TLS'(StartTLS) 연결에 사용됩니다. StartTLS는 연결이 시작된 후 보안 연결로 "업그레이드"되는 방식입니다. 즉, 서버와 클라이언트 간의 초기 연결은 일반 텍스트로 시작되며, STARTTLS 명령이 전송된 후 TLS 보안 연결로 전환됩니다.
포트 465: 이 포트는 'SSL/TLS' 연결에 사용됩니다. SSL/TLS를 사용하는 경우, 연결 자체가 처음부터 보안 연결로 시작됩니다. 즉, 모든 통신이 처음부터 암호화되어 전송됩니다.
두 방식 모두 이메일 전송을 보안적으로 처리하기 위한 방법이지만, 포트 587과 STARTTLS 방식이 더 널리 사용됩니다.
python manage.py shell
from django.db import connections
from django.db.utils import OperationalError
db_conn = connections['default']
try:
c = db_conn.cursor()
except OperationalError:
print("연결 실패")
else:
print("연결 성공")