Skip to content

Commit

Permalink
[DONE] Ptitloup/feature resume video playback -> 3.5.0 (#980)
Browse files Browse the repository at this point in the history
* add the user time model to record the time of video played by a user

* update model and add unit tests

* add db migration folder in test setting to try

* add maker time view and url to call it

* fix test settings

* add init.py

* create test for maker view, add def in video to get marker for user

* fix typo and unit test name

* create template tag to get user video viweving marker and call it in video script and card

* change var name, def name, model name and position of progress bar in card - add some pydoc
  • Loading branch information
ptitloup authored Oct 23, 2023
1 parent 185ce04 commit d919cf4
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 7 deletions.
3 changes: 2 additions & 1 deletion pod/main/static/css/pod.css
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ a:not(.btn):focus {
.link-center-pod > img {
min-width: 100%;
height: 100%;
min-height: 146px;
}

/*********
Expand Down Expand Up @@ -574,7 +575,7 @@ div.card a img {

.card-footer-pod {
position: absolute;
bottom: 3.5rem;
bottom: 4rem;
right: 0;
border: none;
display: flex;
Expand Down
8 changes: 8 additions & 0 deletions pod/main/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,11 @@ def get_shared_secret():
XAPI_LRS_URL = ""
XAPI_LRS_LOGIN = ""
XAPI_LRS_PWD = ""

# Uniquement lors d'environnement conteneurisé
MIGRATION_MODULES = {'flatpages': 'pod.db_migrations'}
MIGRATION_DIRECTORY = os.path.join(settings_base_dir, "db_migrations")
if not os.path.exists(MIGRATION_DIRECTORY):
os.mkdir(MIGRATION_DIRECTORY)
file = os.path.join(MIGRATION_DIRECTORY, "__init__.py")
open(file, 'a').close()
34 changes: 34 additions & 0 deletions pod/video/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,12 @@ def get_viewcount(self, from_nb_day=0):
return 0
return count_sum["count__sum"]

def get_marker_time_for_user(self, user):
"""Get the marker time of a video for the user in parameter."""
if self.usermarkertime_set.filter(user=user).exists():
return self.usermarkertime_set.get(user=user).markerTime
return 0

def get_absolute_url(self):
"""Get the video absolute URL."""
return reverse("video:video", args=[str(self.slug)])
Expand Down Expand Up @@ -1474,6 +1480,34 @@ class Meta:
verbose_name_plural = _("View counts")


class UserMarkerTime(models.Model):
"""Record the time of video played by a user."""
video = models.ForeignKey(
Video, verbose_name=_("Video"), editable=False, on_delete=models.CASCADE
)
user = models.ForeignKey(
User, verbose_name=_("User"), editable=False, on_delete=models.CASCADE
)
markerTime = models.IntegerField(_("Marker time"), default=0, editable=False)

@property
def sites(self):
"""Return the sites of the video."""
return self.video.sites

def __str__(self):
return "Marker time for user %s and video %s: %s" % (
self.user,
self.video,
self.markerTime
)

class Meta:
unique_together = ("video", "user")
verbose_name = _("User viewing time marker of video")
verbose_name_plural = _("Users viewing time marker of video")


class VideoVersion(models.Model):
video = models.OneToOneField(
Video, verbose_name=_("Video"), editable=False, on_delete=models.CASCADE
Expand Down
12 changes: 10 additions & 2 deletions pod/video/templates/videos/card.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load i18n %}
{% load playlist_buttons %}
{% load playlist_buttons video_tags %}

{% spaceless %}
{% if playlist %}
Expand Down Expand Up @@ -38,6 +38,7 @@
{% endif %}
</span>
</div>

</div>
<div class="card-thumbnail">
<a class="link-center-pod"
Expand All @@ -61,6 +62,14 @@
>
{{video.get_thumbnail_card|safe}}
</a>
{% if request.user.is_authenticated %}
{% get_percent_marker_for_user video request.user as percent_view %}
{% if percent_view != 0 %}
<div class="progress">
<div class="progress-bar" role="progressbar" style="width:{{percent_view}}%" aria-valuenow="{{percent_view}}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{% endif %}
{% endif %}
</div>
<div class="card-body px-3 py-2">
{# TODO: replace test below by "if video.is_editable" ? #}
Expand All @@ -69,7 +78,6 @@
{% include "videos/link_video.html" %}
</footer>
{% endif %}

<span class="small video-title">
{% if playlist %}
{% if can_see_video %}
Expand Down
23 changes: 23 additions & 0 deletions pod/video/templates/videos/video-script.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@
);
});

{% if request.user %}
var marker_url = "{% url "video:video_marker" video.id 0 %}";
var last_timeupdate = 0;
player.on('timeupdate', function(){
// Get the current time (in seconds)
let currentTime = parseInt(player.currentTime());
if(currentTime % 5 == 0 && currentTime != last_timeupdate ) {
last_timeupdate = currentTime;
fetch(marker_url.replace("/0/", "/"+currentTime+"/"), {}); //.then(response => console.log(response));
}
});
{% endif %}

{% if video.is_video %}
/** get all mp4 format **/
var mp4_sources = {{ video.get_video_mp4_json|safe }};
Expand All @@ -120,6 +133,11 @@
player.on('loadedmetadata', function() {
{% if request.GET.start and request.GET.start != '0' %}
player.currentTime({{request.GET.start}});
{% else %}
{% if request.user.is_authenticated %}
const markerTime = {% get_marker_time_for_user video request.user %};
if(markerTime !=0) player.currentTime(markerTime);
{% endif %}
{% endif %}
});
//Add source to player
Expand Down Expand Up @@ -198,6 +216,11 @@
player.on('loadedmetadata', function() {
{% if request.GET.start and request.GET.start != '0' %}
player.currentTime({{request.GET.start}});
{% else %}
{% if request.user %}
const markerTime = {% get_marker_time_for_user video request.user %};
if(markerTime !=0) player.currentTime(markerTime);
{% endif %}
{% endif %}
});
{% endif %}
Expand Down
17 changes: 17 additions & 0 deletions pod/video/templatetags/video_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from ..forms import VideoVersionForm
from ..context_processors import get_available_videos
from pod.video_encode_transcript.utils import check_file
from django.contrib.auth.models import User
from pod.video.models import Video

import importlib
import os
Expand All @@ -30,6 +32,21 @@
)


@register.simple_tag(name="get_marker_time_for_user")
def get_marker_time_for_user(video: Video, user: User):
"""Tag to get the marker time of the video for the authenticated user."""
return video.get_marker_time_for_user(user)


@register.simple_tag(name="get_percent_marker_for_user")
def get_percent_marker_for_user(video: Video, user: User):
"""Tag to get the percent time of the video viewed by the authenticated user."""
if video.duration and video.duration != 0:
return int((video.get_marker_time_for_user(user) / video.duration) * 100)
else:
return 0


@register.filter(name="file_exists")
def file_exists(filepath):
return check_file(filepath.path)
Expand Down
79 changes: 79 additions & 0 deletions pod/video/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from django.db import transaction

from ..models import Channel
from ..models import Theme
Expand All @@ -17,6 +19,7 @@
from ..models import get_storage_path_video
from ..models import VIDEOS_DIR
from ..models import Notes, AdvancedNotes
from ..models import UserMarkerTime

from pod.video_encode_transcript.models import VideoRendition
from pod.video_encode_transcript.models import EncodingVideo
Expand Down Expand Up @@ -788,6 +791,8 @@ def test_delete_object(self):


class NotesTestCase(TestCase):
"""Test the Note model."""

fixtures = [
"initial_data.json",
]
Expand Down Expand Up @@ -861,3 +866,77 @@ def test_delete_object(self):
self.assertEqual(AdvancedNotes.objects.all().count(), 0)

print(" ---> test_delete_object of NotesTestCase: OK!")


class UserMarkerTimeTestCase(TestCase):
"""Test the UserMarkerTime model."""
fixtures = [
"initial_data.json",
]

def setUp(self):
user = User.objects.create(username="pod", password="pod1234pod")
Video.objects.create(
title="Video1",
owner=user,
video="test.mp4",
type=Type.objects.get(id=1),
)
print(" ---> SetUp of UserMarkerTimeTestCase: OK!")

def test_create_UserMarkerTime_default(self):
user = User.objects.get(username="pod")
video = Video.objects.get(id=1)
markerTime = UserMarkerTime.objects.create(video=video, user=user)
self.assertTrue(isinstance(markerTime, UserMarkerTime))
self.assertEqual(UserMarkerTime.objects.all().count(), 1)
self.assertEqual(markerTime.markerTime, 0)
print(" ---> test_create_UserMarkerTime_default: OK!")

def test_create_UserMarkerTime_with_attribut(self):
user = User.objects.get(username="pod")
video = Video.objects.get(id=1)
markerTime = UserMarkerTime(video=video, user=user, markerTime=60)
markerTime.save()
self.assertTrue(isinstance(markerTime, UserMarkerTime))
self.assertEqual(UserMarkerTime.objects.all().count(), 1)
self.assertEqual(markerTime.user, user)
self.assertEqual(markerTime.video, video)
self.assertEqual(markerTime.markerTime, 60)
print(" ---> test_create_UserMarkerTime_with_attribut: OK!")

def test_create_UserMarkerTime_already_exist(self):
user = User.objects.get(username="pod")
video = Video.objects.get(id=1)
UserMarkerTime.objects.create(video=video, user=user)
newMarkerTime = UserMarkerTime(video=video, user=user)
try:
with transaction.atomic():
newMarkerTime.save()
self.fail('Duplicate marker allowed.')
except IntegrityError:
pass
self.assertEqual(UserMarkerTime.objects.all().count(), 1)
print(" ---> test_create_UserMarkerTime_already_exist: OK!")

def test_modify_UserMarkerTime(self):
user = User.objects.get(username="pod")
video = Video.objects.get(id=1)
markerTime = UserMarkerTime(video=video, user=user, markerTime=60)
markerTime.save()
self.assertEqual(markerTime.markerTime, 60)
markerTime.markerTime = 120
markerTime.save()
markerTime.refresh_from_db()
self.assertEqual(markerTime.markerTime, 120)
print(" ---> test_modify_UserMarkerTime: OK!")

def test_delete_UserMarkerTime(self):
user = User.objects.get(username="pod")
video = Video.objects.get(id=1)
markerTime = UserMarkerTime(video=video, user=user, markerTime=60)
markerTime.save()
self.assertEqual(UserMarkerTime.objects.all().count(), 1)
markerTime.delete()
self.assertEqual(UserMarkerTime.objects.all().count(), 0)
print(" ---> test_delete_UserMarkerTime: OK!")
44 changes: 44 additions & 0 deletions pod/video/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,50 @@ def test_video_countTestView_post_request(self):
print(" ---> test_video_countTestView_post_request of video_countTestView: OK!")


class video_markerTestView(TestCase):
"""Test the video marker view."""
fixtures = [
"initial_data.json",
]

def setUp(self):
user = User.objects.create(username="pod", password="pod1234pod")
Video.objects.create(
title="Video1",
owner=user,
video="test1.mp4",
type=Type.objects.get(id=1),
)
print(" ---> SetUp of video_markerTestView: OK!")

def test_video_markerTestView_get_request(self):
# anonyme
self.client = Client()
video = Video.objects.get(title="Video1")
url = reverse("video:video_marker", kwargs={"id": video.id, "time": 1})
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
# login and video does not exist
self.user = User.objects.get(username="pod")
self.client.force_login(self.user)
url = reverse("video:video_marker", kwargs={"id": 2, "time": 2})
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
# login and video exist
url = reverse("video:video_marker", kwargs={"id": video.id, "time": 3})
response = self.client.get(url)
self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertTrue(b"ok" in response.content)
self.assertEqual(video.get_marker_time_for_user(self.user), 3)
# update time
url = reverse("video:video_marker", kwargs={"id": video.id, "time": 4})
response = self.client.get(url)
self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertTrue(b"ok" in response.content)
self.assertEqual(video.get_marker_time_for_user(self.user), 4)
print(" ---> test video markerTestView get request: OK!")


class VideoTestUpdateOwner(TransactionTestCase):
fixtures = [
"initial_data.json",
Expand Down
2 changes: 2 additions & 0 deletions pod/video/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
video_notes,
video_xhr,
video_count,
video_marker,
video_version,
get_categories,
add_category,
Expand Down Expand Up @@ -66,6 +67,7 @@
),
url(r"^notes/(?P<slug>[\-\d\w]+)/$", video_notes, name="video_notes"),
url(r"^count/(?P<id>[\d]+)/$", video_count, name="video_count"),
url(r"^marker/(?P<id>[\d]+)/(?P<time>[\d]+)/$", video_marker, name="video_marker"),
url(r"^version/(?P<id>[\d]+)/$", video_version, name="video_version"),
url(r"^xhr/(?P<slug>[\-\d\w]+)/$", video_xhr, name="video_xhr"),
url(
Expand Down
Loading

0 comments on commit d919cf4

Please sign in to comment.