diff --git a/insight/admin/user_weekly_trend_admin.py b/insight/admin/user_weekly_trend_admin.py index 9638875..13a3ffe 100644 --- a/insight/admin/user_weekly_trend_admin.py +++ b/insight/admin/user_weekly_trend_admin.py @@ -145,6 +145,10 @@ def render_full_preview(self, obj: UserWeeklyTrend): { "s_date": obj.week_start_date, "e_date": obj.week_end_date, + "user": { + "username": obj.user.username if obj.user else "N/A", + "email": obj.user.email if obj.user else "N/A", + }, "is_expired_token_user": False, "weekly_trend_html": weekly_trend_html, "user_weekly_trend_html": user_weekly_trend_html, diff --git a/insight/schemas.py b/insight/schemas.py index 0239424..77e2363 100644 --- a/insight/schemas.py +++ b/insight/schemas.py @@ -8,6 +8,7 @@ class NewsletterContext: s_date: str e_date: str + user: dict is_expired_token_user: bool weekly_trend_html: str user_weekly_trend_html: str | None = None diff --git a/insight/tasks/weekly_newsletter_batch.py b/insight/tasks/weekly_newsletter_batch.py index 2f040ab..7da31d2 100644 --- a/insight/tasks/weekly_newsletter_batch.py +++ b/insight/tasks/weekly_newsletter_batch.py @@ -91,6 +91,7 @@ def _get_target_user_chunks(self) -> list[list[dict]]: User.objects.filter( is_active=True, email__isnull=False, + newsletter_subscribed=True, ) .values("id", "email", "username") .distinct("email") @@ -222,6 +223,7 @@ def _get_user_weekly_trend_html( def _get_newsletter_html( self, + user: dict, is_expired_token_user: bool, weekly_trend_html: str, user_weekly_trend_html: str | None, @@ -234,6 +236,7 @@ def _get_newsletter_html( NewsletterContext( s_date=self.weekly_info["s_date"], e_date=self.weekly_info["e_date"], + user=user, is_expired_token_user=is_expired_token_user, weekly_trend_html=weekly_trend_html, user_weekly_trend_html=user_weekly_trend_html, @@ -294,6 +297,7 @@ def _build_newsletters( # 최종 뉴스레터 렌더링 html_body = self._get_newsletter_html( + user=user, is_expired_token_user=is_expired_token_user, weekly_trend_html=weekly_trend_html, user_weekly_trend_html=user_weekly_trend_html, diff --git a/insight/tasks/weekly_user_trend_analysis.py b/insight/tasks/weekly_user_trend_analysis.py index f02cf0f..234ad80 100644 --- a/insight/tasks/weekly_user_trend_analysis.py +++ b/insight/tasks/weekly_user_trend_analysis.py @@ -282,6 +282,7 @@ async def _fetch_data( User.objects.filter( email__isnull=False, is_active=True, + newsletter_subscribed=True, ) .exclude(email="") .values("id", "username") diff --git a/insight/tests/conftest.py b/insight/tests/conftest.py index 7a749ce..448b7d7 100644 --- a/insight/tests/conftest.py +++ b/insight/tests/conftest.py @@ -47,6 +47,7 @@ def user(db): email="test@example.com", username="test_user", is_active=True, + newsletter_subscribed=True, ) diff --git a/insight/tests/tasks/test_weekly_newsletter_batch.py b/insight/tests/tasks/test_weekly_newsletter_batch.py index 794c9ab..1bee5e9 100644 --- a/insight/tests/tasks/test_weekly_newsletter_batch.py +++ b/insight/tests/tasks/test_weekly_newsletter_batch.py @@ -115,6 +115,11 @@ def test_get_target_user_chunks_success( chunks = newsletter_batch._get_target_user_chunks() + mock_filter.assert_called_once_with( + is_active=True, + email__isnull=False, + newsletter_subscribed=True, + ) assert len(chunks) == 1 assert len(chunks[0]) == 1 assert chunks[0][0]["email"] == user.email @@ -197,7 +202,10 @@ def test_build_newsletters_success( assert newsletters[0].user_id == user.id assert newsletters[0].email_message.to[0] == user.email # 제목 포맷 검증 - assert "벨로그 대시보드 주간 뉴스레터" in newsletters[0].email_message.subject + assert ( + "벨로그 대시보드 주간 뉴스레터" + in newsletters[0].email_message.subject + ) @patch("insight.tasks.weekly_newsletter_batch.logger") def test_send_newsletters_success( diff --git a/insight/tests/tasks/test_weekly_newsletter_template.py b/insight/tests/tasks/test_weekly_newsletter_template.py index fef2aa4..b9ab80f 100644 --- a/insight/tests/tasks/test_weekly_newsletter_template.py +++ b/insight/tests/tasks/test_weekly_newsletter_template.py @@ -125,7 +125,10 @@ def test_get_user_weekly_trend_html_success( assert trending_summary[0]["title"] in user_weekly_trend_html assert trend_analysis["insights"] in user_weekly_trend_html - assert f'{user_weekly_stats["new_posts"]}개의 글' in user_weekly_trend_html + assert ( + f'{user_weekly_stats["new_posts"]}개의 글' + in user_weekly_trend_html + ) assert "마지막으로 글을 작성하신지" not in user_weekly_trend_html assert user.username in user_weekly_trend_html assert "이번주에 작성한 글" in user_weekly_trend_html @@ -179,13 +182,16 @@ def test_get_user_weekly_trend_html_exception( ) @patch("insight.tasks.weekly_newsletter_batch.logger") - def test_get_newsletter_html_success(self, mock_logger, newsletter_batch): + def test_get_newsletter_html_success( + self, mock_logger, newsletter_batch, user + ): """정상 사용자 뉴스레터 HTML 렌더링 테스트""" is_expired_token_user = False weekly_trend_html = "test-weekly-trend-html" user_weekly_trend_html = "test-user-weekly-trend-html" newsletter_html = newsletter_batch._get_newsletter_html( + user, is_expired_token_user, weekly_trend_html, user_weekly_trend_html, @@ -198,10 +204,14 @@ def test_get_newsletter_html_success(self, mock_logger, newsletter_batch): assert "대시보드 보러가기" in newsletter_html assert "Weekly Report" in newsletter_html assert "Velog Dashboard" in newsletter_html + assert ( + "user/newsletter-unsubscribe?email=" + user.email + in newsletter_html + ) @patch("insight.tasks.weekly_newsletter_batch.logger") def test_get_newsletter_html_expired_token_user( - self, mock_logger, newsletter_batch + self, mock_logger, newsletter_batch, user ): """토큰 만료 사용자 뉴스레터 HTML 렌더링 테스트""" is_expired_token_user = True @@ -209,6 +219,7 @@ def test_get_newsletter_html_expired_token_user( user_weekly_trend_html = "test-user-weekly-trend-html" newsletter_html = newsletter_batch._get_newsletter_html( + user, is_expired_token_user, weekly_trend_html, user_weekly_trend_html, @@ -216,15 +227,22 @@ def test_get_newsletter_html_expired_token_user( # 템플릿 렌더링 검증 assert "🚨 잠시만요, 토큰이 만료된 것 같아요!" in newsletter_html - assert "토큰이 만료되어 정상적으로 통계를 수집할 수 없었어요" in newsletter_html + assert ( + "토큰이 만료되어 정상적으로 통계를 수집할 수 없었어요" + in newsletter_html + ) assert weekly_trend_html in newsletter_html assert user_weekly_trend_html not in newsletter_html assert "대시보드 보러가기" in newsletter_html assert "활동 리포트" in newsletter_html + assert ( + "user/newsletter-unsubscribe?email=" + user.email + in newsletter_html + ) @patch("insight.tasks.weekly_newsletter_batch.logger") def test_get_newsletter_html_exception( - self, mock_logger, newsletter_batch + self, mock_logger, newsletter_batch, user ): """뉴스레터 HTML 렌더링 실패 시 예외 처리 테스트""" with patch( @@ -234,6 +252,7 @@ def test_get_newsletter_html_exception( with pytest.raises(Exception): newsletter_batch._get_newsletter_html( + user, False, "test-weekly-trend-html", "test-user-weekly-trend-html", diff --git a/insight/tests/test_user_weekly_trend_admin.py b/insight/tests/test_user_weekly_trend_admin.py index c023be0..a25fde3 100644 --- a/insight/tests/test_user_weekly_trend_admin.py +++ b/insight/tests/test_user_weekly_trend_admin.py @@ -413,7 +413,7 @@ def test_processed_at_formatted_with_date( result = user_weekly_trend_admin.processed_at_formatted( user_weekly_trend ) - assert now.strftime("%Y-%m-%d %H:%M") == result + assert now.strftime("%Y-%m-%d %H:%M:%S") == result def test_processed_at_formatted_no_date( self, user_weekly_trend_admin, user_weekly_trend diff --git a/insight/tests/test_weekly_trend_admin.py b/insight/tests/test_weekly_trend_admin.py index e1d69ee..42200af 100644 --- a/insight/tests/test_weekly_trend_admin.py +++ b/insight/tests/test_weekly_trend_admin.py @@ -49,7 +49,7 @@ def test_processed_at_formatted_with_date( weekly_trend.save() result = weekly_trend_admin.processed_at_formatted(weekly_trend) - assert now.strftime("%Y-%m-%d %H:%M") == result + assert now.strftime("%Y-%m-%d %H:%M:%S") == result def test_processed_at_formatted_no_date( self, weekly_trend_admin, weekly_trend: WeeklyTrend diff --git a/templates/insights/index.html b/templates/insights/index.html index 4330354..f26f802 100644 --- a/templates/insights/index.html +++ b/templates/insights/index.html @@ -134,6 +134,23 @@ {{weekly_trend_html}} {% endif %} +

+ {% if user.username %} + {{user.username}}님의 활동 리포트 + {% else %} + 활동 리포트 + {% endif %} +

+ {% if not is_expired_token_user and user_weekly_trend_html %} {{user_weekly_trend_html}} {% endif %} @@ -141,22 +158,6 @@ {% if is_expired_token_user %}
-

- {% if user.username %} - {{user.username}}님의 활동 리포트 - {% else %} - 활동 리포트 - {% endif %} -

+ 수신 거부 +

diff --git a/templates/insights/user_weekly_trend.html b/templates/insights/user_weekly_trend.html index b846e8f..44dc745 100644 --- a/templates/insights/user_weekly_trend.html +++ b/templates/insights/user_weekly_trend.html @@ -1,25 +1,7 @@
- {% if insight.trending_summary or insight.user_weekly_stats or insight.user_weekly_reminder %} -

- {% if user.username %} - {{user.username}}님의 활동 리포트 - {% else %} - 활동 리포트 - {% endif %} -

- {% endif %} {% if insight.user_weekly_stats %}
str: return f"{self.velog_uuid}"