Skip to content

Commit d2c5cdb

Browse files
authored
[25.10.04 / TASK-250] Feature - 주간 뉴스레터 수신거부 기능 추가 (#46)
* feature: User 모델에 newsletter_subscribed 컬럼 추가 * feature: 메일을 구독한 사용자만 분석/발송하도록 수정 * feature: 템플릿에 수신 거부 추가 및 렌더링시 적용 * test: 템플릿 렌더링 테스트 수정 * fix: 까먹은 주석 해제 * fix: 지난 핫픽스 관련 테스트 수정 * fix: 템플릿 스타일 및 어드민 약간 수정 * fix: 주석추가
1 parent 0484cd1 commit d2c5cdb

14 files changed

+118
-44
lines changed

insight/admin/user_weekly_trend_admin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ def render_full_preview(self, obj: UserWeeklyTrend):
145145
{
146146
"s_date": obj.week_start_date,
147147
"e_date": obj.week_end_date,
148+
"user": {
149+
"username": obj.user.username if obj.user else "N/A",
150+
"email": obj.user.email if obj.user else "N/A",
151+
},
148152
"is_expired_token_user": False,
149153
"weekly_trend_html": weekly_trend_html,
150154
"user_weekly_trend_html": user_weekly_trend_html,

insight/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class NewsletterContext:
99
s_date: str
1010
e_date: str
11+
user: dict
1112
is_expired_token_user: bool
1213
weekly_trend_html: str
1314
user_weekly_trend_html: str | None = None

insight/tasks/weekly_newsletter_batch.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def _get_target_user_chunks(self) -> list[list[dict]]:
9191
User.objects.filter(
9292
is_active=True,
9393
email__isnull=False,
94+
newsletter_subscribed=True,
9495
)
9596
.values("id", "email", "username")
9697
.distinct("email")
@@ -222,6 +223,7 @@ def _get_user_weekly_trend_html(
222223

223224
def _get_newsletter_html(
224225
self,
226+
user: dict,
225227
is_expired_token_user: bool,
226228
weekly_trend_html: str,
227229
user_weekly_trend_html: str | None,
@@ -234,6 +236,7 @@ def _get_newsletter_html(
234236
NewsletterContext(
235237
s_date=self.weekly_info["s_date"],
236238
e_date=self.weekly_info["e_date"],
239+
user=user,
237240
is_expired_token_user=is_expired_token_user,
238241
weekly_trend_html=weekly_trend_html,
239242
user_weekly_trend_html=user_weekly_trend_html,
@@ -294,6 +297,7 @@ def _build_newsletters(
294297

295298
# 최종 뉴스레터 렌더링
296299
html_body = self._get_newsletter_html(
300+
user=user,
297301
is_expired_token_user=is_expired_token_user,
298302
weekly_trend_html=weekly_trend_html,
299303
user_weekly_trend_html=user_weekly_trend_html,

insight/tasks/weekly_user_trend_analysis.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ async def _fetch_data(
282282
User.objects.filter(
283283
email__isnull=False,
284284
is_active=True,
285+
newsletter_subscribed=True,
285286
)
286287
.exclude(email="")
287288
.values("id", "username")

insight/tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def user(db):
4747
4848
username="test_user",
4949
is_active=True,
50+
newsletter_subscribed=True,
5051
)
5152

5253

insight/tests/tasks/test_weekly_newsletter_batch.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ def test_get_target_user_chunks_success(
115115

116116
chunks = newsletter_batch._get_target_user_chunks()
117117

118+
mock_filter.assert_called_once_with(
119+
is_active=True,
120+
email__isnull=False,
121+
newsletter_subscribed=True,
122+
)
118123
assert len(chunks) == 1
119124
assert len(chunks[0]) == 1
120125
assert chunks[0][0]["email"] == user.email
@@ -197,7 +202,10 @@ def test_build_newsletters_success(
197202
assert newsletters[0].user_id == user.id
198203
assert newsletters[0].email_message.to[0] == user.email
199204
# 제목 포맷 검증
200-
assert "벨로그 대시보드 주간 뉴스레터" in newsletters[0].email_message.subject
205+
assert (
206+
"벨로그 대시보드 주간 뉴스레터"
207+
in newsletters[0].email_message.subject
208+
)
201209

202210
@patch("insight.tasks.weekly_newsletter_batch.logger")
203211
def test_send_newsletters_success(

insight/tests/tasks/test_weekly_newsletter_template.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ def test_get_user_weekly_trend_html_success(
125125

126126
assert trending_summary[0]["title"] in user_weekly_trend_html
127127
assert trend_analysis["insights"] in user_weekly_trend_html
128-
assert f'{user_weekly_stats["new_posts"]}개의 글' in user_weekly_trend_html
128+
assert (
129+
f'{user_weekly_stats["new_posts"]}개의 글'
130+
in user_weekly_trend_html
131+
)
129132
assert "마지막으로 글을 작성하신지" not in user_weekly_trend_html
130133
assert user.username in user_weekly_trend_html
131134
assert "이번주에 작성한 글" in user_weekly_trend_html
@@ -179,13 +182,16 @@ def test_get_user_weekly_trend_html_exception(
179182
)
180183

181184
@patch("insight.tasks.weekly_newsletter_batch.logger")
182-
def test_get_newsletter_html_success(self, mock_logger, newsletter_batch):
185+
def test_get_newsletter_html_success(
186+
self, mock_logger, newsletter_batch, user
187+
):
183188
"""정상 사용자 뉴스레터 HTML 렌더링 테스트"""
184189
is_expired_token_user = False
185190
weekly_trend_html = "test-weekly-trend-html"
186191
user_weekly_trend_html = "test-user-weekly-trend-html"
187192

188193
newsletter_html = newsletter_batch._get_newsletter_html(
194+
user,
189195
is_expired_token_user,
190196
weekly_trend_html,
191197
user_weekly_trend_html,
@@ -198,33 +204,45 @@ def test_get_newsletter_html_success(self, mock_logger, newsletter_batch):
198204
assert "대시보드 보러가기" in newsletter_html
199205
assert "Weekly Report" in newsletter_html
200206
assert "Velog Dashboard" in newsletter_html
207+
assert (
208+
"user/newsletter-unsubscribe?email=" + user.email
209+
in newsletter_html
210+
)
201211

202212
@patch("insight.tasks.weekly_newsletter_batch.logger")
203213
def test_get_newsletter_html_expired_token_user(
204-
self, mock_logger, newsletter_batch
214+
self, mock_logger, newsletter_batch, user
205215
):
206216
"""토큰 만료 사용자 뉴스레터 HTML 렌더링 테스트"""
207217
is_expired_token_user = True
208218
weekly_trend_html = "test-weekly-trend-html"
209219
user_weekly_trend_html = "test-user-weekly-trend-html"
210220

211221
newsletter_html = newsletter_batch._get_newsletter_html(
222+
user,
212223
is_expired_token_user,
213224
weekly_trend_html,
214225
user_weekly_trend_html,
215226
)
216227

217228
# 템플릿 렌더링 검증
218229
assert "🚨 잠시만요, 토큰이 만료된 것 같아요!" in newsletter_html
219-
assert "토큰이 만료되어 정상적으로 통계를 수집할 수 없었어요" in newsletter_html
230+
assert (
231+
"토큰이 만료되어 정상적으로 통계를 수집할 수 없었어요"
232+
in newsletter_html
233+
)
220234
assert weekly_trend_html in newsletter_html
221235
assert user_weekly_trend_html not in newsletter_html
222236
assert "대시보드 보러가기" in newsletter_html
223237
assert "활동 리포트" in newsletter_html
238+
assert (
239+
"user/newsletter-unsubscribe?email=" + user.email
240+
in newsletter_html
241+
)
224242

225243
@patch("insight.tasks.weekly_newsletter_batch.logger")
226244
def test_get_newsletter_html_exception(
227-
self, mock_logger, newsletter_batch
245+
self, mock_logger, newsletter_batch, user
228246
):
229247
"""뉴스레터 HTML 렌더링 실패 시 예외 처리 테스트"""
230248
with patch(
@@ -234,6 +252,7 @@ def test_get_newsletter_html_exception(
234252

235253
with pytest.raises(Exception):
236254
newsletter_batch._get_newsletter_html(
255+
user,
237256
False,
238257
"test-weekly-trend-html",
239258
"test-user-weekly-trend-html",

insight/tests/test_user_weekly_trend_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ def test_processed_at_formatted_with_date(
413413
result = user_weekly_trend_admin.processed_at_formatted(
414414
user_weekly_trend
415415
)
416-
assert now.strftime("%Y-%m-%d %H:%M") == result
416+
assert now.strftime("%Y-%m-%d %H:%M:%S") == result
417417

418418
def test_processed_at_formatted_no_date(
419419
self, user_weekly_trend_admin, user_weekly_trend

insight/tests/test_weekly_trend_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_processed_at_formatted_with_date(
4949
weekly_trend.save()
5050

5151
result = weekly_trend_admin.processed_at_formatted(weekly_trend)
52-
assert now.strftime("%Y-%m-%d %H:%M") == result
52+
assert now.strftime("%Y-%m-%d %H:%M:%S") == result
5353

5454
def test_processed_at_formatted_no_date(
5555
self, weekly_trend_admin, weekly_trend: WeeklyTrend

templates/insights/index.html

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,29 +134,30 @@
134134
{{weekly_trend_html}}
135135
{% endif %}
136136

137+
<h2
138+
style="
139+
box-sizing: border-box;
140+
margin-top: 40px;
141+
color: #000000;
142+
font-size: 24px;
143+
font-weight: 900;
144+
letter-spacing: 0;
145+
"
146+
>
147+
{% if user.username %}
148+
{{user.username}}님의 활동 리포트
149+
{% else %}
150+
활동 리포트
151+
{% endif %}
152+
</h2>
153+
137154
{% if not is_expired_token_user and user_weekly_trend_html %}
138155
{{user_weekly_trend_html}}
139156
{% endif %}
140157

141158
{% if is_expired_token_user %}
142159
<!-- Token Expired Warning -->
143160
<div style="margin-bottom: 40px; box-sizing: border-box">
144-
<h2
145-
style="
146-
font-size: 24px;
147-
font-weight: 900;
148-
color: #000000;
149-
margin-bottom: 20px;
150-
letter-spacing: 0;
151-
box-sizing: border-box;
152-
"
153-
>
154-
{% if user.username %}
155-
{{user.username}}님의 활동 리포트
156-
{% else %}
157-
활동 리포트
158-
{% endif %}
159-
</h2>
160161
<div
161162
style="
162163
background-color: #fffbd7;
@@ -311,6 +312,21 @@
311312
>
312313
개인정보처리방침
313314
</a>
315+
&nbsp;&nbsp;|&nbsp;&nbsp;
316+
<!-- 뉴스레터 구독 해제: API 엔드포인트로 직접 구독 해제 처리 -->
317+
<a
318+
href="https://velog-dashboard.kro.kr/api/user/newsletter-unsubscribe?email={{user.email}}"
319+
target="_blank"
320+
rel="noopener noreferrer"
321+
style="
322+
color: #4d4d4d;
323+
text-decoration: underline;
324+
box-sizing: border-box;
325+
display: inline-block;
326+
"
327+
>
328+
수신 거부
329+
</a>
314330
</p>
315331
</td>
316332
</tr>

0 commit comments

Comments
 (0)