1
+ from html import escape
2
+
1
3
from django .contrib import admin
2
- from django .db .models import QuerySet
3
4
from django .http import HttpRequest
5
+ from django .template .loader import render_to_string
4
6
from django .urls import reverse
5
7
from django .utils .html import format_html
8
+ from django .utils .safestring import mark_safe
6
9
7
- from insight .admin import BaseTrendAdminMixin , JsonPreviewMixin
8
- from insight .models import UserWeeklyTrend
9
- from utils .utils import get_local_now
10
+ from insight .admin .base_admin import BaseTrendAdminMixin
11
+ from insight .models import (
12
+ UserWeeklyTrend ,
13
+ WeeklyTrend ,
14
+ WeeklyTrendInsight ,
15
+ WeeklyUserTrendInsight ,
16
+ )
17
+ from utils .utils import from_dict
10
18
11
19
12
20
@admin .register (UserWeeklyTrend )
13
- class UserWeeklyTrendAdmin (
14
- admin .ModelAdmin , JsonPreviewMixin , BaseTrendAdminMixin
15
- ):
21
+ class UserWeeklyTrendAdmin (admin .ModelAdmin , BaseTrendAdminMixin ):
16
22
list_display = (
17
23
"id" ,
18
24
"user_info" ,
19
25
"week_range" ,
20
- "insight_preview " ,
26
+ "summarize_insight " ,
21
27
"is_processed_colored" ,
22
28
"processed_at_formatted" ,
23
29
"created_at" ,
@@ -26,7 +32,8 @@ class UserWeeklyTrendAdmin(
26
32
search_fields = ("user__username" , "insight" )
27
33
readonly_fields = (
28
34
"processed_at" ,
29
- "formatted_insight" ,
35
+ "render_full_preview" ,
36
+ "formatted_insight_json" ,
30
37
)
31
38
raw_id_fields = ("user" ,)
32
39
@@ -38,10 +45,17 @@ class UserWeeklyTrendAdmin(
38
45
},
39
46
),
40
47
(
41
- "인사이트 데이터" ,
48
+ "뉴스레터 미리보기" ,
49
+ {
50
+ "fields" : ("render_full_preview" ,),
51
+ "classes" : ("wide" ,),
52
+ },
53
+ ),
54
+ (
55
+ "원본 데이터 (JSON)" ,
42
56
{
43
- "fields" : ("formatted_insight " ,),
44
- "classes" : ("wide" , "extrapretty " ),
57
+ "fields" : ("formatted_insight_json " ,),
58
+ "classes" : ("wide" , "collapse " ),
45
59
},
46
60
),
47
61
(
@@ -71,13 +85,98 @@ def user_info(self, obj: UserWeeklyTrend):
71
85
obj .user .username or f"사용자 { obj .user .id } " ,
72
86
)
73
87
74
- @admin .action (description = "선택된 항목을 처리 완료로 표시하기" )
75
- def mark_as_processed (
76
- self , request : HttpRequest , queryset : QuerySet [UserWeeklyTrend ]
77
- ):
78
- """선택된 항목을 처리 완료로 표시"""
79
- queryset .update (is_processed = True , processed_at = get_local_now ())
80
- self .message_user (
81
- request ,
82
- f"{ queryset .count ()} 개의 사용자 인사이트가 처리 완료로 표시되었습니다." ,
83
- )
88
+ @admin .display (description = "인사이트 요약" )
89
+ def summarize_insight (self , obj : UserWeeklyTrend ):
90
+ if not isinstance (obj .insight , dict ):
91
+ return "데이터 없음"
92
+
93
+ summary_parts = []
94
+ stats = obj .insight .get ("user_weekly_stats" )
95
+ if stats :
96
+ summary_parts .append (
97
+ f"조회수: { stats .get ('views' , 0 )} , 새글: { stats .get ('new_posts' , 0 )} "
98
+ )
99
+
100
+ summary = obj .insight .get ("trending_summary" , [])
101
+ if summary and isinstance (summary , list ) and summary [0 ].get ("title" ):
102
+ summary_parts .append (f"신규글: { summary [0 ]['title' ][:20 ]} ..." )
103
+
104
+ return " | " .join (summary_parts ) if summary_parts else "요약 정보 없음"
105
+
106
+ @admin .display (description = "뉴스레터 템플릿" )
107
+ def render_full_preview (self , obj : UserWeeklyTrend ):
108
+ if not obj .insight :
109
+ return "No insight data to preview."
110
+ try :
111
+ # 공통 주간 트렌드 데이터 조회
112
+ weekly_trend = WeeklyTrend .objects .filter (
113
+ week_start_date = obj .week_start_date ,
114
+ week_end_date = obj .week_end_date ,
115
+ ).first ()
116
+
117
+ if not weekly_trend or not weekly_trend .insight :
118
+ weekly_trend_html = "<p><strong>경고:</strong> 해당 주차의 공통 WeeklyTrend를 찾을 수 없거나 데이터가 없습니다.</p>"
119
+ else :
120
+ weekly_trend_insight = from_dict (
121
+ WeeklyTrendInsight , weekly_trend .insight
122
+ )
123
+ weekly_trend_html = render_to_string (
124
+ "insights/weekly_trend.html" ,
125
+ {"insight" : weekly_trend_insight .to_dict ()},
126
+ )
127
+
128
+ # 사용자 주간 트렌드 렌더링
129
+ user_weekly_insight = from_dict (
130
+ WeeklyUserTrendInsight , obj .insight
131
+ )
132
+ user_weekly_trend_html = render_to_string (
133
+ "insights/user_weekly_trend.html" ,
134
+ {
135
+ "user" : {
136
+ "username" : obj .user .username if obj .user else "N/A"
137
+ },
138
+ "insight" : user_weekly_insight .to_dict (),
139
+ },
140
+ )
141
+
142
+ # 최종 뉴스레터 렌더링
143
+ final_html = render_to_string (
144
+ "insights/index.html" ,
145
+ {
146
+ "s_date" : obj .week_start_date ,
147
+ "e_date" : obj .week_end_date ,
148
+ "is_expired_token_user" : False ,
149
+ "weekly_trend_html" : weekly_trend_html ,
150
+ "user_weekly_trend_html" : user_weekly_trend_html ,
151
+ },
152
+ )
153
+
154
+ # Admin 페이지 너비 확장을 위한 CSS
155
+ style = """
156
+ <style>
157
+ /* iframe을 감싸는 필드의 너비 확장 */
158
+ .field-render_full_preview {
159
+ width: 100% !important;
160
+ max-width: none !important;
161
+ }
162
+
163
+ /* Django admin 전체 콘텐츠 영역 확장 */
164
+ .app-insight.model-weeklytrend .form-row,
165
+ .app-insight.model-weeklytrend .wide,
166
+ .app-insight.model-weeklytrend #content-main {
167
+ max-width: 1400px !important;
168
+ width: 100% !important;
169
+ }
170
+ </style>
171
+ """
172
+
173
+ iframe = f"""
174
+ <iframe
175
+ srcdoc="{ escape (final_html )} "
176
+ style="width: 100%; min-width: 600px; height: 600px; border: 1px solid #ccc;"
177
+ ></iframe>
178
+ """
179
+
180
+ return mark_safe (style + iframe )
181
+ except Exception as e :
182
+ return f"Error rendering preview: { e } "
0 commit comments