Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1713, #1714] Added basic video model, player & cms-plugin #773

Merged
merged 7 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
1 change: 1 addition & 0 deletions src/open_inwoner/cms/plugins/cms_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .videoplayer import VideoPlayerPlugin
18 changes: 18 additions & 0 deletions src/open_inwoner/cms/plugins/cms_plugins/videoplayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.utils.translation import gettext as _

from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool

from open_inwoner.cms.plugins.models.videoplayer import VideoPlayer


@plugin_pool.register_plugin
class VideoPlayerPlugin(CMSPluginBase):
model = VideoPlayer
module = _("Media")
name = _("Video Player")
render_template = "cms/plugins/videoplayer/videoplayer.html"

def render(self, context, instance, placeholder):
context.update({"instance": instance})
return context
46 changes: 46 additions & 0 deletions src/open_inwoner/cms/plugins/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 3.2.20 on 2023-09-19 13:04

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


class Migration(migrations.Migration):

initial = True

dependencies = [
("media", "0001_initial"),
("cms", "0022_auto_20180620_1551"),
]

operations = [
migrations.CreateModel(
name="VideoPlayer",
fields=[
(
"cmsplugin_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
related_name="plugins_videoplayer",
serialize=False,
to="cms.cmsplugin",
),
),
(
"video",
models.ForeignKey(
help_text="The video from the catalog.",
on_delete=django.db.models.deletion.PROTECT,
to="media.video",
),
),
],
options={
"abstract": False,
},
bases=("cms.cmsplugin",),
),
]
Empty file.
1 change: 1 addition & 0 deletions src/open_inwoner/cms/plugins/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .videoplayer import VideoPlayer
20 changes: 20 additions & 0 deletions src/open_inwoner/cms/plugins/models/videoplayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.db import models
from django.utils.translation import gettext_lazy as _

from cms.models import CMSPlugin

from open_inwoner.media.models import Video


class VideoPlayer(CMSPlugin):
video = models.ForeignKey(
Video,
help_text=_("The video from the catalog."),
on_delete=models.PROTECT,
)

def __str__(self):
if self.video_id:
return str(self.video)
else:
return super().__str__()
Empty file.
16 changes: 16 additions & 0 deletions src/open_inwoner/cms/plugins/tests/test_videoplayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.test import TestCase

from open_inwoner.cms.tests import cms_tools
from open_inwoner.media.tests.factories import VideoFactory

from ..cms_plugins import VideoPlayerPlugin


class TestVideoPlayerPlugin(TestCase):
def test_plugin(self):
video = VideoFactory()
html, context = cms_tools.render_plugin(
VideoPlayerPlugin, plugin_data={"video": video}
)
self.assertIn(video.player_url, html)
self.assertIn("<iframe ", html)
3 changes: 3 additions & 0 deletions src/open_inwoner/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
"open_inwoner.questionnaire",
"open_inwoner.extended_sessions",
"open_inwoner.custom_csp",
"open_inwoner.media",
"open_inwoner.cms.profile",
"open_inwoner.cms.cases",
"open_inwoner.cms.inbox",
Expand All @@ -215,6 +216,7 @@
"open_inwoner.cms.banner",
"open_inwoner.cms.extensions",
"open_inwoner.cms.footer",
"open_inwoner.cms.plugins",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -531,6 +533,7 @@
"plugins": [
# "TextPlugin",
"PicturePlugin",
"VideoPlayerPlugin",
"CategoriesPlugin",
"ActivePlansPlugin",
"QuestionnairePlugin",
Expand Down
Empty file.
1 change: 1 addition & 0 deletions src/open_inwoner/media/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .video import VideoAdmin
46 changes: 46 additions & 0 deletions src/open_inwoner/media/admin/video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.contrib import admin
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _

from ..models import Video


@admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
fields = [
"link_id",
"title",
"player_type",
"language",
"external_url_link",
]
list_display_links = [
"link_id",
"title",
]
list_display = list_display_links + [
"player_type",
"language",
"external_url_link",
]
search_fields = [
"link_id",
"title",
]
list_filter = [
"player_type",
]
readonly_fields = [
"external_url_link",
]

@admin.display(description=_("External URL"), ordering=("player_type", "link_id"))
def external_url_link(self, video):
if not video.link_id:
return "-"
url = video.external_url
return format_html(
'<a href="{url}" rel="noopener" target="_blank">{text}</a>',
url=url,
text=url,
)
8 changes: 8 additions & 0 deletions src/open_inwoner/media/choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.utils.translation import ugettext_lazy as _

from djchoices import ChoiceItem, DjangoChoices


class VideoPlayerChoices(DjangoChoices):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

djchoices is deprecated as Django supports enum types natively. See Django docs and the note in the djchoices docs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True but the whole application is using djchoices so let's stick with it for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pi-sigma I made Taiga 1769 issue to swap the old choices for modern native ones.

vimeo = ChoiceItem("vimeo", _("Vimeo"))
youtube = ChoiceItem("youtube", _("Youtube"))
63 changes: 63 additions & 0 deletions src/open_inwoner/media/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generated by Django 3.2.20 on 2023-09-19 11:53

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Video",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"link_id",
models.CharField(
help_text="https://vimeo.com/[Video ID] | https://www.youtube.com/watch?v=[Video ID]",
max_length=100,
verbose_name="video ID",
),
),
(
"player_type",
models.CharField(
choices=[("vimeo", "Vimeo"), ("youtube", "Youtube")],
default="vimeo",
max_length=200,
verbose_name="Player type",
),
),
(
"title",
models.CharField(
blank=True, default="", max_length=200, verbose_name="title"
),
),
(
"language",
models.CharField(
choices=[("nl", "Dutch")],
default="nl",
max_length=20,
verbose_name="language",
),
),
],
options={
"verbose_name": "Video",
"ordering": ("title",),
},
),
]
Empty file.
1 change: 1 addition & 0 deletions src/open_inwoner/media/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .video import Video # noqa
76 changes: 76 additions & 0 deletions src/open_inwoner/media/models/video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _

from open_inwoner.media.choices import VideoPlayerChoices
from open_inwoner.utils.text import middle_truncate


class Video(models.Model):
link_id = models.CharField(
_("video ID"),
max_length=100,
help_text=_(
"https://vimeo.com/[Video ID] | https://www.youtube.com/watch?v=[Video ID]"
),
)
player_type = models.CharField(
_("Player type"),
max_length=200,
default=VideoPlayerChoices.vimeo,
choices=VideoPlayerChoices.choices,
)
title = models.CharField(
_("title"),
max_length=200,
default="",
blank=True,
)
language = models.CharField(
_("language"),
max_length=20,
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE,
)

class Meta:
verbose_name = _("Video")
ordering = ("title",)

def __str__(self):
if not self.title:
return self.link_id

return "{} ({}: {}, {})".format(
middle_truncate(self.title, 50),
self.player_type,
self.link_id,
self.language,
)

@property
def external_url(self):
if self.player_type == VideoPlayerChoices.youtube:
url = "https://www.youtube.com/watch?v={link_id}&enablejsapi=1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

link_id > self.link_id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was from an older project so used old .format(), but I replaced it with f-strings as I should've done before.

elif self.player_type == VideoPlayerChoices.vimeo:
url = "https://vimeo.com/{link_id}"
Bartvaderkin marked this conversation as resolved.
Show resolved Hide resolved
else:
raise Exception("unsupported player_type")
return url.format(link_id=self.link_id)

@property
def player_url(self):
if self.player_type == VideoPlayerChoices.youtube:
separator = "?"
if "?" in self.link_id:
separator = "&"
url = (
"https://www.youtube.com/embed/{link_id}"
+ separator
+ "enablejsapi=1&modestbranding=1"
)
elif self.player_type == VideoPlayerChoices.vimeo:
url = "https://player.vimeo.com/video/{link_id}"
else:
raise Exception("unsupported player_type")
return url.format(link_id=self.link_id)
Empty file.
12 changes: 12 additions & 0 deletions src/open_inwoner/media/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import factory

from open_inwoner.media.choices import VideoPlayerChoices


class VideoFactory(factory.django.DjangoModelFactory):
link_id = factory.Faker("ean13")
title = factory.Faker("sentence")
player_type = VideoPlayerChoices.vimeo

class Meta:
model = "media.Video"
40 changes: 40 additions & 0 deletions src/open_inwoner/media/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.urls import reverse

from cms.utils.permissions import set_current_user
from django_webtest import WebTest

from open_inwoner.accounts.tests.factories import UserFactory

from .factories import VideoFactory


class VideoAdminTests(WebTest):
def setUp(self):
set_current_user(
None
) # otherwise will assume previous user is logged in (who is often deleted after test)
return super().setUp()

def test_get_changelist(self):
user = UserFactory(is_staff=True, is_superuser=True)
url = reverse("admin:media_video_changelist")
response = self.app.get(url, user=user)
self.assertEqual(response.status_code, 200)

def test_get_changelist_with_videos(self):
video = VideoFactory()
user = UserFactory(is_staff=True, is_superuser=True)
url = reverse("admin:media_video_changelist")
response = self.app.get(url, user=user)
self.assertEqual(response.status_code, 200)
self.assertContains(response, video.link_id)
self.assertContains(response, video.external_url)

def test_get_change(self):
video = VideoFactory()
user = UserFactory(is_staff=True, is_superuser=True)
url = reverse("admin:media_video_change", kwargs={"object_id": video.id})
response = self.app.get(url, user=user)
self.assertEqual(response.status_code, 200)
self.assertContains(response, video.link_id)
self.assertContains(response, video.external_url)
Loading
Loading