change release section put video and more

This commit is contained in:
mohamad
2026-06-08 17:08:43 +03:30
parent 33b3477176
commit 7cddc03a57
33 changed files with 1657 additions and 93 deletions
+172
View File
@@ -0,0 +1,172 @@
import re
from django.core.exceptions import ValidationError
from django.db import models
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_PLAIN, render_content
VIDEO_SOURCE_YOUTUBE = "youtube"
VIDEO_SOURCE_UPLOAD = "upload"
VIDEO_SOURCE_CHOICES = [
(VIDEO_SOURCE_YOUTUBE, "YouTube / external link"),
(VIDEO_SOURCE_UPLOAD, "Uploaded file"),
]
VIDEO_SIZE_SMALL = "sm"
VIDEO_SIZE_MEDIUM = "md"
VIDEO_SIZE_LARGE = "lg"
VIDEO_SIZE_FULL = "full"
VIDEO_SIZE_CHOICES = [
(VIDEO_SIZE_SMALL, "Small (480px)"),
(VIDEO_SIZE_MEDIUM, "Medium (720px)"),
(VIDEO_SIZE_LARGE, "Large (960px)"),
(VIDEO_SIZE_FULL, "Full width"),
]
VIDEO_ASPECT_16_9 = "16/9"
VIDEO_ASPECT_4_3 = "4/3"
VIDEO_ASPECT_1_1 = "1/1"
VIDEO_ASPECT_CHOICES = [
(VIDEO_ASPECT_16_9, "16:9 (widescreen)"),
(VIDEO_ASPECT_4_3, "4:3 (standard)"),
(VIDEO_ASPECT_1_1, "1:1 (square)"),
]
VIDEO_UPLOAD_EXTENSIONS = frozenset({".mp4", ".webm", ".ogg", ".mov"})
VIDEO_ADMIN_FIELDS = (
"video_source",
"video_url",
"video_file",
"video_poster",
"video_size",
"video_aspect_ratio",
"video_styled_background",
)
VIDEO_ADMIN_FIELDSET = (
"Video",
{
"fields": VIDEO_ADMIN_FIELDS + ("video_preview",),
"description": (
"Choose YouTube link or uploaded file. Set display size and aspect ratio "
"to control how the preview appears on the site."
),
},
)
YOUTUBE_ID_PATTERNS = (
re.compile(r"(?:youtube\.com/watch\?(?:[^&]+&)*v=|youtube\.com/embed/|youtube\.com/shorts/|youtu\.be/)([\w-]{11})"),
re.compile(r"^([\w-]{11})$"),
)
def parse_youtube_video_id(url):
if not url:
return ""
value = url.strip()
for pattern in YOUTUBE_ID_PATTERNS:
match = pattern.search(value)
if match:
return match.group(1)
return ""
def youtube_embed_url(url):
video_id = parse_youtube_video_id(url)
if not video_id:
return ""
return f"https://www.youtube-nocookie.com/embed/{video_id}"
class VideoBlockMixin(models.Model):
video_source = models.CharField(
max_length=20,
choices=VIDEO_SOURCE_CHOICES,
default=VIDEO_SOURCE_YOUTUBE,
blank=True,
)
video_url = models.CharField(
max_length=500,
blank=True,
help_text="YouTube watch, embed, or youtu.be link.",
)
video_file = models.FileField(
upload_to="videos/",
blank=True,
help_text="MP4, WebM, OGG, or MOV file.",
)
video_poster = models.ImageField(
upload_to="videos/posters/",
blank=True,
null=True,
help_text="Optional thumbnail shown before an uploaded video plays.",
)
video_size = models.CharField(
max_length=10,
choices=VIDEO_SIZE_CHOICES,
default=VIDEO_SIZE_MEDIUM,
)
video_aspect_ratio = models.CharField(
max_length=10,
choices=VIDEO_ASPECT_CHOICES,
default=VIDEO_ASPECT_16_9,
)
video_styled_background = models.BooleanField(
default=False,
help_text="Glass panel with ambient glow (similar to the Downloads section).",
)
description_format = models.CharField(
max_length=20,
choices=CONTENT_FORMAT_CHOICES,
default=FORMAT_PLAIN,
)
class Meta:
abstract = True
@property
def youtube_embed_url(self):
return youtube_embed_url(self.video_url)
@property
def has_video(self):
if self.video_source == VIDEO_SOURCE_UPLOAD:
return bool(self.video_file)
return bool(self.youtube_embed_url)
@property
def video_size_class(self):
return f"video-block--{self.video_size or VIDEO_SIZE_MEDIUM}"
@property
def video_aspect_class(self):
ratio = (self.video_aspect_ratio or VIDEO_ASPECT_16_9).replace("/", "-")
return f"video-block--ratio-{ratio}"
@property
def rendered_description(self):
description = getattr(self, "description", "") or ""
return render_content(description, self.description_format)
def clean_video_fields(self, require=False):
if not require and not self.video_url and not self.video_file:
return
if self.video_source == VIDEO_SOURCE_YOUTUBE:
if not self.video_url.strip():
raise ValidationError({"video_url": "Enter a YouTube link."})
if not self.youtube_embed_url:
raise ValidationError({"video_url": "Enter a valid YouTube link."})
elif self.video_source == VIDEO_SOURCE_UPLOAD:
if not self.video_file:
raise ValidationError({"video_file": "Upload a video file."})
extension = self.video_file.name.rsplit(".", 1)[-1].lower() if self.video_file.name else ""
if f".{extension}" not in VIDEO_UPLOAD_EXTENSIONS:
raise ValidationError(
{
"video_file": "Unsupported format. Use MP4, WebM, OGG, or MOV.",
}
)