- {% include 'loader.html' %}
- {% include "favorite/favorite_video_list.html" %}
-{% endblock page_content %}
-
-{% block page_aside %}
- {% include 'videos/filter_aside.html' %}
-
-
{% trans "Help - Drag & Drop" %}
-
-
-
{% trans "Select the video to move by clicking and holding." %}
-
{% trans "While holding down the video, drag it to the desired location." %}
-
{% trans "Drop the video on another to swap their position." %}
-
-
-
-{% endblock page_aside %}
-
-
-{% block more_script %}
-
-
-
-
-
-
-{% endblock more_script %}
diff --git a/pod/favorite/templatetags/favorite_info.py b/pod/favorite/templatetags/favorite_info.py
deleted file mode 100644
index c9ac9e50d5..0000000000
--- a/pod/favorite/templatetags/favorite_info.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from django.template import Library
-
-from pod.video.models import Video
-
-from ..utils import user_has_favorite_video, get_number_favorites
-
-register = Library()
-
-
-@register.simple_tag(takes_context=True, name="is_favorite")
-def is_favorite(context: dict, video: Video) -> bool:
- """
- Template tag to check if the user has this video as favorite.
-
- Args:
- context (dict): The template context dictionary
- video (:class:`pod.video.models.Video`): The video entity to check
-
- Returns:
- bool: True if the user has the video as favorite, False otherwise
- """
- request = context["request"]
- if not request.user.is_authenticated:
- return False
- return user_has_favorite_video(request.user, video)
-
-
-@register.simple_tag(name="number_favorites")
-def number_favorites(video: Video) -> int:
- """
- Template tag to get the favorite number.
-
- Args:
- video (:class:`pod.video.models.Video`): The video entity
-
- Returns:
- int: The video favorite number
- """
- return get_number_favorites(video)
diff --git a/pod/favorite/tests/__init__.py b/pod/favorite/tests/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/pod/favorite/tests/test_models.py b/pod/favorite/tests/test_models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/pod/favorite/tests/test_utils.py b/pod/favorite/tests/test_utils.py
deleted file mode 100644
index 7bd446cc4e..0000000000
--- a/pod/favorite/tests/test_utils.py
+++ /dev/null
@@ -1,174 +0,0 @@
-"""Unit tests for Esup-Pod favorite video utilities."""
-
-from django.contrib.auth.models import User
-from django.http import HttpRequest
-from django.test import TestCase
-
-from pod.favorite.models import Favorite
-from pod.favorite.utils import get_next_rank, user_add_or_remove_favorite_video
-from pod.favorite.utils import user_has_favorite_video, get_number_favorites
-from pod.favorite.utils import get_all_favorite_videos_for_user
-from pod.video.utils import sort_videos_list
-from pod.video.models import Type, Video
-
-
-class FavoriteTestUtils(TestCase):
- """TestCase for Esup-Pod favorite video utilities."""
-
- fixtures = ["initial_data.json"]
-
- def setUp(self) -> None:
- """Set up required objects for next tests."""
- self.user = User.objects.create(username="pod", password="pod1234pod")
- self.user2 = User.objects.create(username="pod2", password="pod1234pod2")
- self.video = Video.objects.create(
- title="Video1",
- owner=self.user,
- video="test.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
- self.video2 = Video.objects.create(
- title="Video2",
- owner=self.user,
- video="test2.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
- self.video3 = Video.objects.create(
- title="Video3",
- owner=self.user2,
- video="test3.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
-
- def test_next_rank(self) -> None:
- """Test if get_next_rank works correctly"""
- Favorite.objects.create(
- owner=self.user,
- video=self.video,
- rank=1,
- )
- self.assertEqual(
- 2,
- get_next_rank(self.user),
- "Test if user with favorite can generate the next rank",
- )
- self.assertEqual(
- 1,
- get_next_rank(self.user2),
- "Test if user without favorite can generate the next rank",
- )
- print(" ---> test_next_rank ok")
-
- def test_user_add_or_remove_favorite_video(self) -> None:
- """Test if test_user_add_or_remove_favorite_video works correctly"""
- user_add_or_remove_favorite_video(self.user, self.video)
- favorite_tuple_exists = Favorite.objects.filter(
- owner=self.user, video=self.video
- ).exists()
- self.assertTrue(
- favorite_tuple_exists,
- "Test if tuple has been correctly inserted",
- )
- user_add_or_remove_favorite_video(self.user, self.video)
- favorite_tuple_not_exists = Favorite.objects.filter(
- owner=self.user, video=self.video
- ).exists()
- self.assertFalse(
- favorite_tuple_not_exists,
- "Test if tuple has been correctly deleted",
- )
- print(" ---> test_user_add_or_remove_favorite_video ok")
-
- def test_user_has_favorite_video(self) -> None:
- """Test if test_user_has_favorite_video works correctly"""
- Favorite.objects.create(
- owner=self.user,
- video=self.video,
- rank=1,
- )
- self.assertTrue(
- user_has_favorite_video(self.user, self.video),
- "Test if user has a favorite video",
- )
- self.assertFalse(
- user_has_favorite_video(self.user2, self.video),
- "Test if user hasn't a favorite video",
- )
- print(" ---> test_user_has_favorite_video ok")
-
- def test_get_number_favorites(self) -> None:
- """Test if test_get_number_favorites works correctly"""
- self.assertEqual(
- get_number_favorites(self.video),
- 0,
- "Test if there's no favorites in the video",
- )
- Favorite.objects.create(
- owner=self.user,
- video=self.video,
- rank=1,
- )
- Favorite.objects.create(
- owner=self.user2,
- video=self.video,
- rank=1,
- )
- self.assertEqual(
- get_number_favorites(self.video),
- 2,
- "Test if there is 2 favorites in the video",
- )
-
- print(" ---> test_get_number_favorites ok")
-
- def test_get_all_favorite_videos_for_user(self) -> None:
- """Test if get_all_favorite_videos_for_user works correctly."""
- Favorite.objects.create(
- owner=self.user,
- video=self.video,
- rank=1,
- )
- video_list = get_all_favorite_videos_for_user(self.user)
- self.assertEqual(video_list.count(), 1)
- self.assertEqual(video_list.first(), self.video)
- print(" ---> get_all_favorite_videos_for_user ok")
-
- def test_sort_videos_list_1(self) -> None:
- """Test if sort_videos_list works correctly."""
- request = HttpRequest()
- Favorite.objects.create(
- owner=self.user,
- video=self.video,
- rank=1,
- )
- Favorite.objects.create(
- owner=self.user,
- video=self.video2,
- rank=2,
- )
- Favorite.objects.create(
- owner=self.user,
- video=self.video3,
- rank=3,
- )
-
- sorted_videos = [self.video3, self.video2, self.video]
- test_sorted_videos = sort_videos_list(
- get_all_favorite_videos_for_user(self.user), request.GET.get("sort", "rank")
- )
- self.assertEqual(list(test_sorted_videos), sorted_videos)
-
- request.GET["sort"] = "rank"
- request.GET["sort_direction"] = "on"
- sorted_videos = [self.video, self.video2, self.video3]
- test_sorted_videos = sort_videos_list(
- get_all_favorite_videos_for_user(self.user),
- request.GET.get("sort", "rank"),
- request.GET.get("sort_direction"),
- )
- self.assertEqual(list(test_sorted_videos), sorted_videos)
-
- print(" ---> sort_videos_list ok")
diff --git a/pod/favorite/tests/test_views.py b/pod/favorite/tests/test_views.py
deleted file mode 100644
index cba7713bea..0000000000
--- a/pod/favorite/tests/test_views.py
+++ /dev/null
@@ -1,268 +0,0 @@
-"""Esup-Pod favorite views tests.
-
-* run with 'python manage.py test pod.favorite.tests.test_views'
-"""
-from django.test import override_settings, TestCase
-from django.contrib.auth.models import User
-from django.urls import reverse
-from django.utils.translation import ugettext_lazy as _
-
-from pod.favorite import context_processors
-from pod.favorite.models import Favorite
-from pod.video.models import Type, Video
-
-import importlib
-
-
-class TestShowStarTestCase(TestCase):
- """Favorite star icon test case."""
-
- fixtures = ["initial_data.json"]
-
- def setUp(self) -> None:
- """Set up required objects for next tests."""
- self.user_with_favorite = User.objects.create(
- username="pod", password="pod1234pod"
- )
- self.user_without_favorite = User.objects.create(
- username="pod2", password="pod1234pod2"
- )
- self.video = Video.objects.create(
- title="Video1",
- owner=self.user_without_favorite,
- video="test.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
- Favorite.objects.create(
- owner=self.user_with_favorite,
- video=self.video,
- rank=1,
- )
- self.url = reverse("video:video", args=[self.video.slug])
-
- @override_settings(USE_FAVORITES=True)
- def test_show_star_unfill(self) -> None:
- """Test if the star is unfill when the video isn't favorite."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_without_favorite)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the video isn't favorite",
- )
- self.assertTrue(
- "bi-star" in response.content.decode(),
- "Test if the star is correctly present when the video isn't favorite",
- )
- self.client.logout()
- print(" ---> test_show_star_unfill ok")
-
- @override_settings(USE_FAVORITES=True)
- def test_show_star_fill(self) -> None:
- """Test if the star is filled when the video is favorite."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_with_favorite)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the video is favorite",
- )
- self.assertTrue(
- "bi-star-fill" in response.content.decode(),
- "Test if the star is correctly present when the video is favorite",
- )
- self.client.logout()
- print(" ---> test_show_star_fill ok")
-
- @override_settings(USE_FAVORITES=True)
- def test_favorite_star_hidden(self) -> None:
- """Test if the favorite star is hidden when the user is disconnected."""
- importlib.reload(context_processors)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the user is disconnected",
- )
- self.assertFalse(
- 'class="btn btn-lg btn-link p-1 star_btn"' in response.content.decode(),
- "Test if the star does not appear when the user is disconnected",
- )
- print(" ---> test_favorite_star_hidden ok")
-
- def test_show_star_404_error(self) -> None:
- """Test if we can't navigate in the `favorite/` route with GET method."""
- importlib.reload(context_processors)
- response = self.client.get(reverse("favorite:add-or-remove"))
- self.assertEqual(
- response.status_code,
- 404,
- """
- Test if status code equal 404 when we try to navigate in
- the `favorite/` route with GET method
- """,
- )
- print(" ---> test_show_star_404_error ok")
-
- @override_settings(USE_FAVORITES=False)
- def test_show_star_when_use_favorites_equal_false(self) -> None:
- """Test if the star isn't present when USE_FAVORITES equal False."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_without_favorite)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when USE_FAVORITES equal False",
- )
- self.assertFalse(
- "bi-star" in response.content.decode(),
- "Test if the star isn't present when USE_FAVORITES equal False",
- )
- self.client.logout()
- print(" ---> test_show_star_when_use_favorites_equal_false ok")
-
-
-class TestFavoriteVideoListTestCase(TestCase):
- """Favorite video list test case."""
-
- fixtures = ["initial_data.json"]
-
- def setUp(self) -> None:
- """Set up required objects for next tests."""
- self.user_with_favorite = User.objects.create(
- username="pod", password="pod1234pod"
- )
- self.user_without_favorite = User.objects.create(
- username="pod2", password="pod1234pod2"
- )
- self.video = Video.objects.create(
- title="Video1",
- owner=self.user_without_favorite,
- video="test.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
- Favorite.objects.create(
- owner=self.user_with_favorite,
- video=self.video,
- rank=1,
- )
- self.url = reverse("favorite:list")
-
- @override_settings(USE_FAVORITES=True)
- def test_favorite_video_list_not_empty(self) -> None:
- """Test if the favorite video list isn't empty when the user has favorites."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_with_favorite)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the favorite video list isn't empty",
- )
- self.assertTrue(
- 'data-countvideos="1"' in response.content.decode(),
- "Test if the favorite video list isn't correctly empty",
- )
- self.client.logout()
- print(" ---> test_favorite_video_list_not_empty ok")
-
- @override_settings(USE_FAVORITES=True)
- def test_favorite_video_list_empty(self) -> None:
- """Test if the favorite video list is empty when the user has favorites."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_without_favorite)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the favorite video list isn't empty",
- )
- self.assertTrue(
- 'data-countvideos="0"' in response.content.decode(),
- "Test if the favorite video list is correctly empty",
- )
- self.client.logout()
- print(" ---> test_favorite_video_list_empty ok")
-
- @override_settings(USE_FAVORITES=True)
- def test_favorite_video_list_link_in_navbar(self) -> None:
- """Test if the favorite video list link is present in the navbar."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_with_favorite)
- response = self.client.get("/")
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 in test_favorite_video_list_link_in_navbar",
- )
- self.assertTrue(
- str(_("My favorite videos")) in response.content.decode(),
- "Test if the favorite video list link is present in the navbar",
- )
- self.client.logout()
- print(" ---> test_favorite_video_list_link_in_navbar ok")
-
- @override_settings(USE_FAVORITES=False)
- def test_favorite_video_list_link_in_navbar_when_use_favorites_is_false(self) -> None:
- """Test if the favorite video list link is present in the navbar."""
- importlib.reload(context_processors)
- self.client.force_login(self.user_with_favorite)
- response = self.client.get("/")
- self.assertEqual(
- response.status_code,
- 200,
- """
- Test if status code equal 200 in
- test_favorite_video_list_link_in_navbar_when_use_favorites_equal_false
- """,
- )
- self.assertFalse(
- str(_("My favorite videos")) in response.content.decode(),
- "Test if the favorite video list link is present in the navbar",
- )
- self.client.logout()
- print(
- """
- ---> test_favorite_video_list_link_in_navbar_when_use_favorites_equal_false
- ok
- """
- )
-
-
-class TestShowStarInfoTestCase(TestCase):
- """Favorite star info test case."""
-
- fixtures = ["initial_data.json"]
-
- def setUp(self) -> None:
- """Set up required objects for next tests."""
- self.user = User.objects.create(username="pod", password="pod1234pod")
- self.video = Video.objects.create(
- title="Video1",
- owner=self.user,
- video="test.mp4",
- is_draft=False,
- type=Type.objects.get(id=1),
- )
- self.url = reverse("video:video", args=[self.video.slug])
-
- @override_settings(USE_FAVORITES=True)
- def test_favorites_count(self) -> None:
- """Test favorite count."""
- importlib.reload(context_processors)
- response = self.client.get(self.url)
- self.assertEqual(
- response.status_code,
- 200,
- "Test if status code equal 200 when the user is disconnected",
- )
- self.assertTrue(
- str(_("Number of favorites")) in response.content.decode(),
- "Test if the counter is in video_info",
- )
- print(" ---> test_favorites_count ok")
diff --git a/pod/favorite/urls.py b/pod/favorite/urls.py
deleted file mode 100644
index d8459b8135..0000000000
--- a/pod/favorite/urls.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.urls import path
-
-from .views import favorite_button_in_video_info, favorite_list
-from .views import favorites_save_reorganisation
-
-app_name = "favorite"
-
-urlpatterns = [
- path("", favorite_button_in_video_info, name="add-or-remove"),
- path("list/", favorite_list, name="list"),
- path(
- "save-reorganisation/", favorites_save_reorganisation, name="save-reorganisation"
- ),
-]
diff --git a/pod/favorite/utils.py b/pod/favorite/utils.py
deleted file mode 100644
index 301b27de8c..0000000000
--- a/pod/favorite/utils.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""Esup-Pod favorite video utilities."""
-from django.contrib.auth.models import User
-from django.db.models import Max
-
-from .models import Favorite
-from pod.video.models import Video
-
-
-def user_has_favorite_video(user: User, video: Video) -> bool:
- """
- Know if user has the video in favorite.
-
- Args:
- user (:class:`django.contrib.auth.models.User`): The user entity
- video (:class:`pod.video.models.Video`): The video entity
-
- Returns:
- bool: True if user has the video in favorite, False otherwise
- """
- return Favorite.objects.filter(owner=user, video=video).exists()
-
-
-def user_add_or_remove_favorite_video(user: User, video: Video):
- """
- Add or remove the video in favorite list of the user.
-
- Args:
- user (:class:`django.contrib.auth.models.User`): The user entity
- video (:class:`pod.video.models.Video`): The video entity
- """
- if user_has_favorite_video(user, video):
- Favorite.objects.filter(owner=user, video=video).delete()
- else:
- Favorite.objects.create(owner=user, video=video, rank=get_next_rank(user))
-
-
-def get_next_rank(user: User) -> int:
- """
- Get the next favorite rank for the user.
-
- Args:
- user (:class:`django.contrib.auth.models.User`): The user entity
-
- Returns:
- int: The next rank
- """
- last_rank = Favorite.objects.filter(owner=user).aggregate(Max("rank"))["rank__max"]
- return last_rank + 1 if last_rank is not None else 1
-
-
-def get_number_favorites(video: Video):
- """Return how much a video has been favorited."""
- return Favorite.objects.filter(video=video).count()
-
-
-def get_all_favorite_videos_for_user(user: User) -> list:
- """
- Get all favorite videos for a specific user.
-
- Args:
- user (:class:`django.contrib.auth.models.User`): The user entity
-
- Returns:
- list(:class:`pod.video.models.Video`): The video list
- """
- favorite_id = Favorite.objects.filter(owner=user).values_list("video_id", flat=True)
- video_list = Video.objects.filter(id__in=favorite_id).extra(
- select={"rank": "favorite_favorite.rank"},
- tables=["favorite_favorite"],
- where=[
- "favorite_favorite.video_id=video_video.id",
- "favorite_favorite.owner_id=%s",
- ],
- params=[user.id],
- )
- return video_list
diff --git a/pod/favorite/views.py b/pod/favorite/views.py
deleted file mode 100644
index d9c08906d2..0000000000
--- a/pod/favorite/views.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""Esup-Pod favorite video Views."""
-from django.contrib.sites.shortcuts import get_current_site
-from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
-from django.db import transaction
-from django.http import Http404, HttpResponseBadRequest
-from django.shortcuts import get_object_or_404, redirect, render
-from django.views.decorators.csrf import csrf_protect
-from django.contrib.auth.decorators import login_required
-from django.utils.translation import ugettext_lazy as _
-
-from pod.favorite.models import Favorite
-from pod.main.utils import is_ajax
-from pod.video.models import Video
-from pod.video.views import CURSUS_CODES, get_owners_has_instances
-from pod.video.utils import sort_videos_list
-
-from .utils import user_add_or_remove_favorite_video
-from .utils import get_all_favorite_videos_for_user
-
-import json
-
-
-@csrf_protect
-def favorite_button_in_video_info(request):
- """Add or remove favorite video when the user click on star button."""
- if request.method == "POST":
- video = get_object_or_404(
- Video, pk=request.POST.get("video"), sites=get_current_site(request)
- )
- if video.is_draft:
- return False
- user_add_or_remove_favorite_video(request.user, video)
- return redirect(request.META["HTTP_REFERER"])
- else:
- raise Http404()
-
-
-@login_required(redirect_field_name="referrer")
-def favorite_list(request):
- """Render the main list of favorite videos."""
- sort_field = request.GET.get("sort", "rank")
- sort_direction = request.GET.get("sort_direction")
- videos_list = sort_videos_list(
- get_all_favorite_videos_for_user(request.user), sort_field, sort_direction
- )
- count_videos = len(videos_list)
-
- page = request.GET.get("page", 1)
- full_path = ""
- if page:
- full_path = (
- request.get_full_path()
- .replace("?page=%s" % page, "")
- .replace("&page=%s" % page, "")
- )
-
- paginator = Paginator(videos_list, 12)
- try:
- videos = paginator.page(page)
- except PageNotAnInteger:
- videos = paginator.page(1)
- except EmptyPage:
- videos = paginator.page(paginator.num_pages)
-
- ownersInstances = get_owners_has_instances(request.GET.getlist("owner"))
-
- if is_ajax(request):
- return render(
- request,
- "favorite/favorite_video_list.html",
- {"videos": videos, "full_path": full_path, "count_videos": count_videos},
- )
-
- return render(
- request,
- "favorite/favorite_videos.html",
- {
- "page_title": _("My favorite videos"),
- "videos": videos,
- "count_videos": count_videos,
- "types": request.GET.getlist("type"),
- "owners": request.GET.getlist("owner"),
- "disciplines": request.GET.getlist("discipline"),
- "tags_slug": request.GET.getlist("tag"),
- "cursus_selected": request.GET.getlist("cursus"),
- "full_path": full_path,
- "ownersInstances": ownersInstances,
- "cursus_list": CURSUS_CODES,
- "sort_field": sort_field,
- "sort_direction": sort_direction,
- },
- )
-
-
-@csrf_protect
-def favorites_save_reorganisation(request):
- """Save reorganization when the user click on save button."""
- if request.method == "POST":
- json_data = request.POST.get("json-data")
- try:
- dict_data = json.loads(json_data)
- except json.JSONDecodeError:
- return HttpResponseBadRequest("JSON au mauvais format")
- with transaction.atomic():
- for videos_tuple in dict_data.values():
- fav_video_1 = Favorite.objects.filter(
- owner_id=request.user.id,
- video_id=Video.objects.only("id").get(slug=videos_tuple[0]).id,
- )
- fav_video_2 = Favorite.objects.filter(
- owner_id=request.user.id,
- video_id=Video.objects.only("id").get(slug=videos_tuple[1]).id,
- )
- video_1_rank = fav_video_1[0].rank
- video_2_rank = fav_video_2[0].rank
- fav_video_1.update(rank=video_2_rank)
- fav_video_2.update(rank=video_1_rank)
-
- return redirect(request.META["HTTP_REFERER"])
- else:
- raise Http404()
diff --git a/pod/locale/fr/LC_MESSAGES/django.mo b/pod/locale/fr/LC_MESSAGES/django.mo
index 2503a5d8e6..e925ca5ecd 100644
Binary files a/pod/locale/fr/LC_MESSAGES/django.mo and b/pod/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/pod/locale/fr/LC_MESSAGES/django.po b/pod/locale/fr/LC_MESSAGES/django.po
index 81c0f70069..3c9a9e4510 100644
--- a/pod/locale/fr/LC_MESSAGES/django.po
+++ b/pod/locale/fr/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Pod\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-07-05 15:50+0200\n"
+"POT-Creation-Date: 2023-08-21 08:32+0000\n"
"PO-Revision-Date: \n"
"Last-Translator: obado \n"
"Language-Team: Pod Team pod@esup-portail.org\n"
@@ -101,8 +101,6 @@ msgid "Picture"
msgstr "Image"
#: pod/authentication/models.py pod/live/models.py pod/meeting/models.py
-#: pod/playlist/models.py
-#: pod/playlist/templates/playlist/playlist_element_list.html
#: pod/podfile/models.py pod/video/admin.py pod/video/models.py
#: pod/video_search/templates/search/search.html
msgid "Owner"
@@ -184,8 +182,7 @@ msgstr "Changer votre image de profil"
#: pod/main/templates/navbar.html pod/main/templates/navbar_collapse.html
#: pod/meeting/templates/meeting/add_or_edit.html
#: pod/meeting/templates/meeting/my_meetings.html
-#: pod/playlist/templates/playlist/playlist_video_list.html
-#: pod/playlist/templates/playlist_player-iframe.html
+#: pod/playlist/templates/playlist/playlist-list-modal.html
#: pod/podfile/templates/podfile/customfilewidget.html
#: pod/podfile/templates/podfile/home_content.html
#: pod/podfile/templates/podfile/userfolder.html
@@ -214,6 +211,9 @@ msgstr "Fermer"
#: pod/meeting/templates/meeting/delete.html
#: pod/meeting/templates/meeting/invite.html
#: pod/meeting/templates/meeting/join.html pod/meeting/views.py
+#: pod/playlist/templates/playlist/add_or_edit.html
+#: pod/playlist/templates/playlist/delete.html
+#: pod/playlist/templates/playlist/protected-playlist-form.html
#: pod/playlist/views.py pod/recorder/templates/recorder/add_recording.html
#: pod/recorder/templates/recorder/record_delete.html pod/recorder/views.py
#: pod/video/templates/channel/channel_edit.html
@@ -242,7 +242,9 @@ msgstr "Une ou plusieurs erreurs ont été trouvées dans le formulaire."
#: pod/meeting/templates/meeting/delete.html
#: pod/meeting/templates/meeting/invite.html
#: pod/meeting/templates/meeting/join.html
-#: pod/playlist/templates/playlist/playlist_video_list.html
+#: pod/playlist/templates/playlist/add_or_edit.html
+#: pod/playlist/templates/playlist/delete.html
+#: pod/playlist/templates/playlist/protected-playlist-form.html
#: pod/recorder/templates/recorder/add_recording.html
#: pod/recorder/templates/recorder/record_delete.html
#: pod/video/templates/channel/channel_edit.html
@@ -358,8 +360,8 @@ msgid "URL of the recording thumbnail of the BBB presentation."
msgstr ""
"URL de la vignette correspondant à l’enregistrement de la présentation BBB."
-#: pod/bbb/models.py pod/favorite/models.py pod/import_video/models.py
-#: pod/meeting/models.py pod/recorder/models.py
+#: pod/bbb/models.py pod/import_video/models.py pod/meeting/models.py
+#: pod/playlist/models.py pod/recorder/models.py
msgid "User"
msgstr "Utilisateur"
@@ -380,6 +382,7 @@ msgid "Meeting"
msgstr "Session"
#: pod/bbb/models.py pod/meeting/apps.py
+#: pod/stats/templates/stats/meeting-stats-view.html
msgid "Meetings"
msgstr "Sessions"
@@ -424,7 +427,7 @@ msgstr "Participants"
#: pod/bbb/models.py pod/import_video/models.py pod/live/forms.py
#: pod/live/models.py pod/meeting/forms.py pod/meeting/models.py
-#: pod/video_search/forms.py
+#: pod/stats/templates/stats/utils/video-stats.html pod/video_search/forms.py
msgid "Start date"
msgstr "Date de début"
@@ -433,7 +436,7 @@ msgid "Start date of the live."
msgstr "Date de début du direct."
#: pod/bbb/models.py pod/live/forms.py pod/live/models.py
-#: pod/video_search/forms.py
+#: pod/stats/templates/stats/utils/video-stats.html pod/video_search/forms.py
msgid "End date"
msgstr "Date de fin"
@@ -744,8 +747,7 @@ msgstr "Désolé, aucune session BigBlueButton en cours n’a été trouvée"
#: pod/bbb/templates/bbb/live_record_list.html
#: pod/bbb/templates/bbb/record_list.html
-#: pod/favorite/templates/favorite/favorite_video_list.html
-#: pod/playlist/templates/playlist/playlist_list.html
+#: pod/playlist/templates/playlist/playlist-videos-list.html
#: pod/recorder/templates/recorder/record_list.html
#: pod/video/templates/videos/video_list.html
msgid "More"
@@ -753,9 +755,8 @@ msgstr "Plus"
#: pod/bbb/templates/bbb/live_record_list.html
#: pod/bbb/templates/bbb/record_list.html
-#: pod/favorite/templates/favorite/favorite_video_list.html
#: pod/live/templates/live/events_list.html pod/main/templates/loader.html
-#: pod/playlist/templates/playlist/playlist_list.html
+#: pod/playlist/templates/playlist/playlist-videos-list.html
#: pod/podfile/templates/podfile/home_content.html
#: pod/podfile/templates/podfile/list_folder_files.html
#: pod/recorder/templates/recorder/record_list.html
@@ -816,7 +817,8 @@ msgid "File to import"
msgstr "Fichier à importer"
#: pod/chapter/models.py pod/completion/models.py pod/enrichment/models.py
-#: pod/video/models.py pod/video_encode_transcript/utils.py
+#: pod/playlist/templates/playlist/playlist_card.html pod/video/models.py
+#: pod/video_encode_transcript/utils.py
msgid "video"
msgstr "vidéo"
@@ -825,7 +827,8 @@ msgstr "vidéo"
msgid "title"
msgstr "titre"
-#: pod/chapter/models.py pod/enrichment/models.py pod/video/models.py
+#: pod/chapter/models.py pod/enrichment/models.py pod/playlist/models.py
+#: pod/video/models.py
msgid "slug"
msgstr "titre court"
@@ -892,7 +895,7 @@ msgstr "Une ou plusieurs erreurs ont été trouvées dans le formulaire :"
#: pod/enrichment/templates/enrichment/group_enrichment.html
#: pod/import_video/templates/import_video/add_or_edit.html
#: pod/meeting/templates/meeting/add_or_edit.html
-#: pod/playlist/templates/playlist.html
+#: pod/playlist/templates/playlist/add_or_edit.html
#: pod/recorder/templates/recorder/add_recording.html
#: pod/video/templates/channel/channel_edit.html
#: pod/video/templates/channel/form_theme.html
@@ -908,7 +911,7 @@ msgstr "Sauvegarder"
#: pod/completion/templates/overlay/form_overlay.html
#: pod/completion/templates/track/form_track.html
#: pod/enrichment/templates/enrichment/form_enrichment.html
-#: pod/favorite/templates/favorite/favorite_videos.html
+#: pod/playlist/templates/playlist/playlist.html
#: pod/video/templates/channel/form_theme.html
#: pod/video/templates/videos/category_modal.html
#: pod/video/templates/videos/video_note_comments_display.html
@@ -934,7 +937,6 @@ msgstr "Liste des chapitres"
#: pod/completion/templates/overlay/list_overlay.html
#: pod/enrichment/templates/enrichment/list_enrichment.html pod/live/models.py
#: pod/main/models.py pod/main/views.py pod/playlist/models.py
-#: pod/playlist/templates/playlist/playlist_element_list.html
#: pod/video/models.py pod/video/templates/channel/list_theme.html
#: pod/video/templates/videos/video_sort_select.html
msgid "Title"
@@ -946,7 +948,6 @@ msgstr "Titre"
#: pod/completion/templates/overlay/list_overlay.html
#: pod/completion/templates/track/list_track.html
#: pod/enrichment/templates/enrichment/list_enrichment.html
-#: pod/playlist/templates/playlist/playlist_element_list.html
#: pod/video/templates/channel/list_theme.html
msgid "Actions"
msgstr "Actions"
@@ -977,7 +978,7 @@ msgstr "Supprimer le chapitre"
#: pod/enrichment/templates/enrichment/list_enrichment.html
#: pod/live/templates/live/event_delete.html
#: pod/meeting/templates/meeting/delete.html
-#: pod/playlist/templates/playlist.html
+#: pod/playlist/templates/playlist/delete.html
#: pod/recorder/templates/recorder/record_delete.html
#: pod/video/templates/channel/list_theme.html
#: pod/video/templates/videos/category_modal.html
@@ -1181,9 +1182,9 @@ msgstr ""
"Il existe déjà un contributeur avec le même nom et le même rôle dans cette "
"liste."
-#: pod/completion/models.py pod/enrichment/models.py pod/favorite/models.py
-#: pod/playlist/models.py pod/recorder/models.py pod/video/models.py
-#: pod/video_encode_transcript/models.py
+#: pod/completion/models.py pod/enrichment/models.py pod/playlist/models.py
+#: pod/recorder/models.py pod/video/models.py
+#: pod/video/templates/videos/video.html pod/video_encode_transcript/models.py
msgid "Video"
msgstr "Vidéo"
@@ -1191,7 +1192,7 @@ msgstr "Vidéo"
#: pod/completion/templates/document/list_document.html
#: pod/enrichment/models.py
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html pod/podfile/models.py
+#: pod/podfile/models.py
msgid "Document"
msgstr "Document"
@@ -1317,8 +1318,7 @@ msgstr "bas à gauche"
msgid "left"
msgstr "gauche"
-#: pod/completion/models.py pod/live/models.py pod/playlist/models.py
-#: pod/video/models.py
+#: pod/completion/models.py pod/live/models.py pod/video/models.py
msgid "Slug"
msgstr "Titre court"
@@ -1339,8 +1339,6 @@ msgid "Content"
msgstr "Contenu"
#: pod/completion/models.py pod/completion/templates/overlay/list_overlay.html
-#: pod/playlist/models.py
-#: pod/playlist/templates/playlist/playlist_element_list.html
msgid "Position"
msgstr "Position"
@@ -1484,7 +1482,6 @@ msgid "Remove the subtitle or caption files"
msgstr "Supprimer les fichiers de sous-titres ou de légendes"
#: pod/completion/templates/track/list_track.html
-#: pod/playlist/templates/playlist/playlist_element_list.html
msgid "Remove"
msgstr "Retirer"
@@ -1716,8 +1713,8 @@ msgstr ""
#: pod/completion/templates/video_completion.html
msgid "Subtitles and/or caption(s) files must be in \".vtt\" format."
msgstr ""
-"Les fichiers de sous-titres et/ou de légendes doivent être au format \"."
-"vtt\"."
+"Les fichiers de sous-titres et/ou de légendes doivent être au format \".vtt"
+"\"."
#: pod/completion/templates/video_completion.html
msgid "You can use"
@@ -1822,6 +1819,7 @@ msgid "Total video duration"
msgstr "Durée totale de la vidéo"
#: pod/cut/templates/video_cut.html
+#: pod/playlist/templates/playlist/filter_aside.html
#: pod/video/templates/videos/filter_aside.html
msgid "Reset"
msgstr "Réinitialiser"
@@ -1950,7 +1948,7 @@ msgstr "Type"
#: pod/enrichment/models.py
#: pod/enrichment/templates/enrichment/video_enrichment.html pod/main/models.py
-#: pod/playlist/templates/playlist_player.html pod/podfile/models.py
+#: pod/podfile/models.py
msgid "Image"
msgstr "Image"
@@ -1960,7 +1958,6 @@ msgstr "Intégrer un document (PDF, texte, html)"
#: pod/enrichment/models.py
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "Richtext"
msgstr "Texte riche"
@@ -2074,8 +2071,8 @@ msgid ""
msgstr ""
"Les champs \"Début\" et \"Fin\" doivent contenir des valeurs en secondes. "
"Lancez la lecture de la vidéo, mettez sur pause et cliquez sur \"Récupérer "
-"le temps depuis le lecteur\" pour renseigner automatiquement le champ "
-"\"Début\". Vous pouvez le faire également pour remplir le champ \"Fin\"."
+"le temps depuis le lecteur\" pour renseigner automatiquement le champ \"Début"
+"\". Vous pouvez le faire également pour remplir le champ \"Fin\"."
#: pod/enrichment/templates/enrichment/edit_enrichment.html
msgid "You cannot overlap enrichments."
@@ -2107,8 +2104,6 @@ msgstr "Supprimer l’enrichissement"
#: pod/enrichment/templates/enrichment/video_enrichment-iframe.html
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player-iframe.html
-#: pod/playlist/templates/playlist_player.html
msgid "Enriched"
msgstr "Enrichi"
@@ -2118,29 +2113,24 @@ msgid "Added by"
msgstr "Ajouté par"
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "Informations"
msgstr "Informations"
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "To help you, the different types of enrichments have specific colors:"
msgstr ""
"Pour vous aider, les différents types d’enrichissements ont des couleurs "
"spécifiques :"
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "Weblink"
msgstr "Lien web"
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "Embed"
msgstr "Intégrer"
#: pod/enrichment/templates/enrichment/video_enrichment.html
-#: pod/playlist/templates/playlist_player.html
msgid "They are visible on the video playback bar."
msgstr "Ils sont visibles sur la barre de lecture de la vidéo."
@@ -2148,85 +2138,6 @@ msgstr "Ils sont visibles sur la barre de lecture de la vidéo."
msgid "You cannot enrich this video."
msgstr "Vous ne pouvez pas enrichir cette vidéo."
-#: pod/favorite/apps.py pod/favorite/models.py
-msgid "Favorite videos"
-msgstr "Vidéos favorites"
-
-#: pod/favorite/models.py pod/recorder/models.py pod/video/models.py
-#: pod/video/templates/videos/video_sort_select.html
-msgid "Date added"
-msgstr "Date d’ajout"
-
-#: pod/favorite/models.py pod/video/templates/videos/video_sort_select.html
-msgid "Rank"
-msgstr "Rang"
-
-#: pod/favorite/models.py
-msgid "Favorite video"
-msgstr "Vidéo favorite"
-
-#: pod/favorite/templates/favorite/favorite_video_list.html
-#: pod/video/templates/videos/video_list.html
-msgid "Sorry, no video found."
-msgstr "Désolé, aucune vidéo trouvée."
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-#: pod/video/templates/channel/channel.html
-#: pod/video/templates/videos/my_videos.html
-#: pod/video/templates/videos/videos.html
-#: pod/video_search/templates/search/search.html
-#, python-format
-msgid "%(counter)s video found"
-msgid_plural "%(counter)s videos found"
-msgstr[0] "%(counter)s vidéo trouvée"
-msgstr[1] "%(counter)s vidéos trouvées"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-#: pod/favorite/tests/test_views.py pod/favorite/views.py
-#: pod/main/templates/navbar.html
-msgid "My favorite videos"
-msgstr "Mes vidéos favorites"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Reorganize your favorite videos"
-msgstr "Réorganiser vos vidéos favorites"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Reorganize"
-msgstr "Réorganiser"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Sort by descending rank before you can rearrange"
-msgstr "Trier par ordre décroissant avant de pouvoir réorganiser"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Sort by descending rank"
-msgstr "Trier par ordre décroissant"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Help - Drag & Drop"
-msgstr "Aide - Glisser & Déposer"
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Select the video to move by clicking and holding."
-msgstr ""
-"Sélectionnez la vidéo que vous souhaitez en cliquant et maintenant le bouton "
-"de la souris."
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "While holding down the video, drag it to the desired location."
-msgstr ""
-"Tout en maintenant le bouton de la souris enfoncé, faites-la glisser à "
-"l’endroit que vous souhaitez."
-
-#: pod/favorite/templates/favorite/favorite_videos.html
-msgid "Drop the video on another to swap their position."
-msgstr "Déposez la vidéo sur une autre pour échanger leur position."
-
-#: pod/favorite/tests/test_views.py pod/video/templates/videos/video-info.html
-msgid "Number of favorites"
-msgstr "Nombre de favoris"
-
#: pod/import_video/apps.py
msgid "Import External Video"
msgstr "Importer une vidéo externe"
@@ -2448,6 +2359,7 @@ msgstr "Conditions d’utilisation de YouTube"
#: pod/live/templates/live/filter_aside.html
#: pod/meeting/templates/meeting/filter_aside.html
#: pod/meeting/templates/meeting/filter_aside_recording.html
+#: pod/playlist/templates/playlist/filter_aside.html
#: pod/video/templates/videos/filter_aside.html
msgid "Filters"
msgstr "Filtres"
@@ -2501,6 +2413,7 @@ msgstr ""
#: pod/import_video/templates/import_video/list.html pod/main/forms.py
#: pod/meeting/forms.py pod/meeting/templates/meeting/internal_recordings.html
+#: pod/playlist/templates/playlist/playlist_sort_select.html
#: pod/podfile/models.py pod/podfile/templates/podfile/home_content.html
#: pod/video_encode_transcript/models.py
msgid "Name"
@@ -2639,8 +2552,8 @@ msgid ""
"This video was uploaded to Pod; its origin is %(type)s : %(url)s"
msgstr ""
-"Cette vidéo a été téléversée sur Pod; son origine est %(type)s : %(url)s"
+"Cette vidéo a été téléversée sur Pod; son origine est %(type)s : %(url)s"
#: pod/import_video/views.py pod/meeting/views.py
msgid "Try changing the record type or address for this recording."
@@ -2651,8 +2564,8 @@ msgstr ""
#: pod/import_video/views.py
#, python-format
msgid ""
-"This video '%(name)s' was uploaded to Pod; its origin is Youtube : %(url)s"
+"This video '%(name)s' was uploaded to Pod; its origin is Youtube : %(url)s"
msgstr ""
"Cette vidéo « %(name)s » a été téléversée sur Pod; son origine est Youtube : "
"%(url)s"
@@ -2810,7 +2723,8 @@ msgstr "Erreur de planification."
msgid "An event is already planned at these dates"
msgstr "Un évenement est déjà planifié à cette date"
-#: pod/live/forms.py pod/meeting/forms.py pod/video/forms.py
+#: pod/live/forms.py pod/meeting/forms.py pod/playlist/forms.py
+#: pod/playlist/models.py pod/video/forms.py
msgid "Password"
msgstr "Mot de passe"
@@ -2826,8 +2740,8 @@ msgstr "Bâtiment"
msgid "Broadcaster device"
msgstr "Matériel de captation"
-#: pod/live/forms.py pod/meeting/forms.py pod/recorder/forms.py
-#: pod/video/forms.py
+#: pod/live/forms.py pod/meeting/forms.py pod/playlist/forms.py
+#: pod/recorder/forms.py pod/video/forms.py
msgid "I agree"
msgstr "J’accepte"
@@ -2965,7 +2879,9 @@ msgstr ""
"informations nécessaires, et mettre en forme le résultat en utilisant la "
"barre d’outils."
-#: pod/live/models.py pod/meeting/models.py pod/video/forms.py
+#: pod/live/models.py pod/meeting/models.py pod/playlist/models.py
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+#: pod/stats/templates/stats/playlist-stats-view.html pod/video/forms.py
#: pod/video/models.py
msgid "Additional owners"
msgstr "Propriétaires additionnels"
@@ -3169,8 +3085,7 @@ msgstr "Prochains évènements"
msgid "Manage broadcaster"
msgstr "Gérer le diffuseur"
-#: pod/live/templates/live/direct.html pod/playlist/templates/playlist.html
-#: pod/playlist/templates/playlist/playlist_list.html
+#: pod/live/templates/live/direct.html
#: pod/video/templates/videos/video_edit.html
msgid "Edit"
msgstr "Éditer"
@@ -3244,10 +3159,11 @@ msgstr "Intégrer/Partager"
#: pod/live/templates/live/event-form.html
msgid "This event is protected by password, please fill in and click send."
msgstr ""
-"Cet évènement est protégée par un mot de passe, veuillez remplir le "
+"Cet évènement est protégé par un mot de passe, veuillez remplir le "
"formulaire et cliquez sur envoyer."
#: pod/live/templates/live/event-form.html
+#: pod/playlist/templates/playlist/protected-playlist-form.html
#: pod/video/templates/videos/video-form.html
msgid "Password required"
msgstr "Mot de passe requis"
@@ -3258,6 +3174,7 @@ msgstr "Mot de passe requis"
#: pod/live/templates/live/filter_aside.html pod/main/templates/contact_us.html
#: pod/meeting/templates/meeting/invite.html
#: pod/meeting/templates/meeting/join.html
+#: pod/playlist/templates/playlist/protected-playlist-form.html
#: pod/video/templates/videos/video-form.html
#: pod/video_search/templates/search/search.html
msgid "Send"
@@ -3456,9 +3373,7 @@ msgstr "Éditer l’évènement"
msgid "Delete the event"
msgstr "Supprimer l’évènement"
-#: pod/live/templates/live/event_card.html
-#: pod/playlist/templates/playlist/playlist_video_card.html
-#: pod/video/templates/videos/card.html
+#: pod/live/templates/live/event_card.html pod/video/templates/videos/card.html
msgid "This content is in draft."
msgstr "Ce contenu est en mode brouillon."
@@ -3499,6 +3414,7 @@ msgstr "Pour supprimer l’évènement, veuillez cocher et cliquer sur envoyer."
#: pod/live/templates/live/event_delete.html
#: pod/meeting/templates/meeting/delete.html
+#: pod/playlist/templates/playlist/delete.html
#: pod/recorder/templates/recorder/record_delete.html
#: pod/video/templates/videos/video_delete.html
msgid "Agree required"
@@ -3679,8 +3595,7 @@ msgstr "Vous ne pouvez pas accèder à cette page."
msgid "You cannot watch this event."
msgstr "Vous ne pouvez pas accèder à cet évènement en direct."
-#: pod/live/views.py pod/playlist/templates/playlist/playlist_video_list.html
-#: pod/video/views.py
+#: pod/live/views.py pod/playlist/views.py pod/video/views.py
msgid "The password is incorrect."
msgstr "Le mot de passe est incorrect."
@@ -4496,7 +4411,7 @@ msgstr "Afficher/masquer le menu latéral"
msgid "Breadcrumb"
msgstr "Fil d’Ariane"
-#: pod/main/templates/base.html pod/playlist/templates/playlist_player.html
+#: pod/main/templates/base.html
msgid "Home"
msgstr "Accueil"
@@ -4557,6 +4472,10 @@ msgstr "Version"
msgid "videos availables"
msgstr "vidéos disponibles"
+#: pod/main/templates/footer.html
+msgid "See more statistics"
+msgstr "Voir plus de statistiques"
+
#: pod/main/templates/mail/mail.html pod/main/templates/mail/mail_sender.html
msgid "Hello"
msgstr "Bonjour"
@@ -4606,11 +4525,16 @@ msgstr "Contactez nous"
msgid "Toggle navigation"
msgstr "Basculer le menu"
-#: pod/main/templates/navbar.html pod/recorder/models.py pod/video/forms.py
+#: pod/main/templates/navbar.html pod/recorder/models.py
+#: pod/stats/templates/stats/channel-stats-view.html pod/video/forms.py
#: pod/video/models.py
msgid "Channels"
msgstr "Chaînes"
+#: pod/main/templates/navbar.html
+msgid "Promoted playlists"
+msgstr "Listes de lecture promues"
+
#: pod/main/templates/navbar.html
msgid "Some features are unavailable"
msgstr "Certaines fonctionnalités sont indisponibles"
@@ -4661,6 +4585,14 @@ msgstr "Police ‘Open Dyslexic’"
msgid "Add your picture"
msgstr "Ajouter votre image de profil"
+#: pod/main/templates/navbar.html
+msgid "My playlists"
+msgstr "Mes listes de lecture"
+
+#: pod/main/templates/navbar.html pod/playlist/tests/test_views.py
+msgid "My favorite videos"
+msgstr "Mes vidéos favorites"
+
#: pod/main/templates/navbar.html
msgid "Video Record"
msgstr "Enregistreur"
@@ -4675,11 +4607,6 @@ msgstr "Importer une vidéo externe"
msgid "My channels"
msgstr "Mes chaînes"
-#: pod/main/templates/navbar.html pod/playlist/templates/my_playlists.html
-#: pod/playlist/templates/playlist.html pod/playlist/views.py
-msgid "My playlists"
-msgstr "Mes listes de lecture"
-
#: pod/main/templates/navbar.html pod/podfile/templates/podfile/home.html
#: pod/podfile/views.py
msgid "My files"
@@ -4695,6 +4622,29 @@ msgstr "Revendiquer un enregistrement"
msgid "Log out"
msgstr "Déconnexion"
+#: pod/main/templates/navbar.html
+msgid "Your statistics"
+msgstr "Vos statistiques"
+
+#: pod/main/templates/navbar.html
+#: pod/stats/templates/stats/channel-stats-view.html
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/playlist-stats-view.html
+#: pod/stats/templates/stats/user-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Number of videos"
+msgstr "Nombre de vidéos"
+
+#: pod/main/templates/navbar.html
+#: pod/stats/templates/stats/playlist-stats-view.html
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Number of playlists"
+msgstr "Nombre de listes de lecture"
+
+#: pod/main/templates/navbar.html
+msgid "See more"
+msgstr "En savoir plus"
+
#: pod/main/templates/navbar_collapse.html
#, python-format
msgid "%(counter)s Channel"
@@ -4710,8 +4660,6 @@ msgstr[0] "%(counter)s Thème"
msgstr[1] "%(counter)s Thèmes"
#: pod/main/templates/navbar_collapse.html
-#: pod/playlist/templates/playlist/playlist_list.html
-#: pod/playlist/templates/playlist/playlist_video_list.html
#: pod/video/templates/channel/channel.html
#, python-format
msgid "%(counter)s video"
@@ -4755,9 +4703,7 @@ msgstr "rejoindre"
msgid "Recurring"
msgstr "Récurrence"
-#: pod/meeting/forms.py
-#: pod/playlist/templates/playlist/playlist_element_list.html
-#: pod/video/feeds.py pod/video/models.py
+#: pod/meeting/forms.py pod/video/feeds.py pod/video/models.py
#: pod/video/templates/videos/video_sort_select.html
msgid "Duration"
msgstr "Durée"
@@ -4811,7 +4757,7 @@ msgstr "Récurrence"
msgid "Record session"
msgstr "Enregistrement"
-#: pod/meeting/forms.py pod/video/forms.py
+#: pod/meeting/forms.py pod/playlist/forms.py pod/video/forms.py
msgid "Owner of the video cannot be an additional owner too"
msgstr ""
"Le propriétaire de la video ne peut pas être propriétaire additionnel en "
@@ -5280,7 +5226,7 @@ msgid "Delete the meeting"
msgstr "Supprimer la réunion"
#: pod/meeting/templates/meeting/meeting_card.html
-msgid "Acces to this meeting is restricted"
+msgid "Access to this meeting is restricted"
msgstr "L’accès à cette réunion est restreint"
#: pod/meeting/templates/meeting/meeting_card.html
@@ -5444,16 +5390,16 @@ msgid ""
msgstr ""
"\n"
"
Bonjour,\n"
-"
%(owner)s vous invite à une réunion récurrente "
-"%(meeting_title)s.
\n"
+"
%(owner)s vous invite à une réunion récurrente "
+"%(meeting_title)s.
\n"
"
Date de début : %(start_date_time)s
\n"
"
Récurrent jusqu’à la date : %(end_date)s
\n"
"
La réunion se tiendra tou(te)s les %(frequency)s %(recurrence)s "
"p>\n"
"
Voici le lien pour rejoindre la réunion :\n"
" %(join_link)s
\n"
-"
Vous avez besoin de ce mot de passe pour entrer : "
-"%(password)s
\n"
+"
Vous avez besoin de ce mot de passe pour entrer : "
+"%(password)s
\n"
"
Cordialement
\n"
" "
@@ -5479,8 +5425,8 @@ msgstr ""
"
Date de fin : %(end_date)s
\n"
"
Voici le lien pour rejoindre la réunion :\n"
" %(join_link)s
\n"
-"
Vous avez besoin de ce mot de passe pour entrer : "
-"%(password)s
\n"
+"
Vous avez besoin de ce mot de passe pour entrer : "
+"%(password)s
\n"
"
Cordialement
\n"
" "
@@ -5502,215 +5448,425 @@ msgid "Impossible to create the internal recording"
msgstr "Impossible de créer l’enregistrement"
#: pod/playlist/apps.py pod/playlist/models.py
+#: pod/playlist/templates/playlist/add_or_edit.html
+#: pod/playlist/templates/playlist/delete.html
+#: pod/playlist/templates/playlist/playlist.html
+#: pod/playlist/templates/playlist/playlists.html
+#: pod/playlist/templates/playlist/protected-playlist-form.html
+#: pod/playlist/views.py pod/stats/templates/stats/playlist-stats-view.html
msgid "Playlists"
msgstr "Listes de lecture"
-#: pod/playlist/models.py
-msgid "Short description of the playlist."
-msgstr "Courte description de la liste de lecture."
+#: pod/playlist/apps.py pod/playlist/signals.py
+msgid "Your favorites videos."
+msgstr "Vos vidéos favorites."
-#: pod/playlist/models.py pod/video/models.py
-msgid "Visible"
-msgstr "Visible"
+#: pod/playlist/forms.py
+msgid "General informations"
+msgstr "Informations générales"
+
+#: pod/playlist/forms.py
+msgid "Security informations"
+msgstr "Informations de sécurité"
+
+#: pod/playlist/forms.py pod/playlist/models.py
+msgid "Please choose a password if this playlist is password-protected."
+msgstr ""
+"Veuillez choisir un mot de passe si cette playlist est protégée par un mot "
+"de passe."
+
+#: pod/playlist/forms.py pod/playlist/models.py
+msgid "Promoted"
+msgstr "Promue"
+
+#: pod/playlist/forms.py pod/playlist/models.py
+msgid ""
+"Selecting this setting causes your playlist to be promoted on the page "
+"listing promoted public playlists. However, if this setting is deactivated, "
+"your playlist will still be accessible to everyone. For general use, we "
+"recommend that you leave this setting disabled."
+msgstr ""
+"En sélectionnant ce paramètre, votre liste de lecture sera mise en avant "
+"dans la page listant les listes de lecture publiques promues. En revanche, "
+"si ce paramètre est désactivé, votre liste de lecture restera tout de même "
+"accessible par tous. Pour une utilisation générale, il est conseillé de "
+"laisser ce paramètre désactivé."
+
+#: pod/playlist/forms.py
+#, fuzzy, python-brace-format
+#| msgid "You cannot create a playlist named \"{FAVORITE_PLAYLIST_NAME}\""
+msgid "You cannot create a playlist named \"{FAVORITE_PLAYLIST_NAME}\""
+msgstr "Vous ne pouvez créer une liste de lecture nommée \"Favoris\""
+
+#: pod/playlist/forms.py
+msgid "Remove playlist cannot be undone"
+msgstr "La suppression de réunion est définitive"
+
+#: pod/playlist/models.py
+#: pod/playlist/templates/playlist/playlist_visibility_icon.html
+#: pod/playlist/utils.py pod/video/models.py
+#: pod/video/templates/videos/video_note_comments_display.html
+#: pod/video/templates/videos/video_note_display.html
+msgid "Public"
+msgstr "Public"
#: pod/playlist/models.py
-msgid "If checked, the playlist can be visible to users other than the owner."
-msgstr "Si coché, cette liste de lecture sera visible aux autres utilisateurs."
+#: pod/playlist/templates/playlist/playlist_visibility_icon.html
+#: pod/playlist/utils.py
+msgid "Password-protected"
+msgstr "Protégé par un mot de passe"
#: pod/playlist/models.py
-#: pod/playlist/templates/playlist/playlist_video_list.html
-#: pod/playlist/templates/playlist_player.html
+#: pod/playlist/templates/playlist/playlist_visibility_icon.html
+#: pod/playlist/utils.py
+msgid "Private"
+msgstr "Privé"
+
+#: pod/playlist/models.py pod/playlist/tests/test_models.py
+#: pod/playlist/views.py pod/video/templates/videos/video.html
msgid "Playlist"
msgstr "Liste de lecture"
#: pod/playlist/models.py
-msgid "Position of the video in a playlist."
-msgstr "Position de la vidéo dans la liste de lecture."
+msgid "Please choose a name between 1 and 250 characters."
+msgstr "Veuillez entrer un nom entre 2 et 200 caractères."
+
+#: pod/playlist/models.py
+msgid "Please choose a description. This description is empty by default."
+msgstr "Veuillez choisir une description. Celle-ci est vide par défaut."
+
+#: pod/playlist/models.py pod/playlist/templates/playlist/filter_aside.html
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+#: pod/stats/templates/stats/playlist-stats-view.html
+msgid "Right of access"
+msgstr "Droit d'accès"
+
+#: pod/playlist/models.py
+msgid ""
+"\n"
+" Please chosse a right of access among 'public', 'password-"
+"protected', 'private'.\n"
+" "
+msgstr ""
+"\n"
+" Veuillez choisir un droit d'accès entre 'public', 'protégée par "
+"un mot de passe', 'privée'.\n"
+" "
+
+#: pod/playlist/models.py pod/video/templates/videos/video-info.html
+msgid "Autoplay"
+msgstr "Lecture automatique"
+
+#: pod/playlist/models.py
+msgid "Please choose if this playlist is an autoplay playlist or not."
+msgstr "Veuillez choisir si cette playlist est en lecture automatique ou non."
+
+#: pod/playlist/models.py
+msgid "Editable"
+msgstr "Éditable"
+
+#: pod/playlist/models.py
+msgid "Please choose if this playlist is editable or not."
+msgstr "Veuillez choisir si cette playlist est éditable ou non."
+
+#: pod/playlist/models.py
+msgid "You can add additional owners to the playlist."
+msgstr "Vous pouvez ajouter des propriétaires additionnels à la playlist."
+
+#: pod/playlist/models.py
+msgid "Date created"
+msgstr "Date de création"
+
+#: pod/playlist/models.py
+msgid "Date updated"
+msgstr "Date de mise à jour"
+
+#: pod/playlist/models.py pod/recorder/models.py pod/video/models.py
+#: pod/video/templates/videos/video_sort_select.html
+msgid "Date added"
+msgstr "Date d’ajout"
+
+#: pod/playlist/models.py pod/video/templates/videos/video_sort_select.html
+msgid "Rank"
+msgstr "Rang"
#: pod/playlist/models.py
-msgid "Playlist element"
+msgid "Playlist content"
msgstr "Élément de liste de lecture"
#: pod/playlist/models.py
-msgid "Playlist elements"
+msgid "Playlist contents"
msgstr "Éléments de liste de lecture"
-#: pod/playlist/models.py pod/playlist/views.py
-msgid "A video in draft mode cannot be added to a playlist."
-msgstr ""
-"Une vidéo en mode brouillon ne peut être ajoutée à une liste de lecture."
+#: pod/playlist/templates/playlist/add_or_edit.html
+#: pod/playlist/templates/playlist/playlist_link.html
+#: pod/playlist/tests/test_views.py pod/playlist/views.py
+msgid "Edit the playlist"
+msgstr "Éditer la liste de lecture"
-#: pod/playlist/models.py pod/playlist/views.py
-msgid "A video with a password cannot be added to a playlist."
-msgstr ""
-"Une vidéo avec mot de passe ne peut être ajoutée à une liste de lecture."
+#: pod/playlist/templates/playlist/add_or_edit.html
+#: pod/playlist/templates/playlist/playlists.html
+#: pod/playlist/tests/test_views.py pod/playlist/views.py
+msgid "Add a playlist"
+msgstr "Ajouter une liste de lecture"
+
+#: pod/playlist/templates/playlist/button_start_playlist.html
+msgid "Start the playlist"
+msgstr "Démarrer la liste de lecture"
-#: pod/playlist/templates/my_playlists.html
+#: pod/playlist/templates/playlist/delete.html
#, python-format
-msgid "%(counter)s playlist found"
-msgid_plural "%(counter)s playlists found"
-msgstr[0] "%(counter)s liste de lecture trouvée"
-msgstr[1] "%(counter)s listes de lecture trouvées"
+msgid "Delete the playlist %(name)s"
+msgstr "Supprimer la liste de lecture %(name)s"
-#: pod/playlist/templates/my_playlists.html
-msgid "No playlist found"
-msgstr "Aucune liste de lecture trouvée"
+#: pod/playlist/templates/playlist/delete.html
+msgid "Back to my playlists"
+msgstr "Retour à mes listes de lecture"
-#: pod/playlist/templates/my_playlists.html
-msgid ""
-"You have not created any playlists yet, please use the \"Add a new "
-"playlist\" button to add one."
+#: pod/playlist/templates/playlist/delete.html
+msgid "To delete the playlist, please check the box in and click delete."
msgstr ""
-"Vous n’avez pas encore créé de liste de lecture, veuillez cliquer sur le "
-"bouton « Ajouter une nouvelle liste de lecture » pour en ajouter une."
+"Pour supprimer la liste de lecture, veuillez cocher la case et cliquer sur "
+"supprimer."
-#: pod/playlist/templates/my_playlists.html
-#: pod/playlist/templates/playlist.html
-#: pod/playlist/templates/playlist/playlist_list.html
-#: pod/playlist/tests/test_views.py
-msgid "Add a new playlist"
-msgstr "Ajouter une nouvelle liste de lecture"
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See my private playlists"
+msgstr "Voir mes listes de lecture privées"
-#: pod/playlist/templates/my_playlists.html
-msgid ""
-"This is the page of your playlists. Here you can create, edit or delete them."
-msgstr ""
-"C’est la page de vos listes de lecture. Ici vous pouvez les créer, les "
-"éditer ou les supprimer."
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See my password-protected playlists"
+msgstr "Voir mes listes de lecture protégées par un mot de passe"
-#: pod/playlist/templates/my_playlists.html
-msgid ""
-"A playlist with the eye symbol means that this playlist is visible to all "
-"users when they view this page."
-msgstr ""
-"Une liste de lecture avec un symbole œil signifie que cette liste est "
-"visible à tous les utilisateurs qui visiteront cette page."
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See my public playlists"
+msgstr "Voir mes listes de lecture publiques"
-#: pod/playlist/templates/playlist.html pod/playlist/tests/test_views.py
-msgid "Editing the playlist"
-msgstr "Éditer la liste de lecture"
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See my playlists"
+msgstr "Voir mes listes de lecture"
-#: pod/playlist/templates/playlist.html
-msgid "This playlist has no videos yet."
-msgstr "Cette liste de lecture ne dispose pas encore de vidéos."
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See playlists I can contribute"
+msgstr "Voir les listes de lecture additionnelles"
-#: pod/playlist/templates/playlist.html
-msgid "Back to my playlists"
-msgstr "Retour à mes listes de lecture"
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See all public playlists"
+msgstr "Voir toutes les listes de lecture publiques"
-#: pod/playlist/templates/playlist/player_controls.html
-msgid "Repeat the playlist"
-msgstr "Répéter la liste de lecture"
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See all promoted playlists"
+msgstr "Voir toutes les listes de lecture promues"
-#: pod/playlist/templates/playlist/player_controls.html
-msgid "Switch to next video automatically"
-msgstr "Passer automatiquement à la prochaine vidéo"
+#: pod/playlist/templates/playlist/filter_aside.html
+msgid "See all playlists"
+msgstr "Voir toutes les listes de lecture"
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "List of videos"
-msgstr "Liste des vidéos"
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+msgid "Informations about the playlist"
+msgstr "Informations de la liste de lecture."
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "Thumbnail"
-msgstr "Vignette"
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+msgid "Last updated on"
+msgstr "Mis à jour le"
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "Video thumbnail"
-msgstr "Vignette de la vidéo"
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+msgid "at"
+msgstr "à"
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "Move up"
-msgstr "Déplacer vers le haut"
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+msgid "Playlist owner"
+msgstr "Propriétaire de la liste de lecture"
+
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+msgid "Show statistics for the playlist"
+msgstr "Afficher les statistiques de la liste de lecture"
+
+#: pod/playlist/templates/playlist/playlist-informations-card.html
+#: pod/stats/apps.py pod/stats/templates/stats/channel-stats-view.html
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/meeting-stats-view.html
+#: pod/stats/templates/stats/playlist-stats-view.html
+#: pod/stats/templates/stats/user-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Statistics"
+msgstr "Statistiques"
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "Move down"
-msgstr "Déplacer vers le bas"
+#: pod/playlist/templates/playlist/playlist-list-modal.html
+msgid "Your playlists"
+msgstr "Vos listes de lecture"
-#: pod/playlist/templates/playlist/playlist_element_list.html
-msgid "Save position"
-msgstr "Enregistrer les positions"
+#: pod/playlist/templates/playlist/playlist-list-modal.html
+msgid "Add the video in a new playlist"
+msgstr "Ajouter une vidéo à une nouvelle liste de lecture"
-#: pod/playlist/templates/playlist/playlist_list.html
-msgid "This playlist is visible to all users."
-msgstr "Cette liste de lecture est visible à d’autres utilisateurs."
+#: pod/playlist/templates/playlist/playlist-management-card.html
+msgid "Manage playlist"
+msgstr "Gérer la liste de lecture"
-#: pod/playlist/templates/playlist/playlist_list.html
-msgid "This playlist is only visible by you."
-msgstr "Vous seul pouvez voir cette liste de lecture."
+#: pod/playlist/templates/playlist/playlist-videos-list.html
+#: pod/video/templates/videos/video_list.html
+msgid "Sorry, no video found."
+msgstr "Désolé, aucune vidéo trouvée."
-#: pod/playlist/templates/playlist/playlist_list.html
-msgid "Launch"
-msgstr "Lancer"
+#: pod/playlist/templates/playlist/playlist.html
+#: pod/playlist/templates/playlist/playlist_content.html
+#: pod/video/templates/channel/channel.html
+#: pod/video/templates/videos/my_videos.html
+#: pod/video/templates/videos/videos.html
+#: pod/video_search/templates/search/search.html
+#, python-format
+msgid "%(counter)s video found"
+msgid_plural "%(counter)s videos found"
+msgstr[0] "%(counter)s vidéo trouvée"
+msgstr[1] "%(counter)s vidéos trouvées"
-#: pod/playlist/templates/playlist/playlist_list.html
-msgid "Sorry, no playlist found"
-msgstr "Désolé, aucune liste de lecture trouvée"
+#: pod/playlist/templates/playlist/playlist.html
+#: pod/playlist/templates/playlist/playlist_content.html
+msgid "Playlist videos"
+msgstr "Vidéos de la playlist"
-#: pod/playlist/templates/playlist/playlist_video_card.html
-#: pod/video/templates/videos/card.html
-msgid "This content is password protected."
-msgstr "Ce contenu est protégé par un mot de passe."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "Reorganize your favorite videos"
+msgstr "Réorganiser vos vidéos favorites"
-#: pod/playlist/templates/playlist/playlist_video_card.html
-#: pod/video/templates/videos/card.html
-msgid "This content is chaptered."
-msgstr "Ce contenu est chapitré."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "Reorganize"
+msgstr "Réorganiser"
-#: pod/playlist/templates/playlist/playlist_video_card.html
-#: pod/video/templates/videos/card.html
-#: pod/video/templates/videos/category_modal_card.html
-msgid "Video content."
-msgstr "Contenu vidéo."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "Help - Drag & Drop"
+msgstr "Aide - Glisser & Déposer"
-#: pod/playlist/templates/playlist/playlist_video_card.html
-#: pod/video/templates/videos/card.html
-#: pod/video/templates/videos/category_modal_card.html
-msgid "Audio content."
-msgstr "Contenu audio."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "Select the video to move by clicking and holding."
+msgstr ""
+"Sélectionnez la vidéo que vous souhaitez en cliquant et maintenant le bouton "
+"de la souris."
-#: pod/playlist/views.py
-msgid "You cannot edit this playlist."
-msgstr "Vous ne pouvez éditer cette liste de lecture."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "While holding down the video, drag it to the desired location."
+msgstr ""
+"Tout en maintenant le bouton de la souris enfoncé, faites-la glisser à "
+"l’endroit que vous souhaitez."
-#: pod/playlist/views.py
-msgid "You don't have access to this playlist."
-msgstr "Vous n’avez pas les droits d’accès à cette liste de lecture."
+#: pod/playlist/templates/playlist/playlist.html
+msgid "Drop the video on another to swap their position."
+msgstr "Déposez la vidéo sur une autre pour échanger leur position."
-#: pod/playlist/views.py
+#: pod/playlist/templates/playlist/playlist_card.html pod/video/models.py
+msgid "videos"
+msgstr "vidéos"
+
+#: pod/playlist/templates/playlist/playlist_card.html
+#: pod/playlist/templates/playlist/playlist_player.html
+msgid "Playlist image"
+msgstr "Image de la playlist"
+
+#: pod/playlist/templates/playlist/playlist_link.html
+msgid "Remove the playlist"
+msgstr "Supprimer la liste de lecture"
+
+#: pod/playlist/templates/playlist/playlist_player.html
+msgid "Playlist:"
+msgstr "Liste de lecture :"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+#: pod/video/templates/videos/video_sort_select.html
+msgid "Sort"
+msgstr "Tri"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+#: pod/video/templates/videos/video_sort_select.html
+msgid "Ascending sort"
+msgstr "Tri ascendant"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+#: pod/video/templates/videos/video_sort_select.html
+msgid "Descending sort"
+msgstr "Tri descendant"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+#: pod/video/templates/videos/video_sort_select.html
+msgid "Sort Direction"
+msgstr "Direction de tri"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+msgid "Modification date"
+msgstr "Date de modification"
+
+#: pod/playlist/templates/playlist/playlist_sort_select.html
+msgid "Creation date"
+msgstr "Date de création"
+
+#: pod/playlist/templates/playlist/playlist_visibility_icon.html
+msgid "Public and promoted"
+msgstr "Public et promu"
+
+#: pod/playlist/templates/playlist/playlists.html
#, python-format
-msgid "%(title)s (%(count)d video)"
-msgid_plural "%(title)s (%(count)d videos)"
-msgstr[0] "%(title)s (%(count)d vidéo)"
-msgstr[1] "%(title)s (%(count)d vidéos)"
+msgid "%(counter)s playlist found"
+msgid_plural "%(counter)s playlists found"
+msgstr[0] "%(counter)s liste de lecture trouvée"
+msgstr[1] "%(counter)s listes de lecture trouvées"
-#: pod/playlist/views.py
-msgid "The playlist has been saved!"
-msgstr "La liste de lecture a été sauvegardée !"
+#: pod/playlist/templates/playlist/playlists.html
+msgid "No playlists found"
+msgstr "Aucune liste de lecture trouvée"
-#: pod/playlist/views.py
-msgid "The request is wrong. No video given."
-msgstr "La requête est erronée. Aucune vidéo envoyée."
+#: pod/playlist/templates/playlist/playlists.html
+#, fuzzy
+#| msgid ""
+#| "You have not created any playlists yet, please use the \"Add a new "
+#| "playlist\" button to add one."
+msgid ""
+"You haven't got any playlist yet, please use the \"Add a playlist\" button "
+"to add one."
+msgstr ""
+"Vous n’avez pas encore créé de liste de lecture, veuillez cliquer sur le "
+"bouton « Ajouter une nouvelle liste de lecture » pour en ajouter une."
-#: pod/playlist/views.py
-msgid "Only ajax request are accepted for this action."
-msgstr "Uniquement les requêtes ajax sont autorisées pour cette action."
+#: pod/playlist/templates/playlist/protected-playlist-form.html
+msgid "Protected playlist form"
+msgstr "Formulaire de liste de lecture protégée"
-#: pod/playlist/views.py
-msgid "This video has been removed from your playlist."
-msgstr "Cette vidéo a été retirée de votre liste de lecture."
+#: pod/playlist/templates/playlist/protected-playlist-form.html
+msgid "This playlist is protected by password, please fill in and click send."
+msgstr ""
+"Cet évènement est protégée par un mot de passe, veuillez remplir le "
+"formulaire et cliquez sur envoyer."
-#: pod/playlist/views.py
-msgid "The playlist have been saved."
-msgstr "La liste de lecture a été sauvegardée."
+#: pod/playlist/templates/playlist/protected-playlist-form.html
+#: pod/video/templates/videos/video-form.html
+msgid "If you do not have the password for this content, please"
+msgstr "Si vous n’avez pas le mot de passe pour ce contenu, veuillez"
-#: pod/playlist/views.py
-msgid "The video has been added to your playlist."
-msgstr "La vidéo a été ajoutée à votre liste de lecture."
+#: pod/playlist/templates/playlist/protected-playlist-form.html
+#: pod/video/templates/videos/video-form.html
+msgid "contact its owner"
+msgstr "contacter son propriétaire"
+
+#: pod/playlist/templatetags/favorites_playlist.py
+msgid "Favorites"
+msgstr "Favoris"
+
+#: pod/playlist/tests/test_forms.py
+#, fuzzy
+#| msgid "Title field is required"
+msgid "This field is required."
+msgstr "Champ de titre est requis"
+
+#: pod/playlist/tests/test_forms.py
+msgid "You cannot create a playlist named \"Favorites\""
+msgstr "Vous ne pouvez créer une liste de lecture nommé \"Favoris\""
#: pod/playlist/views.py
-msgid "This playlist has been deleted."
+msgid "The playlist has been deleted."
msgstr "Cette liste de lecture a été supprimée."
+#: pod/playlist/views.py
+msgid "The playlist has been created and the video has been added in it."
+msgstr "La liste de lecture a été créée et la vidéo a été ajoutée dedans."
+
#: pod/podfile/apps.py
msgid "Pod files"
msgstr "Fichiers Pod"
@@ -5798,7 +5954,7 @@ msgstr "Partager avec quelqu’un"
#: pod/podfile/templates/podfile/home_content.html
msgid "Already shared with:"
-msgstr "Déjà partagé avec :"
+msgstr "Déjà partagé avec :"
#: pod/podfile/templates/podfile/home_content.html
msgid "Username"
@@ -6181,8 +6337,8 @@ msgstr "Prévisualisation d’enregistrement"
#: pod/video/templates/videos/video-element.html
msgid ""
"To view this video please enable JavaScript, and consider upgrading to a web "
-"browser that supports HTML5 video"
+"browser that supports HTML5 video"
msgstr ""
"Pour visionner cette vidéo, veuillez activer JavaScript et envisager de "
"passer à un navigateur Web qui un nouvel enregistrement a été ajouté sur la plateforme "
"%(title_site)s à partir de l’enregistreur \"%(recorder)s\". Pour "
-"l’ajouter, cliquez sur le lien ci dessous.%(link_url)s Si le lien n’est pas actif, il "
-"faut le copier-coller dans la barre d’adresse de votre navigateur."
-"i>
Cordialement
"
+"l’ajouter, cliquez sur le lien ci dessous."
+"%(link_url)s Si le lien n’est pas actif, il faut le copier-coller "
+"dans la barre d’adresse de votre navigateur.
Cordialement
"
#: pod/recorder/views.py
msgid "New recording added."
@@ -6278,6 +6433,246 @@ msgstr "L’enregistrement a été supprimé."
msgid "Recorder for Studio not found."
msgstr "Enregistreur studio non trouvé."
+#: pod/stats/templates/stats/channel-stats-view.html
+msgid "Number of themes"
+msgstr "Nombre de thèmes"
+
+#: pod/stats/templates/stats/channel-stats-view.html
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Number of channels"
+msgstr "Nombre de chaînes"
+
+#: pod/stats/templates/stats/channel-stats-view.html
+msgid "Graph of video evolution for the channel "
+msgstr "Graphique de l'évolution des vidéos de la chaîne "
+
+#: pod/stats/templates/stats/channel-stats-view.html
+msgid "Breakdown of video status for the channel "
+msgstr "Répartition des statuts des vidéos pour la chaîne "
+
+#: pod/stats/templates/stats/channel-stats-view.html
+#: pod/stats/templates/stats/playlist-stats-view.html pod/stats/views.py
+msgid "Statistics for playlists"
+msgstr "Statistiques des listes de lecture"
+
+#: pod/stats/templates/stats/channel-stats-view.html
+msgid "Breakdown of channels visibility status "
+msgstr "Répartition des statuts de visibilité des chaînes "
+
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/user-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Duration time of videos"
+msgstr "Durée des vidéos"
+
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Most popular discipline"
+msgstr "Discipline la plus populaire"
+
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "No popular discipline"
+msgstr "Aucune discipline populaire"
+
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Most popular video type"
+msgstr "Type de vidéo le plus populaire"
+
+#: pod/stats/templates/stats/general-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "No popular video type"
+msgstr "Aucun type de vidéo populaire"
+
+#: pod/stats/templates/stats/general-stats-view.html
+msgid "Number of users"
+msgstr "Nombre d'utilisateurs"
+
+#: pod/stats/templates/stats/general-stats-view.html
+msgid "Statistics of videos"
+msgstr "Statistiques des videos"
+
+#: pod/stats/templates/stats/general-stats-view.html
+msgid "Graph of video evolution for "
+msgstr "Graphique de l'évolution de "
+
+#: pod/stats/templates/stats/general-stats-view.html
+msgid "Breakdown of video status for "
+msgstr "Répartition des statuts des vidéos de "
+
+#: pod/stats/templates/stats/playlist-stats-view.html
+msgid "Playlist playback time"
+msgstr "Temps de lecture de la liste de lecture"
+
+#: pod/stats/templates/stats/playlist-stats-view.html
+msgid "Playlist video statistics"
+msgstr "Statistiques des vidéos de la liste de lecture"
+
+#: pod/stats/templates/stats/playlist-stats-view.html
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Graph of video evolution "
+msgstr "Graphique de l'évolution des vidéos "
+
+#: pod/stats/templates/stats/playlist-stats-view.html
+msgid "Breakdown of video status for the playlist "
+msgstr "Répartition des statuts des vidéos pour la liste de lecture "
+
+#: pod/stats/templates/stats/playlist-stats-view.html
+msgid "Breakdown of playlists visibilities for "
+msgstr "Répartition des statuts de visibilité pour la liste de lecture "
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "My statistics"
+msgstr "Mes statistiques"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Number of files"
+msgstr "Nombre de fichiers"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Number of videos added in favorites"
+msgstr "Nombre de vidéos ajoutées en favoris"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Number of meetings"
+msgstr "Nombre de réunions"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Prefered discipline"
+msgstr "Discipline préférée"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "No prefered discipline"
+msgstr "Pas de discipline préférée"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Prefered type"
+msgstr "Type préféré"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "No prefered type"
+msgstr "Pas de type préféré"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Statistics of your videos"
+msgstr "Statistiques de vos vidéos"
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Graph of video evolution for the user "
+msgstr "Graphique de l'évolution des vidéos de l'utilisateur "
+
+#: pod/stats/templates/stats/user-stats-view.html
+msgid "Breakdown of video status for the user "
+msgstr "Répartition des statuts des vidéos pour l'utilisateur "
+
+#: pod/stats/templates/stats/utils/pie-chart.html
+#: pod/stats/templates/stats/utils/video-stats.html
+#: pod/stats/templates/stats/utils/years-video-stats.html
+msgid "Export to CSV"
+msgstr "Exporter au format CSV"
+
+#: pod/stats/templates/stats/utils/years-video-stats.html
+msgid "Start year"
+msgstr "Année de début"
+
+#: pod/stats/templates/stats/utils/years-video-stats.html
+msgid "End year"
+msgstr "Année de fin"
+
+#: pod/stats/templates/stats/video-stats-view.html pod/video/apps.py
+#: pod/video/models.py pod/video/templates/videos/videos.html
+msgid "Videos"
+msgstr "Vidéos"
+
+#: pod/stats/templates/stats/video-stats-view.html
+#: pod/video/templates/videos/video-info.html
+msgid "Number of favorites"
+msgstr "Nombre de favoris"
+
+#: pod/stats/templates/stats/video-stats-view.html
+#: pod/video/templates/videos/video-info.html
+msgid "Addition in a playlist"
+msgstr "Nombre d'ajouts dans une liste de lecture"
+
+#: pod/stats/templates/stats/video-stats-view.html
+#: pod/video/templates/videos/video-info.html
+msgid "Number of views"
+msgstr "Nombre de vues"
+
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Graphics of videos"
+msgstr "Graphiques des vidéos"
+
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Graph of video evolution of "
+msgstr "Graphique de l'évolution des vidéos de "
+
+#: pod/stats/templates/stats/video-stats-view.html
+msgid "Breakdown of video status "
+msgstr "Répartition des statuts des vidéos "
+
+#: pod/stats/views.py
+#, python-format
+msgid "Video statistics: %s"
+msgstr "Statistiques des vidéos : %s"
+
+#: pod/stats/views.py
+msgid "Site video statistics"
+msgstr "Statistiques des vidéo du site"
+
+#: pod/stats/views.py
+#, python-format
+msgid "Video statistics for the theme %s"
+msgstr "Statistiques des vidéos du thème %s"
+
+#: pod/stats/views.py
+#, python-format
+msgid "Video statistics for the channel %s"
+msgstr "Statistiques des vidéos de la chaîne %s"
+
+#: pod/stats/views.py
+msgid "Statistics for channels"
+msgstr "Statistiques des chaînes"
+
+#: pod/stats/views.py
+#, python-format
+msgid "Statistics for user %s"
+msgstr "Statistiques de l'utilisateur %s"
+
+#: pod/stats/views.py
+msgid "Site statistics"
+msgstr "Statistiques du site"
+
+#: pod/stats/views.py
+#, python-format
+msgid "Statistics for the playlist %s"
+msgstr "Statistiques de la liste de lecture %s"
+
+#: pod/stats/views.py pod/video/templates/videos/videos.html
+msgid "Video statistics"
+msgstr "Statistiques des vidéos"
+
+#: pod/stats/views.py
+#, python-format
+msgid "The following video does not exist: %s"
+msgstr "La video suivante n’existe pas : %s"
+
+#: pod/stats/views.py
+#, fuzzy, python-format
+#| msgid "You do not have access rights to this video: %s "
+msgid "You do not have access rights to this video: %s"
+msgstr "Vous n’avez pas les droits d’accès à cette vidéo : %s "
+
+#: pod/stats/views.py
+#, python-format
+msgid "Statistics for the meeting %s"
+msgstr "Statistiques de la réunion %s"
+
+#: pod/stats/views.py
+msgid "Statistics for meetings"
+msgstr "Statistiques des réunions"
+
#: pod/urls.py
msgid "Pod Administration"
msgstr "Administration de Pod"
@@ -6294,11 +6689,6 @@ msgstr "Définir comme brouillon"
msgid "Transcript selected"
msgstr "Transcription selectionnée"
-#: pod/video/apps.py pod/video/models.py pod/video/templates/videos/video.html
-#: pod/video/templates/videos/videos.html
-msgid "Videos"
-msgstr "Vidéos"
-
#: pod/video/forms.py
msgid "File field"
msgstr "Fichier"
@@ -6597,8 +6987,8 @@ msgid ""
"%(url)s\n"
msgstr ""
"vous pouvez changer la date de suppression en éditant votre vidéo :\n"
-"
+ {% endif %}
+{% endblock %}
+
+{% block stats_items %}
+ {% if slug %}
+ {% get_total_favorites_video video as get_total_favorites_video %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-star" label=_("Number of favorites") number=get_total_favorites_video %}
+
+ {% get_count_video_added_in_playlist video as get_count_video_added_in_playlist %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-list-ul" label=_("Addition in a playlist") number=get_count_video_added_in_playlist %}
+
+ {% include "stats/utils/stats-item.html" with icon="bi bi-eye" label=_("Number of views") number=video.get_viewcount %}
+
+ {% else %}
+ {% url 'video:my_videos' as url_for_videos %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-film" url=url_for_videos label=_("Number of videos") number=videos|length %}
+
+ {% get_total_time_videos videos as get_total_time_videos %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-clock" label=_("Duration time of videos") number=get_total_time_videos %}
+
+ {% if prefered_discipline %}
+ {% url 'videos:videos' as videos_url %}
+ {% with url_for_prefered_discipline=videos_url|add:"?discipline="|add:prefered_discipline.slug %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-book" url=url_for_prefered_discipline label=_("Most popular discipline") text=prefered_discipline %}
+ {% endwith %}
+ {% else %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-book" label=_("Most popular discipline") text=_("No popular discipline") %}
+ {% endif %}
+
+ {% if prefered_type %}
+ {% url 'videos:videos' as videos_url %}
+ {% with url_for_prefered_type=videos_url|add:"?type="|add:prefered_type.slug %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-tv" url=url_for_prefered_type label=_("Most popular video type") text=prefered_type %}
+ {% endwith %}
+ {% else %}
+ {% include "stats/utils/stats-item.html" with icon="bi bi-tv" label=_("Most popular video type") text=_("No popular video type") %}
+ {% endif %}
+ {% endif %}
+{% endblock stats_items %}
+
+{% block other_stats %}
+
{% trans "Graphics of videos" %}
+
+ {% if slug %}
+ {% trans "Graph of video evolution of " as graph_title_prefix %}
+ {% include "stats/utils/video-stats.html" with graph_id="videoEvolutionChart" graph_title=graph_title_prefix|add:slug %}
+ {% else %}
+
+
+ {% trans "Graph of video evolution " as graph_title_prefix %}
+ {% include "stats/utils/years-video-stats.html" with graph_id="videosEvolutionChart" graph_title=graph_title_prefix %}
+
+
+ {% trans "Breakdown of video status " as graph_title_prefix %}
+ {% include "stats/utils/pie-chart.html" with graph_id="statusChart" graph_data=status_datas graph_title=graph_title_prefix %}
+
+
+ {% endif %}
+{% endblock other_stats %}
diff --git a/pod/favorite/templatetags/__init__.py b/pod/stats/templatetags/__init__.py
similarity index 100%
rename from pod/favorite/templatetags/__init__.py
rename to pod/stats/templatetags/__init__.py
diff --git a/pod/stats/templatetags/channel_stats.py b/pod/stats/templatetags/channel_stats.py
new file mode 100644
index 0000000000..7ec79113e3
--- /dev/null
+++ b/pod/stats/templatetags/channel_stats.py
@@ -0,0 +1,20 @@
+from django.template import Library
+
+from pod.stats.utils import number_themes
+from pod.video.models import Channel
+
+register = Library()
+
+
+@register.simple_tag(takes_context=False, name="get_number_themes")
+def get_number_themes(channel: Channel = None) -> int:
+ """
+ Get the total number of themes associated with a channel.
+
+ Args:
+ channel (Channel, optional): The channel for which to retrieve the number of themes. Defaults to None.
+
+ Returns:
+ int: The total number of themes associated with the channel.
+ """
+ return number_themes(channel)
diff --git a/pod/stats/templatetags/general_stats.py b/pod/stats/templatetags/general_stats.py
new file mode 100644
index 0000000000..3273f93e86
--- /dev/null
+++ b/pod/stats/templatetags/general_stats.py
@@ -0,0 +1,50 @@
+from typing import List
+from django.template import Library, RequestContext
+
+from pod.stats.utils import number_users, number_videos, total_time_videos
+from pod.video.models import Video
+
+register = Library()
+
+
+@register.simple_tag(takes_context=True, name="get_total_time_videos")
+def get_total_time_videos(context: RequestContext, video_list: List[Video] = None) -> str:
+ """
+ Get the total duration of videos in the specified list.
+
+ Args:
+ context (RequestContext): The context.
+ video_list (List[Video], optional): The list of videos. Defaults to None.
+
+ Returns:
+ str: The formatted total duration of videos in HH:MM:SS format.
+ """
+ request = context["request"]
+ return total_time_videos(request, video_list)
+
+
+@register.simple_tag(takes_context=True, name="get_number_videos")
+def get_number_videos(context: RequestContext, video_list: List[Video] = None) -> int:
+ """
+ Get the total number of videos in the specified list.
+
+ Args:
+ context (RequestContext): The context.
+ video_list (List[Video], optional): The list of videos. Defaults to None.
+
+ Returns:
+ int: The total number of videos.
+ """
+ request = context["request"]
+ return number_videos(request, video_list)
+
+
+@register.simple_tag(takes_context=False, name="get_number_users")
+def get_number_users() -> int:
+ """
+ Get the total number of users.
+
+ Returns:
+ int: The total number of users.
+ """
+ return number_users()
diff --git a/pod/stats/templatetags/playlist_stats.py b/pod/stats/templatetags/playlist_stats.py
new file mode 100644
index 0000000000..372af23c06
--- /dev/null
+++ b/pod/stats/templatetags/playlist_stats.py
@@ -0,0 +1,84 @@
+from django.template import Library
+from pod.playlist.models import Playlist
+
+from pod.video.models import Video
+
+from pod.playlist.utils import (
+ get_additional_owners,
+ get_count_video_added_in_playlist as total_additions_playlist_utils,
+ get_total_favorites_video as total_favorites_utils,
+ get_number_video_in_playlist as total_videos_in_playlist,
+ playlist_visibility,
+)
+
+register = Library()
+
+
+@register.simple_tag(name="get_total_favorites_video")
+def get_total_favorites_video(video: Video) -> int:
+ """
+ Get the total number of times a video has been marked as a favorite.
+
+ Args:
+ video (:class:`pod.video.models.Video`): The video for which to retrieve the total number of favorites.
+
+ Returns:
+ int: The total number of favorites for the video.
+ """
+ return total_favorites_utils(video)
+
+
+@register.simple_tag(name="get_count_video_added_in_playlist")
+def get_count_video_added_in_playlist(video: Video) -> int:
+ """
+ Get the total number of times a video has been added to playlists.
+
+ Args:
+ video (:class:`pod.video.models.Video`): The video for which to retrieve the total number of additions to playlists.
+
+ Returns:
+ int: The total number of additions to playlists for the video.
+ """
+ return total_additions_playlist_utils(video)
+
+
+@register.simple_tag(name="get_number_video_in_playlist")
+def get_number_video_in_playlist(playlist: Playlist) -> int:
+ """
+ Get the number of videos in a playlist.
+
+ Args:
+ playlist (:class:`pod.playlist.models.Playlist`): The playlist for which to retrieve the number of videos.
+
+ Returns:
+ int: The number of videos in the playlist.
+ """
+ return total_videos_in_playlist(playlist)
+
+
+@register.simple_tag(name="get_number_additional_owners_playlist")
+def get_number_additional_owners_playlist(playlist: Playlist) -> int:
+ """
+ Get the number of additional owners of a playlist.
+
+ Args:
+ playlist (:class:`pod.playlist.models.Playlist`): The playlist for which to retrieve the number of additional owners.
+
+ Returns:
+ int: The number of additional owners of the playlist.
+ """
+ return get_additional_owners(playlist).count()
+
+
+@register.simple_tag(name="get_playlist_visibility")
+def get_playlist_visibility(playlist: Playlist) -> str:
+ """
+ Get the visibility status of a playlist.
+
+ Args:
+ playlist (:class:`pod.playlist.models.Playlist`): The playlist for which to retrieve the visibility status.
+
+ Returns:
+ str: The visibility status of the playlist.
+ """
+ return playlist_visibility(playlist)
diff --git a/pod/stats/templatetags/user_stats.py b/pod/stats/templatetags/user_stats.py
new file mode 100644
index 0000000000..f06a8a4762
--- /dev/null
+++ b/pod/stats/templatetags/user_stats.py
@@ -0,0 +1,106 @@
+from django.template import Library, RequestContext
+from pod.playlist.utils import get_number_playlist
+
+from pod.stats.utils import (
+ number_channels,
+ number_favorites,
+ number_files,
+ number_meetings,
+ number_videos,
+)
+
+from pod.video.models import Video
+
+register = Library()
+
+
+@register.simple_tag(takes_context=True, name="get_number_video_user")
+def get_number_video_user(context: RequestContext) -> int:
+ """
+ Get the number of videos for a user.
+
+ Args:
+ context (RequestContext): The context.
+
+ Returns:
+ int: The number of videos for the user.
+ """
+ request = context["request"]
+ user_video_list = Video.objects.filter(owner=request.user)
+ return number_videos(request, user_video_list)
+
+
+@register.simple_tag(takes_context=True, name="get_number_playlist_user")
+def get_number_playlist_user(context: RequestContext):
+ """
+ Get the number of playlists for a user.
+
+ Args:
+ context (RequestContext): The context.
+
+ Returns:
+ int: The number of playlists for the user.
+ """
+ request = context["request"]
+ return get_number_playlist(request.user)
+
+
+@register.simple_tag(takes_context=True, name="get_number_files_user")
+def get_number_files_user(context: RequestContext):
+ """
+ Get the number of files for a user.
+
+ Args:
+ context (RequestContext): The context.
+
+ Returns:
+ int: The number of files for the user.
+ """
+ request = context["request"]
+ return number_files(request.user)
+
+
+@register.simple_tag(takes_context=True, name="get_number_favorites_user")
+def get_number_favorites_user(context: RequestContext):
+ """
+ Get the number of favorites for a user.
+
+ Args:
+ context (RequestContext): The context.
+
+ Returns:
+ int: The number of favorites for user.
+ """
+ request = context["request"]
+ return number_favorites(request.user)
+
+
+@register.simple_tag(takes_context=True, name="get_number_meetings_user")
+def get_number_meetings_user(context: RequestContext):
+ """
+ Get the number of meetings for a user.
+
+ Args:
+ context (RequestContext): The context.
+
+ Returns:
+ int: The number of meetings for user.
+ """
+ request = context["request"]
+ return number_meetings(request.user)
+
+
+@register.simple_tag(takes_context=True, name="get_number_channels")
+def get_number_channels(context: RequestContext, target: str = None):
+ """
+ Get the number of channels for a user.
+
+ Args:
+ context (RequestContext): The context.
+ target (str, optional): The target can be "user" or "None". Defaults to None.
+
+ Returns:
+ int: The number of channels for user if the target is "user", or the number of all channels if target is "None".
+ """
+ request = context["request"]
+ return number_channels(request, target)
diff --git a/pod/stats/tests.py b/pod/stats/tests.py
new file mode 100644
index 0000000000..7ce503c2dd
--- /dev/null
+++ b/pod/stats/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/pod/stats/urls.py b/pod/stats/urls.py
new file mode 100644
index 0000000000..6940ba8e66
--- /dev/null
+++ b/pod/stats/urls.py
@@ -0,0 +1,38 @@
+from django.urls import path
+from django.conf import settings
+
+from .views import (
+ channel_stats_view,
+ general_stats_view,
+ meeting_stats_view,
+ playlist_stats_view,
+ to_do,
+ user_stats_view,
+ video_stats_view,
+)
+
+app_name = "stats"
+
+if getattr(settings, "USE_STATS_VIEW", False):
+ urlpatterns = [
+ path("", general_stats_view, name="general-stats"),
+ # USERS
+ path("my-stats/", user_stats_view, name="my-stats"),
+ # VIDEOS
+ path("videos/", video_stats_view, name="video-stats"),
+ path("videos/", video_stats_view, name="video-stats"),
+ # CHANNELS
+ path("channels/", channel_stats_view, name="channels-stats"),
+ path("channels/", channel_stats_view, name="channels-stats"),
+ path(
+ "channels//",
+ channel_stats_view,
+ name="channels-stats",
+ ),
+ # PLAYLISTS
+ path("playlists/", playlist_stats_view, name="playlist-stats"),
+ path("playlists/", playlist_stats_view, name="playlist-stats"),
+ # MEETINGS
+ path("meetings/", meeting_stats_view, name="meeting-stats"),
+ path("meetings/", meeting_stats_view, name="meeting-stats"),
+ ]
diff --git a/pod/stats/utils.py b/pod/stats/utils.py
new file mode 100644
index 0000000000..6323e450d0
--- /dev/null
+++ b/pod/stats/utils.py
@@ -0,0 +1,469 @@
+"""Esup-Pod stats utilities."""
+from collections import Counter
+from typing import List
+from datetime import date, timedelta
+
+from django.http import HttpRequest
+from django.shortcuts import get_object_or_404
+from django.db.models import Sum
+from django.core.cache import cache
+from django.contrib.auth.models import User
+from django.contrib.sites.shortcuts import get_current_site
+from django.conf import settings
+
+from pod.meeting.models import Meeting
+from pod.playlist.models import Playlist, PlaylistContent
+from pod.playlist.apps import FAVORITE_PLAYLIST_NAME
+from pod.playlist.utils import (
+ get_favorite_playlist_for_user,
+ get_number_video_in_playlist,
+)
+from pod.podfile.models import UserFolder
+from pod.video.context_processors import get_available_videos
+from pod.video.models import Channel, Theme, Video, ViewCount
+
+from json import dumps
+
+CACHE_EXPIRATION = 300 # Cache expiration time in seconds
+
+
+def get_videos_stats(
+ video_list: List[Video], date_filter: date, mode: str = None
+) -> dict:
+ """
+ Get aggregated statistics data for a list of videos based on the specified date filter and mode.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date): The date filter to apply.
+ mode (str, optional): The mode for data aggregation. Defaults to None.
+
+ Returns:
+ dict: A dictionary containing the aggregated statistics data.
+ """
+ if not mode:
+ video_stats = get_days_videos_stats(video_list, date_filter)
+ elif mode == "year":
+ video_stats = get_years_videos_stats(video_list, date_filter)
+ return video_stats
+
+
+def get_views_count(
+ video_list: List[Video],
+ date_filter: date = date.today(),
+ years_only: bool = False,
+ day_only: bool = False,
+) -> dict:
+ """
+ Get the total views count for a list of videos based on the specified date filter and options.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date, optional): The date filter to apply. Defaults to today's date.
+ years_only (bool, optional): If True, calculate views count for years only. Defaults to False.
+ day_only (bool, optional): If True, calculate views count for a specific day only. Defaults to False.
+
+ Returns:
+ dict: A dictionary containing the aggregated views count data.
+ """
+ cache_key = f"views_count_{date_filter}_{years_only}_{day_only}"
+ all_views = cache.get(cache_key)
+
+ if all_views is None:
+ all_views = {}
+ if day_only:
+ all_views["views_day"] = (
+ ViewCount.objects.filter(
+ video_id__in=video_list, date=date_filter
+ ).aggregate(Sum("count"))["count__sum"]
+ or 0
+ )
+ elif years_only:
+ all_views["views_year"] = (
+ ViewCount.objects.filter(
+ date__year=date_filter.year, video_id__in=video_list
+ ).aggregate(Sum("count"))["count__sum"]
+ or 0
+ )
+ else:
+ # view count since video was created
+ all_views["views_since_created"] = (
+ ViewCount.objects.filter(video_id__in=video_list).aggregate(Sum("count"))[
+ "count__sum"
+ ]
+ or 0
+ )
+
+ cache.set(cache_key, all_views, CACHE_EXPIRATION)
+
+ return all_views
+
+
+def get_playlists_count(
+ video_list: List[Video],
+ date_filter: date = date.today(),
+ years_only: bool = False,
+ day_only: bool = False,
+) -> dict:
+ """
+ Get the total playlists count for a list of videos based on the specified date filter and options.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date, optional): The date filter to apply. Defaults to today's date.
+ years_only (bool, optional): If True, calculate playlist addition for years only. Defaults to False.
+ day_only (bool, optional): If True, calculate playlist addition for a specific day only. Defaults to False.
+
+ Returns:
+ dict: A dictionary containing the aggregated playlists count data.
+ """
+ cache_key = f"playlists_count_{date_filter}_{years_only}_{day_only}"
+ all_playlists = cache.get(cache_key)
+
+ if all_playlists is None:
+ all_playlists = {}
+ if day_only:
+ all_playlists["playlist_addition_day"] = PlaylistContent.objects.filter(
+ video_id__in=video_list, date_added__date=date_filter
+ ).count()
+ elif years_only:
+ all_playlists["playlist_addition_year"] = PlaylistContent.objects.filter(
+ video_id__in=video_list, date_added__year=date_filter.year
+ ).count()
+ else:
+ # playlist addition since video was created
+ all_playlists[
+ "playlist_addition_since_created"
+ ] = PlaylistContent.objects.filter(video_id__in=video_list).count()
+
+ cache.set(cache_key, all_playlists, CACHE_EXPIRATION)
+
+ return all_playlists
+
+
+def get_favorites_count(
+ video_list: List[Video],
+ date_filter: date = date.today(),
+ years_only: bool = False,
+ day_only: bool = False,
+) -> dict:
+ """
+ Get the total favorites count for a list of videos based on the specified date filter and options.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date, optional): The date filter to apply. Defaults to today's date.
+ years_only (bool, optional): If True, calculate favorites count for years only. Defaults to False.
+ day_only (bool, optional): If True, calculate favorites count for a specific day only. Defaults to False.
+
+ Returns:
+ dict: A dictionary containing the aggregated favorites count data.
+ """
+ cache_key = f"favorites_count_{date_filter}_{years_only}_{day_only}"
+ all_favorites = cache.get(cache_key)
+
+ if all_favorites is None:
+ all_favorites = {}
+ favorites_playlists = Playlist.objects.filter(name=FAVORITE_PLAYLIST_NAME)
+ if day_only:
+ all_favorites["favorites_day"] = PlaylistContent.objects.filter(
+ playlist__in=favorites_playlists,
+ video_id__in=video_list,
+ date_added__date=date_filter,
+ ).count()
+ elif years_only:
+ all_favorites["favorites_year"] = PlaylistContent.objects.filter(
+ playlist__in=favorites_playlists,
+ video_id__in=video_list,
+ date_added__year=date_filter.year,
+ ).count()
+ else:
+ all_favorites["favorites_since_created"] = PlaylistContent.objects.filter(
+ playlist__in=favorites_playlists, video_id__in=video_list
+ ).count()
+
+ cache.set(cache_key, all_favorites, CACHE_EXPIRATION)
+
+ return all_favorites
+
+
+def get_days_videos_stats(
+ video_list: List[Video], date_filter: date = date.today()
+) -> str:
+ """
+ Get daily aggregated statistics data for a list of videos based on the specified date filter.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date, optional): The date filter to apply. Defaults to today's date.
+
+ Returns:
+ str: A JSON-encoded string containing the aggregated daily statistics data.
+ """
+ all_video_stats = {"date": str(date_filter), "datas": {}}
+
+ all_video_stats["datas"].update(
+ get_views_count(video_list, date_filter, day_only=True)
+ )
+ if getattr(settings, "USE_PLAYLIST", True):
+ all_video_stats["datas"].update(
+ get_playlists_count(video_list, date_filter, day_only=True)
+ )
+ if getattr(settings, "USE_PLAYLIST", True) and getattr(
+ settings, "USE_FAVORITES", True
+ ):
+ all_video_stats["datas"].update(
+ get_favorites_count(video_list, date_filter, day_only=True)
+ )
+
+ return dumps(all_video_stats)
+
+
+def get_years_videos_stats(
+ video_list: List[Video], date_filter: date = date.today()
+) -> str:
+ """
+ Get yearly aggregated statistics data for a list of videos based on the specified date filter.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of video objects.
+ date_filter (date, optional): The date filter to apply. Defaults to today's date.
+
+ Returns:
+ str: A JSON-encoded string containing the aggregated yearly statistics data.
+ """
+ all_years_videos_stats = {"year": date_filter.year, "datas": {}}
+
+ all_years_videos_stats["datas"].update(
+ get_views_count(video_list, date_filter, years_only=True)
+ )
+ if getattr(settings, "USE_PLAYLIST", True):
+ all_years_videos_stats["datas"].update(
+ get_playlists_count(video_list, date_filter, years_only=True)
+ )
+ if getattr(settings, "USE_PLAYLIST", True) and getattr(
+ settings, "USE_FAVORITES", True
+ ):
+ all_years_videos_stats["datas"].update(
+ get_favorites_count(video_list, date_filter, years_only=True)
+ )
+
+ return dumps(all_years_videos_stats)
+
+
+def get_videos_status_stats(video_list: List[Video]) -> str:
+ """
+ Get statistics for the status of videos in the given list.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of Video objects.
+
+ Returns:
+ str: JSON-encoded statistics for different video statuses.
+ """
+ stats = {}
+ number_videos = len(video_list)
+ draft_number = Video.objects.filter(id__in=video_list, is_draft=True).count()
+ restricted_number = Video.objects.filter(
+ id__in=video_list, is_restricted=True
+ ).count()
+ password_number = Video.objects.filter(
+ id__in=video_list, password__isnull=False
+ ).count()
+ public_number = number_videos - draft_number - restricted_number - password_number
+
+ stats["public"] = public_number
+ stats["draft"] = draft_number
+ stats["restricted"] = restricted_number
+ stats["password"] = password_number
+ return dumps(stats)
+
+
+def get_playlists_status_stats(playlist_list: List[Playlist]) -> str:
+ """
+ Get statistics for the visibility statuses of playlists in the given list.
+
+ Args:
+ playlist_list (List[:class:`pod.playlist.models.Playlist`]): List of Playlist objects.
+
+ Returns:
+ str: JSON-encoded statistics for different playlist visibility statuses.
+ """
+ stats = {}
+ visibility_list = list(playlist_list.values_list("visibility", flat=True))
+ stats = Counter(visibility_list)
+ return dumps(stats)
+
+
+def total_time_videos(request: HttpRequest, video_list: List[Video] = None) -> str:
+ """
+ Get the total duration of videos in the specified list or for the user's available videos.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ video_list (List[:class:`pod.video.models.Video`], optional): List of Video objects. Defaults to None.
+
+ Returns:
+ str: The formatted total duration of videos in HH:MM:SS format.
+ """
+ if video_list:
+ total_duration = video_list.aggregate(Sum("duration"))["duration__sum"]
+ else:
+ total_duration = get_available_videos(request).aggregate(Sum("duration"))[
+ "duration__sum"
+ ]
+ return str(timedelta(seconds=total_duration)) if total_duration else "0"
+
+
+def number_videos(request: HttpRequest, video_list: List[Video] = None) -> int:
+ """
+ Get the number of videos in the specified list or for the user's available videos.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ video_list (List[:class:`pod.video.models.Video`], optional): List of Video objects. Defaults to None.
+
+ Returns:
+ int: The number of videos.
+ """
+ if video_list:
+ number_videos = video_list.count()
+ else:
+ number_videos = get_available_videos(request).count()
+ return number_videos
+
+
+def number_files(user: User) -> int:
+ """
+ Get the number of files in the user's home folder.
+
+ Args:
+ user (:class:`django.contrib.auth.models.User`): The User object.
+
+ Returns:
+ int: The number of files.
+ """
+ user_home_folder = get_object_or_404(UserFolder, name="home", owner=user)
+ return len(user_home_folder.get_all_files())
+
+
+def number_favorites(user: User) -> int:
+ """
+ Get the number of videos in the user's favorite playlist.
+
+ Args:
+ user (:class:`django.contrib.auth.models.User`): The User object.
+
+ Returns:
+ int: The number of videos in the favorite playlist.
+ """
+ favorites = get_favorite_playlist_for_user(user)
+ return get_number_video_in_playlist(favorites)
+
+
+def number_meetings(user: User) -> int:
+ """
+ Get the number of meetings owned by the user.
+
+ Args:
+ user (:class:`django.contrib.auth.models.User`): The User object.
+
+ Returns:
+ int: The number of meetings.
+ """
+ return Meeting.objects.filter(owner=user).count()
+
+
+def number_channels(request: HttpRequest, target: str = None) -> int:
+ """
+ Get the number of channels for the user or in total.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ target (str, optional): The target for counting channels. Defaults to None.
+
+ Returns:
+ int: The number of channels.
+ """
+ site = get_current_site(request)
+ if target == "user":
+ return request.user.owners_channels.all().filter(site=site).count()
+ return Channel.objects.all().filter(site=site).distinct().count()
+
+
+def get_channels_visibility_stats(channel_list: List[Channel]) -> str:
+ """
+ Get statistics for the visibility statuses of channels in the given list.
+
+ Args:
+ channel_list (List[:class:`pod.video.models.Channel`]): List of Channel objects.
+
+ Returns:
+ str: JSON-encoded statistics for different channel visibility statuses.
+ """
+ stats = {}
+ number_channels = len(channel_list)
+
+ visible_channels = channel_list.filter(visible=True).count()
+ private_channels = number_channels - visible_channels
+
+ stats["visible"] = visible_channels
+ stats["private"] = private_channels
+ return dumps(stats)
+
+
+def get_most_common_type_discipline(video_list: List[Video]):
+ """
+ Get the most common video type and discipline from the given list of videos.
+
+ Args:
+ video_list (List[:class:`pod.video.models.Video`]): List of Video objects.
+
+ Returns:
+ Tuple[Type, Discipline]: The most common video type and discipline.
+ """
+ if len(video_list) > 0:
+ type_counter = Counter()
+ discipline_counter = Counter()
+
+ for video in video_list:
+ if video.type and video.type.slug != "other":
+ type_counter[video.type] += 1
+ if video.discipline.exists():
+ for discipline in video.discipline.all():
+ discipline_counter[discipline] += 1
+
+ most_common_type = type_counter.most_common(1)[0][0] if type_counter else None
+ most_common_discipline = (
+ discipline_counter.most_common(1)[0][0] if discipline_counter else None
+ )
+ else:
+ most_common_type = None
+ most_common_discipline = None
+ return most_common_type, most_common_discipline
+
+
+def number_users() -> int:
+ """
+ Get the total number of users.
+
+ Returns:
+ int: The number of users.
+ """
+ return User.objects.all().distinct().count()
+
+
+def number_themes(channel: Channel = None) -> int:
+ """
+ Get the number of themes for the specified channel or in total.
+
+ Args:
+ channel (:class:`pod.video.models.Channel`, optional): The Channel object. Defaults to None.
+
+ Returns:
+ int: The number of themes.
+ """
+ if channel:
+ return Theme.objects.filter(channel=channel).distinct().count()
+ else:
+ return Theme.objects.all().distinct().count()
diff --git a/pod/stats/views.py b/pod/stats/views.py
new file mode 100644
index 0000000000..4c5fc7248b
--- /dev/null
+++ b/pod/stats/views.py
@@ -0,0 +1,464 @@
+"""Esup-Pod stats views."""
+import json
+from datetime import date
+from dateutil.parser import parse
+from typing import List
+
+from django.conf import settings
+from django.contrib.auth.decorators import user_passes_test
+from django.contrib.auth.models import User
+from django.contrib.sites.shortcuts import get_current_site
+from django.http import HttpRequest, HttpResponseNotFound, JsonResponse
+from django.shortcuts import get_object_or_404, render
+from django.utils.translation import gettext_lazy as _
+from pod.meeting.models import Meeting
+
+from pod.playlist.utils import (
+ get_all_playlists,
+ get_favorite_playlist_for_user,
+ get_playlist,
+ get_video_list_for_playlist,
+)
+from pod.stats.utils import (
+ get_channels_visibility_stats,
+ get_most_common_type_discipline,
+ get_playlists_status_stats,
+ get_videos_stats,
+ get_videos_status_stats,
+)
+from pod.video.context_processors import get_available_videos
+from pod.video.forms import VideoPasswordForm
+from pod.video.models import Channel, Theme, Video
+from pod.video.views import get_video_access
+
+VIEW_STATS_AUTH = getattr(settings, "VIEW_STATS_AUTH", False)
+
+
+def view_stats_if_authenticated(user: User) -> bool:
+ """
+ Check if the user is authenticated and has permission to view statistics.
+
+
+ Args:
+ user (:class:`django.contrib.auth.models.User`): The user object to check.
+
+
+ Returns:
+ bool: False if the user is not authenticated or VIEW_STATS_AUTH is False, else True.
+ """
+ return user.is_authenticated and VIEW_STATS_AUTH
+
+
+STATS_VIEWS = {
+ "videos": {
+ "filter_func": lambda kwargs: Video.objects.filter(slug=kwargs.get("video_slug"))
+ if kwargs.get("video_slug")
+ else kwargs["available_videos"],
+ "title_func": lambda kwargs: _("Video statistics: %s")
+ % kwargs["video_founded"].title.capitalize()
+ if kwargs["video_founded"]
+ else _("Site video statistics"),
+ },
+ "channel": {
+ "filter_func": lambda kwargs: Video.objects.filter(
+ channel=kwargs["channel_obj"], theme=kwargs["theme_obj"]
+ )
+ if kwargs.get("theme_obj")
+ else Video.objects.filter(channel=kwargs["channel_obj"]),
+ "title_func": lambda kwargs: _("Video statistics for the theme %s")
+ % kwargs["theme_obj"].title
+ if kwargs.get("theme_obj")
+ else _("Video statistics for the channel %s") % kwargs["channel_obj"].title
+ if kwargs["channel_obj"]
+ else _("Statistics for channels"),
+ },
+ "user": {
+ "filter_func": lambda kwargs: Video.objects.filter(owner=kwargs["user"]),
+ "title_func": lambda kwargs: _("Statistics for user %s") % kwargs["user"],
+ },
+ "general": {
+ "filter_func": lambda kwargs: kwargs["available_videos"],
+ "title_func": lambda kwargs: _("Site statistics"),
+ },
+ "playlist": {
+ "filter_func": lambda kwargs: Video.objects.filter(
+ playlistcontent__playlist=kwargs["playlist_obj"]
+ ),
+ "title_func": lambda kwargs: _("Statistics for the playlist %s")
+ % kwargs["playlist_obj"].name
+ if kwargs["playlist_obj"]
+ else _("Statistics for playlists"),
+ },
+}
+
+
+def get_videos(
+ request: HttpRequest,
+ target: str,
+ video_slug=None,
+ channel=None,
+ theme=None,
+ playlist=None,
+):
+ """
+ Get a list of videos based on the specified target and filters.
+
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ target (str): The target of the statistics view.
+ video_slug (str, optional): The slug of the video. Defaults to None.
+ channel (str, optional): The slug of the channel. Defaults to None.
+ theme (str, optional): The slug of the theme. Defaults to None.
+ playlist (str, optional): The slug of the playlist. Defaults to None.
+
+
+ Returns:
+ Tuple[List[Video], str]: A tuple containing a list of videos and the title for the view.
+ """
+ title = _("Video statistics")
+ available_videos = get_available_videos()
+ videos = []
+
+ if target.lower() in STATS_VIEWS:
+ config = STATS_VIEWS[target.lower()]
+ filter_args = {
+ "video_slug": video_slug,
+ "channel": channel,
+ "theme": theme,
+ "playlist": playlist,
+ "available_videos": available_videos,
+ "user": request.user if target.lower() == "user" else None,
+ "channel_obj": get_object_or_404(Channel, slug=channel) if channel else None,
+ "theme_obj": get_object_or_404(Theme, slug=theme) if theme else None,
+ "video_founded": Video.objects.filter(slug=video_slug).first()
+ if video_slug
+ else None,
+ "playlist_obj": get_playlist(playlist) if playlist else None,
+ }
+ videos = config["filter_func"](filter_args)
+ title = config["title_func"](filter_args)
+
+ return videos, title
+
+
+def manage_post_request(
+ request: HttpRequest, videos: List[Video], video: Video = None
+) -> JsonResponse:
+ """
+ Process a POST request to fetch video statistics data.
+
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ videos (List[Video]): A list of video objects to fetch statistics for.
+ video (Video, optional): A specific video object for which to fetch statistics. Defaults to None.
+
+
+ Returns:
+ JsonResponse: A JSON response containing the fetched video statistics data.
+ """
+ date_filter = request.POST.get("periode", date.today())
+ if isinstance(date_filter, str):
+ date_filter = parse(date_filter).date()
+ if video: # For one video
+ data = get_videos_stats(videos, date_filter)
+ else: # For some videos
+ data = get_videos_stats(videos, date_filter, mode="year")
+ return JsonResponse(data, safe=False)
+
+
+@user_passes_test(view_stats_if_authenticated)
+def video_stats_view(request: HttpRequest, video: str = None):
+ """
+ Display video statistics view based on user's authentication status.
+
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ video (str, optional): The video slug. Defaults to None.
+
+
+ Returns:
+ HttpResponse: A response containing the rendered video statistics view.
+ """
+ target = "videos"
+ videos, title = get_videos(request=request, target=target, video_slug=video)
+
+ if not videos:
+ return HttpResponseNotFound(_("The following video does not exist: %s") % video)
+
+ if request.method == "GET":
+ if video and videos:
+ return manage_access_rights_stats_video(request, videos[0], title)
+
+ status_datas_json = get_videos_status_stats(videos)
+ status_datas = json.loads(status_datas_json)
+ status_datas.pop("draft", None)
+ prefered_type, prefered_discipline = get_most_common_type_discipline(videos)
+
+ return render(
+ request,
+ "stats/video-stats-view.html",
+ {
+ "title": title,
+ "videos": videos,
+ "status_datas": json.dumps(status_datas),
+ "prefered_type": prefered_type,
+ "prefered_discipline": prefered_discipline,
+ },
+ )
+ if request.method == "POST":
+ return manage_post_request(request, videos, video)
+
+
+def manage_access_rights_stats_video(request: HttpRequest, video: Video, page_title):
+ """
+ Manage access rights to the video statistics view based on user permissions.
+
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ video (Video): The video object for which to manage access rights.
+ page_title (str): The title of the page.
+
+
+ Returns:
+ HttpResponse: A response containing the rendered video statistics view or an error message.
+ """
+ video_access_ok = get_video_access(request, video, slug_private=None)
+ is_password_protected = video.password is not None and video.password != ""
+ has_rights = (
+ request.user == video.owner
+ or request.user.is_superuser
+ or request.user.has_perm("video.change_viewcount")
+ or request.user in video.additional_owners.all()
+ )
+ if not has_rights and is_password_protected:
+ form = VideoPasswordForm()
+ return render(
+ request,
+ "stats/video-stats-view.html",
+ {"form": form, "title": page_title},
+ )
+ elif (
+ (not has_rights and video_access_ok and not is_password_protected)
+ or (video_access_ok and not is_password_protected)
+ or has_rights
+ ):
+ return render(
+ request,
+ "stats/video-stats-view.html",
+ {
+ "title": page_title,
+ "video": video,
+ "slug": video.slug,
+ },
+ )
+ return HttpResponseNotFound(
+ _("You do not have access rights to this video: %s" % video.slug)
+ )
+
+
+def to_do():
+ ...
+
+
+@user_passes_test(view_stats_if_authenticated)
+def channel_stats_view(request: HttpRequest, channel: str = None, theme: str = None):
+ """
+ Display channel statistics view based on user's authentication status.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ channel (str): The channel slug. Defaults to None.
+ theme (str): The theme slug. Defaults to None.
+
+ Returns:
+ HttpResponse: A response containing the rendered channel statistics view.
+ """
+ target = "channel"
+ videos, title = get_videos(
+ request=request, target=target, channel=channel, theme=theme
+ )
+
+ if request.method == "GET":
+ if channel:
+ status_datas = get_videos_status_stats(videos)
+ return render(
+ request,
+ "stats/channel-stats-view.html",
+ {
+ "title": title,
+ "channel": channel,
+ "theme": theme,
+ "status_datas": status_datas,
+ "videos": videos,
+ "date": date.today(),
+ },
+ )
+ else:
+ site = get_current_site(request)
+ channels = Channel.objects.all().filter(site=site).distinct()
+ visibility_datas = get_channels_visibility_stats(channels)
+ return render(
+ request,
+ "stats/channel-stats-view.html",
+ {
+ "title": title,
+ "visibility_datas": visibility_datas,
+ },
+ )
+
+ elif request.method == "POST":
+ return manage_post_request(request, videos)
+
+
+@user_passes_test(view_stats_if_authenticated)
+def user_stats_view(request: HttpRequest):
+ """
+ Display user statistics view based on user's authentication status.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+
+ Returns:
+ HttpResponse: A response containing the rendered user statistics view.
+ """
+ target = "user"
+ videos, title = get_videos(request=request, target=target)
+
+ if request.method == "GET":
+ status_datas = get_videos_status_stats(videos)
+ prefered_type, prefered_discipline = get_most_common_type_discipline(
+ get_video_list_for_playlist(get_favorite_playlist_for_user(request.user))
+ )
+ return render(
+ request,
+ "stats/user-stats-view.html",
+ {
+ "title": title,
+ "videos": videos,
+ "status_datas": status_datas,
+ "prefered_type": prefered_type,
+ "prefered_discipline": prefered_discipline,
+ },
+ )
+ elif request.method == "POST":
+ return manage_post_request(request, videos)
+
+
+@user_passes_test(view_stats_if_authenticated)
+def general_stats_view(request: HttpRequest):
+ """
+ Display general statistics view based on user's authentication status.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+
+ Returns:
+ HttpResponse: A response containing the rendered general statistics view.
+ """
+ target = "general"
+ videos, title = get_videos(request=request, target=target)
+
+ if request.method == "GET":
+ status_datas_json = get_videos_status_stats(videos)
+ status_datas = json.loads(status_datas_json)
+ status_datas.pop("draft", None)
+ prefered_type, prefered_discipline = get_most_common_type_discipline(videos)
+ return render(
+ request,
+ "stats/general-stats-view.html",
+ {
+ "title": title,
+ "videos": videos,
+ "status_datas": json.dumps(status_datas),
+ "prefered_type": prefered_type,
+ "prefered_discipline": prefered_discipline,
+ },
+ )
+ elif request.method == "POST":
+ return manage_post_request(request, videos)
+
+
+@user_passes_test(view_stats_if_authenticated)
+def playlist_stats_view(request: HttpRequest, playlist: str = None):
+ """
+ Display playlist statistics view based on user's authentication status.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ playlist (str, optional): The playlist slug. Defaults to None.
+
+ Returns:
+ HttpResponse: A response containing the rendered playlist statistics view.
+ """
+ target = "playlist"
+ videos, title = get_videos(request=request, target=target, playlist=playlist)
+
+ if request.method == "GET":
+ if playlist:
+ status_datas = get_videos_status_stats(videos)
+ prefered_type, prefered_discipline = get_most_common_type_discipline(videos)
+ playlist = get_playlist(playlist)
+ return render(
+ request,
+ "stats/playlist-stats-view.html",
+ {
+ "title": title,
+ "videos": videos,
+ "status_datas": status_datas,
+ "prefered_type": prefered_type,
+ "prefered_discipline": prefered_discipline,
+ "playlist": playlist,
+ },
+ )
+ else:
+ playlists = get_all_playlists()
+ status_datas = get_playlists_status_stats(playlists)
+ return render(
+ request,
+ "stats/playlist-stats-view.html",
+ {
+ "title": title,
+ "playlists": playlists,
+ "status_datas": status_datas,
+ },
+ )
+ elif request.method == "POST":
+ return manage_post_request(request, videos)
+
+@user_passes_test(view_stats_if_authenticated)
+def meeting_stats_view(request: HttpRequest, meeting: str = None):
+ """
+ Display meetings statistics view based on user's authentication status.
+
+ Args:
+ request (HttpRequest): The HTTP request object.
+ meeting (str, optional): The meeting slug. Defaults to None.
+
+ Returns:
+ HttpResponse: A response containing the rendered meeting statistics view.
+ """
+ target = "meeting"
+ # Récupérer les vidéos de réupload de la meeting ??
+
+ if meeting:
+ meeting = get_object_or_404(Meeting, meeting_id=meeting)
+ return render(
+ request,
+ "stats/meeting-stats-view.html",
+ {
+ "title": _("Statistics for the meeting %s") % meeting.name,
+ "meeting": meeting
+ }
+ )
+ else:
+ return render(
+ request,
+ "stats/meeting-stats-view.html",
+ {
+ "title": _("Statistics for meetings"),
+ }
+ )
diff --git a/pod/urls.py b/pod/urls.py
index c39d60191e..d10d86b059 100644
--- a/pod/urls.py
+++ b/pod/urls.py
@@ -80,10 +80,6 @@
# cut
url(r"^cut/", include("pod.cut.urls")),
]
-# PLAYLIST
-urlpatterns += [
- path("playlist/", include("pod.playlist.urls", namespace="playlist")),
-]
# CAS
if USE_CAS:
@@ -128,10 +124,10 @@
url(r"^" + apps + "/", include("pod.%s.urls" % apps, namespace=apps)),
]
-# FAVORITE
-if getattr(settings, "USE_FAVORITES", True):
+# PLAYLIST
+if getattr(settings, "USE_PLAYLIST", True):
urlpatterns += [
- path("favorite/", include("pod.favorite.urls", namespace="favorite")),
+ path("playlist/", include("pod.playlist.urls", namespace="playlist")),
]
# IMPORT_VIDEO
@@ -142,6 +138,12 @@
),
]
+# STATISTICS
+if getattr(settings, "USE_STATS_VIEW", True):
+ urlpatterns += [
+ path("stats/", include("pod.stats.urls", namespace="stats"))
+ ]
+
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if importlib.util.find_spec("debug_toolbar") is not None:
diff --git a/pod/video/context_processors.py b/pod/video/context_processors.py
index 6c3240822d..b65223a6fc 100644
--- a/pod/video/context_processors.py
+++ b/pod/video/context_processors.py
@@ -6,13 +6,12 @@
from pod.video.models import Discipline
from pod.video.models import Video
-from django.db.models import Count, Sum
+from django.db.models import Count
from django.db.models import Prefetch
from django.db.models import Q
from django.db.models import Exists
from django.db.models import OuterRef
-from datetime import timedelta
from django.contrib.sites.shortcuts import get_current_site
from pod.main.models import AdditionalChannelTab
from pod.video_encode_transcript.models import EncodingVideo
@@ -142,20 +141,10 @@ def context_navbar(request):
.annotate(video_count=Count("video", distinct=True))
)
- list_videos = get_available_videos(request)
- VIDEOS_COUNT = list_videos.count()
- VIDEOS_DURATION = (
- str(timedelta(seconds=list_videos.aggregate(Sum("duration"))["duration__sum"]))
- if list_videos.aggregate(Sum("duration"))["duration__sum"]
- else 0
- )
-
return {
"ALL_CHANNELS": all_channels,
"ADD_CHANNELS_TAB": add_channels_tab,
"CHANNELS": channels,
"TYPES": types,
"DISCIPLINES": disciplines,
- "VIDEOS_COUNT": VIDEOS_COUNT,
- "VIDEOS_DURATION": VIDEOS_DURATION,
}
diff --git a/pod/video/static/css/video_stats_view.css b/pod/video/static/css/video_stats_view.css
deleted file mode 100644
index f46edc2513..0000000000
--- a/pod/video/static/css/video_stats_view.css
+++ /dev/null
@@ -1,71 +0,0 @@
-.grid-container{
- padding-top: 4em;
- display: flex;
- align-items: center;
- flex-direction: column;
- min-height: calc(100vh - 255px);
-}
-/*.grid-container h1.title{
- margin-bottom: 1em;
- font-size: 24px;
- padding: 0 .8em;
- color: var(--pod-primary) !important;
-}
-.ui-jqgrid tr.jqgrow td{
- height: 30px;
- white-space: normal;
- padding: .5em;
-}
-.ui-state-default .ui-icon{
- background-image: url("/static/img/ui-icons_6da8d5_256x240.png");
-}
-.ui-icon .ui-widget-content .ui-icon{
- background-image: url("/static/img/ui-icons_469bdd_256x240.png");
-}
-.ui-state-default .ui-icon {
- background-image: url("/static/img/ui-icons_6da8d5_256x240.png");
-}
-ui-icon, .ui-widget-content .ui-icon {
- background-image: url("/static/img/ui-icons_469bdd_256x240.png");
-}*/
-
-/* Override jqgrid default css background-image */
-/*.ui-widget-content,
-.ui-state-default,
-.ui-widget-content .ui-state-default,
-.ui-widget-header .ui-state-default,
-.ui-state-hover,
-.ui-widget-content .ui-state-hover,
-.ui-widget-header .ui-state-hover,
-.ui-state-focus,
-.ui-widget-content .ui-state-focus,
-.ui-widget-header .ui-state-focus{
- background-image:none;
-}*/
-
-
-/*.stat-form-group{
- display: flex;
- justify-content: flex-start;
- max-width: 755px;
- align-items: center;
-}
-.stat-form-group .col-form-label{
- font-weight: 700;
- color: var(--secondary);
-}
-.stat-form-group .col-form-label,
-.stat-form-group .col-date{
- width: auto;
- flex: none;
- max-width: unset;
-}
-@media only screen and (max-width: 512px){
- .stat-form-group{
- flex-direction: column;
- width: 100%;
- }
- .stat-form-group .col-date{
- width: calc(100% - 2em);
- }
-}*/
diff --git a/pod/video/static/js/filter_aside_video_list_refresh.js b/pod/video/static/js/filter_aside_video_list_refresh.js
index 1ee5253b16..461194f418 100644
--- a/pod/video/static/js/filter_aside_video_list_refresh.js
+++ b/pod/video/static/js/filter_aside_video_list_refresh.js
@@ -1,11 +1,6 @@
var infinite;
var checkedInputs = [];
var listUser;
-var sortDirectionChars = ["8600", "8599"];
-var sortDirectionTitle = [
- gettext("Descending sort"),
- gettext("Ascending sort"),
-];
let loader = document.querySelector(".lds-ring");
let infinite_loading = document.querySelector(".infinite-loading");
@@ -107,6 +102,15 @@ function refreshVideosSearch() {
});
}
+// Add trigger event to manage sort direction.
+document
+ .getElementById("sort_direction_label")
+ .addEventListener("click", function (e) {
+ e.preventDefault();
+ toggleSortDirection();
+ refreshVideosSearch();
+});
+
// Return url with filter and sort parameters
function getUrlForRefresh() {
let newUrl = window.location.pathname;
@@ -208,38 +212,6 @@ document.getElementById("resetFilters").addEventListener("click", function () {
refreshVideosSearch();
});
-// Add trigger event to manage sort direction
-document
- .getElementById("sort_direction_label")
- .addEventListener("click", function (e) {
- e.preventDefault();
- toggleSortDirection();
- refreshVideosSearch();
- });
-
-// Update arrow char of ascending or descending sort order
-function updateSortDirectionChar(sortDirectionAsc) {
- document.getElementById("sort_direction_label").innerHTML =
- "" + sortDirectionChars[+sortDirectionAsc].toString();
-}
-
-// Update title for input sort direction
-function updateSortDirectionTitle(sortDirectionAsc) {
- let newTitle = sortDirectionTitle[+sortDirectionAsc];
- document
- .getElementById("sort_direction_label")
- .setAttribute("title", newTitle);
-}
-
-// Toggle direction of sort
-function toggleSortDirection() {
- document.getElementById("sort_direction").checked =
- !document.getElementById("sort_direction").checked;
- const direction = document.getElementById("sort_direction").checked;
- updateSortDirectionChar(direction);
- updateSortDirectionTitle(direction);
-}
-
// Enable / Disable toggle inputs to prevent user actions during loading
function disabledInputs(value) {
document
@@ -260,7 +232,7 @@ document
.querySelectorAll("input[type=checkbox]:checked[class=form-check-input]")
.forEach((e) => {
checkedInputs.push(e);
- });
+});
// First launch of the infinite scroll
infinite = new InfiniteLoader(
diff --git a/pod/video/static/js/video_stats_view.js b/pod/video/static/js/video_stats_view.js
deleted file mode 100644
index 02a8e29ebf..0000000000
--- a/pod/video/static/js/video_stats_view.js
+++ /dev/null
@@ -1,102 +0,0 @@
-function linkedCell(cellValue, options, rowObject) {
- return (
- "" +
- cellValue +
- ""
- );
-}
-$(() => {
- let data_url = window.location.href;
- $("#grid").jqGrid({
- url: data_url,
- datatype: "json",
- mtype: "POST",
- styleUI: "Bootstrap5",
- iconSet: "Bootstrap5",
- colNames: [
- gettext("Title"),
- gettext("View during the day"),
- gettext("View during the month"),
- gettext("View during the year"),
- gettext("Total view from creation"),
- gettext("Favorite additions during the day"),
- gettext("Favorite additions during the month"),
- gettext("Favorite additions during the year"),
- gettext("Total favorite additions from creation"),
- gettext("Slug"),
- ],
- colModel: [
- {
- name: "title",
- align: "center",
- sortable: true,
- sorttype: "text",
- formatter: linkedCell,
- },
- { name: "day", align: "center", sortable: true, sorttype: "int" },
- { name: "month", align: "center", sortable: true, sorttype: "int" },
- { name: "year", align: "center", sortable: true, sorttype: "int" },
- {
- name: "since_created",
- align: "center",
- sortable: true,
- sorttype: "int",
- },
- { name: "fav_day", align: "center", sortable: true, sorttype: "int" },
- { name: "fav_month", align: "center", sortable: true, sorttype: "int" },
- { name: "fav_year", align: "center", sortable: true, sorttype: "int" },
- {
- name: "fav_since_created",
- align: "center",
- sortable: true,
- sorttype: "int",
- },
- {
- name: "slug",
- align: "center",
- sortable: true,
- sorttype: "text",
- hidden: true,
- },
- ],
- loadonce: true,
- rowNum: 10,
- rowList: [10, 15, 20, 25, 30, 50, 100],
- gridview: true,
- autoencode: true,
- pager: "#pager",
- sortorder: "asc",
- beforeProcessing: function (data) {
- // Set min date
- let min_date = data.filter((obj) => {
- return obj.min_date != undefined;
- });
- // remove date_min in data
- data.pop();
- document.querySelector("#jsperiode").min = min_date[0].min_date;
- },
- postData: {
- csrfmiddlewaretoken: $("[name=csrfmiddlewaretoken]").val(),
- },
- });
- let today = new Date().toISOString().split("T")[0];
- $("#jsperiode").val(today); // set date input value to today
- document.querySelector("#jsperiode").max = today;
-
- $("#jsperiode").on("change paste keyup", function (e) {
- if ($(this).val() != undefined && $(this).val().trim() !== "") {
- try {
- let data = { periode: $(this).val() };
- $("#grid")
- .jqGrid("setGridParam", { datatype: "json", postData: data })
- .trigger("reloadGrid");
- } catch (e) {
- console.log(e);
- }
- }
- });
-});
diff --git a/pod/video/templates/channel/channel.html b/pod/video/templates/channel/channel.html
index c873d69776..7141bb2b00 100644
--- a/pod/video/templates/channel/channel.html
+++ b/pod/video/templates/channel/channel.html
@@ -56,11 +56,11 @@
- {% if USE_FAVORITES %}
+ {% if USE_PLAYLIST %}
+
+ {% trans 'Addition in a playlist' %}
+ {% if USE_STATS_VIEW and not video.encoding_in_progress %}
+ {% get_count_video_added_in_playlist video %}
+ {% else %}
+ {% get_count_video_added_in_playlist video %}
+ {% endif %}
+
+ {% endif %}
+ {% if USE_PLAYLIST and USE_FAVORITES %}
{% trans 'Number of favorites' %}
- {% if USE_STATS_VIEW and not video.encoding_in_progress %}
- {% number_favorites video %}
+ {% if USE_STATS_VIEW and not video.encoding_in_progress %}
+ {% get_total_favorites_video video %}
{% else %}
- {% number_favorites video %}
+ {% get_total_favorites_video video %}
{% endif %}
{% endif %}
- {% if user.is_authenticated and not video.is_draft %}
- {% is_favorite video as is_fav %}
- {% if USE_FAVORITES %}
-
+ {% if USE_PLAYLIST and user.is_authenticated and not video.is_draft %}
+
+ {% include "playlist/playlist-list-modal.html" %}
+
+ {% is_favorite user video as is_favorite %}
+ {% get_favorite_playlist user as fav_playlist %}
+ {% if USE_PLAYLIST and USE_FAVORITES and video.is_draft is False and not hide_favorite_link is True %}
+ {% if is_favorite %}
+
+
+
+ {% else %}
+
+
+
+ {% endif %}
+
{% endif %}
{% endif %}
{% if video.is_draft == False or video.owner == request.user or request.user in video.additional_owners.all %}
@@ -268,7 +289,7 @@
-{% if video.is_draft == False or video.owner == request.user or request.user in video.additional_owners.all%}
+{% if video.is_draft == False or video.owner == request.user or request.user in video.additional_owners.all%}
-
- {# uploadDate doit être au format 'c' - ISO 8601 (yyyy-mm-dd) #}
-
-
-
-
-
-
-
+
+ {# uploadDate doit être au format 'c' - ISO 8601 (yyyy-mm-dd) #}
+
+
+
+
+
+
+
-{% if channel %}
-
+ {% trans "The video is currently being transcripted." %}
+
+ {% endif %}
+
+ {% block video-element %}
+ {% if form %}
+ {% include 'videos/video-form.html' %}
+ {% else %}
+
+
+ {% include 'videos/video-element.html' %}
+
+
{% include 'videos/video-all-info.html' %}
+ {% endif %}
+ {% endblock video-element %}
+
+
{% endblock page_content %}
{% block page_aside %}
+ {% if playlist_in_get %}
+ {% include 'playlist/playlist_player.html' %}
+ {% endif %}
{% if video.owner == request.user or request.user.is_superuser or request.user in video.additional_owners.all or perms.video.change_video or perms.enrichment.edit_enrichment or perms.completion.add_contributor or perms.completion.add_track or perms.completion.add_document or perms.completion.add_overlay or perms.chapter.change_chapter or perms.video.delete_video %}
{% trans "Manage video"%}
- {% include "videos/link_video.html" %}
+ {% include "videos/link_video.html" with hide_favorite_link=True %}
{% endif %}
@@ -229,8 +256,9 @@
});
var maintenance_mode = {{MAINTENANCE_MODE|yesno:"true,false"}};
- {% include 'videos/video-script.html' %}
-
+
+ {% include 'videos/video-script.html' %}
+
{% if active_video_comment and not video.disable_comment %}
+{% if USE_PLAYLIST and USE_FAVORITES %}
+
+
+{% endif %}
{% endblock %}
diff --git a/pod/video/templates/videos/video_sort_select.html b/pod/video/templates/videos/video_sort_select.html
index b24517c1a7..84bd904139 100644
--- a/pod/video/templates/videos/video_sort_select.html
+++ b/pod/video/templates/videos/video_sort_select.html
@@ -10,7 +10,7 @@