Skip to content

Commit

Permalink
Merge pull request #66 from pyvec/session-slides-video
Browse files Browse the repository at this point in the history
Session details: videos + slides
  • Loading branch information
jsmitka authored Nov 1, 2023
2 parents df97de8 + 312dfa6 commit 2bbda6a
Show file tree
Hide file tree
Showing 25 changed files with 650 additions and 235 deletions.
150 changes: 112 additions & 38 deletions program/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from pathlib import PurePath
from urllib.parse import quote

import requests
from django.contrib import admin
from django.core.files.base import ContentFile
from django.db import transaction
from django.utils.html import format_html

from program import pretalx, pretalx_sync
from program.models import Room, Slot, Speaker, Talk, Utility, Workshop
Expand Down Expand Up @@ -140,7 +146,6 @@ class TalkAdmin(admin.ModelAdmin):
"fields": [
"pretalx_code",
"og_image",
"video_id",
],
},
),
Expand All @@ -153,6 +158,17 @@ class TalkAdmin(admin.ModelAdmin):
],
},
),
(
"Slides and Video",
{
"fields": [
"video_url",
"video_image_html",
"slides_file",
"slides_description",
],
},
),
(
"Talk info (edit in pretalx)",
{
Expand Down Expand Up @@ -181,10 +197,29 @@ class TalkAdmin(admin.ModelAdmin):
"minimum_python_knowledge",
"minimum_topic_knowledge",
"type",
"video_image_html",
]
actions = [make_public, make_not_public, talk_update_from_pretalx]
change_form_template = "program/admin/change_form_session.html"

@admin.display(description="Video Image")
def video_image_html(self, obj: Talk):
if not obj.video_image:
return "(no image)"

html = (
'<a href="{image_url}" style="display: inline-block">'
'<img src="{image_url}" height="180"/><br>'
'<span style="display: inline-block; margin-top: 1ex;">{image_name}</span>'
"</a>"
)

return format_html(
html,
image_url=obj.video_image.url,
image_name=obj.video_image.name,
)

def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.prefetch_related("talk_speakers")
Expand All @@ -201,12 +236,52 @@ def get_readonly_fields(self, request, obj=None):
return ro_fields

def save_model(self, request, obj: Talk, form, change: bool) -> None:
if change:
self._update_video_image(obj)

obj.save()

if not change and obj.pretalx_code:
sync = create_pretalx_sync()
sync.update_talks([obj])

def _update_video_image(self, talk: Talk):
video_id = talk.video_id
if not video_id:
# Delete the existing image, if any.
if talk.video_image:
talk.video_image.delete(save=False)
return

# Get the video ID of the current image:
# the image is always named <video_id>.jpg
image_video_id: str | None = None
if talk.video_image:
image_path = PurePath(talk.video_image.name)
image_video_id = image_path.stem

# Check if the video ID has changed and download a new image when necessary.
if video_id != image_video_id:
image_data = self._download_youtube_video_image(video_id)
talk.video_image.save(
name=image_data.name,
content=image_data,
save=False,
)

def _download_youtube_video_image(self, video_id: str) -> ContentFile:
image_url = self._format_youtube_video_image_url(video_id)

with requests.get(image_url, timeout=30) as image_response:
image_response.raise_for_status()
return ContentFile(
content=image_response.content,
name=f"{video_id}.jpg",
)

def _format_youtube_video_image_url(self, video_id):
return f"https://img.youtube.com/vi/{quote(video_id)}/maxresdefault.jpg"


@admin.action(description="Update from pretalx")
def workshop_update_from_pretalx(modeladmin, request, queryset):
Expand Down Expand Up @@ -319,93 +394,92 @@ def save_model(self, request, obj: Workshop, form, change: bool) -> None:

@admin.register(Utility)
class UtilityAdmin(admin.ModelAdmin):
empty_value_display = 'not set'
empty_value_display = "not set"
list_display = [
'title',
'short_description',
'url',
'is_streamed',
"title",
"short_description",
"url",
"is_streamed",
]
list_editable = [
'is_streamed',
"is_streamed",
]
prepopulated_fields = {
'slug': ['title'],
"slug": ["title"],
}


@admin.display(description="Description", empty_value="not set")
def short_description(self, obj: Utility) -> str | None:
"""Shorten the description for admin inline.
If there is no description, return None, so the "empty_value" fires.
"""
return obj.description[:180] + '...' if obj.description else None
return obj.description[:180] + "..." if obj.description else None


@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
list_display = [
'label',
'order',
'slug',
"label",
"order",
"slug",
]
list_editable = [
'order',
"order",
]
fields = [
'label',
'order',
'slug',
"label",
"order",
"slug",
]
prepopulated_fields = {
'slug': ['label'],
"slug": ["label"],
}


@admin.register(Slot)
class SlotAdmin(admin.ModelAdmin):
list_display = [
'event',
'start',
'end',
'room',
"event",
"start",
"end",
"room",
]
list_filter = [
'room',
"room",
]
list_editable = [
'room',
'start',
'end',
"room",
"start",
"end",
]
fieldsets = [
(
'Event',
"Event",
{
'description': 'Select only one of the following.',
'fields': [
'talk',
'workshop',
'utility',
"description": "Select only one of the following.",
"fields": [
"talk",
"workshop",
"utility",
],
},
),
(
'Times',
"Times",
{
'fields': [
('start', 'end'),
"fields": [
("start", "end"),
],
},
),
(
None,
{
'fields': [
'room',
"fields": [
"room",
],
},
),
]
date_hierarchy = 'start'
date_hierarchy = "start"
3 changes: 1 addition & 2 deletions program/management/commands/pretalx_sync_submissions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.core.management.base import BaseCommand

from program import pretalx
from program import pretalx_sync
from program import pretalx, pretalx_sync


class Command(BaseCommand):
Expand Down
3 changes: 2 additions & 1 deletion program/management/commands/program_generate_og_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def _generate_og_images(
"""
objects = model_type.objects.filter(is_public=True)
self.stdout.write(
f"Generating OG images for {len(objects)} {model_type._meta.verbose_name_plural}"
f"Generating OG images for {len(objects)} "
f"{model_type._meta.verbose_name_plural}",
)
for obj in objects:
result = generator.generate_image(
Expand Down
3 changes: 2 additions & 1 deletion program/management/commands/program_import_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ def add_arguments(self, parser: ArgumentParser) -> None:

def handle(self, xlsx: str, output: str, *args, **options) -> None:
# Lazy imports to save a bit of memory.
from program.schedule_import import ScheduleImporter
from django.core import serializers

from program.schedule_import import ScheduleImporter

importer = ScheduleImporter()
new_objects = importer.import_xlsx(xlsx)
data = serializers.serialize("json", new_objects)
Expand Down
7 changes: 2 additions & 5 deletions program/management/commands/program_link_og_images.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import pathlib
import re
from functools import cached_property
from typing import Type

from django.conf import settings
from django.core.management.base import BaseCommand

from program import models

import re


from django.conf import settings


class Command(BaseCommand):
@cached_property
Expand Down
2 changes: 1 addition & 1 deletion program/migrations/0017_slot.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated by Django 4.2.1 on 2023-08-28 19:37

from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
Expand Down
2 changes: 1 addition & 1 deletion program/migrations/0018_room_alter_slot_room.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated by Django 4.2.1 on 2023-08-28 19:46

from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated by Django 4.2.1 on 2023-08-28 20:22

from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
Expand Down
2 changes: 1 addition & 1 deletion program/migrations/0020_room_slug.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ class Migration(migrations.Migration):
migrations.RunPython(
code=populate_room_slugs,
reverse_code=migrations.RunPython.noop,
)
),
]
46 changes: 46 additions & 0 deletions program/migrations/0022_talk_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 4.2.1 on 2023-11-01 10:05

import re

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("program", "0021_alter_room_options_remove_room_floor_room_order_and_more"),
]

operations = [
migrations.RemoveField(
model_name="talk",
name="video_id",
),
migrations.AddField(
model_name="talk",
name="video_image",
field=models.ImageField(
blank=True, null=True, upload_to="video-images/session/"
),
),
migrations.AddField(
model_name="talk",
name="video_url",
field=models.CharField(
blank=True,
help_text="YouTube video URL: <code>https://www.youtube.com/watch?v=&lt;VIDEO_ID&gt;</code>. Do not include any additional parameters, such as start time or tracking (UTM) parameters.",
max_length=1024,
null=True,
validators=[
django.core.validators.RegexValidator(
code="invalid_youtube_url",
message="Invalid YouTube URL format.",
regex=re.compile(
"^https://www\\.youtube\\.com/watch\\?v=(?P<video_id>[0-9A-Za-z_-]{11,})$"
),
)
],
verbose_name="Video URL",
),
),
]
Loading

0 comments on commit 2bbda6a

Please sign in to comment.