1
+ from pathlib import PurePath
2
+ from urllib .parse import quote
3
+
4
+ import requests
1
5
from django .contrib import admin
6
+ from django .core .files .base import ContentFile
2
7
from django .db import transaction
8
+ from django .utils .html import format_html
3
9
4
10
from program import pretalx , pretalx_sync
5
11
from program .models import Room , Slot , Speaker , Talk , Utility , Workshop
@@ -140,7 +146,6 @@ class TalkAdmin(admin.ModelAdmin):
140
146
"fields" : [
141
147
"pretalx_code" ,
142
148
"og_image" ,
143
- "video_id" ,
144
149
],
145
150
},
146
151
),
@@ -153,6 +158,15 @@ class TalkAdmin(admin.ModelAdmin):
153
158
],
154
159
},
155
160
),
161
+ (
162
+ "Slides and Video" ,
163
+ {
164
+ "fields" : [
165
+ "video_url" ,
166
+ "video_image_html" ,
167
+ ],
168
+ },
169
+ ),
156
170
(
157
171
"Talk info (edit in pretalx)" ,
158
172
{
@@ -181,10 +195,29 @@ class TalkAdmin(admin.ModelAdmin):
181
195
"minimum_python_knowledge" ,
182
196
"minimum_topic_knowledge" ,
183
197
"type" ,
198
+ "video_image_html" ,
184
199
]
185
200
actions = [make_public , make_not_public , talk_update_from_pretalx ]
186
201
change_form_template = "program/admin/change_form_session.html"
187
202
203
+ @admin .display (description = "Video Image" )
204
+ def video_image_html (self , obj : Talk ):
205
+ if not obj .video_image :
206
+ return "(no image)"
207
+
208
+ html = (
209
+ '<a href="{image_url}" style="display: inline-block">'
210
+ '<img src="{image_url}" height="180"/><br>'
211
+ '<span style="display: inline-block; margin-top: 1ex;">{image_name}</span>'
212
+ "</a>"
213
+ )
214
+
215
+ return format_html (
216
+ html ,
217
+ image_url = obj .video_image .url ,
218
+ image_name = obj .video_image .name ,
219
+ )
220
+
188
221
def get_queryset (self , request ):
189
222
qs = super ().get_queryset (request )
190
223
qs = qs .prefetch_related ("talk_speakers" )
@@ -201,12 +234,52 @@ def get_readonly_fields(self, request, obj=None):
201
234
return ro_fields
202
235
203
236
def save_model (self , request , obj : Talk , form , change : bool ) -> None :
237
+ if change :
238
+ self ._update_video_image (obj )
239
+
204
240
obj .save ()
205
241
206
242
if not change and obj .pretalx_code :
207
243
sync = create_pretalx_sync ()
208
244
sync .update_talks ([obj ])
209
245
246
+ def _update_video_image (self , talk : Talk ):
247
+ video_id = talk .video_id
248
+ if not video_id :
249
+ # Delete the existing image, if any.
250
+ if talk .video_image :
251
+ talk .video_image .delete (save = False )
252
+ return
253
+
254
+ # Get the video ID of the current image:
255
+ # the image is always named <video_id>.jpg
256
+ image_video_id : str | None = None
257
+ if talk .video_image :
258
+ image_path = PurePath (talk .video_image .name )
259
+ image_video_id = image_path .stem
260
+
261
+ # Check if the video ID has changed and download a new image when necessary.
262
+ if video_id != image_video_id :
263
+ image_data = self ._download_youtube_video_image (video_id )
264
+ talk .video_image .save (
265
+ name = image_data .name ,
266
+ content = image_data ,
267
+ save = False ,
268
+ )
269
+
270
+ def _download_youtube_video_image (self , video_id : str ) -> ContentFile :
271
+ image_url = self ._format_youtube_video_image_url (video_id )
272
+
273
+ with requests .get (image_url , timeout = 30 ) as image_response :
274
+ image_response .raise_for_status ()
275
+ return ContentFile (
276
+ content = image_response .content ,
277
+ name = f"{ video_id } .jpg" ,
278
+ )
279
+
280
+ def _format_youtube_video_image_url (self , video_id ):
281
+ return f"https://img.youtube.com/vi/{ quote (video_id )} /maxresdefault.jpg"
282
+
210
283
211
284
@admin .action (description = "Update from pretalx" )
212
285
def workshop_update_from_pretalx (modeladmin , request , queryset ):
0 commit comments